From 707b536569079bc15c1b8a140e5fec46c9ef0afb Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Fri, 24 Jul 2026 09:17:41 +0530 Subject: [PATCH 1/2] Merge Gemini tool mode into caller-supplied ToolConfig buildParams replaced cfg.ToolConfig wholesale when a tool mode was set, dropping any RetrievalConfig or IncludeServerSideToolInvocations the caller passed through GenerateContentConfig. Mutate the existing ToolConfig so only FunctionCallingConfig is overridden. --- provider/geminiprovider/agent.go | 8 +++- provider/geminiprovider/agent_test.go | 58 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/provider/geminiprovider/agent.go b/provider/geminiprovider/agent.go index 3dfd88d3..e1800496 100644 --- a/provider/geminiprovider/agent.go +++ b/provider/geminiprovider/agent.go @@ -241,9 +241,13 @@ func (a *client) buildParams(messages []*message.Message, opts []agent.Option) ( fc.Mode = genai.FunctionCallingConfigModeAny fc.AllowedFunctionNames = mode.Required() } - cfg.ToolConfig = &genai.ToolConfig{ - FunctionCallingConfig: fc, + // Merge into any caller-supplied ToolConfig (e.g. a RetrievalConfig for + // Vertex grounding passed through GenerateContentConfig) rather than + // replacing it, so only the function-calling mode is overridden. + if cfg.ToolConfig == nil { + cfg.ToolConfig = &genai.ToolConfig{} } + cfg.ToolConfig.FunctionCallingConfig = fc } // Build a map of CallID → function name by scanning all messages first. diff --git a/provider/geminiprovider/agent_test.go b/provider/geminiprovider/agent_test.go index dfa5c05b..e878812d 100644 --- a/provider/geminiprovider/agent_test.go +++ b/provider/geminiprovider/agent_test.go @@ -14,6 +14,7 @@ import ( "github.com/microsoft/agent-framework-go/agent" "github.com/microsoft/agent-framework-go/message" "github.com/microsoft/agent-framework-go/provider/geminiprovider" + "github.com/microsoft/agent-framework-go/tool" "github.com/microsoft/agent-framework-go/tool/functool" "google.golang.org/genai" ) @@ -1628,6 +1629,63 @@ func TestGenerateContentConfigOption(t *testing.T) { } } +// TestToolModeMergesCallerToolConfig verifies that applying a tool mode only +// overrides the FunctionCallingConfig and preserves other fields a caller set on +// ToolConfig via the GenerateContentConfig escape hatch (e.g. a RetrievalConfig +// used for Vertex grounding). +func TestToolModeMergesCallerToolConfig(t *testing.T) { + weatherTool := functool.MustNew(functool.Config{ + Name: "get_weather", + Description: "Get the weather for a city.", + }, func(_ context.Context, args struct{ City string }) (string, error) { + return "sunny", nil + }) + + bodyCh := make(chan []byte, 1) + server := httptest.NewServer(captureAndRespond(t, bodyCh, "application/json", minimalTextResponse("ok"))) + defer server.Close() + + a := newTestClient(t, server) + + _, err := a.RunText(t.Context(), "what's the weather?", + geminiprovider.GenerateContentConfig(genai.GenerateContentConfig{ + ToolConfig: &genai.ToolConfig{ + RetrievalConfig: &genai.RetrievalConfig{LanguageCode: "en-US"}, + }, + }), + agent.WithTool(weatherTool), + agent.WithToolMode(tool.ToolModeRequired), + ).Collect() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var req map[string]any + if err := json.Unmarshal(<-bodyCh, &req); err != nil { + t.Fatalf("unmarshal request body: %v", err) + } + toolConfig, ok := req["toolConfig"].(map[string]any) + if !ok { + t.Fatalf("request missing toolConfig, got %T", req["toolConfig"]) + } + // The requested tool mode must be applied. + fcc, ok := toolConfig["functionCallingConfig"].(map[string]any) + if !ok { + t.Fatalf("toolConfig missing functionCallingConfig, got %T", toolConfig["functionCallingConfig"]) + } + if mode, _ := fcc["mode"].(string); mode != string(genai.FunctionCallingConfigModeAny) { + t.Errorf("functionCallingConfig.mode = %q, want %q", mode, genai.FunctionCallingConfigModeAny) + } + // The caller-supplied RetrievalConfig must be preserved, not dropped. + retrieval, ok := toolConfig["retrievalConfig"].(map[string]any) + if !ok { + t.Fatalf("toolConfig dropped caller retrievalConfig, got %T", toolConfig["retrievalConfig"]) + } + if lang, _ := retrieval["languageCode"].(string); lang != "en-US" { + t.Errorf("retrievalConfig.languageCode = %q, want %q", lang, "en-US") + } +} + // Gemini streams usageMetadata cumulatively across chunks, with the final // chunk's totals authoritative. The provider must report that final total once, // not sum the running totals from every chunk. From d8c9bc92af27e282e15b831e09451d0c0b319ba6 Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Fri, 24 Jul 2026 14:54:19 +0530 Subject: [PATCH 2/2] geminiprovider: shallow-clone ToolConfig before overriding FunctionCallingConfig --- provider/geminiprovider/agent.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/provider/geminiprovider/agent.go b/provider/geminiprovider/agent.go index e1800496..13205387 100644 --- a/provider/geminiprovider/agent.go +++ b/provider/geminiprovider/agent.go @@ -243,9 +243,15 @@ func (a *client) buildParams(messages []*message.Message, opts []agent.Option) ( } // Merge into any caller-supplied ToolConfig (e.g. a RetrievalConfig for // Vertex grounding passed through GenerateContentConfig) rather than - // replacing it, so only the function-calling mode is overridden. + // replacing it, so only the function-calling mode is overridden. Shallow- + // clone the struct first so overriding FunctionCallingConfig preserves the + // caller's other fields without mutating their ToolConfig pointer (which is + // aliased via the shallow *cfg = p copy above). if cfg.ToolConfig == nil { cfg.ToolConfig = &genai.ToolConfig{} + } else { + tc := *cfg.ToolConfig + cfg.ToolConfig = &tc } cfg.ToolConfig.FunctionCallingConfig = fc }