diff --git a/internal/apischema/openai/openai.go b/internal/apischema/openai/openai.go index 56277e3563..1d716a8d97 100644 --- a/internal/apischema/openai/openai.go +++ b/internal/apischema/openai/openai.go @@ -1076,11 +1076,27 @@ const ( ToolTypeFunction ToolType = "function" ToolTypeImageGeneration ToolType = "image_generation" ToolTypeEnterpriseWebSearch ToolType = "enterprise_search" + ToolTypeGoogleSearch ToolType = "google_search" ) +// GCPGoogleSearchConfig contains GCP-specific configuration for Google Search grounding. +// https://pkg.go.dev/google.golang.org/genai#GoogleSearch +type GCPGoogleSearchConfig struct { + ExcludeDomains []string `json:"exclude_domains,omitempty"` //nolint:tagliatelle + BlockingConfidence string `json:"blocking_confidence,omitempty"` //nolint:tagliatelle + TimeRangeFilter *GCPTimeRangeFilter `json:"time_range_filter,omitempty"` //nolint:tagliatelle +} + +// GCPTimeRangeFilter filters search results to a specific time range. +type GCPTimeRangeFilter struct { + StartTime string `json:"start_time,omitempty"` //nolint:tagliatelle + EndTime string `json:"end_time,omitempty"` //nolint:tagliatelle +} + type Tool struct { - Type ToolType `json:"type"` - Function *FunctionDefinition `json:"function,omitempty"` + Type ToolType `json:"type"` + Function *FunctionDefinition `json:"function,omitempty"` + GoogleSearch *GCPGoogleSearchConfig `json:"google_search,omitempty"` //nolint:tagliatelle } // ToolChoiceType represents the type of tool choice. diff --git a/internal/translator/gemini_helper.go b/internal/translator/gemini_helper.go index ea81bee567..283422d3a5 100644 --- a/internal/translator/gemini_helper.go +++ b/internal/translator/gemini_helper.go @@ -13,6 +13,7 @@ import ( "net/url" "path" "strings" + "time" "github.com/google/uuid" openaisdk "github.com/openai/openai-go/v2" @@ -403,6 +404,20 @@ func openAIToolsToGeminiTools(openaiTools []openai.Tool, parametersJSONSchemaAva genaiTools = append(genaiTools, genai.Tool{ EnterpriseWebSearch: &genai.EnterpriseWebSearch{}, }) + case openai.ToolTypeGoogleSearch: + gs := &genai.GoogleSearch{} + if tool.GoogleSearch != nil { + gs.ExcludeDomains = tool.GoogleSearch.ExcludeDomains + if tool.GoogleSearch.BlockingConfidence != "" { + gs.BlockingConfidence = genai.PhishBlockThreshold(tool.GoogleSearch.BlockingConfidence) + } + if tool.GoogleSearch.TimeRangeFilter != nil { + gs.TimeRangeFilter = openAITimeRangeFilterToGemini(tool.GoogleSearch.TimeRangeFilter) + } + } + genaiTools = append(genaiTools, genai.Tool{ + GoogleSearch: gs, + }) default: return nil, fmt.Errorf("unsupported tool type: %s", tool.Type) } @@ -422,6 +437,24 @@ func openAIToolsToGeminiTools(openaiTools []openai.Tool, parametersJSONSchemaAva return genaiTools, nil } +func openAITimeRangeFilterToGemini(filter *openai.GCPTimeRangeFilter) *genai.Interval { + if filter == nil { + return nil + } + interval := &genai.Interval{} + if filter.StartTime != "" { + if t, err := time.Parse(time.RFC3339, filter.StartTime); err == nil { + interval.StartTime = t + } + } + if filter.EndTime != "" { + if t, err := time.Parse(time.RFC3339, filter.EndTime); err == nil { + interval.EndTime = t + } + } + return interval +} + // openAIToolChoiceToGeminiToolConfig converts OpenAI tool_choice to Gemini ToolConfig. // Example Input // diff --git a/internal/translator/gemini_helper_test.go b/internal/translator/gemini_helper_test.go index 7b6497d508..493730f39c 100644 --- a/internal/translator/gemini_helper_test.go +++ b/internal/translator/gemini_helper_test.go @@ -8,6 +8,7 @@ package translator import ( "fmt" "testing" + "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -1637,6 +1638,128 @@ func TestOpenAIToolsToGeminiTools(t *testing.T) { }, }, }, + { + name: "google search tool only - no config", + openaiTools: []openai.Tool{ + { + Type: openai.ToolTypeGoogleSearch, + }, + }, + parametersJSONSchemaAvailable: false, + expected: []genai.Tool{ + { + GoogleSearch: &genai.GoogleSearch{}, + }, + }, + }, + { + name: "google search tool with exclude domains", + openaiTools: []openai.Tool{ + { + Type: openai.ToolTypeGoogleSearch, + GoogleSearch: &openai.GCPGoogleSearchConfig{ + ExcludeDomains: []string{"example.com", "test.com"}, + }, + }, + }, + parametersJSONSchemaAvailable: false, + expected: []genai.Tool{ + { + GoogleSearch: &genai.GoogleSearch{ + ExcludeDomains: []string{"example.com", "test.com"}, + }, + }, + }, + }, + { + name: "google search tool with all options", + openaiTools: []openai.Tool{ + { + Type: openai.ToolTypeGoogleSearch, + GoogleSearch: &openai.GCPGoogleSearchConfig{ + ExcludeDomains: []string{"spam.com"}, + BlockingConfidence: "BLOCK_MEDIUM_AND_ABOVE", + }, + }, + }, + parametersJSONSchemaAvailable: false, + expected: []genai.Tool{ + { + GoogleSearch: &genai.GoogleSearch{ + ExcludeDomains: []string{"spam.com"}, + BlockingConfidence: genai.PhishBlockThresholdBlockMediumAndAbove, + }, + }, + }, + }, + { + name: "google search tool with time range filter", + openaiTools: []openai.Tool{ + { + Type: openai.ToolTypeGoogleSearch, + GoogleSearch: &openai.GCPGoogleSearchConfig{ + TimeRangeFilter: &openai.GCPTimeRangeFilter{ + StartTime: "2024-01-01T00:00:00Z", + EndTime: "2024-12-31T23:59:59Z", + }, + }, + }, + }, + parametersJSONSchemaAvailable: false, + expected: []genai.Tool{ + { + GoogleSearch: &genai.GoogleSearch{ + TimeRangeFilter: &genai.Interval{ + StartTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + EndTime: time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), + }, + }, + }, + }, + }, + { + name: "mixed function and google search tools", + openaiTools: []openai.Tool{ + { + Type: openai.ToolTypeFunction, + Function: &openai.FunctionDefinition{ + Name: "search_products", + Description: "Search for products", + Parameters: funcParams, + }, + }, + { + Type: openai.ToolTypeGoogleSearch, + GoogleSearch: &openai.GCPGoogleSearchConfig{ + ExcludeDomains: []string{"competitor.com"}, + }, + }, + }, + parametersJSONSchemaAvailable: false, + expected: []genai.Tool{ + { + GoogleSearch: &genai.GoogleSearch{ + ExcludeDomains: []string{"competitor.com"}, + }, + }, + { + FunctionDeclarations: []*genai.FunctionDeclaration{ + { + Name: "search_products", + Description: "Search for products", + Parameters: &genai.Schema{ + Type: "object", + Properties: map[string]*genai.Schema{ + "a": {Type: "integer"}, + "b": {Type: "integer"}, + }, + Required: []string{"a", "b"}, + }, + }, + }, + }, + }, + }, } for _, tc := range tests { diff --git a/site/docs/capabilities/llm-integrations/vendor-specific-fields.md b/site/docs/capabilities/llm-integrations/vendor-specific-fields.md index 9b7abaa1b1..b70a90c766 100644 --- a/site/docs/capabilities/llm-integrations/vendor-specific-fields.md +++ b/site/docs/capabilities/llm-integrations/vendor-specific-fields.md @@ -38,6 +38,8 @@ The following backends support extension fields: - **Supported Fields**: - `safetySettings`: Configure the safety settings for gemini models that translates to `SafetySetting`. [Gemini Docs](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters) - `thinking`: Configure thinking process for reasoning models that automatically translates to `generationConfig.thinkingConfig`. [Gemini Docs](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/thinking) +- **Supported Tools**: + - `google_search`: Enable Google Search grounding for Gemini models. Configuration options vary by platform: `exclude_domains` and `blocking_confidence` are Vertex AI only, while `time_range_filter` is Gemini API only. [Google Search Grounding Docs](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/grounding/grounding-with-google-search) ### GCP Anthropic @@ -104,6 +106,52 @@ For more fine-grained control or provider-specific features, you can use the ven } ``` +### Using Google Search Grounding + +To enable Google Search grounding for Gemini models, add `google_search` to the tools array. + +For basic usage without configuration options: + +```json +{ + "model": "gemini-2.0-flash", + "messages": [ + { + "role": "user", + "content": "What are the latest developments in quantum computing?" + } + ], + "tools": [ + { + "type": "google_search" + } + ] +} +``` + +For Vertex AI, you can add filtering options: + +```json +{ + "model": "gemini-2.0-flash", + "messages": [ + { + "role": "user", + "content": "What are the latest developments in quantum computing?" + } + ], + "tools": [ + { + "type": "google_search", + "google_search": { + "exclude_domains": ["example.com"], + "blocking_confidence": "BLOCK_LOW_AND_ABOVE" + } + } + ] +} +``` + ### Field Conflicts Vendor fields override translated fields when conflicts occur.