Merge pull request #5725 from tamseno/fix/gemini-include-server-side-tool-invocations

fix(gemini): support includeServerSideToolInvocations in GeminiToolConfig
This commit is contained in:
Wesley Liddick
2026-08-18 15:35:01 +08:00
committed by GitHub
3 changed files with 80 additions and 1 deletions
@@ -112,7 +112,8 @@ type GeminiImageSearch struct {
// GeminiToolConfig Gemini 工具配置
type GeminiToolConfig struct {
FunctionCallingConfig *GeminiFunctionCallingConfig `json:"functionCallingConfig,omitempty"`
FunctionCallingConfig *GeminiFunctionCallingConfig `json:"functionCallingConfig,omitempty"`
IncludeServerSideToolInvocations *bool `json:"includeServerSideToolInvocations,omitempty"`
}
// GeminiFunctionCallingConfig 函数调用配置
@@ -156,6 +156,13 @@ func TransformClaudeToGeminiWithOptions(claudeReq *ClaudeRequest, projectID, map
Mode: "VALIDATED",
},
}
// 内置工具(googleSearch)与函数调用混用时,上游要求显式开启
// includeServerSideToolInvocations,否则返回 400(issue #5709)。
// 与 raw 透传路的 enableMixedGeminiToolInvocations 注入保持同一语义。
if hasMixedToolInvocations(tools) {
enabled := true
innerRequest.ToolConfig.IncludeServerSideToolInvocations = &enabled
}
}
if systemInstruction != nil {
@@ -703,6 +710,21 @@ func isWebSearchTool(tool ClaudeTool) bool {
}
}
// hasMixedToolInvocations 判断构建后的工具声明是否同时包含函数声明与内置工具
// (googleSearch)。仅在两者并存时需要开启 includeServerSideToolInvocations。
func hasMixedToolInvocations(declarations []GeminiToolDeclaration) bool {
hasFunc, hasBuiltin := false, false
for _, d := range declarations {
if len(d.FunctionDeclarations) > 0 {
hasFunc = true
}
if d.GoogleSearch != nil {
hasBuiltin = true
}
}
return hasFunc && hasBuiltin
}
// buildTools 构建 tools
func buildTools(tools []ClaudeTool) []GeminiToolDeclaration {
if len(tools) == 0 {
@@ -565,3 +565,59 @@ func TestTransformClaudeToGeminiWithOptions_PreservesWebSearchAlongsideFunctions
require.Equal(t, "get_weather", req.Request.Tools[0].FunctionDeclarations[0].Name)
require.NotNil(t, req.Request.Tools[1].GoogleSearch)
}
func TestGeminiToolConfig_IncludeServerSideToolInvocations(t *testing.T) {
functionTool := ClaudeTool{
Name: "get_weather",
Description: "Get weather information",
InputSchema: map[string]any{"type": "object"},
}
webSearchTool := ClaudeTool{
Type: "web_search_20250305",
Name: "web_search",
}
transform := func(t *testing.T, tools []ClaudeTool) (V1InternalRequest, string) {
t.Helper()
body, err := TransformClaudeToGeminiWithOptions(&ClaudeRequest{
Model: "claude-3-5-sonnet-latest",
Messages: []ClaudeMessage{
{
Role: "user",
Content: json.RawMessage(`[{"type":"text","text":"hello"}]`),
},
},
Tools: tools,
}, "project-1", "gemini-2.5-flash", DefaultTransformOptions())
require.NoError(t, err)
var req V1InternalRequest
require.NoError(t, json.Unmarshal(body, &req))
return req, string(body)
}
t.Run("mixed builtin and function tools enable server-side tool invocations", func(t *testing.T) {
req, raw := transform(t, []ClaudeTool{functionTool, webSearchTool})
require.NotNil(t, req.Request.ToolConfig)
require.NotNil(t, req.Request.ToolConfig.IncludeServerSideToolInvocations)
require.True(t, *req.Request.ToolConfig.IncludeServerSideToolInvocations)
require.Contains(t, raw, `"includeServerSideToolInvocations":true`)
})
t.Run("function tools only leave the flag unset", func(t *testing.T) {
req, raw := transform(t, []ClaudeTool{functionTool})
require.NotNil(t, req.Request.ToolConfig)
require.Nil(t, req.Request.ToolConfig.IncludeServerSideToolInvocations)
require.NotContains(t, raw, "includeServerSideToolInvocations")
})
t.Run("web search only leaves the flag unset", func(t *testing.T) {
req, raw := transform(t, []ClaudeTool{webSearchTool})
require.NotNil(t, req.Request.ToolConfig)
require.Nil(t, req.Request.ToolConfig.IncludeServerSideToolInvocations)
require.NotContains(t, raw, "includeServerSideToolInvocations")
})
}