This guide covers all public-facing functions of the Responses package for LLM agents. Responses is an Elixir client library for interacting with OpenAI's Large Language Models (LLMs), providing a simple and powerful interface for AI-powered text generation, structured outputs, function calling, and real-time streaming.
# Add to mix.exs
{:responses, "~> 0.1.3"}
# Set API keys via environment variables (only the providers you use are required)
export OPENAI_API_KEY="your-openai-key"
export XAI_API_KEY="your-xai-key"
# Optional: configure in your config files instead
config :responses, :openai_api_key, System.fetch_env!("OPENAI_API_KEY")
config :responses, :xai_api_key, System.fetch_env!("XAI_API_KEY")- Use
provider:modelto address a specific provider explicitly, e.g."openai:gpt-4.1"or"xai:grok-3". - For common OpenAI and xAI models you can omit the prefix; the library infers the provider using the following heuristics:
gpt-*,o1*,o3*,o4-mini*map to OpenAI.grok-*maps to xAI.
- Unknown or ambiguous models raise an
ArgumentErrorso you can address new models explicitly. - To silence provider capability warnings on a single request pass
provider_warnings: :ignore; setconfig :responses, :provider_warning_mode, :ignoreto silence them globally.
Creates a new AI response. The bang version raises on error.
# Simple text input
{:ok, response} = Responses.create(input: "Write a haiku", model: "gpt-4.1-mini")
response = Responses.create!(input: "Write a haiku", model: "gpt-4.1-mini")
# With options
{:ok, response} = Responses.create(
input: "Explain quantum physics",
model: "gpt-4.1-mini",
temperature: 0.7,
max_tokens: 500
)
# Target xAI explicitly
{:ok, response} = Responses.create(
input: "Summarise the latest xAI blog post",
model: "xai:grok-3"
)
# Streaming text from xAI while silencing unsupported option warnings
Responses.create(
input: "List three takeaways about Grok-4",
model: "xai:grok-4-fast",
stream: Responses.Stream.delta(&IO.write/1),
provider_warnings: :ignore
)
# With structured output
response = Responses.create!(
input: "List 3 facts",
model: "gpt-4.1-mini",
schema: %{facts: {:array, :string}}
)
response.parsed # => %{"facts" => ["fact1", "fact2", "fact3"]}
# With streaming callback
Responses.create(
input: "Tell a story",
model: "gpt-4.1-mini",
stream: fn
{:ok, %{event: "response.output_text.delta", data: %{"delta" => text}}} ->
IO.write(text)
:ok
_ -> :ok
end
)Creates follow-up responses maintaining conversation state on the OpenAI side. Only specific generation settings are preserved.
first = Responses.create!(input: "What is Elixir?", model: "gpt-4.1-mini")
followup = Responses.create!(first, input: "Tell me more about its concurrency")
# Preservation rules for follow-ups:
# - Preserved: model, reasoning.effort, text.verbosity
# - Not preserved: text.format (schema) and any other options
# If you need structured output on a follow-up, pass schema: ... explicitly.
# - When a provider marks one of the preserved options as unsupported (e.g. xAI and reasoning
# effort), the library automatically drops that field before sending the follow-up request.
# Note: Passing a bare binary to `create/1` or `stream/1` is deprecated.
# Always pass options with `input: ...` and an explicit `model`.Returns an Enumerable stream of response chunks.
# Stream text content
text = Responses.stream(input: "Write a poem", model: "gpt-4.1-mini")
|> Responses.Stream.text_deltas()
|> Enum.join()
# Process stream with error handling
Responses.stream(input: "Generate data", model: "gpt-4.1-mini")
|> Enum.each(fn
{:ok, chunk} -> IO.inspect(chunk)
{:error, reason} -> IO.puts("Error: #{inspect(reason)}")
end)Automates function calling by repeatedly calling functions until completion.
# Define functions
functions = %{
"get_weather" => fn %{"location" => loc} ->
"15°C in #{loc}"
end
}
# Define tools
weather_tool = Responses.Schema.build_function(
"get_weather",
"Get weather for a location",
%{location: :string}
)
# Run conversation
responses = Responses.run(
[input: "What's the weather in Paris?", tools: [weather_tool], model: "gpt-4.1-mini"],
functions
)
# Last response has final answer
final_answer = List.last(responses).textExecutes function calls from a response and appends formatted results to input.
# Get a response with function calls
{:ok, response} = Responses.create(
input: "What's the weather in Paris?",
tools: [weather_tool],
model: "gpt-4.1-mini"
)
# Define function implementations
functions = %{
"get_weather" => fn %{"location" => loc} ->
# Custom logic before/after the actual call
result = fetch_weather_data(loc)
log_api_call(:weather, loc)
# Result must be JSON-encodable (map, list, string, number, boolean, nil)
%{temperature: result.temp, unit: "C", conditions: result.conditions}
end
}
alias Responses.Prompt
# Execute functions and append outputs to the prompt
opts = Prompt.add_function_outputs(%{input: []}, response.function_calls, functions)
# Continue conversation with custom context or extra messages
{:ok, final} = Responses.create(response,
Prompt.append(opts, %{role: :user, content: "Convert to Fahrenheit"})
)Lists available models for a specific provider with optional filtering.
# List all OpenAI models
models = Responses.list_models(:openai)
# Filter xAI models by pattern
grok_models = Responses.list_models(:xai, "grok-4")
# Inspect the raw payload returned by xAI
{:ok, raw} = Responses.request(provider: Responses.Provider.get!(:xai), url: "/models", method: :get)Low-level API request function for custom endpoints.
{:ok, response} = Responses.request(
url: "/models",
method: :get
)
# Override the provider when hitting xAI endpoints
{:ok, response} = Responses.request(
provider: Responses.Provider.get!(:xai),
url: "/models",
method: :get
)
# Suppress provider warnings for a single call
{:ok, response} = Responses.create(
input: "Tell me a joke",
model: "grok-3",
provider_warnings: :ignore
)Response struct fields:
text- Extracted assistant message textparsed- Parsed JSON for structured outputsparse_error- Parsing error details if anyfunction_calls- Extracted function callsbody- Raw API response bodycost- Usage cost breakdown
Rebuilds a %Response{} struct from a map with atom or string keys. Useful for rehydrating stored responses.
alias Responses.Response
stored = %{
"text" => "hello",
"body" => %{"id" => "resp_123", "model" => "gpt-4.1-mini"},
"cost" => %{
"input_cost" => "0.0001",
"output_cost" => 0.0,
"total_cost" => 0.0001,
"cached_discount" => 0
}
}
response = Response.from_map(stored)Extracts assistant messages from response. Automatically called by create functions.
response = Response.extract_text(response)
IO.puts(response.text)Extracts structured data from JSON responses. Automatically called for structured outputs.
response = Response.extract_json(response)
data = response.parsed # => %{"key" => "value"}Extracts and parses function calls. Automatically called by create functions.
response = Response.extract_function_calls(response)
calls = response.function_calls
# => [%{name: "get_weather", call_id: "...", arguments: %{"location" => "Paris"}}]Calculates token usage costs. Automatically called by create functions.
response = Response.calculate_cost(response)
response.cost # => %{
# input_cost: #Decimal<0.0001>,
# output_cost: #Decimal<0.0002>,
# total_cost: #Decimal<0.0003>,
# cached_discount: #Decimal<0.0000>
# }Streams responses with a callback function, returns final response.
{:ok, response} = Stream.stream_with_callback(
fn
{:ok, %{event: "response.output_text.delta", data: %{"delta" => text}}} ->
IO.write(text)
:ok
{:error, reason} ->
IO.puts("Error: #{inspect(reason)}")
:ok
_ -> :ok
end,
input: "Write a story",
model: "gpt-4.1-mini"
)Returns an Enumerable stream for flexible processing.
stream = Stream.stream(input: "Generate content", model: "gpt-4.1-mini")
# Each item is {:ok, chunk} or {:error, reason}Helper for creating simple text streaming callbacks.
Responses.create(
input: "Write a story",
model: "gpt-4.1-mini",
stream: Stream.delta(&IO.write/1)
)Extracts only text deltas from event stream.
text = Responses.stream(input: "Write a poem", model: "gpt-4.1-mini")
|> Stream.text_deltas()
|> Enum.join()Converts stream to JSON parsing events for incremental processing.
Responses.stream(
input: "Generate JSON data",
schema: %{items: {:array, %{name: :string}}},
model: "gpt-4.1-mini"
)
|> Stream.json_events()
|> Enum.each(&IO.inspect/1)
# Yields: :start_object, {:string, "items"}, :colon, :start_array, etc.Converts Elixir syntax to JSON Schema for structured outputs.
# Simple types
schema = Schema.build_output(%{
name: :string,
age: :integer,
active: :boolean
})
# With constraints
schema = Schema.build_output(%{
email: {:string, format: "email"},
username: {:string, pattern: "^[a-z]+$", min_length: 3},
score: {:number, minimum: 0, maximum: 100}
})
# Arrays and nested objects
schema = Schema.build_output(%{
tags: {:array, :string},
addresses: {:array, %{
street: :string,
city: :string,
country: :string
}}
})
# Union types
schema = Schema.build_output(%{
result: {:anyOf, [:string, :number, :boolean]}
})
# Arrays at the root level (automatic wrapping)
{:ok, response} = Responses.create(
input: "List 3 US presidents",
schema: {:array, %{
name: :string,
birth_year: :integer,
facts: {:array, :string}
}},
model: "gpt-4.1-mini"
)
# response.parsed is a list (the library wraps/unpacks automatically)Creates function tool definitions for function calling.
tool = Schema.build_function(
"search_products",
"Search for products by name and category",
%{
query: {:string, description: "Search query"},
category: {:string, enum: ["electronics", "books", "clothing"]},
max_results: {:integer, minimum: 1, maximum: 100, description: "Max results to return"}
}
)
# Use with create
response = Responses.create!(
input: "Find me some laptops",
tools: [tool],
model: "gpt-4.1-mini"
)# Initial response sets context
chat = Responses.create!(
input: [
%{role: :developer, content: "You are a helpful assistant"},
%{role: :user, content: "Hello!"}
],
model: "gpt-4.1-mini"
)
# Follow-ups maintain context
chat = Responses.create!(chat, input: "What can you help with?")alias Responses.Prompt
opts = %{}
opts = Prompt.append(opts, [
%{role: :developer, content: "Talk like a pirate."},
"Write me a haiku about Elixir"
])
response = Responses.create!(opts)
IO.puts(response.text)
## Prompt Normalization Notes
- Prompt helpers (`append/2`, `prepend/2`, `add_user/2`, `add_developer/2`, `add_system/2`) always normalize `:input` to a list of message maps.
- Strings are converted to `%{role: :user, content: string}`.
- A single message map is wrapped in a list; `:input` is never a single map.
- When routing to xAI models, any `role: :developer` messages are converted to `:system` and emit a warning unless provider warnings are disabled (`provider_warnings: :ignore`).response = Responses.create!(
input: "Extract contact info from: John Doe, john@example.com, +1-555-0123",
schema: %{
name: :string,
email: {:string, format: "email"},
phone: {:string, pattern: "^\\+\\d{1,3}-\\d{3}-\\d{4}$"}
},
model: "gpt-4.1-mini"
)
contact = response.parsedResponses.stream(input: "Generate a long report", model: "gpt-4.1-mini")
|> Stream.with_index()
|> Stream.each(fn
{{:ok, %{event: "response.output_text.delta", data: %{"delta" => text}}}, _i} ->
IO.write(text)
{{:ok, %{event: "response.completed"}}, _i} ->
IO.puts("\n✓ Complete")
_ -> nil
end)
|> Stream.run()case Responses.create(input: "Generate content", model: "gpt-4.1-mini") do
{:ok, response} ->
IO.puts(response.text)
IO.puts("Cost: $#{response.cost.total_cost}")
{:error, %{"message" => msg}} ->
IO.puts("API Error: #{msg}")
{:error, reason} ->
IO.puts("Error: #{inspect(reason)}")
end- Always provide a model explicitly
- All responses include automatic cost calculation
- Text extraction is idempotent (safe to call multiple times)
- Streaming callbacks should return
:okto continue or{:error, reason}to stop - Function calling with
run/2handles multiple rounds automatically - Use
Prompt.add_function_outputs/3for manual control over function execution - Function outputs must be JSON-encodable (no tuples, atoms except true/false/nil)
- Structured outputs guarantee exact schema compliance
- Use
!versions for simpler code when errors should crash - Passing a bare binary to
create/1orstream/1is deprecated; useinput: ... - Prompt helpers ensure
:inputis always a list of messages