The ResponseBuilder behaviour centralizes provider-specific response assembly logic. This ensures that both streaming and non-streaming paths produce identical Response structs and that provider-specific quirks are handled in one place.
Routing Logic
ResponseBuilder.for_model/1 routes to specific builders:
- Anthropic models $\rightarrow$
Anthropic.ResponseBuilder - Google/Vertex models $\rightarrow$
Google.ResponseBuilder - OpenAI Responses API models $\rightarrow$
OpenAI.ResponsesAPI.ResponseBuilder - All others $\rightarrow$
Provider.Defaults.ResponseBuilder
When to implement a custom ResponseBuilder
Most providers can use Provider.Defaults.ResponseBuilder. Implement a custom one if you need to handle:
- Content block requirements: e.g., Anthropic requiring non-empty content blocks.
- Provider-specific metadata: e.g., OpenAI Responses API needing
response_id. - Finish reason detection: e.g., Google needing to detect
functionCall. - Custom tool call handling: Non-standard tool call representations.
Implementation Pattern
You can delegate to the default builder and then apply post-processing:
@impl ReqLLM.Provider.ResponseBuilder
def build_response(chunks, metadata, opts) do
with {:ok, response} <- DefaultBuilder.build_response(chunks, metadata, opts) do
response = apply_provider_quirks(response, metadata)
{:ok, response}
end
end
defmodule ReqLLM.Providers.Zephyr.ResponseBuilder do
@moduledoc "Custom ResponseBuilder for Zephyr provider."
@behaviour ReqLLM.Provider.ResponseBuilder
alias ReqLLM.Provider.Defaults.ResponseBuilder, as: DefaultBuilder
@impl true
def build_response(chunks, metadata, opts) do
# Delegate to default builder for standard processing
with {:ok, response} <- DefaultBuilder.build_response(chunks, metadata, opts) do
# Apply provider-specific post-processing
response = apply_zephyr_quirks(response, metadata)
{:ok, response}
end
end
defp apply_zephyr_quirks(response, metadata) do
# Example: Zephyr includes session_id in metadata
case metadata[:session_id] do
nil -> response
sid -> %{response | provider_meta: Map.put(response.provider_meta, :session_id, sid)}
end
end
end