Python: Mark hosted tool calls informational-only#6997
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Automated Code Review
Reviewers: 5 | Confidence: 82%
✓ Correctness
The PR correctly implements the informational_only flag across the type system, invocation logic, and all provider mappings. The serialization correctly omits the field for non-function_call types and when the value is False. The invocation filtering properly skips informational-only calls at all relevant checkpoints (approval, unknown-call, streaming continuation, extraction). One minor defensive programming concern: the OpenAI
_parse_hosted_function_call_contentmatch statement has no default branch, socall_idwould be unbound if somehow neither case matches (practically unreachable given type annotations).
✓ Security Reliability
This PR correctly implements informational-only marking for hosted/provider-executed tool calls across all provider adapters. The security-critical function invocation filtering is well-designed: informational calls are excluded from execution, approval checks, unknown-call termination, and streaming continuation. The
mcp_server_tool_calltype correctly forcesinformational_only=Trueregardless of input. The streaming merge uses OR semantics (safe direction). One minor reliability concern exists in the OpenAI helper where a match statement lacks a default case, leaving a local variable potentially unbound.
✓ Test Coverage
The test coverage for this PR is thorough. Tests cover serialization round-trips, invocation skipping, approval skipping, streaming continuation, and provider-level parsing for all four providers (OpenAI, Anthropic, Gemini, Foundry). Two minor gaps exist: (1) no test for informational-only calls bypassing terminate_on_unknown_calls, and (2) no streaming test for the OpenAI tool_search_call path via response.output_item.done. Neither is blocking since the core behavior is well-tested through existing coverage of the underlying _is_actionable_function_call helper and the custom_tool_call streaming test covers the shared code path.
✓ Failure Modes
The PR is well-implemented with comprehensive test coverage. The informational_only flag is correctly propagated through serialization, invocation filtering, and all provider adapters. The double-filtering in _extract_function_calls and _try_execute_function_calls provides defense-in-depth. No high-severity failure modes were identified. One minor concern exists in the OpenAI _parse_hosted_function_call_content method where the match statement lacks a default case, which could produce a confusing UnboundLocalError if the method is ever called with an unexpected item type, though current callers properly constrain inputs.
✓ Design Approach
The core invocation changes look directionally right, but the OpenAI path still loses transcript fidelity on replay: hosted
custom_tool_callandtool_search_callitems are parsed into informational-onlyfunction_callcontent, then serialized back out as ordinaryfunction_callhistory on the next request. That means the design currently fixes local execution, but not the stated goal of preserving provider-executed tool records across turns for OpenAI.
Suggestions
- Consider adding a test that verifies an informational-only function call with an unknown name does NOT raise KeyError when terminate_on_unknown_calls=True, to directly cover the new guard at _tools.py:1702-1707.
Automated review by eavanvalkenburg's agents
There was a problem hiding this comment.
Pull request overview
This PR adds first-class Python support for hosted/provider-executed tool-call transcript items by marking them as informational_only, ensuring they remain visible in conversation history while being excluded from Agent Framework’s local function invocation/approval/termination flow.
Changes:
- Introduces
Content.informational_onlyforfunction_callcontent (with serialization rules) and formalizes that MCP server tool calls are always informational-only. - Updates function invocation logic to skip informational-only calls (including approval checks, unknown-call termination, and streaming continuation).
- Maps provider-hosted tool-call records to informational-only content across OpenAI Responses, Foundry Hosting Responses, Anthropic server tools, and Gemini tool_call/tool_response parts, with expanded test coverage.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| python/packages/openai/tests/openai/test_openai_chat_client.py | Adds tests asserting hosted OpenAI Responses tool-call items parse as informational-only. |
| python/packages/openai/agent_framework_openai/_chat_client.py | Parses custom_tool_call / tool_search_call as informational-only function_call content. |
| python/packages/gemini/tests/test_gemini_client.py | Adds tests for Gemini server-side tool_call/tool_response parsing and replay behavior. |
| python/packages/gemini/agent_framework_gemini/_chat_client.py | Parses Gemini tool_call as informational-only function_call and preserves native tool_* parts on replay. |
| python/packages/foundry_hosting/tests/test_responses.py | Updates expectations so hosted tool calls are informational-only while local function calls remain actionable. |
| python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py | Marks Foundry-hosted tool call transcript items as informational-only in message conversion. |
| python/packages/core/tests/core/test_types.py | Adds serialization/round-trip tests for informational-only function calls and MCP calls. |
| python/packages/core/tests/core/test_function_invocation_logic.py | Verifies informational-only function calls are not invoked and do not trigger approval flow (incl. streaming). |
| python/packages/core/agent_framework/_types.py | Adds informational_only to Content, enforces MCP tool calls as informational-only, and refines serialization/merge behavior. |
| python/packages/core/agent_framework/_tools.py | Centralizes “actionable function call” detection and skips informational-only calls in invocation/streaming logic. |
| python/packages/anthropic/tests/test_anthropic_client.py | Ensures Anthropic server_tool_use maps to informational-only function_call. |
| python/packages/anthropic/agent_framework_anthropic/_chat_client.py | Marks Anthropic server_tool_use content as informational-only during parsing. |
| function_calls = [ | ||
| function_call | ||
| for function_call in function_calls | ||
| if function_call.type != "function_call" or _is_actionable_function_call(function_call) | ||
| ] |
| case "function_result": | ||
| raw_part = content.raw_representation | ||
| if isinstance(raw_part, types.Part) and raw_part.tool_response is not None: | ||
| parts.append(raw_part) | ||
| else: | ||
| logger.debug("Skipping unsupported content type for Gemini: %s", content.type) |
Motivation & Context
Hosted/provider-executed tool calls are currently close enough to local function calls that they can be confused with work Agent Framework should execute itself. Providers such as OpenAI, Anthropic, Gemini, and hosted MCP surfaces return tool-call-like records for actions already handled by the service or remote server. Python needs to preserve those records in transcripts without sending them through local function invocation.
This contributes to safer hosted tool support and keeps conversation history faithful for provider-executed tool activity. The companion message injection design work is tracked in #6996.
Description & Review Guide
Content.informational_onlyfor function-call content and documents the function-call and MCP server tool-call constructors.informational_only: falsefields and preserve true informational calls where needed.Related Issue
Fixes #6995
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.