GPT model integration for Amplifier via OpenAI's Responses API.
- Python 3.11+
- UV - Fast Python package manager
# macOS/Linux/WSL
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"Provides access to OpenAI's GPT-5 and GPT-4 models as an LLM provider for Amplifier using the Responses API for enhanced capabilities.
Module Type: Provider
Mount Point: providers
Entry Point: amplifier_module_provider_openai:mount
gpt-5.6-sol/gpt-5.6-terra/gpt-5.6-luna- GPT-5.6 tiers (flagship / balanced / cost-efficient); aliasgpt-5.6→gpt-5.6-sol.gpt-5.6-solis the default. 1.05M context, addsreasoning.effort="max",reasoning.mode="pro", andprompt_cache_options. Note: gpt-5.6 bills cache-write tokens at 1.25× input (automatic on prompts >1024 tokens) and rejectsin_memoryretention (auto-dropped to 24h).gpt-5.5- Prior-generation GPT-5 modelgpt-5.4- Balanced GPT-5 modelgpt-5-mini- Smaller, faster GPT-5gpt-5-nano- Smallest GPT-5 variant
[[providers]]
module = "provider-openai"
name = "openai"
config = {
base_url = null, # Optional custom endpoint (null = OpenAI default)
default_model = "gpt-5.6-sol",
max_tokens = null, # Output-token budget. null (default) = the model
# capability's max_output_tokens (e.g. 128K for
# gpt-5.x); set a number to cap explicitly. The
# old fixed 4096 default silently truncated large
# tool calls mid-arguments.
temperature = 0.7,
reasoning_effort = "low", # CANONICAL effort key: none|minimal|low|medium|
# high|xhigh|max (set is model-specific; gpt-5.6
# adds "max", rejects "minimal"). Validated at
# mount. "none"/unset sends no reasoning param.
reasoning = null, # LEGACY alias, still works. Use when you need the
# dict form to also set reasoning.mode:
# {effort="high", mode="pro"}. If both are set,
# reasoning_effort wins and a warning is logged.
reasoning_summary = "detailed", # Reasoning verbosity: auto|concise|detailed
truncation = null, # null omits the field; OpenAI returns an explicit
# error on context overflow. Opt in to legacy
# auto-drop with truncation = "auto" (busts cache).
prompt_cache_key = "", # Stable cache-routing identifier; empty = unset
prompt_cache_retention = "24h", # "24h" | "in_memory" | null (use model default)
# gpt-5.6 rejects "in_memory" (auto-dropped to 24h).
prompt_cache_options = null, # GPT-5.6 explicit cache control, COEXISTS with
# retention: {mode="explicit", ttl="30m"}. null = unset.
enable_response_chaining = "auto", # "auto" | true | false (reasoning-model chaining)
enable_state = false,
reasoning_replay_scope = "turn", # "turn" | "all" | "none" (bounds inline reasoning
# replay on stateless requests; see
# [Reasoning State Preservation](#reasoning-state-preservation))
debug = false, # Enable standard debug events
raw_debug = false # Enable ultra-verbose raw API I/O logging
}Note:
safety_identifieris intentionally NOT a deployment config field. It is a per-end-user signal (abuse tracking) and must be set per-call viakwargs. See Prompt Caching below.
At construction, the provider warns once (with a did you mean-style
suggestion when a close match exists) about any config key it does not
recognize -- a typo like promt_cache_retention or a stale/removed option
otherwise has no effect and no signal that anything is wrong. The check is
silent on every key documented above, plus api_key / id / module /
source / priority (infrastructure fields an app or kernel may place
alongside a provider's config).
Extending the recognized set for a subclass. A provider module that
subclasses OpenAIProvider and passes its own config straight through
(e.g. provider-azure-openai, which wraps this provider for Azure-specific
auth) can declare its own additional keys so they don't trip this warning:
from amplifier_module_provider_openai import OpenAIProvider
class MyProvider(OpenAIProvider):
EXTRA_KNOWN_CONFIG_KEYS = frozenset({"my_custom_key", "another_key"})EXTRA_KNOWN_CONFIG_KEYS defaults to an empty frozenset and only widens
the recognized set for that subclass -- it does not suppress the warning
for genuine typos. A typo of one of the subclass's own declared keys (e.g.
my_custom_ky) still warns, with a suggestion drawn from the combined
base + subclass key set.
The reasoning_effort config key (canonical — it matches the kernel's portable
request.reasoning_effort field) sets a session-level default reasoning effort
applied to every request, so you can opt into stronger reasoning once
instead of supplying it per-request. The legacy reasoning key remains a
working alias; when both are set, reasoning_effort wins (a warning is logged).
providers:
- module: provider-openai
config:
default_model: gpt-5.6-sol
reasoning_effort: xhigh # legacy alias: reasoningPrecedence (highest wins):
kwargs["reasoning"]— the full dict form, per callkwargs["reasoning_effort"]— effort string, per callrequest.reasoning_effort— the kernel's portable per-request fieldconfig["reasoning_effort"]— canonical session default (this key)config["reasoning"]— legacy session default- Nothing sent — the model's own default applies
Notes:
"none"and unset both send no reasoning parameter."none"is the provisioning default and means "use the provider/model default behavior" — it deliberately does not emitreasoning={"effort": "none"}.- Values are validated at mount, not at request time. An unrecognized
effort, or one the default model rejects (
gpt-5.5-proaccepts onlymedium,high,xhigh), raises immediately instead of surfacing as an HTTP 400 mid-session. - Non-reasoning models are skipped with a warning, not an error — the config is ignored rather than producing an API failure.
- Use the legacy
reasoningkey when you need the dict form to also setreasoning.mode, e.g.{effort = "high", mode = "pro"}.
The provider exposes OpenAI's prompt-caching hint parameters (prompt_cache_key,
prompt_cache_retention, safety_identifier) plus an enable_response_chaining
toggle that activates the Responses API's previous_response_id mechanism for
reasoning models.
See also: OpenAI Cookbook — Prompt Caching 201.
prompt_cache_retention = "24h"— extended GPU-local KV storage on every supported model, instead of OpenAI's per-model"in_memory"default (5–10 min) for gpt-5.4 and below.enable_response_chaining = "auto"— for reasoning-capable models, the provider sendsstore = trueandprevious_response_idon subsequent turns, and stops re-inserting encrypted reasoning blocks inline. Empirical smoke against gpt-5.5 measured 85% prefix cache hit on turn 2 with chaining on, vs 0% off.truncation = null— the field is omitted from requests so the cached prefix is never silently rewritten on context overflow. Opt back into the legacy auto-drop behavior withtruncation = "auto".
prompt_cache_key is empty by default; setting it is opt-in. safety_identifier
is kwargs-only.
OpenAI shards Responses API traffic across machines by hashing the first ~256
input tokens. Without a stable key, requests with identical prefixes still hit
the same shard most of the time, but as soon as anything in the prefix shifts
(time-of-day stamp, shuffled tools, rewritten system prompt), routing diverges.
A stable prompt_cache_key keeps a logical conversation pinned to one machine
regardless of small prefix drift, and is the recommended cache signal as of
OpenAI's July 2025 guidance. (The legacy user field still works on the API
but is no longer the recommended cache signal.)
Granularity guidance:
| Deployment shape | Recommended key |
|---|---|
| Single-user agent loop (typical Amplifier) | conversation/session ID, e.g. "conv_abc123" |
| Multi-tenant with shared system prompt | f"{tenant_id}:{system_prompt_version}" |
| Low-volume single-session | leave unset; prefix-hash routing is sufficient |
Watch out for the ~15 RPM threshold per (prefix, key) pair. Past that, OpenAI spills overflow requests to fresh machines; high-volume conversations should prefer a key that distributes across tenants/sessions rather than one global constant.
| Value | Meaning |
|---|---|
"24h" |
Extended GPU-local KV storage. Provider default for all supported models. |
"in_memory" |
5–10 min in-process cache. OpenAI's per-model default for gpt-5.4 and below. |
null |
Field omitted; OpenAI picks the per-model default. |
The capability layer auto-drops values a model would reject:
- gpt-5.5+ rejects
"in_memory"— provider drops it with a[PROVIDER] Dropping prompt_cache_retention='in_memory'warning. - Any future model that rejects
"24h"(capability flagsupports_24h_retention = False) gets the field dropped the same way.
You do not need to special-case retention per model; the default "24h" is
safe everywhere.
Tri-state config (and per-call kwarg). Controls whether the provider uses the
Responses API's previous_response_id mechanism, which is the high-leverage
caching path for reasoning models:
| Value | Behavior |
|---|---|
"auto" |
On iff get_capabilities(model).supports_reasoning is True. Default. Right answer for most. |
true |
Force on regardless of model. Useful for testing or non-reasoning models that still benefit. |
false |
Force off. Use for ZDR / regulated-industry deployments that cannot retain server-side state. |
When chaining is active for a reasoning model, on each call:
store = trueis set automatically (chaining requires it; this overridesenable_statefor reasoning models).previous_response_id = <id from last assistant.metadata>is sent on subsequent turns.include=["reasoning.encrypted_content"]is requested regardless of whether chaining is active — live measurement (see below) found this cache-neutral, so ciphertext is captured unconditionally and every reset/resume path can replay reasoning (see Reasoning State Preservation).
enable_response_chaining = false is authoritative. It NEVER attaches
previous_response_id, regardless of the legacy enable_state setting — this
is the ZDR / regulated-industry opt-out and must not be silently overridden.
If you set enable_response_chaining = false together with enable_state = true, the provider warns at mount time: chaining is disabled as requested,
but enable_state = true still sets store = true, so responses ARE
retained server-side. For a full ZDR posture, set enable_state = false too
(see Recommended configurations).
For non-reasoning models, or enable_response_chaining = false, behavior is
unchanged: stateless mode with explicit reasoning re-insertion, bounded by
reasoning_replay_scope (see
Reasoning State Preservation).
On previous_response_id invalidation (HTTP 404 + response_not_found, or a
context_length_exceeded overflow while a chain is active), the provider
retries once without the field — restoring the full converted history
with reasoning items re-inserted (bounded by reasoning_replay_scope) so
the retry does not replay function_call items without their originating
reasoning items — and emits a provider:response_chain_invalidated event.
The same reasoning-continuity guarantee applies to a post-compaction chain
reset: the request immediately after a compaction event drops
previous_response_id but keeps store = true (so the next turn can still
chain) and re-inserts reasoning items + include, because the server no
longer holds state for that one request.
On the stateless path (chaining off, or a chain-broken retry), the provider
re-inserts prior ThinkingBlock state inline. Unbounded, this grows linearly
with conversation length: encrypted reasoning blobs measured ~1,200 chars
each, over 50% of the replayed payload by turn 4 in live probing. This config
key bounds how far back that replay reaches:
| Value | Behavior |
|---|---|
"turn" (default) |
Replay reasoning only for assistant turns since the last user message — the in-flight tool loop, per OpenAI's own "single turn spans multiple API calls" guidance. Flat cost, independent of conversation length. |
"all" |
Replay every turn's reasoning. Unbounded growth (pre-fix behavior on the stateless path). Escape hatch if cross-turn replay measurably helps your workload. |
"none" |
No inline reasoning replay. |
An unrecognized value falls back to "turn" with a warning. Not exposed as a
deployment ConfigField (same precedent as safety_identifier) — it's a
tuning knob, not a per-deployment decision most operators need to touch.
This bound does not affect the chained path (chaining suppresses replay entirely — the server already holds the state) nor the amount of reasoning collected for the orphan-stripping guard, only how much is emitted inline.
The request-side counterpart to prompt_cache_key: an abuse-tracking signal
that should carry a per-end-user value. Intentionally NOT exposed as a
deployment ConfigField — surfacing it in deployment config invites operators
to set one global value, which defeats its purpose. Set it via per-call
kwargs only.
The truncation default flipped from "auto" to null (omit the field).
- Before: silently dropped oldest messages on context overflow.
- After: OpenAI returns an explicit
context_length_exceedederror.
Reason: truncation = "auto" rewrites the cached prefix and is on OpenAI's own
troubleshooting checklist as a top cause of low cache hit rates. To opt back
into the legacy auto-drop:
config = { truncation = "auto" }Single-user agent loop (typical Amplifier session): defaults are correct. For high-volume sessions, optionally pin to a session ID:
config = { prompt_cache_key = "session_${SESSION_ID}" }Multi-tenant deployment: key per tenant + system-prompt version to shard
load while preserving cache stickiness. Use safety_identifier per-call for
abuse tracking:
config = { prompt_cache_key = "tenant_42:sysprompt_v7" }
# In application code, per request:
# await provider.complete(request, safety_identifier="end_user_abc")ZDR / regulated industries: disable chaining so no server-side state is retained for reasoning models:
config = { enable_response_chaining = false, enable_state = false }Cache hit rate surfaces as usage.cache_read_tokens on responses, and is
emitted in llm:response events as cache_read_tokens. Note: OpenAI does NOT
report a cache_creation_tokens metric (unlike Anthropic) — cache writes are
implicit and not counted separately.
Chain invalidation is observable via the provider:response_chain_invalidated
event.
Standard Debug (debug: true):
- Emits
llm:request:debugandllm:response:debugevents - Contains request/response summaries with message counts, model info, usage stats
- Moderate log volume, suitable for development
Raw Debug (debug: true, raw_debug: true):
- Emits
llm:request:rawandllm:response:rawevents - Contains complete, unmodified request params and response objects
- Extreme log volume, use only for deep provider integration debugging
- Captures the exact data sent to/from OpenAI API before any processing
Example:
providers:
- module: provider-openai
config:
debug: true # Enable debug events
raw_debug: true # Enable raw API I/O capture
default_model: gpt-5.6-solexport OPENAI_API_KEY="your-api-key-here"# In amplifier configuration
[provider]
name = "openai"
model = "gpt-5.5"- Reasoning Control - Adjust reasoning effort (minimal, low, medium, high, xhigh)
- Reasoning Summary Verbosity - Control detail level of reasoning output (auto, concise, detailed)
- Extended Thinking Toggle - Enables high-effort reasoning with automatic token budgeting
- Explicit Reasoning Preservation - Re-inserts reasoning items (with encrypted content) into conversation for robust multi-turn reasoning
- Prompt Caching Hints -
prompt_cache_key,prompt_cache_retention(default"24h"), and per-callsafety_identifierwired into the request builder. See Prompt Caching. - Response Chaining for Reasoning Models -
enable_response_chainingactivatesprevious_response_idfor reasoning models, materially improving prefix cache hit rate (default"auto"). - Cache-Stable Truncation -
truncationdefaults tonull(omitted) so the cached prefix is never silently rewritten on context overflow. - Stateful Conversations - Optional conversation persistence
- Native Tools - Built-in web search, image generation, code interpreter
- Structured Output - JSON schema-based output formatting
- Function Calling - Custom tool use support
- Token Counting - Usage tracking and management (including
cache_read_tokens)
The reasoning_summary config controls the verbosity of reasoning blocks in the model's response:
auto(default if not specified) - Model decides appropriate detail levelconcise- Brief reasoning summaries (faster, fewer tokens)detailed- Verbose reasoning output similar to Anthropic's extended thinking blocks
Example comparison:
# Concise reasoning (brief summaries)
providers:
- module: provider-openai
config:
reasoning: "medium"
reasoning_summary: "concise"
# Detailed reasoning (verbose like Anthropic's thinking blocks)
providers:
- module: provider-openai
config:
reasoning: "high"
reasoning_summary: "detailed"Note: Detailed reasoning consumes more output tokens but provides deeper insight into the model's thought process, useful for complex problem-solving and debugging.
The provider detects OpenAI Responses API function_call / tool_call
blocks automatically, decodes JSON arguments, and returns standard
ToolCall objects to Amplifier. No extra configuration is required—tools
declared in your config or profiles execute as soon as the model requests
them.
The provider automatically handles incomplete responses from the OpenAI Responses API:
The Problem: OpenAI may return status: "incomplete" when generation is cut off due to:
max_output_tokenslimit reached- Content filter triggered
- Other API constraints
The Solution: The provider automatically continues generation using previous_response_id until the response is complete:
- Transparent continuation - Makes follow-up calls automatically (up to 5 attempts)
- Output accumulation - Merges reasoning items and messages from all continuations
- Single response - Returns complete ChatResponse to orchestrator
- Full observability - Emits
provider:incomplete_continuationevents for each continuation
Example flow:
# User request triggers large response
response = await provider.complete(request)
# Provider internally (if incomplete):
# 1. Initial call returns status="incomplete", reason="max_output_tokens"
# 2. Continuation 1: Uses previous_response_id, gets more output
# 3. Continuation 2: Uses previous_response_id, gets final output
# 4. Returns merged response with all content
# Orchestrator receives complete response, unaware of continuationsConfiguration: Set maximum continuation attempts (default: 5):
# In _constants.py
MAX_CONTINUATION_ATTEMPTS = 5 # Prevents infinite loopsObservability: Monitor via events in session logs:
{
"event": "provider:incomplete_continuation",
"provider": "openai",
"response_id": "resp_abc123",
"reason": "max_output_tokens",
"continuation_number": 1,
"max_attempts": 5
}The provider preserves reasoning state across conversation steps for improved multi-turn performance:
The Problem: Reasoning models (o3, o4, gpt-5.5) produce internal reasoning traces (rs_* IDs) that improve subsequent responses by ~3-5% when preserved. This is especially critical when tool calls are involved.
Important Distinction:
- Turn: A user prompt → (possibly multiple API calls) → final assistant response
- Step: Each individual API call within a turn (tool call loops = multiple steps per turn)
- Reasoning items must be preserved across STEPS, not just TURNS
The Solution: The provider uses explicit reasoning re-insertion for robust step-by-step reasoning:
- Requests encrypted content - API call includes
include=["reasoning.encrypted_content"] - Stores complete reasoning state - Both encrypted content and reasoning ID stored in
ThinkingBlock.contentfield - Re-inserts reasoning items - Explicitly converts reasoning blocks back to OpenAI format in subsequent turns
- Maintains metadata - Also tracks reasoning IDs in metadata for backward compatibility
How it works (tool call example showing step-by-step preservation):
# Step 1: User asks question requiring tool
response_1 = await provider.complete(request)
# response_1.output contains:
# - reasoning item: rs_abc123 (with encrypted_content)
# - tool_call: get_weather(latitude=48.8566, longitude=2.3522)
#
# Provider stores ThinkingBlock with:
# - thinking: "reasoning summary text"
# - content: [encrypted_content, "rs_abc123"] # Full reasoning state
# - metadata: {"openai:reasoning_items": ["rs_abc123"], ...}
# Orchestrator executes tool, adds result to context
# (Note: This is still within the SAME TURN, just a different STEP)
# Step 2: Provider called again with tool result (SAME TURN!)
response_2 = await provider.complete(request_with_tool_result)
# Provider reconstructs reasoning item from previous step:
# {
# "type": "reasoning",
# "id": "rs_abc123",
# "encrypted_content": "...", # From ThinkingBlock.content[0]
# "summary": [{"type": "summary_text", "text": "..."}]
# }
# OpenAI receives: [user_msg, reasoning_item, tool_call, tool_result]
# Model uses preserved reasoning from step 1 to generate final answerKey insight from OpenAI docs: "While this is another API call, we consider this as a single turn in the conversation." Reasoning must be preserved across steps (API calls) within the same turn, especially when tools are involved. This is also the scope reasoning_replay_scope bounds replay to by default (see Configuration) — it does not by itself say whether replaying earlier turns' reasoning adds value; that's left as an explicit tuning knob (reasoning_replay_scope = "all") rather than assumed.
Benefits:
- More robust - Explicit re-insertion doesn't rely on server-side state
- Stateless compatible - Works with
store: falseconfiguration - Better multi-turn performance - ~5% improvement per OpenAI benchmarks
- Critical for tool calling - Recommended by OpenAI for reasoning models with tools
- 关注s OpenAI docs - Implements "context += response.output" pattern
- Ciphertext always captured -
include=["reasoning.encrypted_content"]is requested unconditionally, even while chaining is active. Live measurement found this cache-neutral (requestingincludeon an already-chained call changes the response shape returned, not the request prefix caching keys on), so every reset/resume path can replay reasoning regardless of whether the response that produced it was chained.
ThinkingBlock.content encoding: reasoning state is stored as a
single-element list containing a named dict —
content: [{"encrypted_content": ..., "id": "rs_*", "summary": ...}] — not a
positional [encrypted_content, reasoning_id] list. A named dict survives
Amplifier's transcript sanitizer (which drops None values/entries) without
losing the meaning of what remains: {"id": "rs_abc"} after
encrypted_content: None is dropped is still unambiguously an id, whereas a
positional list collapsing to ["rs_abc"] is not distinguishable from
ciphertext without guessing. The provider still reads two legacy on-disk
shapes for backward compatibility with transcripts written before this
change (a 2-element positional list, and a 1-element collapsed list —
detected by the rs_* id prefix, never guessed positionally); a legacy
1-element block whose id cannot be paired with ciphertext is unrecoverable
and is dropped with a warning (it was never written with the ciphertext
present, so this is not new data loss — it makes the pre-existing loss
audible instead of silent).
The provider supports OpenAI's optional truncation parameter for automatic
conversation history management. The default flipped from "auto" to null
(omitted) as of PR #34, because truncation = "auto" rewrites the cached
prefix on overflow and busts prompt caching.
Configuration:
providers:
- module: provider-openai
config:
truncation: null # Default. Field omitted; OpenAI errors on overflow.
# OR
truncation: "auto" # Opt in: drop oldest messages on overflow (busts cache).How "auto" works (when explicitly opted in):
- OpenAI automatically removes oldest messages when context limit approached
- FIFO (first-in, first-out) - most recent messages preserved
- Transparent to application - no errors or warnings
- Works with all conversation types (reasoning, tools, multi-turn)
Trade-offs:
null(default): explicitcontext_length_exceedederror on overflow, but the cached prefix is preserved across turns."auto": simplicity and never-hit-the-limit, at the cost of cache hit rate. Listed on OpenAI's troubleshooting checklist as a top cause of low cache utilization.
When to use "auto":
- Legacy behavior compatibility, or workloads where simplicity outweighs
cache efficiency. For most users, the new default (
null) is correct; pair it with explicit context management upstream.
See also: Prompt Caching for the broader cache-stability rationale.
The provider populates ChatResponse.metadata with OpenAI-specific state:
| Key | Type | Description |
|---|---|---|
openai:response_id |
str |
Response ID for continuation and reasoning preservation |
openai:status |
str |
Response status: "completed" or "incomplete" |
openai:incomplete_reason |
str |
Reason if incomplete: "max_output_tokens" or "content_filter" |
openai:reasoning_items |
list[str] |
Reasoning item IDs (rs_*) for state preservation |
openai:continuation_count |
int |
Number of auto-continuations performed (if > 0) |
Example metadata:
{
"openai:response_id": "resp_05fb664e4d9dca6a016920b9b1153c819487f88da867114925",
"openai:status": "completed",
"openai:reasoning_items": ["rs_05fb664e4d9dca6a016920b9b1daac81949b7ea950bddef95a"],
"openai:continuation_count": 2
}Namespacing: All keys use openai: prefix to prevent collisions with other providers (per kernel philosophy).
The provider implements graceful degradation for incomplete tool call sequences:
The Problem: If tool results are missing from conversation history (due to context compaction bugs, parsing errors, or state corruption), the OpenAI API rejects the entire request, breaking the user's session.
The Solution: The provider automatically detects missing tool results and injects synthetic results that:
- Make the failure visible - LLM sees
[SYSTEM ERROR: Tool result missing]message - Maintain conversation validity - API accepts the request, session continues
- Enable recovery - LLM can acknowledge the error and ask user to retry
- Provide observability - Emits
provider:tool_sequence_repairedevent with details
Example:
# Broken conversation history (missing tool result)
messages = [
{"role": "assistant", "tool_calls": [{"id": "call_123", "function": {"name": "get_weather", ...}}]},
# MISSING: {"role": "tool", "tool_call_id": "call_123", "content": "..."}
{"role": "user", "content": "Thanks"}
]
# Provider injects synthetic result:
{
"role": "tool",
"tool_call_id": "call_123",
"content": "[SYSTEM ERROR: Tool result missing from conversation history]\n\nTool: get_weather\n..."
}
# LLM responds: "I notice the weather tool failed. Let me try again..."
# Session continues instead of crashingObservability: Repairs are logged as warnings and emit provider:tool_sequence_repaired events for monitoring.
Two repair sites emit this event. The provider repairs unpaired tool calls in two distinct places, and each tags its emission with a repair_site field so consumers can tell them apart:
repair_site |
Where | What it repairs |
|---|---|---|
message_level |
_find_missing_tool_results |
A tool call in the local conversation history whose result is absent. Synthetic results are injected into the transcript. |
chain_pairing |
_enforce_chain_output_pairing |
A tool call that lives server-side in a chained response (previous_response_id) whose output is missing from, or mis-keyed in, the delta input. |
Both emissions carry provider, repair_count, and repairs (a list of {tool_call_id, tool_name}). repair_count counts synthesized results only and always equals len(repairs).
The chain_pairing site adds dropped_count and kept_count alongside the chain-specific diagnostics (expected_call_ids, provided_call_ids, synthesized_for, dropped_item_id_outputs, kept_item_id_outputs). Dropping an unpairable output is a separate action from synthesizing a missing one, so a turn may legitimately report repair_count: 0 with dropped_count: 1 — the provider dropped an output that could pair with nothing server-side while every genuine call was already correctly paired.
An fc_-keyed (Responses-API item id) output has two possible outcomes, and each is counted separately:
| Field | Outcome |
|---|---|
dropped_count / dropped_item_id_outputs |
The fc_ id matches no chained call. It can pair with nothing server-side, so it is discarded and the orphaned call gets a synthesized error result. |
kept_count / kept_item_id_outputs |
The fc_ id matches the chained turn's local tool-call record — both were keyed from the same ToolCallBlock.id. The real tool output is kept: dropping it would destroy the payload while the orphan repair re-synthesized an error under that same unpairable id, which is strictly worse. |
Total fc_ keying anomalies observed on a turn is therefore dropped_count + kept_count, not dropped_count alone.
Philosophy: This is graceful degradation following kernel philosophy - errors in other modules (context management) don't crash the provider or kill the user's session.
amplifier-core>=1.0.0openai>=1.0.0
Note
This project is not currently accepting external contributions, but we're actively working toward opening this up. We value community input and look forward to collaborating in the future. For now, feel free to fork and experiment!
Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit Contributor License Agreements.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.