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

Default implementations for common provider behavior patterns.

This module extracts shared logic between OpenAI-compatible providers (OpenAI, Groq, etc.)
into reusable runtime functions and a `__using__` macro that provides default callback
implementations.

## Usage

    defmodule MyProvider do
      @behaviour ReqLLM.Provider
      use ReqLLM.Provider.DSL, [...]
      use ReqLLM.Provider.Defaults

      # All default implementations are available and overridable
      # Override only what you need to customize
    end

## Design Principles

- Runtime functions are pure and testable
- Provider module is passed as first argument to access attributes
- All defaults are `defoverridable` for selective customization
- Providers can override individual methods or use helper functions directly

## Default Implementations

The following methods get default implementations:

- `prepare_request/4` - Standard chat/object/embedding request preparation
- `attach/3` - OAuth Bearer authentication and standard pipeline steps
- `build_body/1` - OpenAI-compatible request body construction
- `encode_body/1` - OpenAI-compatible request body encoding
- `decode_response/1` - Standard response decoding with error handling
- `extract_usage/2` - Usage extraction from standard `usage` field
- `translate_options/3` - No-op translation (pass-through)
- `decode_stream_event/2` - OpenAI-compatible SSE event decoding
- `attach_stream/4` - OpenAI-compatible streaming request building
- `display_name/0` - Human-readable provider name from provider_id

## Runtime Functions

All default implementations delegate to pure runtime functions that can be
called independently:

- `prepare_chat_request/4`
- `prepare_object_request/4`
- `prepare_json_schema_object_request/5`
- `prepare_embedding_request/4`
- `default_attach/3`
- `default_build_body/1`
- `default_encode_body/1`
- `default_decode_response/1`
- `default_extract_usage/2`
- `default_translate_options/3`
- `default_decode_stream_event/2`
- `default_attach_stream/5`
- `default_display_name/1`

## Customization Examples

    # Override just the body encoding while keeping everything else
    def build_body(request) do
      request
      |> ReqLLM.Provider.Defaults.default_build_body()
      |> Map.put(:my_provider_field, request.options[:my_provider_field])
    end

    def encode_body(request) do
      request
      |> ReqLLM.Provider.Defaults.encode_body_from_map(build_body(request))
      |> add_custom_headers()
    end

    # Use runtime functions directly for testing
    test "encoding produces correct format" do
      request = build_test_request()
      body = ReqLLM.Provider.Defaults.default_build_body(request)
      assert body[:model]
    end

# `__using__`
*macro* 

Provides default implementations for common provider patterns.

All methods are `defoverridable`, so providers can selectively override
only the methods they need to customize.

# `build_openai_chat_body`

Build a complete OpenAI-style chat body from a Req request.

This helper function encodes context, adds common options (temperature, max_tokens, etc.),
converts tools to OpenAI schema, and handles stream flags. Providers can use this as a
starting point and add provider-specific fields.

## Example

    def encode_body(req) do
      body = Defaults.build_openai_chat_body(req)
      |> Map.put(:my_provider_field, req.options[:my_provider_field])

      req
      |> Req.Request.put_header("content-type", "application/json")
      |> Map.put(:body, Jason.encode!(body))
    end

# `decode_response_body_openai_format`

```elixir
@spec decode_response_body_openai_format(map(), LLMDB.Model.t()) ::
  {:ok, ReqLLM.Response.t()} | {:error, term()}
```

Decodes OpenAI-format response body to ReqLLM.Response.

This function moves the logic from ReqLLM.Response.Codec.Map directly into
Provider.Defaults for the protocol removal refactoring.

# `default_attach`

```elixir
@spec default_attach(module(), Req.Request.t(), term(), keyword()) :: Req.Request.t()
```

# `default_attach_stream`

```elixir
@spec default_attach_stream(
  module(),
  LLMDB.Model.t(),
  ReqLLM.Context.t(),
  keyword(),
  atom()
) :: {:ok, Finch.Request.t()} | {:error, Exception.t()}
```

Default implementation of attach_stream/4.

Builds complete streaming requests using OpenAI-compatible format and returns
a complete Finch.Request.t() ready for streaming execution.

# `default_build_body`

```elixir
@spec default_build_body(Req.Request.t()) :: map()
```

Default body building for OpenAI-compatible APIs.

# `default_decode_response`

```elixir
@spec default_decode_response({Req.Request.t(), Req.Response.t()}) ::
  {Req.Request.t(), Req.Response.t() | Exception.t()}
```

Default response decoding with success/error handling.

# `default_decode_stream_event`

```elixir
@spec default_decode_stream_event(map(), LLMDB.Model.t()) :: [ReqLLM.StreamChunk.t()]
```

Default SSE event decoding for OpenAI-compatible providers.

This function moves the logic from ReqLLM.Response.Codec.Map directly into
Provider.Defaults for the protocol removal refactoring.

# `default_display_name`

```elixir
@spec default_display_name(module()) :: String.t()
```

Default display name implementation.

Returns a human-readable display name based on the provider_id from DSL,
or falls back to capitalizing the module name.

# `default_encode_body`

```elixir
@spec default_encode_body(Req.Request.t()) :: Req.Request.t()
```

Default body encoding for OpenAI-compatible APIs.

# `default_extract_usage`

```elixir
@spec default_extract_usage(term(), LLMDB.Model.t() | nil) ::
  {:ok, map()} | {:error, term()}
```

