README.md

# Claudio

An Elixir client library for the Anthropic API, providing a comprehensive interface for interacting with Claude models.

## Features

- **Messages API** with streaming support
- **Tool/Function calling** with structured schemas
- **Message Batches API** for large-scale async processing
- **Prompt caching** (up to 90% cost reduction)
- **Vision/image support** (base64, URL, Files API)
- **PDF/document support**
- **MCP (Model Context Protocol) servers**
- **Request building** with fluent API and validation
- **Structured response handling** with content block parsing
- **Token counting** for cost estimation
- **Cache metrics tracking**
- **Configurable timeouts** and retry logic

## Installation

Add `claudio` to your list of dependencies in `mix.exs`:

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

## Quick Start

```elixir
# Create a client
client = Claudio.Client.new(%{
  token: System.get_env("ANTHROPIC_API_KEY"),
  version: "2023-06-01"
})

# Simple message
{:ok, response} = Claudio.Messages.create(client, %{
  model: "claude-3-5-sonnet-20241022",
  max_tokens: 1024,
  messages: [
    %{role: "user", content: "Hello, Claude!"}
  ]
})

IO.puts(response.content |> hd() |> Map.get(:text))
```

## Configuration

### API Client

```elixir
# config/config.exs
config :claudio,
  default_api_version: "2023-06-01",
  default_beta_features: ["prompt-caching-2024-07-31"]
```

### Timeout Configuration

Configure connection and receive timeouts (important for streaming):

```elixir
# config/config.exs
config :claudio, Claudio.Client,
  timeout: 60_000,        # Connection timeout in ms (default: 60s)
  recv_timeout: 120_000   # Receive timeout in ms (default: 120s)
```

**For long-running streaming operations:**

```elixir
config :claudio, Claudio.Client,
  timeout: 60_000,
  recv_timeout: 600_000   # 10 minutes for long streams
```

**For production environments:**

```elixir
# config/prod.exs
config :claudio, Claudio.Client,
  timeout: 30_000,
  recv_timeout: 180_000,
  retry: true  # Enable automatic retries
```

### Retry Configuration

Enable automatic retries for transient failures:

```elixir
config :claudio, Claudio.Client,
  retry: true  # Uses default retry strategy

# Or customize:
config :claudio, Claudio.Client,
  retry: [
    delay: 1000,
    max_retries: 3,
    max_delay: 10_000
  ]
```

## Usage

### Request Builder API (Recommended)

Use the fluent request builder for type-safe requests:

```elixir
alias Claudio.Messages.Request

request =
  Request.new("claude-3-5-sonnet-20241022")
  |> Request.add_message(:user, "What is the capital of France?")
  |> Request.set_max_tokens(1024)
  |> Request.set_temperature(0.7)

{:ok, response} = Claudio.Messages.create(client, request)
```

### Streaming Responses

Stream responses for real-time output:

```elixir
alias Claudio.Messages.{Request, Stream}

request =
  Request.new("claude-3-5-sonnet-20241022")
  |> Request.add_message(:user, "Write a short story")
  |> Request.set_max_tokens(1024)
  |> Request.enable_streaming()

{:ok, stream_response} = Claudio.Messages.create(client, request)

# Parse and accumulate text
text =
  stream_response.body
  |> Stream.parse_events()
  |> Stream.accumulate_text()

IO.puts(text)
```

### Prompt Caching

Reduce costs by caching large contexts:

```elixir
request =
  Request.new("claude-3-5-sonnet-20241022")
  |> Request.set_system_with_cache("""
    You are an expert on Shakespeare's works.
    Here is the full text of Hamlet...
    [large context that will be cached]
    """, ttl: "5m")
  |> Request.add_message(:user, "Analyze Hamlet's soliloquy")
  |> Request.set_max_tokens(2048)

{:ok, response} = Claudio.Messages.create(client, request)

# Check cache metrics
IO.inspect(response.usage.cache_creation_input_tokens)
IO.inspect(response.usage.cache_read_input_tokens)
```

### Vision/Image Support

Send images for analysis:

```elixir
# From base64
image_data = File.read!("image.jpg") |> Base.encode64()

request =
  Request.new("claude-3-5-sonnet-20241022")
  |> Request.add_message_with_image(:user, "Describe this image", image_data, "image/jpeg")
  |> Request.set_max_tokens(1024)
```

### Tool/Function Calling

Define and use tools:

```elixir
alias Claudio.Tools

# Define a tool
weather_tool = Tools.define_tool(
  "get_weather",
  "Get the current weather in a location",
  %{
    type: "object",
    properties: %{
      location: %{type: "string", description: "City name"},
      unit: %{type: "string", enum: ["celsius", "fahrenheit"]}
    },
    required: ["location"]
  }
)

# Create request with tool
request =
  Request.new("claude-3-5-sonnet-20241022")
  |> Request.add_message(:user, "What's the weather in Paris?")
  |> Request.add_tool(weather_tool)
  |> Request.set_max_tokens(1024)

{:ok, response} = Claudio.Messages.create(client, request)

# Extract and handle tool uses
if Tools.has_tool_uses?(response) do
  tool_uses = Tools.extract_tool_uses(response)
  # Execute your tools and continue conversation...
end
```

### Message Batches API

Process large volumes of requests asynchronously:

```elixir
# Create a batch of requests
requests = [
  %{
    custom_id: "req-1",
    params: %{
      model: "claude-3-5-sonnet-20241022",
      max_tokens: 1024,
      messages: [%{role: "user", content: "Hello"}]
    }
  }
]

# Submit batch (up to 100,000 requests)
{:ok, batch} = Claudio.Batches.create(client, requests)

# Wait for completion with progress callback
{:ok, completed_batch} = Claudio.Batches.wait_for_completion(
  client,
  batch["id"],
  fn status ->
    IO.puts("Progress: #{status["request_counts"]["succeeded"]}")
  end
)

# Get results
{:ok, results} = Claudio.Batches.get_results(client, batch["id"])
```

## Error Handling

All API calls return structured errors:

```elixir
case Claudio.Messages.create(client, request) do
  {:ok, response} ->
    # Handle success
    IO.puts("Success!")

  {:error, %Claudio.APIError{} = error} ->
    # Handle API errors
    IO.puts("Error #{error.status}: #{error.message}")
    IO.inspect(error.type)  # :rate_limit_error, :authentication_error, etc.

  {:error, reason} ->
    # Handle other errors (network, timeout, etc.)
    IO.inspect(reason)
end
```

## Testing

```bash
# Unit tests only
mix test

# Include integration tests (requires ANTHROPIC_API_KEY)
mix test --include integration
```

## Documentation

Generate documentation:

```bash
mix docs
open doc/index.html
```

## License

MIT License - see [LICENSE](LICENSE) for details.

## Links

- [Anthropic API Documentation](https://docs.anthropic.com/)
- [GitHub Repository](https://github.com/thetonymaster/claudio)