tool_choice naming a function tool called web_search, file_search, mcp, etc. selects the built-in tool instead

オープン 初心者向け
#5,472 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
70/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python
領域
api

調査の方向性

src/agents/models/openai_responses.py の Converter.convert_tool_choice から始めてください。ここでは web_search のような組み込みツール名が、名前付き関数の分岐より前にマッピングされます。shell と apply_patch の既存のゲートと比較してください。httpx2 の MockTransport を使って issue の再現手順を実行し、リクエストの tools には関数のみが含まれている一方で tool_choice が {'type': 'web_search'} として送信されることを確認してください。完了条件は、web_search という名前で設定された関数ツールが {'type': 'function', 'name': 'web_search'} を生成すること、そして tools がない場合または WebSearchTool が設定されている場合には組み込みセレクターが引き続き返され、tests/models が通ることです。

索引モデルが issue の本文から書いたものです。

説明

Please read this first
  • Have you read the docs? Yes
  • Have you searched for related issues? Yes. #4952 fixed this for shell and apply_patch, and computer and tool_search are already gated with tests. The other built-in names were left out.
Describe the bug

An agent on the Responses API has a function tool whose name matches a built-in tool type, for example its own web_search backed by another search API. Setting ModelSettings(tool_choice="web_search") to force that tool sends tool_choice={"type": "web_search"}, which selects the hosted web search tool, instead of {"type": "function", "name": "web_search"}. The request's tools list contains only the function, so the caller's tool can't be forced. The same happens for file_search, web_search_preview, computer_use_preview (with no ComputerTool), image_generation, code_interpreter, mcp and programmatic_tool_calling.

#4952 described exactly this for shell and apply_patch: without a gate, "a function tool named shell or apply_patch would silently stop being targetable".

Debug information
  • Agents SDK version: main (e355d69), also 0.24.0
  • Related library versions: openai 3.29.0
  • Python version: 3.14.7
  • Operating system: macOS
  • Model and model provider: OpenAI Responses API
  • 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, OpenAIResponsesModel, RunConfig, Runner, function_tool

def handler(request: httpx2.Request) -> httpx2.Response:
    body = json.loads(request.content)
    print("tools sent      :", [(t["type"], t.get("name")) for t in body["tools"]])
    print("tool_choice sent:", body["tool_choice"])
    return httpx2.Response(200, json={
        "id": "resp_1", "object": "response", "created_at": 0, "model": "gpt-4.1",
        "status": "completed", "parallel_tool_calls": True, "tool_choice": "auto", "tools": [],
        "output": [{"type": "message", "id": "msg_1", "role": "assistant", "status": "completed",
                    "content": [{"type": "output_text", "text": "ok", "annotations": []}]}],
    })

@function_tool(name_override="web_search")
def my_search(query: str) -> str:
    """Search my own index."""
    return "result"

async def main() -> None:
    client = AsyncOpenAI(api_key="x", http_client=httpx2.AsyncClient(transport=httpx2.MockTransport(handler)))
    agent = Agent(name="a", tools=[my_search], model=OpenAIResponsesModel("gpt-4.1", client),
                  model_settings=ModelSettings(tool_choice="web_search"))
    await Runner.run(agent, "find docs", run_config=RunConfig(tracing_disabled=True))

asyncio.run(main())
tools sent      : [('function', 'web_search')]
tool_choice sent: {'type': 'web_search'}
Expected behavior

tool_choice is {"type": "function", "name": "web_search"} whenever a function tool (or handoff) with that name is configured, as already happens for shell, apply_patch, computer and tool_search.

Root cause and a possible fix

Converter.convert_tool_choice maps these strings to built-in selectors before the named-function branch and doesn't look at the configured tools (openai_responses.py#L1856-L1894). The shell/apply_patch branches are gated (#L1895-L1902). The gate below checks that a local function tool with that name exists, not that the built-in is configured, so prompt-managed built-in tools (#1304) keep working:

--- a/src/agents/models/openai_responses.py
+++ b/src/agents/models/openai_responses.py
+# Strings that convert_tool_choice maps to a built-in tool selector without checking
+# whether that built-in tool is configured.
+_HOSTED_TOOL_CHOICE_NAMES = frozenset(
+    {
+        "programmatic_tool_calling",
+        "file_search",
+        "web_search",
+        "web_search_preview",
+        "computer_use_preview",
+        "image_generation",
+        "code_interpreter",
+        "mcp",
+    }
+)
@@ class Converter:
         elif tool_choice == "none":
             return "none"
+        elif tool_choice in _HOSTED_TOOL_CHOICE_NAMES and cls._has_top_level_function_named(
+            tool_choice, tools=tools, handoffs=handoffs
+        ):
+            # Like computer, shell and apply_patch, a function tool with a built-in tool's
+            # name must stay targetable.
+            return {"type": "function", "name": tool_choice}
         elif tool_choice == "programmatic_tool_calling":
@@ class Converter:
+    @classmethod
+    def _has_top_level_function_named(
+        cls,
+        name: str,
+        *,
+        tools: Sequence[Tool] | None,
+        handoffs: Sequence[Handoff[Any, Any]] | None,
+    ) -> bool:
+        return any(handoff.tool_name == name for handoff in handoffs or ()) or any(
+            isinstance(tool, FunctionTool)
+            and tool.name == name
+            and get_explicit_function_tool_namespace(tool) is None
+            and not tool.defer_loading
+            for tool in tools or ()
+        )

With the patch, the repro sends {'type': 'function', 'name': 'web_search'}. With no tools, or with a configured WebSearchTool(), "web_search" still maps to {'type': 'web_search'}. tests/models passes (1023 passed, 1 skipped).

主要言語
Python
スター
30.4k
フォーク
4.9k
平均マージ
16時間 32分
マージ済み PR(30日)
85

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

openai/openai-agents-python のほかの issue

openai/openai-agents-python の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。