Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions provider/geminiprovider/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,19 @@ 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. 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
}

// Build a map of CallID → function name by scanning all messages first.
Expand Down
58 changes: 58 additions & 0 deletions provider/geminiprovider/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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.
Expand Down
Loading