Skip to content

Surface Gemini server-side tool-call and tool-response parts - #1107

Open
PratikDhanave (PratikDhanave) wants to merge 2 commits into
microsoft:mainfrom
PratikDhanaveFork:fix/gemini-server-side-tool-parts
Open

PratikDhanave (PratikDhanave) wants to merge 2 commits into
microsoft:mainfrom
PratikDhanaveFork:fix/gemini-server-side-tool-parts

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

Gemini can echo back server-side tool invocations as toolCall / toolResponse response parts (e.g. when a function tool is combined with a native tool). buildResponsePart only handled Thought, Text, FunctionCall, InlineData, FileData, ExecutableCode, and CodeExecutionResult — it never read part.ToolCall or part.ToolResponse, so those parts were silently dropped and the caller never observed the model's server-side tool interactions.

The Python client (_parse_parts) maps both: tool_call → a function call with informational_only=True, tool_response → a function result.

Change

  • Map part.ToolCallFunctionCallContent{InformationalOnly: true} — surfaced so callers can observe it, but marked informational so the tool-call loop does not try to execute a call the server already ran (toolautocall skips InformationalOnly calls).
  • Map part.ToolResponseFunctionResultContent.
  • Tool name comes from ToolType (fallback "tool_call"); missing call ids are synthesized like the existing FunctionCall path.

Test

  • TestServerSideToolCallAndResponsePartsSurfaced: a candidate carrying a toolCall and a toolResponse now surfaces an informational-only FunctionCallContent and a FunctionResultContent. Fails before the change (both dropped), passes after.

buildResponsePart never inspected part.ToolCall or part.ToolResponse, so a
server-side tool invocation echoed back by the model (its call and result)
was silently dropped and the caller never saw it.

Map part.ToolCall to an informational-only FunctionCallContent (so the tool
loop observes but does not re-execute the server-side call) and
part.ToolResponse to a FunctionResultContent, mirroring the Python client.
Tool names come from ToolType; missing call ids are synthesized like the
existing FunctionCall path.
Copilot AI lite review requested due to automatic review settings September 19, 2026 11:01
@github-actions github-actions Bot added area:provider Changes files in the provider area area:provider/gemini Changes files in the provider / gemini area size:medium At most 100 changed lines across at most 5 files labels Sep 19, 2026

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.

Copilot review overview

🟡 Changes recommended

Address call-ID correlation and ThoughtSignature preservation; strengthen the response-payload assertion.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Surfaces Gemini server-side tool calls and responses as framework function content.

Changes:

  • Maps ToolCall to informational-only function calls.
  • Maps ToolResponse to function results.
  • Adds regression coverage for both mappings.
File Summary
provider/​geminiprovider/​agent.go Converts server-side tool parts into framework content.
provider/​geminiprovider/​agent_test.go Tests surfaced tool calls and responses.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +615 to +618
callID := part.ToolResponse.ID
if callID == "" {
callID = "tool-call-" + uuid.NewString()
}
@github-actions github-actions Bot added kind:code Changes production behavior or code kind:tests Changes tests, fixtures, or test infrastructure labels Sep 19, 2026
@github-actions

This comment has been minimized.

When a server-side tool response omits its own id, fall back to the preceding
tool call's id (threaded through responsePartState) before synthesizing one,
so the FunctionResultContent shares a CallID with its FunctionCallContent.
@github-actions github-actions Bot added size:large At most 300 changed lines across at most 10 files kind:dependencies Changes dependencies or manifests and removed size:medium At most 100 changed lines across at most 5 files labels Sep 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Scope: user-visible behavior, internal-only
Changed Go contract: None exported. buildResponsePart (unexported) now maps genai.Part.ToolCallmessage.FunctionCallContent{InformationalOnly: true} and genai.Part.ToolResponsemessage.FunctionResultContent; responsePartState (unexported) gained a pendingToolCallID field to correlate a response lacking its own id with the preceding call.
Upstream evidence reviewed: python/packages/gemini/agent_framework_gemini/_chat_client.py _parse_parts (lines ~1194-1240): maps part.tool_callContent.from_function_call(..., informational_only=True) and part.tool_responseContent.from_function_result(call_id=tool_response.id or generated, result=tool_response.response). No .NET Gemini provider exists upstream (dotnet/src/Microsoft.Agents.AI.*), so Python is the sole parity reference for this integration.
Result: aligned

The Go change mirrors the Python _parse_parts semantics: server-side tool calls are surfaced as informational-only function calls (so toolautocall skips re-executing them — confirmed via agent/harness/toolautocall/autocall.go checks on InformationalOnly), and tool responses surface as function results. This closes a real bug (these parts were previously silently dropped in Go).

One minor, non-blocking observation: when a tool_response omits its own id, Go correlates it with the immediately preceding tool call's id (pendingToolCallID) before falling back to a synthesized id, whereas Python's _parse_parts always generates an independent random id in that case with no correlation fallback. This makes Go's edge-case behavior more correct/consistent than Python's (verified by the added TestServerSideToolResponseInheritsCallID test), not a regression, so it isn't flagged as a parity defect — just noting the intentional, beneficial divergence for future reference.

No exported Go API surface changed (both touched symbols are unexported), so public-api-change is not applicable.

Generated by Go API Consistency Review Agent · copilot · auto · 59.6 AIC · ⌖ 8.05 AIC · ⊞ 9.2K ·

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider/gemini Changes files in the provider / gemini area area:provider Changes files in the provider area kind:code Changes production behavior or code kind:dependencies Changes dependencies or manifests kind:tests Changes tests, fixtures, or test infrastructure parity-approved Go API consistency review found no parity issues size:large At most 300 changed lines across at most 10 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants