Minimal Elixir AI SDK scaffolding for streaming text with tool calls.
Install the dependency and set your OpenRouter API key:
def deps do
[
{:ai_sdk_ex, "~> 0.1.2"}
]
endexport OPENROUTER_API_KEY=...Then stream text with an OpenRouter-backed model:
case AI.stream_text(
model: AI.OpenRouter.chat("anthropic/claude-haiku-4.5"),
prompt: "Say hello in one short sentence.",
max_tokens: 64
) do
{:ok, stream} ->
Enum.each(stream, fn
{:text_delta, _id, text} -> IO.write(text)
{:finish, _info} -> IO.puts("")
_ -> :ok
end)
other ->
IO.inspect(other, label: "stream_text")
endOther providers: OpenAI · Anthropic · OpenCode Zen
This project loads dotenv files automatically via config/config.exs.
Create a .env file (see .env.example) and set:
OPENAI_API_KEY=...
ANTHROPIC_API_KEY=...
OPENROUTER_API_KEY=...
OPENCODE_API_KEY=...
Run the streaming examples (OpenAI + Anthropic + OpenCode Zen, with tool calls):
bun run -- mix run examples/stream_text.exs
The stream emits structured events. Here is the full pattern matching flow used in the example:
case AI.stream_text(opts) do
{:ok, stream} ->
Enum.each(stream, fn
{:text_start, _id} ->
:ok
{:text_delta, _id, text} ->
IO.write(text)
{:text_end, _id} ->
IO.puts("")
{:tool_call, call} ->
IO.inspect(call, label: "tool_call")
{:tool_result, result} ->
IO.inspect(result, label: "tool_result")
{:finish, info} ->
IO.inspect(info, label: "finish")
{:error, error} ->
IO.inspect(error, label: "error")
{:raw, _} ->
:ok
other ->
IO.inspect(other, label: "event")
end)
other ->
IO.inspect(other, label: "stream_text")
endFor a minimal handler that only prints text and handles completion:
case AI.stream_text(opts) do
{:ok, stream} ->
Enum.each(stream, fn
{:text_delta, _id, text} ->
IO.write(text)
{:finish, _info} ->
IO.puts("")
_ ->
:ok
end)
other ->
IO.inspect(other, label: "stream_text")
endAnthropic Messages-compatible streaming with the claude-haiku-4-5 model:
run.(
"Anthropic Messages (text)",
model: AI.Anthropic.messages("claude-haiku-4-5"),
prompt: "Say hello in one short sentence.",
max_tokens: 64
)Tool calling works the same way:
run.(
"Anthropic Messages (tool calling)",
model: AI.Anthropic.messages("claude-haiku-4-5"),
prompt:
"Use the add tool to add 7 and 5, then respond with the sum in one short sentence.",
tools: %{"add" => add_tool},
tool_choice: :required,
max_steps: 3,
max_tokens: 64
)OpenRouter Chat Completions streaming using the anthropic/claude-haiku-4.5 model:
run.(
"OpenRouter Chat Completions (text)",
model: AI.OpenRouter.chat("anthropic/claude-haiku-4.5"),
prompt: "Say hello in one short sentence.",
max_tokens: 64
)You can also set optional attribution headers:
run.(
"OpenRouter Chat Completions (tool calling)",
model:
AI.OpenRouter.chat(
"anthropic/claude-haiku-4.5",
referer: "https://example.com",
title: "AiSdkEx"
),
prompt:
"Use the add tool to add 4 and 6, then respond with the sum in one short sentence.",
tools: %{"add" => add_tool},
tool_choice: :required,
max_steps: 3,
max_tokens: 64
)OpenCode Zen exposes OpenAI Responses-compatible and Anthropic Messages-compatible endpoints under the Zen base URL.
You can target Zen by passing base_url (and api_key if needed) in the model options:
model: AI.OpenAI.responses(
"gpt-5.1-codex",
base_url: "https://opencode.ai/zen/v1",
api_key: System.get_env("OPENCODE_API_KEY")
)run.(
"OpenCode Zen (Anthropic Messages, text)",
model:
AI.Anthropic.messages(
"claude-haiku-4-5",
base_url: "https://opencode.ai/zen/v1",
api_key: System.get_env("OPENCODE_API_KEY")
),
prompt: "Say hello in one short sentence.",
max_tokens: 64
)If available in Hex, the package can be installed
by adding ai_sdk_ex to your list of dependencies in mix.exs:
def deps do
[
{:ai_sdk_ex, "~> 0.1.2"}
]
endDocumentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/ai_sdk_ex.