Packages
llm_db
2026.2.3
2026.7.3
2026.7.2
2026.7.1
2026.7.0
2026.6.4
2026.6.3
2026.6.2
2026.6.1
2026.6.0
2026.5.2
2026.5.1
2026.5.0
2026.4.8
2026.4.7
2026.4.6
2026.4.5
2026.4.4
2026.4.3
2026.4.2
2026.4.1
2026.4.0
2026.3.3
2026.3.2
2026.3.1
2026.3.0
2026.2.9
2026.2.8
2026.2.7
2026.2.6
2026.2.5
2026.2.4
2026.2.3
2026.2.2
2026.2.1
2026.2.0
2026.1.5
2026.1.4
2026.1.3
2026.1.2
2026.1.1
2026.1.0
2025.12.4
2025.12.3
2025.12.2
2025.12.1
2025.11.18-preview
2025.11.14-preview
2025.11.7-preview
LLM model metadata catalog with fast, capability-aware lookups.
Current section
Files
Jump to
Current section
Files
lib/llm_db/sources/xai.ex
defmodule LLMDB.Sources.XAI do
@moduledoc """
Remote source for xAI (Grok) models (https://api.x.ai/v1/models).
- `pull/1` fetches data from xAI API and caches locally
- `load/1` reads from cached file (no network call)
## Options
- `:url` - API endpoint (default: "https://api.x.ai/v1/models")
- `:api_key` - xAI API key (required, or set `XAI_API_KEY` env var)
- `:region` - Optional regional endpoint (`:us_east_1` or `:eu_west_1`)
- `:req_opts` - Additional Req options for testing
## Configuration
Cache directory can be configured in application config:
config :llm_db,
xai_cache_dir: "priv/llm_db/remote"
Default: `"priv/llm_db/remote"`
## Regional Endpoints
You can use regional endpoints for data residency:
- `:us_east_1` - `https://us-east-1.api.x.ai/v1/models`
- `:eu_west_1` - `https://eu-west-1.api.x.ai/v1/models`
## Usage
# Pull remote data and cache (requires API key)
mix llm_db.pull --source xai
# Pull with regional endpoint
mix llm_db.pull --source xai --region eu_west_1
# Load from cache
{:ok, data} = XAI.load(%{})
"""
@behaviour LLMDB.Source
require Logger
@default_url "https://api.x.ai/v1/models"
@default_cache_dir "priv/llm_db/remote"
@regional_endpoints %{
us_east_1: "https://us-east-1.api.x.ai/v1/models",
eu_west_1: "https://eu-west-1.api.x.ai/v1/models"
}
@impl true
def pull(opts) do
api_key = get_api_key(opts)
if is_nil(api_key) or api_key == "" do
{:error, :no_api_key}
else
do_pull(opts, api_key)
end
end
defp do_pull(opts, api_key) do
url = get_url(opts)
cache_dir = get_cache_dir()
cache_path = cache_path(url, cache_dir)
manifest_path = manifest_path(url, cache_dir)
req_opts = Map.get(opts, :req_opts, [])
headers = build_headers(api_key)
headers = headers ++ Keyword.get(req_opts, :headers, [])
req_opts = Keyword.put(req_opts, :headers, headers)
cond_headers = build_cond_headers(manifest_path)
headers = cond_headers ++ Keyword.get(req_opts, :headers, [])
req_opts = Keyword.put(req_opts, :headers, headers)
case Req.get(url, req_opts) do
{:ok, %Req.Response{status: 304}} ->
:noop
{:ok, %Req.Response{status: 200, body: body, headers: resp_headers}} ->
bin =
cond do
is_map(body) or is_list(body) ->
Jason.encode!(body, pretty: true)
is_binary(body) ->
case Jason.decode(body) do
{:ok, decoded} -> Jason.encode!(decoded, pretty: true)
{:error, _} -> body
end
true ->
Jason.encode!(body, pretty: true)
end
write_cache(cache_path, manifest_path, bin, url, resp_headers)
{:ok, cache_path}
{:ok, %Req.Response{status: status}} when status >= 400 ->
{:error, {:http_status, status}}
{:ok, %Req.Response{status: status}} ->
Logger.warning("Unexpected status #{status}")
{:error, {:http_status, status}}
{:error, reason} ->
{:error, reason}
end
end
@impl true
def load(opts) do
url = get_url(opts)
cache_dir = get_cache_dir()
cache_path = cache_path(url, cache_dir)
case File.read(cache_path) do
{:ok, bin} ->
case Jason.decode(bin) do
{:ok, decoded} -> {:ok, transform(decoded)}
{:error, err} -> {:error, {:json_error, err}}
end
{:error, :enoent} ->
{:error, :no_cache}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Transforms xAI API response to canonical Zoi format.
xAI uses OpenAI-compatible format.
## Input Format (xAI)
```json
{
"object": "list",
"data": [
{
"id": "grok-4-fast-reasoning",
"object": "model",
"created": 1234567890,
"owned_by": "xai"
}
]
}
```
## Output Format (Canonical Zoi)
```elixir
%{
"xai" => %{
id: :xai,
name: "xAI",
models: [
%{
id: "grok-4-fast-reasoning",
provider: :xai,
extra: %{
created: 1234567890,
owned_by: "xai"
}
}
]
}
}
```
"""
def transform(content) when is_map(content) do
models_list =
content
|> Map.get("data", [])
|> Enum.map(&transform_model/1)
%{
"xai" => %{
id: :xai,
name: "xAI",
models: models_list
}
}
end
defp transform_model(model) do
%{
id: model["id"],
provider: :xai,
extra: %{
created: model["created"],
owned_by: model["owned_by"]
}
}
end
defp get_url(opts) do
case Map.get(opts, :region) do
nil ->
Map.get(opts, :url, @default_url)
region when is_atom(region) ->
Map.get(@regional_endpoints, region) || @default_url
region when is_binary(region) ->
region_atom = String.to_existing_atom(region)
Map.get(@regional_endpoints, region_atom) || @default_url
end
end
defp get_api_key(opts) do
Map.get(opts, :api_key) || System.get_env("XAI_API_KEY")
end
defp get_cache_dir do
Application.get_env(:llm_db, :xai_cache_dir, @default_cache_dir)
end
defp cache_path(url, cache_dir) do
hash = :crypto.hash(:sha256, url) |> Base.encode16(case: :lower) |> binary_part(0, 8)
Path.join(cache_dir, "xai-#{hash}.json")
end
defp manifest_path(url, cache_dir) do
hash = :crypto.hash(:sha256, url) |> Base.encode16(case: :lower) |> binary_part(0, 8)
Path.join(cache_dir, "xai-#{hash}.manifest.json")
end
defp build_headers(api_key) do
[{"authorization", "Bearer #{api_key}"}]
end
defp write_cache(cache_path, manifest_path, content, url, headers) do
File.mkdir_p!(Path.dirname(cache_path))
File.write!(cache_path, content)
manifest = %{
source_url: url,
etag: get_header(headers, "etag"),
last_modified: get_header(headers, "last-modified"),
sha256: :crypto.hash(:sha256, content) |> Base.encode16(case: :lower),
size_bytes: byte_size(content),
downloaded_at: DateTime.utc_now() |> DateTime.to_iso8601()
}
File.write!(manifest_path, Jason.encode!(manifest, pretty: true))
end
defp build_cond_headers(manifest_path) do
case File.read(manifest_path) do
{:ok, bin} ->
case Jason.decode(bin) do
{:ok, manifest} ->
headers = []
headers =
case Map.get(manifest, "etag") do
etag when is_binary(etag) -> [{"if-none-match", etag} | headers]
_ -> headers
end
headers =
case Map.get(manifest, "last_modified") do
last_mod when is_binary(last_mod) -> [{"if-modified-since", last_mod} | headers]
_ -> headers
end
headers
_ ->
[]
end
_ ->
[]
end
end
defp get_header(headers, name) do
case Enum.find(headers, fn {k, _} -> String.downcase(k) == name end) do
{_, [v | _]} when is_list(v) -> v
{_, v} when is_binary(v) -> v
{_, v} when is_list(v) -> List.first(v)
_ -> nil
end
end
end