tool_choice naming a function tool called web_search, file_search, mcp, etc. selects the built-in tool instead
Los mantenedores suelen responder en 1 día
@LVsarff ya está trabajando en esto.
Desde el 3/11/2026.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 70/100
Línea de trabajo
Empieza en Converter.convert_tool_choice en src/agents/models/openai_responses.py, donde los nombres de herramientas integradas como web_search se asignan antes de la rama de funciones con nombre, y compáralo con la validación existente de shell y apply_patch. Ejecuta el repro del issue con un httpx2 MockTransport para confirmar que tool_choice se envía como {'type': 'web_search'} mientras que las herramientas de la solicitud solo contienen la función. Está terminado cuando una herramienta de función configurada llamada web_search produce {'type': 'function', 'name': 'web_search'}, y cuando no hay herramientas o hay un WebSearchTool configurado se sigue devolviendo el selector integrado y tests/models pasa.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Please read this first
- Have you read the docs? Yes
- Have you searched for related issues? Yes. #4952 fixed this for
shellandapply_patch, andcomputerandtool_searchare 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).
- Lenguaje dominante
- Python
- Estrellas
- 30.4k
- Forks
- 4.9k
- Merge medio
- 16 h 21 min
- PR fusionados (30 d)
- 86
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de openai/openai-agents-python
-
Preserve wire aliases when converting Responses output items to inputPosiblemente ocupada @LVsarff la tomó hace 1 día. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
openai/openai-agents-python#5477 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Voice: default sentence splitter never splits Chinese/Japanese, so TTS waits for the whole replyAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
openai/openai-agents-python#5475 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
OpenAIConversationsSession and OpenAIResponsesCompactionSession ignore set_default_openai_key()Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
openai/openai-agents-python#5471 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
openai/openai-agents-python#5468 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Docs: `tool_choice="required"` can select a state-changing tool when the needed one is not shownAbierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
openai/openai-agents-python#5461 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de openai/openai-agents-python
Issues similares
-
HandyModRTransform.deriv3 is wrong for m > 2Posiblemente ocupada @SajalDevX la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
-
read_messages: the first read after subscribe/psubscribe returns no messages even when some are waitingPosiblemente ocupada @matheussilvano la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 80/100
The-Swarm-Corporation/swarms-framework-docs#399 ·
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
Los mantenedores suelen responder en 1 día
-
bump patch
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
autowarefoundation/autoware_carla_scenario#1321 ·
Los mantenedores suelen responder en 1 día