BeamWeaver Models
Models are the reasoning engines used directly by applications and by BeamWeaver agents. The public API is Elixir-native: structs, behaviours, keyword options, Task-backed async helpers, Enumerable streams, typed stream envelopes, telemetry, and tagged errors.
Basic Usage
Models can be used in two places:
-
with agents, where the agent loop decides when to call the model and tools
-
standalone, where application code calls the model directly
The same %BeamWeaver.Core.Message{} values work in both places.
Initialize A Model
Provider secrets and endpoint defaults are application config. A typical config/runtime.exs loads them from the OS environment once:
import Config
config :beam_weaver,
openai: [api_key: System.fetch_env!("OPENAI_API_KEY")],
anthropic: [api_key: System.fetch_env!("ANTHROPIC_API_KEY")],
xai: [api_key: System.fetch_env!("XAI_API_KEY")],
google: [api_key: System.fetch_env!("GOOGLE_API_KEY")],
deepseek: [api_key: System.fetch_env!("DEEPSEEK_API_KEY")],
zai: [api_key: System.fetch_env!("ZAI_API_KEY")]
BeamWeaver.Models.init_chat_model/2 accepts provider-prefixed identifiers. Unprefixed gpt-* and o* names infer OpenAI. Unprefixed claude-* names infer Anthropic. Unprefixed grok-* names infer xAI. Gemini, DeepSeek, and GLM models must use the explicit google:, deepseek:, and zai: prefixes.
{:ok, model} =
BeamWeaver.Models.init_chat_model("openai:gpt-5.4",
temperature: 0.2,
timeout: 30_000
)
Anthropic:
{:ok, model} =
BeamWeaver.Models.init_chat_model("anthropic:claude-opus-5",
effort: :xhigh,
max_tokens: 64_000
)
xAI:
{:ok, model} = BeamWeaver.Models.init_chat_model("xai:grok-4.6")
Google:
{:ok, model} =
BeamWeaver.Models.init_chat_model("google:gemini-3.7-flash",
thinking_level: :medium
)
DeepSeek:
{:ok, model} =
BeamWeaver.Models.init_chat_model("deepseek:deepseek-v4-flash",
reasoning_effort: :low
)
Z.ai:
{:ok, model} = BeamWeaver.Models.init_chat_model("zai:glm-5.2")
Fake model for tests:
{:ok, model} =
BeamWeaver.Models.init_chat_model("fake:chat",
response: BeamWeaver.Core.Message.assistant("fixture response")
)
Provider structs are available when you want direct control:
model =
BeamWeaver.OpenAI.ChatModel.new(
model: "gpt-5.4",
reasoning_effort: :low,
timeout: 30_000
)
OpenAI defaults to the Responses API. Use Chat Completions explicitly when that API shape is required:
{:ok, model} =
BeamWeaver.Models.init_chat_model("openai:gpt-5.4-mini",
api: :chat_completions
)
Supported Providers And Models
Provider scope is intentionally narrow:
-
BeamWeaver.OpenAI.ChatModelfor OpenAI Responses API -
BeamWeaver.OpenAI.ChatCompletionsModelfor OpenAI Chat Completions -
BeamWeaver.OpenAI.EmbeddingModelfor OpenAI embeddings -
BeamWeaver.Anthropic.ChatModelfor Anthropic Messages API -
BeamWeaver.Google.ChatModelfor Gemini Developer API -
BeamWeaver.DeepSeek.ChatModelfor DeepSeek Chat Completions -
BeamWeaver.DeepSeek.ResponsesModelfor DeepSeek Responses -
BeamWeaver.Moonshot.ChatModelfor Moonshot/Kimi Chat Completions -
BeamWeaver.XAI.ChatModelfor xAI Responses API -
BeamWeaver.XAI.ChatCompletionsModelfor xAI Chat Completions -
BeamWeaver.XAI.EmbeddingModelfor xAI embeddings -
BeamWeaver.ZAI.ChatModelfor Z.ai GLM-5.2 Chat Completions -
BeamWeaver.Models.FakeChatModelandFakeEmbeddingModelfor tests
Checked-in model profiles cover common OpenAI, Anthropic, Google Gemini, DeepSeek, Moonshot/Kimi, xAI, and Z.ai families. DeepSeek requires explicit deepseek:deepseek-v4-flash or deepseek:deepseek-v4-pro; Chat Completions is the default and both models also support Responses. Moonshot chat supports moonshot:kimi-k3, moonshot:kimi-k2.7-code, moonshot:kimi-k2.7-code-highspeed, moonshot:kimi-k2.6, and moonshot:kimi-k2.5. xAI chat defaults to grok-4.6; current checked-in xAI profiles also include grok-4.5, grok-4.3, grok-4.20-0309-reasoning, grok-4.20-0309-non-reasoning, grok-4.20-multi-agent-0309, grok-build-0.1, and embedding model v1. Z.ai chat currently supports only zai:glm-5.2. Anthropic includes anthropic:claude-opus-5 and anthropic:claude-sonnet-5. Opus 5 uses adaptive thinking by default and supports effort from :low through :max; disabling thinking is valid only at :high or below. Manual enabled thinking budgets are rejected before transport for models that only support adaptive thinking. OpenAI profiles include openai:gpt-5.6-sol, openai:gpt-5.6-terra, and openai:gpt-5.6-luna; openai:gpt-5.6 is the official alias for Sol. All three advertise a 1.05M-token context window, 128K maximum output, text and image input, function tools, structured output, streaming, and reasoning efforts from none through max. BeamWeaver keeps its existing gpt-5.5 constructor default so adding the family does not silently migrate callers. Future OpenAI gpt-*/o*, Anthropic claude-*, explicit Google google:gemini-*, explicit Moonshot moonshot:kimi-*, and xAI grok-* identifiers use permissive fallback profiles unless they are known deprecated/unsupported slugs. Bare gemini-*, kimi-*, and glm-* IDs are rejected so provider routing is explicit.
Composed Agent Model Recommendations
For composed agents, choose a model with reliable tool calling, structured output, streaming, and token-budget support. The Composed Agent model matrix maps supported BeamWeaver model strings to the capabilities that matter for planning, tools, virtual filesystems, subagents, structured output, human review, streaming, and context management.
Recommended starting points:
| Provider family | BeamWeaver model strings |
|---|---|
| OpenAI GPT |
openai:gpt-5.6-sol, openai:gpt-5.6-terra, openai:gpt-5.6-luna, openai:gpt-5.4-mini
|
| Anthropic Claude |
anthropic:claude-opus-5, anthropic:claude-sonnet-5, anthropic:claude-sonnet-4-6, anthropic:claude-opus-*, anthropic:claude-haiku-*
|
| Google Gemini |
google:gemini-3.7-flash, google:gemini-3.6-flash, google:gemini-3.5-flash-lite, explicit google:gemini-* profiles
|
| DeepSeek V4 |
deepseek:deepseek-v4-flash, deepseek:deepseek-v4-pro
|
| Moonshot/Kimi |
moonshot:kimi-k3, moonshot:kimi-k2.7-code, moonshot:kimi-k2.7-code-highspeed, moonshot:kimi-k2.6, moonshot:kimi-k2.5
|
| xAI Grok |
xai:grok-4.6, xai:grok-4.5, xai:grok-4.3
|
| Z.ai GLM |
zai:glm-5.2
|
Use the matrix as capability guidance, then validate model quality against your own prompts, tools, latency, and cost constraints.
Exact integer pricing
Applications that persist budgets or reconcile costs can use BeamWeaver.Models.UsageCost.calculate_usd_micros/2. The function accepts a closed integer pricing profile and normalized token counts, performs no floating-point arithmetic, and rounds the combined result once with the declared half_up rule.
pricing = %{
schema_version: 1,
currency: "USD",
rounding: "half_up",
dimensions: [
%{name: "input_tokens", unit_size: 1_000_000, unit_price_usd_micros: 2_000_000},
%{name: "output_tokens", unit_size: 1_000_000, unit_price_usd_micros: 8_000_000}
]
}
{:ok, %{cost_micros: cost}} =
BeamWeaver.Models.UsageCost.calculate_usd_micros(pricing, %{
input_tokens: 100,
output_tokens: 10
})
The API returns an error for missing usage, duplicate or unknown dimensions, invalid units, and cached-input counts greater than total input. Pricing-profile selection, effective dates, budgets, and whether an estimate may authorize work remain application policy.
Model profiles expose tool_call_streaming separately from streaming and tool_calling. Use it when a UI or replay test needs incremental tool argument chunks instead of waiting for the final assistant message. Current checked-in profiles enable it for OpenAI GPT and xAI Grok chat profiles with provider stream evidence; unsupported or unknown provider fallbacks leave it false.
Key Methods
Use the behaviour modules as the stable call boundary:
-
BeamWeaver.Core.ChatModel.invoke/3 -
BeamWeaver.Core.ChatModel.stream/3 -
BeamWeaver.Core.ChatModel.stream_events/3 -
BeamWeaver.Core.ChatModel.stream_typed_events/3 -
BeamWeaver.Core.ChatModel.batch/3 -
BeamWeaver.Core.ChatModel.async_invoke/3 -
BeamWeaver.Core.ChatModel.async_batch/3 -
BeamWeaver.Core.EmbeddingModel.embed_documents/3 -
BeamWeaver.Core.EmbeddingModel.embed_query/3
Provider modules may expose additional provider-specific helpers, such as stream_response/3, provider lifecycle stream_events/3, count_tokens/3, and deferred request helpers. Use stream_typed_events/3 when standalone model callers need normalized BeamWeaver envelopes with tokens, message chunks, reasoning chunks, tool-call chunks, errors, and done events.
Parameters
Common chat model options include:
| Option | Meaning |
|---|---|
:model
| provider model identifier |
:api_key
| provider API key, usually from environment |
:temperature
| sampling temperature where supported |
:max_tokens, :max_output_tokens
| output token limit |
:timeout
| transport receive timeout in milliseconds |
:top_p, :frequency_penalty, :presence_penalty, :seed
| standard sampling controls |
:tools, :tool_choice, :parallel_tool_calls
| tool calling controls |
:response_format, :structured_output
| structured output controls |
:metadata, :user, :service_tier
| provider request metadata |
:model_kwargs, :extra_body
| explicit provider escape hatches |
:transport, :transport_opts
| transport boundary for live, fake, or replay calls |
:profile, :profile_registry, :param_policy
| capability metadata and validation |
:max_bytes, :max_items, :max_depth
| normalized provider-response bounds; defaults are 16 MiB, 100,000 items, and depth 64 |
:max_tool_calls
| provider request limit when supported and normalized-response ceiling; normalization defaults to 256 when absent |
:max_response_bytes
| maximum collected HTTP response body; defaults to 16 MiB |
:max_stream_events, :max_stream_bytes, :max_stream_value_bytes
| live provider-stream event, transport-byte, and decoded-value bounds |
Provider constructors use native option names. Use :model, not :model_name. Use :endpoint or a custom transport for exact routing. The xAI constructors also accept :base_url as an upstream alias.
Known profiles default to strict parameter validation. Unknown future profiles are permissive so new model names can work before profile data catches up.
Provider Response Validation
BeamWeaver.Core.ChatModel.invoke/3 validates a provider message before and after metadata normalization. Invalid message structure, oversized decoded values, excessive nesting, and excessive tool calls return %BeamWeaver.Core.Error{type: :invalid_provider_response} instead of entering agent or graph state.
The limits are ordinary invocation options, so applications may lower them for their workload. BeamWeaver's defaults are hard safety ceilings for normal responses, not token-budget or provider-quota controls. Provider token limits such as :max_tokens remain separate request options.
Provider Profiles
BeamWeaver.Agent.ProviderProfile packages model-construction defaults for agent model strings. It is the BeamWeaver equivalent of Deep Agents provider profiles, scoped to BeamWeaver.Agent.build/1 and the use BeamWeaver.Agent capability pipeline.
Provider profiles apply when the agent receives a binary or atom model identifier:
alias BeamWeaver.Agent.ProviderProfile
:ok =
ProviderProfile.register_provider_profile(
"openai",
ProviderProfile.new(init_kwargs: [temperature: 0])
)
:ok =
ProviderProfile.register_provider_profile(
"openai:gpt-5.4",
ProviderProfile.new(init_kwargs: [reasoning_effort: :medium])
)
{:ok, agent} =
BeamWeaver.Agent.build(
model: "openai:gpt-5.4",
tools: []
)
Provider-level profiles such as "openai" apply to every model for that provider. Model-level profiles such as "openai:gpt-5.4" merge on top of the provider-level profile. Caller options still win over profile defaults.
BeamWeaver includes a built-in "openai" provider profile that sets use_responses_api: true for agent model strings. Passing a preconfigured model struct bypasses provider profile initialization because the model has already been built:
model =
BeamWeaver.Models.init_chat_model!("openai:gpt-5.4",
temperature: 0.2
)
{:ok, agent} = BeamWeaver.Agent.build(model: model, tools: [])
Connection Resilience
BeamWeaver does not automatically retry every provider request six times the way LangChain does. The live Req/Finch transport disables implicit retries. In agents, attach retry and fallback policies as middleware:
defmodule MyApp.Agent do
use BeamWeaver.Agent
model BeamWeaver.Models.init_chat_model!("openai:gpt-5.4",
timeout: 120_000
)
middleware do
use BeamWeaver.Agent.Middleware.ModelRetry,
policy: [
max_attempts: 4,
initial_delay: 250,
retry_on: :transient
]
end
end
Useful retry_on values include :error, :all, an error type atom, a list of error type atoms, a one-argument predicate, {module, function, extra_args}, or :transient. The :transient predicate covers common provider and transport failures such as timeouts, closed connections, HTTP 408/429/5xx responses, overload, and rate-limit errors.
middleware do
use BeamWeaver.Agent.Middleware.ModelFallback,
fallbacks: [backup_model],
retry_on: [:rate_limit, :timeout, :transport_error]
end
Retry and fallback policies are intentionally middleware-only. Keep provider model values simple; compose resilience at the agent boundary where tracing, call limits, interrupts, context editing, and tool middleware share the same runtime.
Use checkpointers for long-running agents and graphs so application progress is not tied to one provider request.
Invocation
Invoke with a string:
alias BeamWeaver.Core.ChatModel
{:ok, message} = ChatModel.invoke(model, "Explain OTP supervision in one paragraph.")
IO.puts(BeamWeaver.Core.Message.text(message))
Invoke with message history:
alias BeamWeaver.Core.{ChatModel, Message}
messages = [
Message.system("You translate English to French."),
Message.user("Translate: I enjoy building applications.")
]
{:ok, response} = ChatModel.invoke(model, messages)
Maps and {role, content} tuples can be normalized by MessageLike, but new code should prefer %BeamWeaver.Core.Message{} constructors.
Streaming
stream/3 returns an Enumerable of provider text deltas for scoped live providers. OpenAI, Anthropic, Google, xAI, and Z.ai live transports emit chunks as the provider sends them; replay transports emit deterministic chunks from the saved stream body for tests. Use stream_events/3 when you need provider semantic events such as tool-call chunks, reasoning, usage, or lifecycle metadata.
{:ok, deltas} = BeamWeaver.Core.ChatModel.stream(model, "Draft a short release note.")
Enum.each(deltas, &IO.write/1)
Semantic events:
{:ok, events} =
BeamWeaver.Core.ChatModel.stream_events(model, "Show the reasoning outline.",
run_id: "run-123"
)
for event <- events do
case event do
%BeamWeaver.Stream.Envelope{event: %{text: text}} ->
IO.write(text)
%{"event" => event_name} ->
IO.inspect(event_name, label: "provider event")
_other ->
:ok
end
end
OpenAI, Anthropic, Google, xAI, and Z.ai provider modules also expose stream_response/3 when a caller wants the reconstructed final assistant message from a streamed call. Provider HTTP errors returned before any successful stream body are represented as stream error events when consuming a lazy stream. OpenAI and xAI streams preserve empty initial role-only chunks, incremental tool-call arguments, final reconstructed tool calls, finish reasons, and detailed usage metadata. Check model.profile.tool_call_streaming before building UI that depends on live tool argument chunks.
For agent and graph event projections, see Event Streaming .
Batch
batch/3 returns ordered tagged results:
results =
BeamWeaver.Core.ChatModel.batch(model, [
"Summarize the deployment plan.",
"List the migration risks."
])
For concurrent work, use Task-backed helpers:
tasks =
BeamWeaver.Core.ChatModel.async_batch(model, [
[BeamWeaver.Core.Message.user("Summarize the deployment plan.")],
[BeamWeaver.Core.Message.user("List the migration risks.")]
])
results = BeamWeaver.Core.Async.await_batch(tasks, 30_000)
Tool Calling
Bind user-defined tools to a standalone model with BeamWeaver.Models.bind_tools/3:
tool =
BeamWeaver.Core.Tool.from_function!(
name: "get_weather",
description: "Get weather for a location.",
input_schema: %{
"type" => "object",
"required" => ["location"],
"properties" => %{"location" => %{"type" => "string"}}
},
handler: fn %{"location" => location}, _opts ->
{:ok, "Weather for #{location}: clear"}
end
)
model_with_tools =
BeamWeaver.Models.bind_tools(model, [tool],
tool_choice: :auto,
parallel_tool_calls: true
)
{:ok, message} =
BeamWeaver.Core.ChatModel.invoke(model_with_tools, "Check the weather for Paris.")
for call <- message.tool_calls do
{:ok, tool_message} = BeamWeaver.Core.Tool.invoke(tool, call)
# Send tool_message back in the next model turn, or let an agent run the loop.
end
Standalone model calls only request tool execution. Agents handle the tool execution loop automatically.
Server-side provider tools are provider request values:
tools = [
BeamWeaver.OpenAI.ToolCalling.web_search(),
BeamWeaver.OpenAI.ToolCalling.code_interpreter(%{type: :auto})
]
BeamWeaver.Core.ChatModel.invoke(model, "Find one current release note.", tools: tools)
Anthropic server tools are available through BeamWeaver.Anthropic.Tools. Google server tools are available through BeamWeaver.Google.Tools. xAI server tools are available through BeamWeaver.XAI.Tools.
Structured Output
BeamWeaver accepts JSON Schema maps and optional Elixir validator/parser functions. See Structured Output for agent response formats, tool strategy, provider strategy, and retry behavior.
schema = %{
"title" => "Project",
"type" => "object",
"required" => ["name", "status"],
"properties" => %{
"name" => %{"type" => "string"},
"status" => %{"type" => "string"}
}
}
structured_model =
BeamWeaver.Models.with_structured_output(model, schema)
{:ok, response} =
BeamWeaver.Core.ChatModel.invoke(structured_model, "Extract project name and status.")
response.metadata.structured_response
Provider-native structured output is selected when the model profile advertises support. Otherwise BeamWeaver can fall back to tool-strategy behavior.
For direct provider calls, OpenAI, Anthropic, Google, xAI, and Z.ai also accept :response_format/:structured_output options. Z.ai maps schema-shaped input to JSON object mode, injects the schema into the provider-visible messages, and parses locally.
Model Profiles
Profiles describe model capabilities such as context window, tool support, structured output, streaming, reasoning, usage metadata, modalities, tokenizer, and supported parameters.
{:ok, model} = BeamWeaver.Models.init_chat_model("openai:gpt-5.4")
model.profile.max_input_tokens
BeamWeaver.Models.Profile.supports?(model.profile, :tool_calling)
Override stale or missing profile data explicitly:
{:ok, model} =
BeamWeaver.Models.init_chat_model("openai:future-model",
profile: %{
max_input_tokens: 200_000,
tool_calling: true,
structured_output: true,
streaming: true
}
)
Inspect checked-in profiles:
mix beam_weaver.models.profiles
mix beam_weaver.models.profiles --provider openai --json
mix beam_weaver.models.profiles --provider xai --json
Refresh models.dev-style profile data into a local artifact:
mix beam_weaver.models.profiles --refresh --provider anthropic --data-dir priv/model_profiles
The refresh command is native BeamWeaver tooling; it does not use the Python langchain-model-profiles CLI.
Multimodal
Messages can carry typed content blocks:
alias BeamWeaver.Core.{ContentBlock, Message}
message =
Message.user([
ContentBlock.text("Describe this image."),
ContentBlock.image(%{url: "https://example.com/image.png"})
])
BeamWeaver.Core.ChatModel.invoke(model, [message])
OpenAI, Anthropic, Google, and xAI translators support the scoped image, audio, file/document, reasoning, citation, server-tool, and unknown provider blocks covered by tests. Z.ai GLM-5.2 currently exposes text-only input in BeamWeaver. Video and arbitrary provider-native formats require provider-specific translation before they should be used in portable BeamWeaver code.
Reasoning
OpenAI reasoning controls can be passed as request options:
BeamWeaver.Core.ChatModel.invoke(model, "Plan the migration.",
reasoning_effort: :low,
verbosity: :medium
)
Anthropic thinking controls are provider options:
BeamWeaver.Core.ChatModel.invoke(model, "Plan the migration.",
thinking: %{type: :enabled, budget_tokens: 1_024}
)
xAI reasoning controls follow OpenAI-compatible request shapes:
BeamWeaver.Core.ChatModel.invoke(model, "Plan the migration.",
reasoning: %{effort: :high}
)
Google thinking controls are Gemini generation config options:
BeamWeaver.Core.ChatModel.invoke(model, "Plan the migration.",
thinking_budget: 512,
include_thoughts: true
)
Z.ai GLM-5.2 accepts thinking plus reasoning_effort:
BeamWeaver.Core.ChatModel.invoke(model, "Plan the migration.",
thinking: %{type: :enabled},
reasoning_effort: :low
)
DeepSeek Chat uses the same thinking and reasoning_effort shapes. DeepSeek Responses accepts reasoning: %{effort: ...} and is selected with api: :responses. V4 thinking requests must omit Chat tool_choice; set thinking to disabled before sending an explicit choice. Forced function/custom choices in Responses require reasoning: %{effort: "none"}.
Reasoning output is surfaced as content blocks or stream events when the underlying provider returns it.
Prompt Caching
Prompt caching is provider-specific:
-
Agent calls can pass explicit provider overrides with
model_opts: [...]. -
OpenAI Responses and Chat Completions requests support
:prompt_cache_key. GPT-5.6 additionally supports:prompt_cache_optionsand explicitprompt_cache_breakpointmetadata on supported content blocks. -
xAI Responses supports
:prompt_cache_key. -
xAI Chat Completions supports
:x_grok_conv_id, which BeamWeaver maps to thex-grok-conv-idheader. -
prompt_cachingandBeamWeaver.Agent.Middleware.PromptCachingroute cache controls for supported providers; Anthropic marks the static system prompt withcache_control. -
Moonshot/Kimi supports
:prompt_cache_key. -
Gemini, DeepSeek, and Z.ai cached-token usage is normalized when providers report it.
-
Usage metadata preserves cache-read/cache-write/cache-creation token details when providers return them.
See Prompt Caching for provider-specific examples.
Rate Limiting
Use an explicit limiter and wrapper:
{:ok, limiter} =
BeamWeaver.RateLimiter.TokenBucket.start_link(
capacity: 10,
refill_amount: 1,
refill_interval: 1_000
)
model =
BeamWeaver.Models.with_rate_limiter(model,
limiter: limiter,
amount: 1,
timeout: 5_000
)
The limiter controls request count only. It does not estimate request token weight unless the caller chooses a token-aware policy externally.
Token Usage And Token Counting
Provider responses store usage on the assistant message:
{:ok, message} = BeamWeaver.Core.ChatModel.invoke(model, "Say hello.")
message.usage_metadata
Agents aggregate model and tool usage into agent state. Standalone applications can aggregate message.usage_metadata directly or consume telemetry/tracing events.
Token counting is available when a provider implements it or when a tokenizer is configured:
BeamWeaver.OpenAI.ChatModel.count_tokens(model, [
BeamWeaver.Core.Message.user("Count this.")
])
Anthropic and Google use provider count-token endpoints. OpenAI and xAI can use tokenizer adapters from profile data or explicit tokenizers. Approximate counting remains available for fallback behavior.
Invocation Context
BeamWeaver uses keyword options on model calls:
BeamWeaver.Core.ChatModel.invoke(model, "Generate a concise answer.",
run_id: "run-123",
metadata: %{user_id: "user-123"},
tools: [],
stream_options: %{include_usage: true}
)
Configurable Models
LangChain's configurable_fields and config_prefix runtime model wrappers map to explicit Elixir values and middleware:
-
build the model you want with
BeamWeaver.Models.init_chat_model/2 -
select dynamic models with agent
wrap_model_callmiddleware -
pass provider options as keyword arguments at the call boundary
-
use structs when a model configuration should be shared