# `ReqLLM`
[🔗](https://github.com/agentjido/req_llm/blob/main/lib/req_llm.ex#L1)

Main API facade for Req AI.

Inspired by the Vercel AI SDK, provides a unified interface to AI providers with
flexible model specifications, rich prompt support, configuration management,
and structured data generation.

## Quick Start

    # Simple text generation using string format
    ReqLLM.generate_text("anthropic:claude-3-5-sonnet", "Hello world")
    #=> {:ok, "Hello! How can I assist you today?"}

    # Structured data generation with schema validation
    schema = [
      name: [type: :string, required: true],
      age: [type: :pos_integer, required: true]
    ]
    ReqLLM.generate_object("anthropic:claude-3-5-sonnet", "Generate a person", schema)
    #=> {:ok, %{name: "John Doe", age: 30}}

## Model Specifications

Multiple formats supported for maximum flexibility:

    # String format: "provider:model"
    ReqLLM.generate_text("anthropic:claude-sonnet-4-5-20250929", messages)

    # Tuple format: {provider, options}
    ReqLLM.generate_text({:anthropic, id: "claude-3-5-sonnet"}, messages)

    # Model struct format
    model = ReqLLM.model!("anthropic:claude-3-5-sonnet")
    ReqLLM.generate_text(model, messages)

    # Inline model format for models not yet in LLMDB
    model =
      ReqLLM.model!(%{
        provider: :openai,
        id: "gpt-6-mini",
        base_url: "http://localhost:8000/v1",
        extra: %{openai_compatible_backend: :ollama}
      })

    ReqLLM.generate_text(model, messages)

## Configuration

ReqLLM loads API keys from standard sources in order of precedence:

1. Per-request `:api_key` option
2. Application config: `config :req_llm, :anthropic_api_key, "..."`
3. System environment: `ANTHROPIC_API_KEY` (loaded from .env via dotenvy)

The recommended approach is to use a .env file:

    # .env
    ANTHROPIC_API_KEY=sk-ant-...
    OPENAI_API_KEY=sk-...

Under Mix, `.env` is loaded at startup; releases skip it unless
`config :req_llm, load_dotenv: true` is set.

For programmatic key management:

    # Store keys (uses Application config)
    ReqLLM.put_key(:anthropic_api_key, "sk-ant-...")

    # Retrieve keys
    ReqLLM.get_key(:anthropic_api_key)
    ReqLLM.get_key("ANTHROPIC_API_KEY")

## Providers

Built-in support for major AI providers:

- **Anthropic**: Claude 3.5 Sonnet, Claude 3 Haiku, Claude 3 Opus

    # Access provider modules directly
    provider = ReqLLM.provider(:anthropic)
    provider.generate_text(model, messages, opts)

# `generation_model_input`

```elixir
@type generation_model_input() :: model_input() | ReqLLM.Router.t()
```

Model input for generation APIs.

In addition to a static model input, generation accepts an application struct
whose module implements `ReqLLM.Router`.

# `model_input`

```elixir
@type model_input() :: static_model_input()
```

# `plan_diagnostic`

```elixir
@type plan_diagnostic() :: %{
  model: %{provider: atom(), id: String.t()},
  operation: :chat | :object,
  surface: atom(),
  transport: :req | :finch | :websocket,
  route: %{method: :post | :websocket, path: String.t()},
  options: %{canonical: [atom()], translated: [atom()]},
  fallbacks: [],
  warnings: [String.t()]
}
```

Redacted, JSON-serializable diagnostic returned by `plan/3`.

The diagnostic contains route and option names only. It never contains provider modules,
model metadata, option values, credentials, prompt/message/tool/file values, or encoded bodies.

# `static_model_input`

```elixir
@type static_model_input() ::
  String.t()
  | map()
  | {atom(), String.t(), keyword()}
  | {atom(), keyword()}
  | LLMDB.Model.t()
```

Model input accepted by ReqLLM public APIs.

Strings and tuples resolve through the LLMDB catalog. `%LLMDB.Model{}` values and
plain maps are treated as inline model specs and bypass catalog lookup.

# `available_models`

```elixir
@spec available_models(keyword()) :: [String.t()]
```

Returns catalog model specs for providers that are currently configured.

This is intended for building model selectors or narrowing choices to providers
that have usable credentials in the current environment or application config.

The result is filtered by provider availability first, then by the supplied
LLMDB query options.

## Options

  * `:scope` - Restrict results to a specific provider, or use `:all` (default)
  * `:prefer` - Preferred provider ordering, forwarded to `LLMDB.Query.candidates/1`
  * `:require` - Required capability filters, forwarded to `LLMDB.Query.candidates/1`
  * `:forbid` - Forbidden capability filters, forwarded to `LLMDB.Query.candidates/1`
  * `:provider_options` - Optional auth-related provider settings for scoped discovery

## Examples

    ReqLLM.available_models()
    #=> ["openai:gpt-4o", "anthropic:claude-3-5-sonnet-20240620", ...]

    ReqLLM.available_models(scope: :openai, require: [embeddings: true])
    #=> ["openai:text-embedding-3-small", "openai:text-embedding-3-large", ...]

# `compact_context`

```elixir
@spec compact_context(
  model_input(),
  ReqLLM.Context.t() | ReqLLM.Message.t() | [term()] | String.t() | nil,
  keyword()
) :: {:ok, ReqLLM.Response.t()} | {:error, term()}
```

Compacts a conversation through the OpenAI Responses API (`POST /responses/compact`).

Supported for OpenAI and Azure OpenAI Responses API models. The service folds
the conversation into opaque `compaction` items that carry the prior state in
fewer tokens. They come back as `:provider_block` content parts on the
assistant message of the returned `ReqLLM.Response`, whose `context` holds
only that message so the next user message can be appended directly:

    {:ok, first} = ReqLLM.generate_text("openai:gpt-5.4", "Draft a landing page.")
    {:ok, compacted} = ReqLLM.compact_context("openai:gpt-5.4", first.context)

    next = ReqLLM.Context.append(compacted.context, ReqLLM.Context.user("Add a booking form."))
    {:ok, follow_up} = ReqLLM.generate_text("openai:gpt-5.4", next)

A stored response can be compacted without replaying its messages:

    {:ok, compacted} =
      ReqLLM.compact_context("openai:gpt-5.4", nil, previous_response_id: first.id)

Server-side compaction during ordinary requests uses the `context_management`
provider option instead; see `ReqLLM.Compaction`.

# `compact_context!`

```elixir
@spec compact_context!(
  model_input(),
  ReqLLM.Context.t() | ReqLLM.Message.t() | [term()] | String.t() | nil,
  keyword()
) :: ReqLLM.Response.t() | no_return()
```

Compacts a conversation, raising on error. See `compact_context/3`.

# `context`

```elixir
@spec context([struct()] | struct() | String.t()) :: ReqLLM.Context.t()
```

Creates a context from a list of messages, a single message struct, or a string.

## Parameters

  * `messages` - List of Message structs, a single Message struct, or a string

## Examples

    messages = [
      ReqLLM.Context.system("You are helpful"),
      ReqLLM.Context.user("Hello!")
    ]
    ctx = ReqLLM.context(messages)
    # Now you can use Enum functions on the context
    user_msgs = ctx |> Enum.filter(&(&1.role == :user))

    # Single message struct
    ctx = ReqLLM.context(ReqLLM.Context.user("Hello!"))

    # String prompt
    ctx = ReqLLM.context("Hello!")

# `cosine_similarity`

```elixir
@spec cosine_similarity([number()], [number()]) :: float()
```

Calculates cosine similarity between two embedding vectors.

Equivalent to Vercel AI SDK's `cosineSimilarity()` function.
Returns a similarity score between -1 and 1, where:
- 1.0 indicates identical vectors (maximum similarity)
- 0.0 indicates orthogonal vectors (no similarity)
- -1.0 indicates opposite vectors (maximum dissimilarity)

## Parameters

  * `embedding_a` - First embedding vector (list of numbers)
  * `embedding_b` - Second embedding vector (list of numbers)

## Examples

    # Identical vectors
    ReqLLM.cosine_similarity([1.0, 0.0, 0.0], [1.0, 0.0, 0.0])
    #=> 1.0

    # Orthogonal vectors
    ReqLLM.cosine_similarity([1.0, 0.0], [0.0, 1.0])
    #=> 0.0

    # Opposite vectors
    ReqLLM.cosine_similarity([1.0, 0.0], [-1.0, 0.0])
    #=> -1.0

    # Similar vectors
    ReqLLM.cosine_similarity([0.5, 0.8, 0.3], [0.6, 0.7, 0.4])
    #=> 0.9487...

# `embed`

Generates embeddings for single or multiple text inputs.

Accepts either a single string or a list of strings, automatically handling
both cases using pattern matching.

## Parameters

  * `model_spec` - Model specification in various formats
  * `input` - Text string or list of text strings to generate embeddings for
  * `opts` - Additional options (keyword list)

## Options

  * `:dimensions` - Number of dimensions for embeddings
  * `:provider_options` - Provider-specific options
  * `:return_usage` - Return `%{embedding: vectors, usage: usage_map}` with token and cost metadata

## Examples

    # Single text input
    {:ok, embedding} = ReqLLM.embed("openai:text-embedding-3-small", "Hello world")
    #=> {:ok, [0.1, -0.2, 0.3, ...]}

    # Multiple text inputs
    {:ok, embeddings} = ReqLLM.embed(
      "openai:text-embedding-3-small",
      ["Hello", "World"]
    )
    #=> {:ok, [[0.1, -0.2, ...], [0.3, 0.4, ...]]}

    # With usage and cost data
    {:ok, %{embedding: embedding, usage: usage}} =
      ReqLLM.embed("openai:text-embedding-3-small", "Hello world", return_usage: true)

    Map.has_key?(usage, :total_cost)
    #=> true

# `evaluate`

```elixir
@spec evaluate(model_input(), String.t() | map() | list(), map(), keyword()) ::
  {:ok, ReqLLM.Response.t()} | {:error, term()}
```

Evaluates one state against named questions with an evaluation model.

This call does not use chat or `generate_object/4`. OpenAI evaluation models
use the Decisions API and accept text or JSON-compatible state.

# `evaluate!`

```elixir
@spec evaluate!(model_input(), String.t() | map() | list(), map(), keyword()) ::
  ReqLLM.Response.t() | no_return()
```

Same as `evaluate/4`, but raises on error.

# `evaluation_models`

```elixir
@spec evaluation_models() :: [String.t()]
```

Lists model specs with a callable evaluation adapter.

Use this list to select a model for `evaluate/4`. LLMDB capability and
execution metadata control model eligibility. A full inline model spec can
describe another unlisted model with a supported execution contract.

# `generate_image`

```elixir
@spec generate_image(
  String.t() | {atom(), keyword()} | struct(),
  String.t() | list() | ReqLLM.Context.t(),
  keyword()
) :: {:ok, ReqLLM.Response.t()} | {:error, term()}
```

Generates images using an AI model with full response metadata.

Returns a canonical `ReqLLM.Response` where images are represented as message content parts.

# `generate_image!`

```elixir
@spec generate_image!(
  String.t() | {atom(), keyword()} | struct(),
  String.t() | list() | ReqLLM.Context.t(),
  keyword()
) :: ReqLLM.Response.t() | no_return()
```

Generates images using an AI model, raising on error.

# `generate_object`

Generates structured data using an AI model with schema validation.

Equivalent to Vercel AI SDK's `generateObject()` function, this method
generates structured data according to a provided schema and validates
the output against that schema.

## Parameters

  * `model_spec` - Model specification in various formats
  * `messages` - Text prompt or list of messages
  * `schema` - Schema definition for structured output (NimbleOptions schema or JSON Schema map)
  * `opts` - Additional options (keyword list)

## Options

  * `:temperature` - Control randomness in responses (0.0 to 2.0)
  * `:max_tokens` - Limit the length of the response
  * `:receive_timeout` - Provider-transport inactivity timeout in milliseconds
  * `:total_timeout` - Optional whole-call deadline in milliseconds, including retries
  * `:stream_idle_timeout` - Optional semantic-progress timeout for streaming calls
  * `:provider_options` - Provider-specific options

## Examples

    # Generate a structured object
    schema = [
      name: [type: :string, required: true],
      age: [type: :pos_integer, required: true]
    ]
    {:ok, object} = ReqLLM.generate_object("anthropic:claude-3-sonnet", "Generate a person", schema)
    #=> {:ok, %{name: "John Doe", age: 30}}

    # Generate an array of objects (requires JSON Schema-capable provider like OpenAI)
    person_schema = ReqLLM.Schema.to_json([
      name: [type: :string, required: true],
      age: [type: :pos_integer, required: true]
    ])

    array_schema = %{"type" => "array", "items" => person_schema}

    {:ok, response} = ReqLLM.generate_object(
      "openai:gpt-4o",
      "Generate 3 heroes",
      array_schema
    )
    # Note: Array outputs currently require manual extraction from the response

    # Recommended: Use Zoi for cleaner array schema definition
    person = Zoi.object(%{
      name: Zoi.string(),
      age: Zoi.number()
    })

    array_schema = Zoi.array(person) |> ReqLLM.Schema.to_json()

    {:ok, response} = ReqLLM.generate_object(
      "openai:gpt-4o",
      "Generate 3 heroes",
      array_schema
    )

> **Note**: Top-level non-object outputs (arrays, enums) require raw JSON Schema
> and are only supported by providers with native JSON Schema capabilities (e.g., OpenAI).
> Most providers only support object-type schemas. For cleaner array schema definitions,
> consider using the Zoi library as shown above.

# `generate_object!`

Generates structured data using an AI model, returning only the object content.

This is a convenience function that extracts just the object from the response.
For access to usage metadata and other response data, use `generate_object/4`.

## Parameters

Same as `generate_object/4`.

## Examples

    ReqLLM.generate_object!("anthropic:claude-3-sonnet", "Generate a person", schema)
    #=> %{name: "John Doe", age: 30}

# `generate_text`

Generates text using an AI model with full response metadata.

Returns a canonical ReqLLM.Response which includes usage data, context, and metadata.
For simple text-only results, use `generate_text!/3`.

## Parameters

  * `model_spec` - Model specification in various formats
  * `messages` - Text prompt or list of messages
  * `opts` - Additional options (keyword list)

## Options

  * `:temperature` - Control randomness in responses (0.0 to 2.0)
  * `:max_tokens` - Limit the length of the response
  * `:top_p` - Nucleus sampling parameter
  * `:presence_penalty` - Penalize new tokens based on presence
  * `:frequency_penalty` - Penalize new tokens based on frequency
  * `:tools` - List of tool definitions
  * `:tool_choice` - Tool choice strategy
  * `:output` - A `ReqLLM.Output` descriptor for text, object, array, choice,
    or arbitrary JSON output
  * `:output_validation` - Final validation policy: `:compatible`, `:warn`,
    or `:strict`; omission preserves current V1 behavior
  * `:output_repair` - Optional one-shot local repair callback for an invalid
    complete structured output
  * `:system_prompt` - System prompt to prepend
  * `:receive_timeout` - Provider-transport inactivity timeout in milliseconds
  * `:total_timeout` - Optional whole-call deadline in milliseconds, including retries
  * `:stream_idle_timeout` - Optional semantic-progress timeout for streaming calls
  * `:provider_options` - Provider-specific options
  * `:pricing_context` - Caller-confirmed pricing facts, such as billing period,
    actual service tier, region, account plan, and cache duration. Used only for
    local usage pricing and never sent to the provider

## Examples

    {:ok, response} = ReqLLM.generate_text("anthropic:claude-3-sonnet", "Hello world")
    ReqLLM.Response.text(response)
    #=> "Hello! How can I assist you today?"

    # Access usage metadata
    ReqLLM.Response.usage(response)
    #=> %{input_tokens: 10, output_tokens: 8}

    output = ReqLLM.Output.array([name: [type: :string, required: true]])
    {:ok, response} = ReqLLM.generate_text(model, "Generate people", output: output)
    ReqLLM.Response.output(response, output)
    #=> [%{"name" => "Ada"}]

# `generate_text!`

Generates text using an AI model, returning only the text content.

This is a convenience function that extracts just the text from the response.
For access to usage metadata and other response data, use `generate_text/3`.
Raises on error. This function remains a text-only convenience; use
`generate_text/3` and `ReqLLM.Response.output/2` for structured descriptors.

## Parameters

Same as `generate_text/3`.

## Examples

    ReqLLM.generate_text!("anthropic:claude-3-sonnet", "Hello world")
    #=> "Hello! How can I assist you today?"

# `generate_video`

```elixir
@spec generate_video(model_input(), keyword(), keyword()) ::
  {:ok, ReqLLM.Video.Task.t()} | {:error, term()}
```

Submits a video generation task and returns a `ReqLLM.Video.Task` with the
provider `task_id`. The task runs asynchronously; poll with `query_video/3`
or block until completion with `wait_video/3`.

# `get_key`

```elixir
@spec get_key(atom() | String.t()) :: String.t() | nil
```

Gets an API key from application config or system environment.

Under Mix, `.env` is loaded at startup; releases skip it unless
`config :req_llm, load_dotenv: true` is set.

## Parameters

  * `key` - The configuration key (atom or string)

## Examples

    ReqLLM.get_key(:anthropic_api_key)
    ReqLLM.get_key("ANTHROPIC_API_KEY")  # Auto-loaded from .env

# `json_schema`

```elixir
@spec json_schema(keyword(), keyword()) :: map()
```

Creates a JSON schema object compatible with ReqLLM.

Equivalent to Vercel AI SDK's `jsonSchema()` helper, this function
creates schema objects for structured data generation and validation.

## Parameters

  * `schema` - NimbleOptions schema definition (keyword list)
  * `opts` - Additional options (optional)

## Options

  * `:validate` - Custom validation function (optional)

## Examples

    # Basic schema
    schema = ReqLLM.json_schema([
      name: [type: :string, required: true, doc: "User name"],
      age: [type: :integer, doc: "User age"]
    ])

    # Schema with custom validation
    schema = ReqLLM.json_schema(
      [email: [type: :string, required: true]],
      validate: fn value ->
        if String.contains?(value["email"], "@") do
          {:ok, value}
        else
          {:error, "Invalid email format"}
        end
      end
    )

# `model`

```elixir
@spec model(static_model_input()) :: {:ok, LLMDB.Model.t()} | {:error, term()}
```

Creates a model struct from various specifications.

## Parameters

  * `model_spec` - Model specification in various formats:
    - String format: `"anthropic:claude-3-sonnet"` (looks up in LLMDB catalog)
    - Map format: `%{id: "my-model", provider: :my_provider}` (inline model spec)
    - Tuple format: `{:anthropic, "claude-3-sonnet", temperature: 0.7}`
    - Model struct: `%LLMDB.Model{}`

## Inline Models

Three-element tuple options are applied as low-precedence defaults when the tuple is
passed directly to an operation. Explicit operation options win. `model/1` resolves
only model identity, so equivalent two- and three-element tuples return the same model.

For models not in the LLMDB catalog yet, use an inline model spec:

    model =
      ReqLLM.model!(%{
        id: "acme-chat-mini",
        provider: :acme,
        base_url: "http://localhost:4000/v1"
      })

    ReqLLM.generate_text(model, "Hello!")

This bypasses catalog lookup, enriches the model metadata, and returns `%LLMDB.Model{}`.
Inline maps are accepted for backwards compatibility, but `model!/1` is the recommended
entry point for advanced workflows because it validates the spec up front.

## Examples

    ReqLLM.model("anthropic:claude-3-sonnet")
    #=> {:ok, %LLMDB.Model{provider: :anthropic, model: "claude-3-sonnet"}}

    ReqLLM.model(%{id: "custom-model", provider: :my_provider})
    #=> {:ok, %LLMDB.Model{provider: :my_provider, id: "custom-model"}}

    ReqLLM.model!({:anthropic, id: "claude-3-sonnet"})
    #=> %LLMDB.Model{provider: :anthropic, model: "claude-3-sonnet"}

# `model!`

```elixir
@spec model!(model_input()) :: LLMDB.Model.t() | no_return()
```

Same as `model/1` but raises on error.

This is the recommended entry point for advanced inline model workflows because it
validates and normalizes the model spec up front.

# `ocr`

Extracts rich markdown from documents using OCR models.

Processes PDFs, images, and other documents through OCR models (e.g., Mistral OCR
on Vertex AI) and returns structured markdown with interleaved images.

## Parameters

  * `model_spec` - Model specification (e.g., `%{provider: :google_vertex, id: "mistral-ocr-2505"}`)
  * `document_binary` - Raw document bytes
  * `opts` - Options (`:include_images`, `:document_type`, `:provider_options`)

## Examples

    pdf = File.read!("report.pdf")
    model = ReqLLM.model!(%{provider: :google_vertex, id: "mistral-ocr-2505"})

    {:ok, result} = ReqLLM.ocr(model, pdf,
      provider_options: [region: "europe-west4"]
    )
    result.markdown  #=> "# Report Title\n\nContent..."

# `ocr!`

Extracts rich markdown from documents using OCR models. Raises on error.

# `ocr_file`

Process a file at the given path through an OCR model.
Detects document type from file extension.

# `ocr_file!`

Process a file through an OCR model. Raises on error.

# `plan`

```elixir
@spec plan(model_input(), :chat | :object, keyword()) ::
  {:ok, plan_diagnostic()} | {:error, term()}
```

Returns an experimental redacted diagnostic for a text request before execution.

`plan/3` uses the same internal planner as OpenAI Chat Completions, OpenAI Responses,
and Anthropic Messages execution. It resolves the selected surface and transport, but
does not encode a request, resolve credentials, access fixtures, or perform network I/O.

The returned map includes only the resolved provider/model ID, operation, named surface,
transport, relative route template, sorted option names, fallbacks, and warnings. Credential
and request-payload control options are omitted even from the option-name lists. ReqLLM 1.x
does not silently fall back to another surface, so `:fallbacks` is currently always empty.

This API is additive and experimental. Its diagnostic fields are not a stable compatibility
contract during ReqLLM 1.x, but existing generation and provider behavior are unaffected.

## Examples

    {:ok, diagnostic} =
      ReqLLM.plan("openai:gpt-4o-mini", :chat,
        max_tokens: 256,
        stream: true
      )

    diagnostic.surface
    #=> :openai_responses

    diagnostic.transport
    #=> :finch

    diagnostic.route
    #=> %{method: :post, path: "/responses"}

# `provider`

```elixir
@spec provider(atom()) ::
  {:ok, module()}
  | {:error,
     ReqLLM.Error.Invalid.Provider.t()
     | ReqLLM.Error.Invalid.Provider.NotImplemented.t()}
```

Gets a provider module from the registry.

## Parameters

  * `provider` - Provider identifier (atom)

## Examples

    ReqLLM.provider(:anthropic)
    #=> {:ok, ReqLLM.Providers.Anthropic}

    ReqLLM.provider(:unknown)
    #=> {:error, %ReqLLM.Error.Invalid.Provider{provider: :unknown}}

# `put_key`

```elixir
@spec put_key(atom(), term()) :: :ok
```

Stores an API key in application configuration.

Under Mix, `.env` is loaded at startup; releases skip it unless
`config :req_llm, load_dotenv: true` is set.
This function is useful for programmatic key management in tests or at runtime.

## Parameters

  * `key` - The configuration key (atom)
  * `value` - The value to store

## Examples

    ReqLLM.put_key(:anthropic_api_key, "sk-ant-...")

# `query_video`

```elixir
@spec query_video(model_input(), String.t(), keyword()) ::
  {:ok, ReqLLM.Video.Task.t()} | {:error, term()}
```

Queries the status of a video generation task by `task_id`.

# `rerank`

```elixir
@spec rerank(model_input(), keyword()) ::
  {:ok, ReqLLM.RerankResponse.t()} | {:error, term()}
```

Reranks documents against a query.

Returns a `ReqLLM.RerankResponse` with the most relevant documents first.

# `rerank!`

```elixir
@spec rerank!(model_input(), keyword()) :: ReqLLM.RerankResponse.t() | no_return()
```

Reranks documents against a query, raising on error.

# `retrieve_video_file`

```elixir
@spec retrieve_video_file(model_input(), String.t() | integer(), keyword()) ::
  {:ok, ReqLLM.Video.File.t()} | {:error, term()}
```

Resolves a video `file_id` to a time-limited download URL (V1 providers).

# `speak`

Generates speech audio from text using an AI model.

Inspired by the Vercel AI SDK's `generateSpeech()` function. Returns a
`ReqLLM.Speech.Result` with the generated audio binary, media type, and format.

## Parameters

  * `model_spec` - Model specification (e.g., `"openai:tts-1"`, `"openai:gpt-4o-mini-tts"`)
  * `text` - The text to convert to speech
  * `opts` - Additional options (keyword list)

## Options

  * `:voice` - Voice identifier (e.g., "alloy", "echo", "nova", "shimmer")
  * `:speed` - Speech speed multiplier (0.25 to 4.0)
  * `:output_format` - Audio format: `:mp3`, `:opus`, `:aac`, `:flac`, `:wav`, `:pcm`
  * `:language` - ISO-639-1 language code
  * `:provider_options` - Provider-specific options (e.g., `[instructions: "Speak slowly"]`)
  * `:receive_timeout` - HTTP timeout in milliseconds (default: 120_000)

## Examples

    # Basic usage
    {:ok, result} = ReqLLM.speak("openai:tts-1", "Hello world", voice: "alloy")
    File.write!("hello.mp3", result.audio)

    # High quality with options
    {:ok, result} = ReqLLM.speak("openai:tts-1-hd", "Welcome!",
      voice: "nova",
      speed: 1.2,
      output_format: :wav
    )

    # With instructions (gpt-4o-mini-tts)
    {:ok, result} = ReqLLM.speak("openai:gpt-4o-mini-tts", "Breaking news!",
      voice: "coral",
      provider_options: [instructions: "Speak in an excited tone"]
    )

# `speak!`

Generates speech audio from text, raising on error.

Same as `speak/3` but raises on error.

# `speak_detailed`

Generates speech and returns the unchanged speech result with sparse metadata
for the same provider call.

See `ReqLLM.Speech.speak_detailed/3` for details.

# `speak_detailed!`

Generates speech with call metadata, raising on error.

Same as `speak_detailed/3` but raises on error.

# `stream_image`

```elixir
@spec stream_image(
  String.t() | {atom(), keyword()} | struct(),
  String.t() | list() | ReqLLM.Context.t(),
  keyword()
) :: {:ok, ReqLLM.StreamResponse.t()} | {:error, term()}
```

Streams image generation, yielding preview frames before the final image.

Supported for OpenAI and Azure gpt-image models. Returns a
`ReqLLM.StreamResponse` whose stream carries, in order:

  * zero or more `:content_part` chunks (up to `:partial_images` of them) whose
    `:image` part has metadata `partial?: true` and `partial_image_index: n`;
    the chunk itself is flagged `stream_only?: true`
  * one `:content_part` chunk whose `:image` part has metadata `partial?: false`
  * a terminal `:meta` chunk with `usage` and `finish_reason: :stop`

`ReqLLM.StreamResponse.images/1` yields just the image parts, and
`ReqLLM.StreamResponse.to_response/1` builds a `ReqLLM.Response` holding only
the final image. Preview frames are opaque even with `background: :transparent`;
only the final image carries alpha.

Options are those of `generate_image/3` plus `:partial_images` (0-3), an
upper bound: fast generations may send fewer preview frames, or none.
`:source_image` (edits) and `n > 1` are rejected, and `generate_image/3`
rejects `:stream` and `:partial_images` in turn. `:receive_timeout` defaults
to the image timeout (120 s) rather than the streaming default, since the first
frame can take longer than 30 s at higher quality tiers.

Errors before the request starts return `{:error, error}`; failures mid-stream
surface through `to_response/1` as `{:error, _}` or raise while enumerating.
A provider event that carries no decodable image data ends the stream with an
error rather than waiting for the receive timeout.

Hosts that consume `ReqLLM.StreamResponse.events/1` see every preview frame as
an `:output_item` event carrying `partial?: true` and `stream_only?: true` in
its metadata, so they can render previews live and skip them when persisting.

## Examples

    {:ok, stream} = ReqLLM.stream_image("openai:gpt-image-1.5", "A red fox", partial_images: 2)

    chunks =
      Stream.each(stream.stream, fn
        %ReqLLM.StreamChunk{type: :content_part, content_part: part} ->
          IO.inspect(part.metadata)

        _chunk ->
          :ok
      end)

    {:ok, response} = ReqLLM.StreamResponse.to_response(%{stream | stream: chunks})
    [final] = ReqLLM.Response.images(response)

The stream can be consumed once. `Stream.each/2` above processes image parts
as `ReqLLM.StreamResponse.to_response/1` consumes the chunks and builds the
final response. Use `ReqLLM.StreamResponse.images/1` when you only need the
image parts.

# `stream_image!`

```elixir
@spec stream_image!(
  String.t() | {atom(), keyword()} | struct(),
  String.t() | list() | ReqLLM.Context.t(),
  keyword()
) :: ReqLLM.StreamResponse.t() | no_return()
```

Streams image generation, raising on error.

See `stream_image/3`. Only errors raised before the request starts are
surfaced here; failures mid-stream still surface while enumerating or through
`ReqLLM.StreamResponse.to_response/1`.

# `stream_object`

Streams structured data generation using an AI model with schema validation.

Equivalent to Vercel AI SDK's `streamObject()` function, this method
streams structured data generation according to a provided schema.

## Parameters

  * `model_spec` - Model specification in various formats
  * `messages` - Text prompt or list of messages
  * `schema` - Schema definition for structured output
  * `opts` - Additional options (keyword list)

## Options

  Same as `generate_object/4`.

## Examples

    # Stream structured object generation
    schema = [
      name: [type: :string, required: true],
      description: [type: :string, required: true]
    ]
    {:ok, stream} = ReqLLM.stream_object("anthropic:claude-3-sonnet", "Generate a character", schema)
    stream |> Enum.each(&IO.inspect/1)

# `stream_object!`

> This function is deprecated. Use stream_object/4 with StreamResponse instead.

**DEPRECATED**: This function will be removed in a future version.

The streaming API has been redesigned to return a composite `StreamResponse` struct
that provides both the stream and metadata. Use `stream_object/4` instead:

    {:ok, response} = ReqLLM.stream_object(model, messages, schema)
    response.stream |> Enum.each(&IO.inspect/1)

For simple object extraction, use:

    object = ReqLLM.StreamResponse.object(response)

## Legacy Parameters

Same as `stream_object/4`.

## Legacy Examples

    ReqLLM.stream_object!("anthropic:claude-3-sonnet", "Generate a character", schema)
    |> Enum.each(&IO.inspect/1)

# `stream_text`

Streams text generation using an AI model with concurrent metadata collection.

Returns a `ReqLLM.StreamResponse` that provides both real-time token streaming
and asynchronous metadata collection (usage, finish_reason). This enables
zero-latency content delivery while collecting billing/usage data concurrently.

Structured `:output` descriptors reuse the existing object-stream path.
Partial chunks are not final-schema validation results. Convert the response
once with `ReqLLM.StreamResponse.to_response/1`, then project the complete
value with `ReqLLM.Response.output/2`.

The streaming implementation uses Finch directly for production-grade performance
with automatic connection pooling and configurable checkout timeouts.

## Parameters

Same as `generate_text/3`.

## Returns

 * `{:ok, stream_response}` - StreamResponse with stream and metadata task
 * `{:error, reason}` - Request failed or invalid parameters

## Examples

   # Real-time streaming
   {:ok, response} = ReqLLM.stream_text("anthropic:claude-3-sonnet", "Tell me a story")
   response
   |> ReqLLM.StreamResponse.tokens()
   |> Stream.each(&IO.write/1)
   |> Stream.run()

   # Concurrent metadata collection
   usage = ReqLLM.StreamResponse.usage(response)
   #=> %{input_tokens: 15, output_tokens: 42, total_cost: 0.087}

   # Simple text collection
   text = ReqLLM.StreamResponse.text(response)

   # Backward compatibility
   {:ok, legacy_response} = ReqLLM.StreamResponse.to_response(response)

## StreamResponse Fields

 * `stream` - Lazy enumerable of `StreamChunk` structs for real-time consumption
 * `metadata_handle` - Concurrent handle collecting usage and finish_reason
 * `cancel` - Function to terminate streaming and cleanup resources
 * `model` - Model specification that generated this response
 * `context` - Updated conversation context including assistant's response

## Performance Notes

The stream is lazy and supports backpressure. Metadata collection happens
concurrently and won't block token delivery. Use `ReqLLM.StreamResponse.close/1`
for early termination or after direct metadata access to free resources.
High-concurrency streaming workloads can tune
Finch pool protocols with `:stream_pool_protocols`, capacity with
`:stream_pool_size` and `:stream_pool_count`, shard selection with
`:stream_pool_strategy`, and checkout behavior with `:stream_pool_timeout`
config or per-request `pool_timeout: ...`.

Use `total_timeout: ...` for a whole-call deadline that includes retries and
`stream_idle_timeout: ...` to fail a stream that stops making semantic
progress. Existing `receive_timeout` behavior remains the provider-transport
inactivity control. See the configuration guide for precedence and terminal
error semantics.

# `stream_text!`

> This function is deprecated. Use stream_text/3 with StreamResponse instead.

**DEPRECATED**: This function will be removed in a future version.

The streaming API has been redesigned to return a composite `StreamResponse` struct
that provides both the stream and metadata. Use `stream_text/3` instead:

    {:ok, response} = ReqLLM.stream_text(model, messages)
    response.stream |> Enum.each(&IO.write/1)

For simple text extraction, use:

    text = ReqLLM.StreamResponse.text(response)

## Legacy Behavior

This function currently returns `:ok` and logs a deprecation warning.
It will be formally removed in the next major version.

# `tool`

```elixir
@spec tool(keyword()) :: ReqLLM.Tool.t()
```

Creates a Tool struct for AI model function calling.

Equivalent to Vercel AI SDK's `tool()` helper, providing type-safe tool
definitions with parameter validation. This is a convenience function
for creating ReqLLM.Tool structs.

## Parameters

  * `opts` - Tool definition options (keyword list)

## Options

  * `:name` - Tool name (required, must be valid identifier)
  * `:description` - Tool description for AI model (required)
  * `:parameters` - Parameter schema as NimbleOptions keyword list (optional)
  * `:callback` - Callback function or MFA tuple (required)

## Examples

    # Simple tool with no parameters
    tool = ReqLLM.tool(
      name: "get_time",
      description: "Get the current time",
      callback: fn _args -> {:ok, DateTime.utc_now()} end
    )

    # Tool with parameters
    weather_tool = ReqLLM.tool(
      name: "get_weather",
      description: "Get current weather for a location",
      parameters: [
        location: [type: :string, required: true, doc: "City name"],
        units: [type: :string, default: "metric", doc: "Temperature units"]
      ],
      callback: {WeatherAPI, :fetch_weather}
    )

# `transcribe`

Transcribes audio using an AI model.

Inspired by the Vercel AI SDK's `transcribe()` function. Returns a
`ReqLLM.Transcription.Result` with transcribed text, timing segments,
detected language, and duration.

## Parameters

  * `model_spec` - Model specification (e.g., `"openai:whisper-1"`, `"groq:whisper-large-v3"`)
  * `audio` - Audio input:
    - `String.t()` - File path to an audio file
    - `{:binary, binary(), String.t()}` - Raw audio data with media type
    - `{:base64, String.t(), String.t()}` - Base64-encoded audio with media type
  * `opts` - Additional options (keyword list)

## Options

  * `:language` - Language hint in ISO-639-1 format (e.g., "en")
  * `:provider_options` - Provider-specific options
  * `:receive_timeout` - HTTP timeout in milliseconds (default: 120_000)

## Examples

    # From file path
    {:ok, result} = ReqLLM.transcribe("openai:whisper-1", "speech.mp3")
    result.text #=> "Hello world"

    # From binary data
    data = File.read!("speech.mp3")
    {:ok, result} = ReqLLM.transcribe("openai:whisper-1", {:binary, data, "audio/mpeg"})

    # With options
    {:ok, result} = ReqLLM.transcribe("openai:whisper-1", "speech.mp3",
      language: "en",
      provider_options: [prompt: "Technical terms: ReqLLM, Elixir"]
    )

# `transcribe!`

Transcribes audio, raising on error.

Same as `transcribe/3` but raises on error.

# `transcribe_detailed`

Transcribes audio with sparse metadata for the same provider call.

The returned `ReqLLM.Transcription.DetailedResult` wraps the unchanged
`ReqLLM.Transcription.Result`. Unavailable metadata is omitted.

# `transcribe_detailed!`

Transcribes audio with call metadata, raising on error.

Same as `transcribe_detailed/3` but raises on error.

# `upload_video_file`

```elixir
@spec upload_video_file(model_input(), binary(), keyword()) ::
  {:ok, ReqLLM.Video.File.t()} | {:error, term()}
```

Uploads a file to the provider platform for use as video generation input.

# `wait_video`

```elixir
@spec wait_video(model_input(), String.t(), keyword()) ::
  {:ok, ReqLLM.Video.Task.t()} | {:error, term()}
```

Polls a video generation task until it reaches a terminal state.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
