README.md

# AiSdkEx

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

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

```elixir
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:

```elixir
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:

```elixir
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:

```elixir
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
)
```

## 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:

```elixir
model: AI.OpenAI.responses(
  "gpt-5.1-codex",
  base_url: "https://opencode.ai/zen/v1",
  api_key: System.get_env("OPENCODE_API_KEY")
)
```

```elixir
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](https://hex.pm/docs/publish), the package can be installed
by adding `ai_sdk_ex` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ai_sdk_ex, "~> 0.1.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/ai_sdk_ex>.