ReqLLM. Provider. ResponseBuilder behaviour
(ReqLLM v1.13.0)
View Source
Behaviour for provider-specific Response assembly from StreamChunks.
Both streaming and non-streaming paths converge here, ensuring identical Response structs regardless of the code path taken.
Why This Exists
Different LLM providers have subtle differences in how they represent responses, tool calls, finish reasons, and metadata. Previously, these differences were handled in multiple places (streaming vs non-streaming, provider-specific decoders), leading to behavioral inconsistencies.
This behaviour centralizes provider-specific Response assembly logic, ensuring that:
- Streaming and non-streaming produce identical Response structs
- Provider quirks are handled in one place per provider
- New providers have a clear extension point
Provider-Specific Handling
- Anthropic: Ensures content blocks are never empty (API requirement)
- OpenAI Responses API: Propagates
response_idfor stateless multi-turn - Google: Detects
functionCallto set correct finish_reason - Default (OpenAI Chat): Standard handling for OpenAI-compatible providers
Usage
Both streaming and non-streaming paths should:
Decode wire format to
[StreamChunk.t()]Collect metadata (usage, finish_reason, provider-specific)
Call the appropriate builder:
builder = ResponseBuilder.for_model(model) {:ok, response} = builder.build_response(chunks, metadata, opts)
Summary
Callbacks
Build a Response struct from accumulated StreamChunks and metadata.
Functions
Get the appropriate ResponseBuilder module for a given model.
Types
@type chunks() :: [ReqLLM.StreamChunk.t()]
@type opts() :: [context: ReqLLM.Context.t(), model: LLMDB.Model.t()]
Callbacks
@callback build_response(chunks(), metadata(), opts()) :: {:ok, ReqLLM.Response.t()} | {:error, term()}
Build a Response struct from accumulated StreamChunks and metadata.
This is the central point where provider-specific logic is applied to produce a consistent Response struct.
Parameters
chunks- List of StreamChunk structs from stream processing or converted from non-streaming responsemetadata- Map containing usage, finish_reason, and provider-specific metadata collected during response processingopts- Keyword list with:contextand:model
Returns
{:ok, Response.t()}- Successfully built response{:error, term()}- Build failed
Functions
@spec for_model(LLMDB.Model.t()) :: module()
Get the appropriate ResponseBuilder module for a given model.
Routes to provider-specific builders based on model metadata:
- Anthropic models →
Anthropic.ResponseBuilder - Google/Gemini models →
Google.ResponseBuilder - Vertex Claude models →
Anthropic.ResponseBuilder - OpenAI Responses API models →
OpenAI.ResponsesAPI.ResponseBuilder - All others →
Provider.Defaults.ResponseBuilder
Examples
iex> model = ReqLLM.model!("anthropic:claude-3-haiku-20240307")
iex> ResponseBuilder.for_model(model)
ReqLLM.Providers.Anthropic.ResponseBuilder
iex> model = ReqLLM.model!("openai:gpt-4o")
iex> ResponseBuilder.for_model(model)
ReqLLM.Provider.Defaults.ResponseBuilder