Skip to content
20 changes: 18 additions & 2 deletions internal/apischema/openai/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
33 changes: 33 additions & 0 deletions internal/translator/gemini_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"net/url"
"path"
"strings"
"time"

"github.com/google/uuid"
openaisdk "github.com/openai/openai-go/v2"
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
//
Expand Down
123 changes: 123 additions & 0 deletions internal/translator/gemini_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package translator
import (
"fmt"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
Loading