Skip to content

davis7dotsh/ai-sdk-ex

Repository files navigation

AiSdkEx

Minimal Elixir AI SDK scaffolding for streaming text with tool calls.

Getting started

Install the dependency and set your OpenRouter API key:

def deps do
  [
    {:ai_sdk_ex, "~> 0.1.2"}
  ]
end
export 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")
end

Other providers: OpenAI · Anthropic · OpenCode Zen

Environment

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=...

Examples

Run the streaming examples (OpenAI + Anthropic + OpenCode Zen, with tool calls):

bun run -- mix run examples/stream_text.exs

Consuming the stream

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")
end

For 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")
end

Anthropic

Anthropic 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

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

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
)

Installation

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"}
  ]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/ai_sdk_ex.

About

A super basic AI SDK in elixir for elixir, to help with some of the nonsense I'm working on...

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages