Skip to content

Python: Mark hosted tool calls informational-only#6997

Open
eavanvalkenburg wants to merge 1 commit into
microsoft:mainfrom
eavanvalkenburg:informational-only-function-calls
Open

Python: Mark hosted tool calls informational-only#6997
eavanvalkenburg wants to merge 1 commit into
microsoft:mainfrom
eavanvalkenburg:informational-only-function-calls

Conversation

@eavanvalkenburg

Copy link
Copy Markdown
Member

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

  • What are the major changes?
    • Adds Content.informational_only for function-call content and documents the function-call and MCP server tool-call constructors.
    • Skips informational-only function calls in function invocation, approval checks, unknown-call termination, and streaming continuation.
    • Maps hosted/provider-executed tool calls to informational-only content across OpenAI, Foundry hosting, Anthropic, and Gemini, including MCP server tool calls.
    • Adds tests for serialization, invocation behavior, and provider parsing/round-trip behavior.
  • What is the impact of these changes?
    • Local function calls remain actionable by default.
    • Hosted/server-executed tool call transcript items remain visible but are not locally invoked by Agent Framework.
    • Serialized payloads avoid noisy informational_only: false fields and preserve true informational calls where needed.
  • What do you want reviewers to focus on?
    • Whether each provider mapping correctly distinguishes local/actionable tool calls from provider-executed hosted tool records.
    • Whether the function invocation filtering covers approval, unknown calls, and streaming continuation without hiding visible transcript content.

Related Issue

Fixes #6995

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 13:14
@giles17 giles17 added the python Usage: [Issues, PRs], Target: Python label Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/anthropic/agent_framework_anthropic
   _chat_client.py5143792%467, 470, 551, 668, 680, 768, 802, 892, 928–929, 1007, 1009, 1039–1040, 1085, 1101–1102, 1109–1111, 1115–1117, 1121–1124, 1241, 1251, 1303, 1451–1452, 1469, 1482, 1495, 1520–1521
packages/core/agent_framework
   _tools.py11608392%226–227, 404, 406, 419, 444–446, 454, 472, 486, 493, 500, 523, 525, 532, 540, 669, 703–705, 708–710, 712, 718, 769–771, 796, 822, 826, 864–866, 870, 892, 1043, 1055, 1062–1065, 1086, 1094, 1108–1110, 1460, 1552, 1580, 1602, 1610, 1668, 1715, 1722–1723, 1782, 1786, 1832, 1893–1894, 1905, 1977, 1991, 1994, 2007, 2010, 2033, 2040, 2050, 2054, 2131, 2184, 2206, 2262, 2341, 2538, 2604–2605, 2771–2772, 2863
   _types.py12189792%61, 70–71, 125, 130, 149, 151, 155, 159, 161, 163, 165, 183, 187, 213, 235, 240, 245, 249, 279, 705–706, 887–888, 1346, 1421, 1456, 1476, 1486, 1538, 1672–1674, 1959–1964, 1989, 2044, 2049, 2059, 2067, 2074–2078, 2096, 2169, 2182, 2187, 2290, 2313, 2568, 2592, 2691, 2866–2867, 2969, 3198, 3251, 3270, 3309, 3320, 3322–3326, 3328, 3331–3339, 3349, 3438, 3575, 3580, 3585, 3590, 3594, 3680–3682, 3711, 3799–3803
packages/foundry_hosting/agent_framework_foundry_hosting
   _responses.py8149088%186–189, 276, 281, 305, 360–361, 375, 378–379, 427–428, 438, 483, 515–517, 520–521, 532, 575, 589, 664, 667, 673, 675, 747, 834–835, 1144, 1157, 1652–1654, 1656, 1703–1704, 1706–1707, 1709–1710, 1712–1713, 1718, 1727, 1730–1732, 1734, 1744, 1748, 1761, 1806–1807, 1809, 1814–1818, 1820, 1827–1828, 1830–1831, 1837, 1839–1843, 1850, 1856, 1878, 1884, 1890, 1892, 1894–1897, 1905, 1907, 1958–1960, 1970–1971
packages/gemini/agent_framework_gemini
   _chat_client.py4351796%401, 708–709, 727–728, 737–738, 741–742, 774, 894, 905, 1080–1081, 1085, 1096–1097
packages/openai/agent_framework_openai
   _chat_client.py126212789%279, 292, 642–646, 654–657, 663–667, 717–724, 726–728, 735–737, 795, 803, 826, 944, 1043, 1102, 1104, 1106, 1108, 1174, 1188, 1268, 1278, 1283, 1326, 1437–1438, 1453, 1680, 1685, 1689–1691, 1695–1696, 1779, 1789, 1816, 1822, 1832, 1838, 1843, 1849, 1854–1855, 1935, 1979, 1982–1985, 1999, 2001, 2009–2010, 2022, 2064, 2129, 2146, 2149, 2176–2178, 2217, 2234, 2237, 2299, 2306, 2330–2331, 2366, 2404–2405, 2423–2424, 2467, 2594–2595, 2613, 2696–2704, 2734, 2879, 2894, 2943–2946, 2956–2958, 2985–2987, 2997–2998, 3004, 3019, 3141–3142
TOTAL44550535587% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
8827 33 💤 0 ❌ 0 🔥 2m 12s ⏱️

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_content match statement has no default branch, so call_id would 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_call type correctly forces informational_only=True regardless 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_call and tool_search_call items are parsed into informational-only function_call content, then serialized back out as ordinary function_call history 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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_only for function_call content (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.

Comment on lines +1662 to +1666
function_calls = [
function_call
for function_call in function_calls
if function_call.type != "function_call" or _is_actionable_function_call(function_call)
]
Comment on lines +698 to +703
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: Support informational-only hosted tool calls

3 participants