Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

A provider

An OpenAI-compatible endpoint

A LiteLLM proxy on your machine, or any server that speaks the OpenAI chat format.

uji.provider.add({
  id = "litellm",
  name = "LiteLLM",
  api = uji.api.openai(),
  base_url = os.getenv("LITELLM_BASE_URL") or "http://localhost:4000",
  auth_env = { "LITELLM_API_KEY" },
  models = {
    { id = "claude-sonnet-4-5", context = 1000000, output = 64000, reasoning = true },
    { id = "glm-4.6", context = 204800, output = 131072, reasoning = true },
  },
})

An endpoint with its own quirks

Some differences need only an option. This server wants the output limit in max_completion_tokens:

uji.provider.add({
  id = "gateway",
  name = "Gateway",
  api = uji.api.openai({ max_tokens_field = "max_completion_tokens" }),
  base_url = "https://gateway.example.com/v1",
  auth_env = { "GATEWAY_API_KEY" },
  models = { { id = "qwen3-coder", reasoning = true, efforts = { "off", "high" } } },
})

For anything else, make a class from the API and redefine the method that differs. This server turns thinking on and off with enable_thinking:

local Qwen = uji.class(uji.api.openai)

function Qwen:thinking(body, request)
  body.enable_thinking = request.effort ~= "off"
end

uji.provider.add({
  id = "dashscope",
  name = "DashScope",
  api = Qwen(),
  base_url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
  auth_env = { "DASHSCOPE_API_KEY" },
  models = { { id = "qwen3-max", reasoning = true, efforts = { "off", "high" } } },
})

Provider APIs lists the options and methods of each API.

Reading model sizes from the endpoint

A LiteLLM proxy reports each model’s context and output limits at /model/info. This plugin reads them when uji starts and corrects the models the provider lists. It sends back each model whole, with its reasoning and other fields, because uji.provider.add replaces a model with the same id as a whole.

local BASE_URL = os.getenv("LITELLM_BASE_URL") or "http://localhost:4000"

uji.provider.add({
  id = "litellm",
  name = "LiteLLM",
  api = uji.api.openai(),
  base_url = BASE_URL,
  auth_env = { "LITELLM_API_KEY" },
  models = {
    { id = "claude-sonnet-4-5", reasoning = true },
  },
})

local function listed(id)
  for _, provider in ipairs(uji.provider.list()) do
    if provider.id == id then
      local models = {}
      for _, model in ipairs(provider.models) do
        models[model.id] = model
      end
      return models
    end
  end
  return {}
end

uji.http.request({
  url = BASE_URL .. "/model/info",
  headers = { Authorization = "Bearer " .. (os.getenv("LITELLM_API_KEY") or "") },
}, function(response)
  if not response or response.status ~= 200 then
    return
  end
  local ok, info = pcall(uji.json.decode, response.body, { nulls = false })
  if not ok or type(info) ~= "table" or type(info.data) ~= "table" then
    return
  end
  local known, changed = listed("litellm"), {}
  for _, entry in ipairs(info.data) do
    local model = known[entry.model_name]
    local limits = entry.model_info or {}
    if model and (limits.max_input_tokens or limits.max_output_tokens) then
      model.context = limits.max_input_tokens or model.context
      model.output = limits.max_output_tokens or model.output
      changed[#changed + 1] = model
    end
  end
  if #changed > 0 then
    uji.provider.add({ id = "litellm", models = changed })
    uji.emit("status_changed", {})
  end
end)

Changing a built-in provider

With the id of a built-in provider, uji.provider.add changes only the fields you give.

uji.provider.add({ id = "openai", base_url = "https://llm-proxy.internal/v1" })

uji.provider.add({
  id = "anthropic",
  models = { { id = "claude-fable-5-1", context = 1000000, output = 128000, reasoning = true } },
})