Packages

Elixir client for the Anthropic Messages API — typed content blocks, native tool use, streaming, retries.

Current section

Files

Jump to
Raw

README.md

# Anthropic Elixir API Client
An unofficial Elixir client for the [Anthropic API](https://docs.anthropic.com/claude/reference/getting-started-with-the-api), built around the same shape as Anthropic's official SDKs: an explicit `Client` struct, resource modules (`Messages`, `Models`, `Batches`), typed content blocks, native tool use, streaming, and automatic retries.
## Features
- **Typed content blocks**`Text`, `ToolUse`, `ToolResult`, `Thinking`, `RedactedThinking`, and `Image` structs instead of raw maps.
- **Native tool use** — the real `tool_use`/`tool_result` protocol, with `Anthropic.ToolRunner` driving the full agentic loop for you.
- **Streaming**`Anthropic.Messages.stream/2` returns a lazy `Stream` of typed SSE events, plus a convenience to fold it into a final message.
- **Automatic retries** — exponential backoff with jitter on `429`/`5xx`, honoring `retry-after`.
- **Unified errors** — one `Anthropic.Error` struct/exception mirroring the API's error taxonomy, with `!` bang variants that raise.
- **Models and Batches resources**, in addition to Messages.
## Installation
```elixir
def deps do
[
{:anthropic, "~> 0.5", hex: :anthropic_community}
]
end
```
## Configuration
Build a `Client` explicitly and pass it to every call:
```elixir
client = Anthropic.Client.new(api_key: System.fetch_env!("ANTHROPIC_API_KEY"))
```
`api_key` and `base_url` also fall back to `Application.get_env(:anthropic, ...)` and then to `ANTHROPIC_API_KEY`/`ANTHROPIC_BASE_URL` environment variables if not passed explicitly:
```elixir
# config/config.exs
import Config
config :anthropic, api_key: System.get_env("ANTHROPIC_API_KEY")
```
Other `Client` options: `base_url`, `api_version`, `max_retries` (default `2`), `timeout` (default `600_000` ms), `default_model`, `default_headers`.
## Usage
### Basic conversation
```elixir
{:ok, message} =
Anthropic.Messages.create(client,
model: "claude-opus-4-8",
max_tokens: 1024,
messages: [%{role: "user", content: "Explain monads in computer science. Be concise."}]
)
message.content
#=> [%Anthropic.Messages.Content.Text{text: "Monads are..."}]
message.stop_reason
#=> "end_turn"
```
Use `Anthropic.Messages.create!/2` for a bang variant that returns the message directly and raises `Anthropic.Error` on failure.
### Streaming
```elixir
client
|> Anthropic.Messages.stream(model: "claude-opus-4-8", max_tokens: 1024,
messages: [%{role: "user", content: "Write a haiku about Elixir"}])
|> Stream.each(fn
%Anthropic.Messages.StreamEvent.ContentBlockDelta{delta: %{"type" => "text_delta", "text" => text}} ->
IO.write(text)
_other ->
:ok
end)
|> Stream.run()
```
Or fold the stream into a final `Message`, equivalent to `create/2`:
```elixir
{:ok, message} =
client
|> Anthropic.Messages.stream(model: "claude-opus-4-8", max_tokens: 1024,
messages: [%{role: "user", content: "Write a haiku about Elixir"}])
|> Anthropic.Messages.stream_to_message()
```
### Tool use
Define a tool with a JSON Schema `input_schema`:
```elixir
defmodule MyApp.WeatherTool do
use Anthropic.Tools
@impl true
def name, do: "get_weather"
@impl true
def description, do: "Get the current weather for a given city."
@impl true
def input_schema do
%{
"type" => "object",
"properties" => %{
"location" => %{"type" => "string", "description" => "City and state, e.g. San Francisco, CA"}
},
"required" => ["location"]
}
end
@impl true
def execute(%{"location" => location}) do
{:ok, "72F and sunny in #{location}"}
end
end
```
Then drive the full agentic loop with `Anthropic.ToolRunner`:
```elixir
{:ok, message, _history} =
Anthropic.ToolRunner.run(
client,
[model: "claude-opus-4-8", max_tokens: 1024,
messages: [%{role: "user", content: "What's the weather in Paris?"}]],
[MyApp.WeatherTool]
)
```
`ToolRunner` executes every requested tool, feeds results back to the API, and repeats until the assistant stops requesting tools.
### Server tools
Web search, web fetch, code execution, bash, text editor, and memory are executed by Anthropic server-side — no `execute/1` to implement, just add the tool definition and read the typed result block back:
```elixir
{:ok, message} =
Anthropic.Messages.create(client,
model: "claude-opus-4-8",
max_tokens: 1024,
tools: [Anthropic.Tools.WebSearch.new(max_uses: 3)],
messages: [%{role: "user", content: "What's the latest Elixir release?"}]
)
for %Anthropic.Messages.Content.WebSearchToolResult{content: results} <- message.content do
IO.inspect(results)
end
```
Available: `Anthropic.Tools.WebSearch`, `WebFetch`, `CodeExecution`, `Bash`, `TextEditor`, `Memory` — each a thin, validated builder around that tool's versioned wire shape (`version:` defaults to the latest known version, override to pin an older one). Results decode into `Anthropic.Messages.Content.WebSearchToolResult`, `WebFetchToolResult`, `CodeExecutionToolResult`, `BashCodeExecutionToolResult`, `TextEditorCodeExecutionToolResult``content` on each is the raw decoded JSON payload (not deeply typed; see each tool's docs for its shape). The invocation itself decodes as `Anthropic.Messages.Content.ServerToolUse`, distinct from `ToolUse` so `Anthropic.ToolRunner` never tries to dispatch it to a client-side tool.
Computer use and the MCP connector are beta-only and not yet supported.
### Prompt caching
Attach `Anthropic.CacheControl.ephemeral/1` to a content block's `:cache_control` field to mark it as a cache breakpoint:
```elixir
{:ok, message} =
Anthropic.Messages.create(client,
model: "claude-opus-4-8",
max_tokens: 1024,
messages: [
%{
role: "user",
content: [
%Anthropic.Messages.Content.Text{text: large_document, cache_control: Anthropic.CacheControl.ephemeral()},
%{type: "text", text: "Summarize the above."}
]
}
]
)
```
### Extended thinking
```elixir
{:ok, message} =
Anthropic.Messages.create(client,
model: "claude-opus-4-8",
max_tokens: 4096,
thinking: Anthropic.Thinking.enabled(budget_tokens: 10_000),
messages: [%{role: "user", content: "..."}]
)
```
### Structured outputs
Constrain Claude's response to a given JSON Schema:
```elixir
{:ok, message} =
Anthropic.Messages.create(client,
model: "claude-opus-4-8",
max_tokens: 1024,
output_config:
Anthropic.OutputConfig.json_schema(%{
"type" => "object",
"properties" => %{"answer" => %{"type" => "string"}},
"required" => ["answer"]
}),
messages: [%{role: "user", content: "..."}]
)
```
### Images
```elixir
{:ok, image} = Anthropic.Messages.Content.Image.process_image("/path/to/image.png", :path)
{:ok, message} =
Anthropic.Messages.create(client,
model: "claude-opus-4-8",
max_tokens: 1024,
messages: [
%{role: "user", content: [image, %{type: "text", text: "What's in this image?"}]}
]
)
```
### Documents (PDF/text)
```elixir
{:ok, doc} = Anthropic.Messages.Content.Document.process_document("/path/to/report.pdf", :path)
{:ok, message} =
Anthropic.Messages.create(client,
model: "claude-opus-4-8",
max_tokens: 1024,
messages: [
%{role: "user", content: [doc, %{type: "text", text: "Summarize this document."}]}
]
)
```
`Anthropic.Messages.Content.Document` also supports `from_url/2` (a hosted PDF), `from_text/2` (inline plain text), and `from_content/2` (pre-formatted content, for citing structured content rather than a raw document).
### Citations
Pass `citations: %{enabled: true}` to a document (or search-result) block to have Claude cite the specific passages it draws on. Citations come back attached to the response's `Text` blocks, decoded into typed structs:
```elixir
{:ok, doc} =
Anthropic.Messages.Content.Document.process_document("/path/to/report.pdf", :path,
citations: %{enabled: true}
)
{:ok, message} =
Anthropic.Messages.create(client,
model: "claude-opus-4-8",
max_tokens: 1024,
messages: [%{role: "user", content: [doc, %{type: "text", text: "What was Q3 revenue?"}]}]
)
for %Anthropic.Messages.Content.Text{citations: citations} <- message.content, citations do
for %Anthropic.Messages.Content.Citation.CharLocation{cited_text: cited_text} <- citations do
IO.puts("Cited: #{cited_text}")
end
end
```
Five citation types are supported, discriminated by struct: `Citation.CharLocation`, `Citation.PageLocation`, `Citation.ContentBlockLocation`, `Citation.SearchResultLocation`, `Citation.WebSearchResultLocation`.
### Models
```elixir
{:ok, %{data: models}} = Anthropic.Models.list(client)
{:ok, model} = Anthropic.Models.retrieve(client, "claude-opus-4-8")
# Or auto-paginate through every page as a lazy Stream:
client |> Anthropic.Models.list_all() |> Enum.each(&IO.puts(&1["id"]))
```
### Batches
```elixir
{:ok, batch} =
Anthropic.Batches.create(client, [
%{custom_id: "request-1", params: [model: "claude-opus-4-8", max_tokens: 100, messages: [%{role: "user", content: "Hi"}]]},
%{custom_id: "request-2", params: [model: "claude-opus-4-8", max_tokens: 100, messages: [%{role: "user", content: "Hello"}]]}
])
{:ok, batch} = Anthropic.Batches.retrieve(client, batch.id)
if batch.processing_status == "ended" do
{:ok, results} = Anthropic.Batches.results(client, batch)
end
# List (a page at a time, or auto-paginated):
{:ok, %{data: batches}} = Anthropic.Batches.list(client)
client |> Anthropic.Batches.list_all() |> Enum.to_list()
{:ok, _deleted} = Anthropic.Batches.delete(client, batch.id)
```
### Files
Upload a file once and reference it by `file_id` instead of inlining base64 data. This is a beta API — `Anthropic.Files` automatically sends the `anthropic-beta` header it currently requires:
```elixir
{:ok, file} = Anthropic.Files.create(client, "/path/to/report.pdf")
{:ok, %{data: files}} = Anthropic.Files.list(client)
client |> Anthropic.Files.list_all() |> Enum.to_list()
{:ok, file} = Anthropic.Files.retrieve(client, file.id)
{:ok, binary} = Anthropic.Files.download(client, file.id)
{:ok, _deleted} = Anthropic.Files.delete(client, file.id)
```
Referencing the uploaded file back in a message is also beta and not yet modeled by `Content.Image`/`Document` — pass a raw map and add the same beta header to that call (see `Anthropic.Files` moduledoc for a full example).
### Counting tokens
```elixir
{:ok, %{input_tokens: n}} =
Anthropic.Messages.count_tokens(client,
model: "claude-opus-4-8",
messages: [%{role: "user", content: "Hello, Claude"}]
)
```
### Error handling
Every resource function returns `{:ok, result} | {:error, %Anthropic.Error{}}`. `Anthropic.Error` mirrors the API's `error.type` taxonomy (`invalid_request_error`, `rate_limit_error`, `overloaded_error`, `api_error`, etc.) plus client-local types (`:connection_error`, `:timeout`, `:decode_error`, `:validation_error`):
```elixir
case Anthropic.Messages.create(client, model: "claude-opus-4-8", max_tokens: 1024, messages: []) do
{:ok, message} -> message
{:error, %Anthropic.Error{type: :validation_error, message: msg}} -> {:error, msg}
{:error, error} -> raise error
end
```