OpenAI
BeamWeaver uses Responses by default and also supports Chat Completions, embeddings, typed decisions, image generation and moderation. A shared provider name does not mean those endpoints accept the same fields or tool types.
Setup and first request
Save this in your application's config/runtime.exs:
import Config
config :beam_weaver, :openai, api_key: System.fetch_env!("OPENAI_API_KEY")
alias BeamWeaver.Core.{ChatModel, Message}
{:ok, model} = BeamWeaver.Models.init_chat_model("openai:gpt-5.4-mini", max_tokens: 256, timeout: 60_000)
{:ok, reply} = ChatModel.invoke(model, "Explain parcel tracking in one sentence.")
true = byte_size(Message.text(reply)) > 0
true = reply.usage_metadata.total_tokens > 0
The same identifier works in an agent's model declaration. organization: and project: select account billing context; base_url: changes the API root. Keep trace labels separate from native request metadata.
Responses or Chat Completions
Responses carries native output items, hosted tools and continuation IDs. Use Chat Completions when your target endpoint requires that contract:
{:ok, chat} =
BeamWeaver.Models.init_chat_model("openai:gpt-5.4-mini", api: :chat_completions, max_tokens: 256, timeout: 60_000)
{:ok, reply} = ChatModel.invoke(chat, "Write a brief greeting.")
true = byte_size(Message.text(reply)) > 0
Some newer models restrict Chat Completions tools: Astra and GPT-6.1 Sol require Responses; GPT-5.6, GPT-6 Sol and Luna require nonreasoning mode for that endpoint's tools. Local profiles validate these combinations. Hosted tools and reasoning summaries use Responses. Tool-returned media on Chat Completions is carried in a following user message rather than a text-only tool result.
Options
| Option | Meaning |
|---|---|
api:
|
:responses or :chat_completions
|
reasoning_effort: / reasoning:
| Model-supported effort and reasoning controls |
verbosity:
| Supported low/medium/high answer verbosity |
max_tokens:
| Native output-token cap |
service_tier:
| Provider processing tier |
store:
| Remote response storage policy |
use_previous_response_id:
| Continue eligible stored Responses replies |
prompt_cache_key:, prompt_cache_options:
| Prefix cache controls where supported |
metadata:
| Native OpenAI stored-response metadata |
endpoint:
| Full endpoint override |
Metadata is bounded to 16 string pairs with documented key/value limits. Application trace labels use trace_metadata: instead. Unknown/unsupported parameters fail locally. Sampling support depends on the model and reasoning mode; do not copy temperature options indiscriminately between generations.
Reasoning and continuation
Set only effort levels accepted by the selected profile. Returned reasoning lives in content blocks and is excluded from Message.text/1. Preserve full messages for replay. Reasoning can consume the output budget before visible text, so choose token/time limits appropriate to the task.
GPT-6 Responses can accept configuration-update input blocks for supported mid-conversation effort changes. Additional-tool input blocks can add tools at a point in the conversation. These are native Responses features, not portable chat options. A configuration update is carried in conversation content; ordinary reasoning_effort: configures the model request itself.
With use_previous_response_id, BeamWeaver continues only compatible stored replies. Other-provider replies and store: false replies remain full history. Remote continuation does not replace your application's checkpoint ownership.
Configure reasoning effort for an actual call, and read only its visible answer:
{:ok, reasoning} =
BeamWeaver.Models.init_chat_model("openai:gpt-5.4-mini", reasoning_effort: :low, max_tokens: 512, timeout: 60_000)
{:ok, reply} = BeamWeaver.Core.ChatModel.invoke(reasoning, "What is 19 + 23? Give the result briefly.")
true = BeamWeaver.Core.Message.text(reply) =~ "42"
Built-in tools
Hosted tools run on OpenAI's infrastructure. This call verifies a search item in the actual response:
{:ok, searched} =
ChatModel.invoke(
model,
"Use web search to find the official Elixir programming language website. Return its URL.",
tools: [BeamWeaver.OpenAI.ToolCalling.web_search()]
)
true = byte_size(Message.text(searched)) > 0
true = Enum.any?(List.wrap(searched.content), &(Map.get(&1, :type) == :web_search_call))
Other helpers describe code interpreter, file search, image generation, remote MCP and tool search. File search requires actual vector-store IDs; remote MCP requires an actual server and its authorization/setup.
apply_patch and custom tools declare client actions. Your application must execute requests and send results; adding the declaration does not implement a file editor. Agent tools execute through BeamWeaver; native hosted tools belong in model call options. See
Tools
and OpenAI's
function-calling contract
.
Prompt caching
Long eligible prefixes may be cached. prompt_caching derives a stable routing key; explicit prompt-cache options are model-specific. Inspect actual input_token_details.cache_read usage to establish a hit. Short examples can return zero.
Caching
links a repeated-call example and explains invalidation versus full-answer caching.
The agent declaration configures caching hints. Measure the returned cache-read count; this short call is not expected to force a cache hit:
defmodule MyApp.CachedPolicyAgent do
use BeamWeaver.Agent
model "openai:gpt-5.4-mini", max_tokens: 128, timeout: 60_000
system_prompt "Refunds take five business days. Answer briefly from this policy."
prompt_caching true
end
{:ok, result} = MyApp.CachedPolicyAgent.invoke(%{messages: ["How long do refunds take?"]})
reply = List.last(result.messages)
true = byte_size(BeamWeaver.Core.Message.text(reply)) > 0
IO.inspect(get_in(reply.usage_metadata, [:input_token_details, :cache_read]) || 0, label: "Cached input tokens")
Compact a conversation
The standalone compaction endpoint accepts the full current input window and returns the next window. It is separate from graph checkpoints:
client = BeamWeaver.OpenAI.Client.new(timeout: 120_000)
input = [%{"role" => "user", "content" => "Remember order A-1001 arrives Thursday. Acknowledge briefly."}]
{:ok, initial} =
BeamWeaver.OpenAI.Client.responses(client, %{
"model" => "gpt-5.4-mini",
"input" => input,
"max_output_tokens" => 128,
"store" => false
})
{:ok, compacted} =
BeamWeaver.OpenAI.Client.compact(client, %{
"model" => "gpt-5.4-mini",
"input" => input ++ initial["output"]
})
true = Enum.any?(compacted["output"], &(&1["type"] == "compaction"))
{:ok, continued} =
BeamWeaver.OpenAI.Client.responses(client, %{
"model" => "gpt-5.4-mini",
"max_output_tokens" => 256,
"input" => compacted["output"] ++ [%{"role" => "user", "content" => "Which order arrives on which day?"}]
})
{:ok, continued_message} = BeamWeaver.OpenAI.Messages.response_to_message(continued)
true = Message.text(continued_message) =~ "A-1001"
true = String.downcase(Message.text(continued_message)) =~ "thursday"
Keep the complete returned output, including retained messages and opaque compaction items. Client.compact_response/4 can first retrieve the full input/output of an accessible stored response. See OpenAI's
compaction contract
.
Embeddings
{:ok, embeddings} = BeamWeaver.Models.init_embeddings("openai:text-embedding-3-small", dimensions: 512)
{:ok, vector} = BeamWeaver.Core.EmbeddingModel.embed_query(embeddings, "When will my refund arrive?")
512 = length(vector)
true = Enum.all?(vector, &is_number/1)
Use the same dimensions/model for documents and queries. Retrieval checks relevance with real embeddings; Storage covers pgvector.
Decisions
GPT-6 Luna's Decisions integration returns typed answers/probabilities:
alias BeamWeaver.OpenAI.Decisions.Question
{:ok, decision_model} = BeamWeaver.Models.init_decision_model("openai:gpt-6-luna", timeout: 60_000)
{:ok, response} =
BeamWeaver.Core.DecisionModel.invoke(decision_model, %{
state: "Checkout crashes for every customer. Nobody can pay.",
questions: %{
team:
Question.choice(
instructions: "Which team owns fixing this software outage?",
choices: %{"engineering" => "Software failures and outages", "billing" => "Invoice corrections"}
)
}
})
"engineering" = response.answers["team"].choice
Predicate questions return a probability; choice questions return choice, confidence and probabilities; score questions return an expected zero-based level. Handle refusals explicitly. State can contain supported text/images; unsupported media must first be represented appropriately. Decisions usage and billing differ from chat output; inspect the returned decision response rather than reading it as an assistant message.
Predicate and score questions share the same typed decision request. Their fields differ from the choice result above:
{:ok, assessment} =
BeamWeaver.Core.DecisionModel.invoke(decision_model, %{
state: "Checkout is completely broken for every customer. Nobody can buy anything.",
questions: %{
urgent: Question.predicate(instructions: "Is this an urgent service outage?"),
severity:
Question.score(
instructions: "Rate how much purchasing is affected.",
levels: ["Not affected", "Partly affected", "Completely blocked"]
)
}
})
IO.inspect(assessment.answers, label: "Typed predicate and score answers")
true = assessment.answers["urgent"].probability > 0.5
true = assessment.answers["severity"].score > 1.5
Images and moderation
Image generation is a separate endpoint. This verifies returned bytes rather than treating a successful response tuple as an image:
{:ok, response} = BeamWeaver.OpenAI.generate_image("A plain red square on a white background", size: "1024x1024")
[%{"b64_json" => encoded} | _] = response["data"]
{:ok, bytes} = Base.decode64(encoded)
true = byte_size(bytes) > 1_000
generate_image/2 accepts size:, quality:, background:, output_format:, output_compression:, n:, moderation: and safety_identifier:. Legacy response_format:, style: and user: fields are passed through where the selected image model supports them.
OpenAIModeration middleware checks configured input/output text through the moderation endpoint. It is separate from tool authorization and stream redaction;
Guardrails
explains those boundaries.
Models
Limits are local profile data. A dash means not applicable or not recorded, not an unlimited allowance.
| Model | Input / output tokens | Operations and capabilities |
|---|---|---|
openai:gpt-4.1
| 1,047,576 / 32,768 | chat; text, image, PDF; tools; structured output |
openai:gpt-5
| 400,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5-mini
| 400,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5-nano
| 400,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5.4
| 1,050,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5.4-mini
| 400,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5.4-nano
| 400,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5.4-pro
| 1,050,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5.5
| 1,050,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5.5-pro
| 1,050,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5.6-luna
| 1,050,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5.6-sol
| 1,050,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-5.6-terra
| 1,050,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-6-astra
| 1,050,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-6-luna
| 1,050,000 / 128,000 | chat, decision; text, image, PDF; tools; structured output; reasoning |
openai:gpt-6-sol
| 1,050,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:gpt-6.1-sol
| 922,000 / 128,000 | chat; text, image, PDF; tools; structured output; reasoning |
openai:text-embedding-3-large
| 8,191 / — | embedding; text |
openai:text-embedding-3-small
| 8,191 / — | embedding; text |
GPT-5, GPT-5 Mini and GPT-5 Nano are scheduled to retire on December 11, 2026; GPT-4.1 on January 14, 2027; and GPT-5.4 Nano on April 1, 2027. Their profiles retain support until retirement and include recommended replacements. See OpenAI's deprecation notices .
Profiles include aliases and access-controlled models; account access is not implied. Azure routing and OpenAI managed-agent resources are outside this integration. The operations described on this page are the implemented integration; other OpenAI services require their own client integration.
Further reading
Use Testing for scripted model behavior and replay. The examples above use the native endpoints described alongside each operation.