Default usage extraction from standard `usage` field.

# `default_translate_options`

```elixir
@spec default_translate_options(atom(), LLMDB.Model.t(), keyword()) ::
  {keyword(), [String.t()]}
```

Default options translation (pass-through).

# `encode_body_from_map`

```elixir
@spec encode_body_from_map(Req.Request.t(), map()) :: Req.Request.t()
```

Encode a request body map as JSON and attach it to the Req request.

# `encode_context_to_openai_format`

```elixir
@spec encode_context_to_openai_format(ReqLLM.Context.t(), String.t()) :: map()
```

Encodes ReqLLM.Context to OpenAI-compatible format.

This function moves the logic from ReqLLM.Context.Codec.Map directly into
Provider.Defaults for the protocol removal refactoring.

# `encode_context_to_openai_format`

```elixir
@spec encode_context_to_openai_format(ReqLLM.Context.t(), String.t(), keyword()) ::
  map()
```

# `extra_option_keys`

```elixir
@spec extra_option_keys(module()) :: [atom()]
```

Returns extra option keys that should be registered on Req requests.

# `fetch_api_key_and_extra_options`

```elixir
@spec fetch_api_key_and_extra_options(module(), LLMDB.Model.t(), keyword()) ::
  {binary(), [atom()]}
```

Fetches API key and extra common option keys.

# `filter_req_opts`

```elixir
@spec filter_req_opts(keyword()) :: keyword()
```

Filters out internal ReqLLM keys that should not be passed to Req.

These keys are used by ReqLLM for internal processing but are not valid Req options.

# `finch_option`

```elixir
@spec finch_option(Req.Request.t(), keyword()) :: keyword()
```

# `media_type_to_extension`

```elixir
@spec media_type_to_extension(String.t()) :: String.t()
```

Maps an audio media type to a file extension.

# `merge_finch_options`

```elixir
@spec merge_finch_options(keyword(), keyword()) :: keyword()
```

# `normalize_openai_annotations`

```elixir
@spec normalize_openai_annotations(term()) :: [map()]
```

Normalize OpenAI annotation maps to the flat Responses API shape.

Chat Completions nests url_citation fields under `"url_citation"`, while the
Responses API returns them flat. Both normalize to the flat form so
`ReqLLM.Response.annotations/1` yields a single shape across API surfaces:

    %{"type" => "url_citation", "url" => ..., "title" => ..., "start_index" => ..., "end_index" => ...}

Other annotation types (`file_citation`, `file_path`, ...) are already flat
and pass through unchanged. Non-list input returns `[]`.

# `prepare_chat_request`

```elixir
@spec prepare_chat_request(module(), term(), term(), keyword()) ::
  {:ok, Req.Request.t()} | {:error, Exception.t()}
```

Prepares a chat completion request.

# `prepare_embedding_request`

```elixir
@spec prepare_embedding_request(module(), term(), term(), keyword()) ::
  {:ok, Req.Request.t()} | {:error, Exception.t()}
```

Prepares an embedding generation request.

# `prepare_json_schema_object_request`

```elixir
@spec prepare_json_schema_object_request(
  module(),
  term(),
  term(),
  keyword(),
  keyword()
) ::
  {:ok, Req.Request.t()} | {:error, Exception.t()}
```

Prepares an object generation request using a native `json_schema` response format.

OpenAI-compatible local servers such as Ollama and LM Studio constrain output through
`response_format` instead of a synthetic tool. The compiled schema is placed in
`provider_options[:response_format]` regardless of the container shape the caller
used, a token limit default is applied, and the request is prepared as a chat request
with `operation: :object`. Pass `strict: true` to request strict schema adherence.

# `prepare_object_request`

```elixir
@spec prepare_object_request(module(), term(), term(), keyword()) ::
  {:ok, Req.Request.t()} | {:error, Exception.t()}
```

Prepares an object generation request using tool calling.

# `prepare_request`

```elixir
@spec prepare_request(module(), atom(), term(), term(), keyword()) ::
  {:ok, Req.Request.t()} | {:error, Exception.t()}
```

Runtime implementation of prepare_request/4.

Delegates to operation-specific preparation functions.

# `prepare_speech_request`

```elixir
@spec prepare_speech_request(module(), term(), String.t(), keyword()) ::
  {:ok, Req.Request.t()} | {:error, Exception.t()}
```

Prepares a text-to-speech request.

Sends a JSON POST to the provider's speech endpoint. The response is raw audio
binary, so this bypasses the standard decode_response pipeline.

# `prepare_transcription_request`

```elixir
@spec prepare_transcription_request(module(), term(), binary(), keyword()) ::
  {:ok, Req.Request.t()} | {:error, Exception.t()}
```

Prepares a transcription (speech-to-text) request.

Sends audio data as multipart/form-data to the provider's transcription endpoint.
The audio is sent as a file part along with the model name and any provider options.

# `validate_image_only_attachments`

```elixir
@spec validate_image_only_attachments(ReqLLM.Context.t()) ::
  :ok | {:error, String.t()}
```

Validates that a context contains only image file attachments.

Returns `:ok` if all file attachments are images (JPEG, PNG, GIF, WebP),
or `{:error, reason}` with a descriptive message if non-image files are found.

This is used by providers like OpenAI and xAI that only support image attachments
via their Chat Completions API.

---

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