mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
fix(gemini): preserve Hermes web search functions
This commit is contained in:
@@ -3428,17 +3428,12 @@ func normalizeGeminiRequestForAIStudio(body []byte) []byte {
|
||||
|
||||
func isClaudeWebSearchToolMap(tool map[string]any) bool {
|
||||
toolType, _ := tool["type"].(string)
|
||||
if strings.HasPrefix(toolType, "web_search") || toolType == "google_search" {
|
||||
return true
|
||||
}
|
||||
|
||||
name, _ := tool["name"].(string)
|
||||
switch strings.TrimSpace(name) {
|
||||
case "web_search", "google_search", "web_search_20250305":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
// A function named web_search is still a client-side function. This is
|
||||
// especially important for Chat Completions clients such as Hermes, whose
|
||||
// built-in runtime tools are represented as ordinary function tools.
|
||||
// Promote only explicitly typed server-side search tools to Gemini's
|
||||
// built-in googleSearch tool.
|
||||
return strings.HasPrefix(toolType, "web_search") || toolType == "google_search"
|
||||
}
|
||||
|
||||
// cleanToolSchema 清理工具的 JSON Schema,移除 Gemini 不支持的字段
|
||||
|
||||
@@ -170,6 +170,70 @@ func TestGeminiForwardAsChatCompletions_StreamsOpenAIChunksFromGeminiSSE(t *test
|
||||
require.Contains(t, out, "data: [DONE]")
|
||||
}
|
||||
|
||||
func TestGeminiForwardAsChatCompletions_FunctionNamedWebSearchStaysClientSide(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
httpStub := &geminiCompatHTTPUpstreamStub{
|
||||
response: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
Body: io.NopCloser(strings.NewReader(
|
||||
`{"candidates":[{"content":{"parts":[{"text":"hello"}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":2,"candidatesTokenCount":1}}`,
|
||||
)),
|
||||
},
|
||||
}
|
||||
svc := &GeminiMessagesCompatService{
|
||||
httpUpstream: httpStub,
|
||||
cfg: &config.Config{},
|
||||
}
|
||||
account := &Account{
|
||||
ID: 103,
|
||||
Platform: PlatformGemini,
|
||||
Type: AccountTypeAPIKey,
|
||||
Credentials: map[string]any{
|
||||
"api_key": "gemini-api-key",
|
||||
},
|
||||
Concurrency: 1,
|
||||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
body := []byte(`{
|
||||
"model":"gemini-3.6-flash-high",
|
||||
"messages":[{"role":"user","content":"search and read"}],
|
||||
"tools":[
|
||||
{"type":"function","function":{"name":"web_search","description":"Search through the Hermes client","parameters":{"type":"object","properties":{"query":{"type":"string"}},"required":["query"]}}},
|
||||
{"type":"function","function":{"name":"read_file","description":"Read a local file","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}}}
|
||||
]
|
||||
}`)
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", bytes.NewReader(body))
|
||||
|
||||
result, err := svc.ForwardAsChatCompletions(context.Background(), c, account, body)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.NotNil(t, httpStub.lastReq)
|
||||
|
||||
postedBody, err := io.ReadAll(httpStub.lastReq.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var posted map[string]any
|
||||
require.NoError(t, json.Unmarshal(postedBody, &posted))
|
||||
tools, ok := posted["tools"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, tools, 1, "Chat Completions function tools must not be promoted to Gemini built-ins by name")
|
||||
|
||||
functionTool, ok := tools[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
functionDecls, ok := functionTool["functionDeclarations"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, functionDecls, 2)
|
||||
require.Equal(t, "web_search", functionDecls[0].(map[string]any)["name"])
|
||||
require.Equal(t, "read_file", functionDecls[1].(map[string]any)["name"])
|
||||
require.NotContains(t, functionTool, "googleSearch")
|
||||
require.NotContains(t, functionTool, "google_search")
|
||||
}
|
||||
|
||||
// TestConvertClaudeToolsToGeminiTools_CustomType 测试custom类型工具转换
|
||||
func TestConvertClaudeToolsToGeminiTools_CustomType(t *testing.T) {
|
||||
tests := []struct {
|
||||
|
||||
Reference in New Issue
Block a user