Support the OpenAI Decisions API
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
调研方向
The issue names agents.models.openai_decisions/, tests/models/openai_decisions/, docs/decisions.md, and examples/decisions/ as implementation, test, documentation, and example entry points. Start by reviewing the linked implementation branch and its tests, then check how the SDK handles providers, tool input guardrails, needs_approval, and Agent.clone. Done means the typed client, tool risk gate, routing example, and documentation work together with the stated compatibility change.
由索引模型根据 Issue 内容生成。
描述
Please read this first
- Have you read the docs? Yes. The SDK has no support for the Decisions API today.
- Have you searched for related issues? Yes. No existing issue or PR covers the Decisions API, in this repo or in openai/openai-agents-js.
Describe the feature
Add support for OpenAI's Decisions API (POST /v1/decisions, public beta since 2026-10-06, model gpt-6-luna) to the Agents SDK:
- A typed client for asking predicate, choice, and score questions and reading typed answers by name.
- An auto-mode tool risk gate that classifies each call to a guarded function tool before it runs, and either blocks risky calls or sends them to human approval.
- A documented routing recipe for picking a model per run.
A complete implementation is available as a possible solution: martimfasantos/openai-agents-python#1 (branch feat/openai-decisions-api). I can't open a pull request here because PRs are limited to collaborators. I'd be glad to open one if you grant access, or a maintainer is welcome to take the branch as is.
Motivation
Agents constantly make small judgment calls around the main model: is this request in scope, which model should handle it, is this tool call safe to run. Today the SDK's answer is another agent with a structured output type (as in examples/agent_patterns/input_guardrails.py), which costs a full generation per check and returns free-form confidence.
The Decisions API is built for exactly these calls. According to the guide, it:
- answers named questions about an input with typed answers and probabilities instead of generated text;
- is roughly 10x faster than the Responses API;
- bills input tokens only ($0.10 per 1M), with no output-token charges;
- returns a per-question refusal instead of failing the whole request.
LangChain already shipped an integration in langchain-openai 1.7.0 (docs, langchain-ai/langchain#41125) with a client, a model router, and an auto-mode middleware. Agents SDK users currently have no equivalent. The proposal below brings the same capabilities through the SDK's existing seams (input guardrails, tool input guardrails, needs_approval, agent.clone) instead of new runtime machinery.
Proposed API
Everything is importable from agents.models.openai_decisions, a package split by concern (questions.py, result.py, client.py, auto_mode.py) that re-exports its public names from __init__.py, following the SDK's package convention (agents.testing, agents.realtime). Nothing is re-exported from top-level agents, so the beta surface stays out of the released API contract until the Decisions API reaches GA.
Client and questions
from agents.models.openai_decisions import Choice, Level, OpenAIDecisions, Predicate, Score
decisions = OpenAIDecisions("gpt-6-luna") # optional: openai_client=AsyncOpenAI(...)
result = await decisions.decide(
"The deploy failed twice and customers see 500 errors at checkout.",
{
"urgent": Predicate("Does this need attention right now?"),
"team": Choice(
"Which team should own this?",
{"infra": "Servers, deploys, and uptime.", "billing": "Payments and invoices."},
),
"severity": Score(
"How severe is the impact?",
["Cosmetic", Level("Degraded", "Slower or partly broken."), "Down"],
),
},
safety_identifier="user-123", # optional, forwarded to the API
)
result.predicates["urgent"].probability # 0.91
result.choices["team"].choice # "infra"
result.scores["severity"].score # 1.2 (probability-weighted level index)
result.refusals # questions the model declined to answer
result.usage, result.request_id
| Piece | Behavior |
|---|---|
Predicate, Choice, Score, Level |
Frozen dataclasses that serialize to the openai SDK's request types. Choice values may be strings or booleans; descriptions are optional and omitted from the wire when absent. Question names are the mapping keys, so they are unique by construction. |
OpenAIDecisions |
Calls the typed client.decisions.create. The client is resolved lazily, in the same order as OpenAIProvider: the injected client, then set_default_openai_client, then a new AsyncOpenAI using set_default_openai_key or OPENAI_API_KEY on the SDK's shared HTTP pool. Base URL, timeout, retries, and headers are configured on the AsyncOpenAI instance, as elsewhere in the SDK. |
DecisionResult |
answers keyed by question name, using the openai SDK's answer models directly (no duplicated schema, forward compatible with new fields), plus typed views predicates, choices, scores, refusals, and model, usage, request_id. |
API-enforced limits (1–200 questions, 2–255 choices, 2–10 levels, input size) are not duplicated client-side; the API rejects invalid requests with its own errors.
Auto mode (tool risk gate)
from agents import Agent
from agents.models.openai_decisions import OpenAIAutoMode
auto_mode = OpenAIAutoMode(model="gpt-6-luna") # on_risky="reject_content" by default
agent = Agent(name="Files", tools=[*auto_mode.guard([read_file, delete_file])])
# Or escalate risky calls to the existing human-approval flow instead of blocking them:
approving = OpenAIAutoMode(model="gpt-6-luna", on_risky="require_approval", threshold=0.6)
guard(tools)returns copies of function tools (the same copy pattern astool_namespace()); the originals are not modified.- Each guarded call asks one
is_riskypredicate. The classifier sees the run input (the latest 30 items, with images, files, audio, screenshots, and generated images replaced by placeholders), the namespace-qualified tool name, the raw arguments, and the tool description. The default instructions tell it to treat all of that as data and to accept authorization only from explicit user messages. on_risky="reject_content"adds a tool input guardrail: a risky call is not executed, and the model receives a message saying it was blocked.on_risky="require_approval"setsneeds_approval: risky calls pause the run as a standardToolApprovalIteminterruption, safe calls run without approval, and an approved call is not classified again on resume.- It fails closed: a refusal or missing answer counts as risky, and a Decisions API error stops the run before the tool executes.
threshold(default 0.5, LangChain's value) is a parameter because the guide recommends setting thresholds from labelled data.
Routing recipe
route = await decisions.decide(question, {"route": Choice("Which model should answer?", {"fast": "...", "powerful": "..."})})
answer = route.choices.get("route")
model = MODELS[str(answer.choice)] if answer else MODELS["fast"]
result = await Runner.run(agent.clone(model=model), question)
Routing ships as an example rather than a router Model. I implemented and reviewed a Model that classifies once per run and delegates to the chosen model, then removed it. A Model that wraps other models cannot see server-held history after a handoff or resume (with previous_response_id or conversation_id), would resolve model names outside the run's provider, and has to forward settings, retries, streams, and cleanup itself. The pre-run recipe has none of those problems, and Agent.clone re-derives the chosen model's default settings.
Documented limitations
- Inside an agent run through
Agent.as_tool(), the input was written by the calling agent, so guard the outer agent tool rather than the inner agent's tools. - With
on_risky="require_approval", calls whose arguments the SDK has to convert (enums, dates, models, omitted defaults) always pause for approval without being classified, because the SDK skipsneeds_approvalfor them. This fails closed;reject_contentclassifies every call. - Authorization given only inside an attachment, or older than the latest 30 input items, is not seen by the classifier.
- In approval mode, apply
tool_namespace()beforeguard()so classification uses the namespaced name. RealtimeSessiondoes not populateturn_input, so realtime tool calls are classified without conversation context.
Compatibility
- The
openaifloor rises from>=3.0.0to>=3.26.0, the first release with the typedclient.decisionsresource (same major version). The lock moves toopenai3.29.0 andjiter0.17.0. - Because of that floor,
agents.testing.ScriptedModelno longer setsnameon scriptedresponse.function_call_arguments.doneevents. The field is not part of that event type inopenai>=3.26, and nothing in the SDK reads it. - No released public API changes.
Testing and review on the branch
.agents/skills/code-change-verification/scripts/run.sh: format, lint, mypy, pyright, andmake testspass (12167 passed, 38 skipped).tests/models/openai_decisions/(19 tests acrosstest_client.py,test_auto_mode.py,test_exports.py): the exact request body and answer parsing for every question type, client resolution order, both auto-mode modes, fail-closed paths, approval resume without re-classification, media stripping, JSON schemas in replayed history, namespaced tool names, the context cap, guarded copies leaving originals untouched, and the package's public exports.tests/models/openai_decisions/test_live.py: opt-in live check (OPENAI_RUN_LIVE_DECISIONS_TESTS=1).examples/decisions/:classify.py(all question types plus a predicate-backed@input_guardrail),tool_risk_gate.py(both auto-mode modes), andmodel_router.py(the routing recipe).- The change went through this repo's high-risk
$implementation-final-reviewprocess, with two independent reviewers per round, until both returned clean reviews on the final content. It also incorporates the relevant review feedback from langchain-ai/langchain#41125: SDK-native types instead of custom ones, a bounded classification context, and documented media handling. - The branch also includes a guide (
docs/decisions.md) and an API reference page as separatedocs:commits, which can be split into a docs-only PR to match the Documentation Release Timing rule.
- 主要语言
- Python
- 星标
- 30k
- 派生
- 4.9k
- 平均合并
- 18 小时 13 分钟
- 30 天内合并 PR
- 215
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
openai/openai-agents-python 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 77/100
openai/openai-agents-python#5348 · 2 条评论 ·
维护者通常 1 天内回复
-
难度 3/5 半天 新手友好度 70/100
openai/openai-agents-python#5342 · 3 条评论 ·
维护者通常 1 天内回复
-
enhancement
难度 3/5 1-2 天 新手友好度 65/100
openai/openai-agents-python#5336 · 1 条评论 ·
维护者通常 1 天内回复
-
Finished tool call is dropped when a parallel tool's guardrail trips可能已有人在做 @jbeckwith-oai 于 6 天前认领。 未关闭
难度 4/5 3-5 天 新手友好度 35/100
openai/openai-agents-python#5327 · 1 条评论 ·
维护者通常 1 天内回复
-
难度 5/5 一周以上 新手友好度 35/100
openai/openai-agents-python#5322 · 2 条评论 ·
维护者通常 1 天内回复
查看 openai/openai-agents-python 的全部 Issue
相似的 Issue
-
[Bug]: LangGraph Store sync put bypasses namespace validation可能已有人在做 @ktz03 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 72/100
volcengine/OpenViking#6050 ·
维护者通常 1 天内回复
-
[Bug]: Streamed /v1/chat/completions sends unredacted agent error text (keys) in the final chunk可能已有人在做 @rodricksz4h5 今天认领。 未关闭area/streaming comp/gateway P2 type/security
难度 2/5 1-3 小时 新手友好度 78/100
NousResearch/hermes-agent#138245 ·
维护者通常 1 天内回复
-
[oblt-aw][autodoc] Update user onboarding guide for mandatory trigger CODEOWNERS step可能已有人在做 @github-actions 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 66/100
维护者通常 1 天内回复
-
correction metadata
难度 2/5 1 小时以内 新手友好度 68/100
acl-org/acl-anthology#10378 · 1 条评论 ·
维护者通常 1 天内回复
-
cloudtrail_multi_region_enabled_logging_management_events fails when the advanced event selector has a custom name可能已有人在做 @12YYCCYY16 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
prowler-cloud/prowler#13033 ·
维护者通常 1 天内回复