OpenAIChatCompletionsModel sends tool_choice without tools, which the API rejects, on turns with no tools
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
src/agents/models/openai_chatcompletions.py から始めてください。そこでは tool_choice が変換されて渡されており、parallel_tool_calls はすでに converted_tools で制御されています。同じ制御が src/agents/extensions/models/litellm_model.py の tool_choice にも必要です。変更の前後で tests/models を実行してください。ツールがない場合に tool_choice が省略され、追加したテストがリクエストボディを検証すれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Please read this first
- Have you read the docs? Yes
- Have you searched for related issues? Yes. #4326 (fixed by #4359 and #4330) covered the same API rule for
parallel_tool_calls.tool_choicewas left out.
Describe the bug
OpenAIChatCompletionsModel always sends ModelSettings.tool_choice, even when the converted tool list is empty and tools is left out of the request. The Chat Completions API rejects that combination:
Invalid value for 'tool_choice': 'tool_choice' is only allowed when 'tools' are specified.
So an app-wide RunConfig(model_settings=ModelSettings(tool_choice="auto")) breaks every turn of an agent that has no function tools and no handoffs in that turn. Examples are a final summarizer agent after a handoff, or an agent whose tools are all switched off with is_enabled. parallel_tool_calls is already omitted in this case. LitellmModel sends the same request; I checked the body LiteLLM actually posts. AnyLLMModel's chat path has the same line, but I didn't run it.
Debug information
- Agents SDK version:
main(e355d69), also 0.24.0 - Related library versions: openai 3.29.0, litellm 1.83.0
- Python version: 3.14.7
- Operating system: macOS
- Model and model provider: any Chat Completions model (
OpenAIChatCompletionsModel,LitellmModel) - Does the issue reproduce with the latest Agents SDK release? Yes
- Does the issue occur consistently or intermittently? Consistently
Repro steps
A real AsyncOpenAI client on an httpx2.MockTransport, printing what's sent:
import asyncio, json
import httpx2
from openai import AsyncOpenAI
from agents import Agent, ModelSettings, OpenAIChatCompletionsModel, RunConfig, Runner
def handler(request: httpx2.Request) -> httpx2.Response:
body = json.loads(request.content)
print("tools in request:", "tools" in body, "| tool_choice:", body.get("tool_choice"),
"| parallel_tool_calls in request:", "parallel_tool_calls" in body)
return httpx2.Response(200, json={
"id": "chatcmpl-1", "object": "chat.completion", "created": 0, "model": "gpt-4o-mini",
"choices": [{"index": 0, "finish_reason": "stop",
"message": {"role": "assistant", "content": "OK"}}],
})
async def main() -> None:
client = AsyncOpenAI(api_key="x", http_client=httpx2.AsyncClient(transport=httpx2.MockTransport(handler)))
agent = Agent(name="Summarizer", instructions="Reply OK",
model=OpenAIChatCompletionsModel("gpt-4o-mini", client))
cfg = RunConfig(tracing_disabled=True,
model_settings=ModelSettings(tool_choice="auto", parallel_tool_calls=False))
await Runner.run(agent, "Say OK.", run_config=cfg)
asyncio.run(main())
tools in request: False | tool_choice: auto | parallel_tool_calls in request: False
Expected behavior
tool_choice is left out when the request has no tools, the same way parallel_tool_calls already is.
Root cause and a possible fix
tool_choice is converted at openai_chatcompletions.py#L642 and sent at #L709, while only parallel_tool_calls is gated on converted_tools (#L652-L656). LiteLLM: litellm_model.py#L646. AnyLLM: any_llm_model.py#L951.
--- a/src/agents/models/openai_chatcompletions.py
+++ b/src/agents/models/openai_chatcompletions.py
@@ -654,6 +654,9 @@ class OpenAIChatCompletionsModel(Model):
parallel_tool_calls: bool | Omit = (
self._non_null_or_omit(model_settings.parallel_tool_calls) if converted_tools else omit
)
+ # The API rejects tool_choice without tools, the same as parallel_tool_calls.
+ if not converted_tools:
+ tool_choice = omit
--- a/src/agents/extensions/models/litellm_model.py
+++ b/src/agents/extensions/models/litellm_model.py
@@ -643,7 +643,7 @@ class LitellmModel(Model):
- tool_choice=self._remove_not_given(tool_choice),
+ tool_choice=self._remove_not_given(tool_choice) if converted_tools else None,
With that change, both repros send no tool_choice, and tests/models passes (1023 passed, 1 skipped). The placeholder Response.tool_choice built for streaming is separate and unchanged.
- 主要言語
- Python
- スター
- 30.4k
- フォーク
- 4.9k
- 平均マージ
- 16時間 21分
- マージ済み PR(30日)
- 86
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
openai/openai-agents-python のほかの issue
-
Preserve wire aliases when converting Responses output items to input対応中かも @LVsarff が 1 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
openai/openai-agents-python#5477 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
Voice: default sentence splitter never splits Chinese/Japanese, so TTS waits for the whole replyオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
openai/openai-agents-python#5475 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
tool_choice naming a function tool called web_search, file_search, mcp, etc. selects the built-in tool instead対応中かも @LVsarff が 1 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
openai/openai-agents-python#5472 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
OpenAIConversationsSession and OpenAIResponsesCompactionSession ignore set_default_openai_key()オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
openai/openai-agents-python#5471 ·
メンテナーはふだん 1 日以内に返信
-
Docs: `tool_choice="required"` can select a state-changing tool when the needed one is not shownオープン
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
openai/openai-agents-python#5461 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
openai/openai-agents-python の issue をすべて見る
似ている issue
-
Backend issue lists report in-patch warnings and out-of-patch errors as not publishable対応中かも @RaphaelFakhri が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
mozilla/code-review#3794 ·
メンテナーはふだん 1 日以内に返信
-
[oblt-aw][autodoc] Update docs that still describe the security registry as empty対応中かも @github-actions が今日担当しました。 オープン
難易度 1/5 1〜3時間 初心者へのやさしさ 85/100
メンテナーはふだん 1 日以内に返信
-
[Bug] adb backup script reports "Done" & exits 0 when the backup fails対応中かも @bhattmanthan が今日担当しました。 オープンrequest-issue-assignment
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
agent-ui bug p2
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
correction metadata
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
acl-org/acl-anthology#10768 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信