diff --git a/pkg/providers/factory_provider.go b/pkg/providers/factory_provider.go index 7d5566eef..fba34ed00 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -84,7 +84,9 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err if apiBase == "" { apiBase = getDefaultAPIBase(protocol) } - return NewHTTPProviderWithMaxTokensField(cfg.APIKey, apiBase, cfg.Proxy, cfg.MaxTokensField), modelID, nil + p := NewHTTPProviderWithMaxTokensField(cfg.APIKey, apiBase, cfg.Proxy, cfg.MaxTokensField) + p.SetSupportPromptCache(true) + return p, modelID, nil case "openrouter", "groq", "zhipu", "gemini", "nvidia", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", @@ -97,7 +99,12 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err if apiBase == "" { apiBase = getDefaultAPIBase(protocol) } - return NewHTTPProviderWithMaxTokensField(cfg.APIKey, apiBase, cfg.Proxy, cfg.MaxTokensField), modelID, nil + p := NewHTTPProviderWithMaxTokensField(cfg.APIKey, apiBase, cfg.Proxy, cfg.MaxTokensField) + // Gemini rejects unknown fields in the request body; disable prompt_cache_key for it. + if protocol != "gemini" { + p.SetSupportPromptCache(true) + } + return p, modelID, nil case "anthropic": if cfg.AuthMethod == "oauth" || cfg.AuthMethod == "token" { diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index d0c4344f3..166a8611e 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -38,6 +38,11 @@ func (p *HTTPProvider) Chat( return p.delegate.Chat(ctx, messages, tools, model, options) } +// SetSupportPromptCache enables or disables sending the prompt_cache_key field. +func (p *HTTPProvider) SetSupportPromptCache(v bool) { + p.delegate.SetSupportPromptCache(v) +} + func (p *HTTPProvider) GetDefaultModel() string { return "" } diff --git a/pkg/providers/openai_compat/provider.go b/pkg/providers/openai_compat/provider.go index a8d244d4a..387bb50e8 100644 --- a/pkg/providers/openai_compat/provider.go +++ b/pkg/providers/openai_compat/provider.go @@ -28,10 +28,11 @@ type ( ) type Provider struct { - apiKey string - apiBase string - maxTokensField string // Field name for max tokens (e.g., "max_completion_tokens" for o1/glm models) - httpClient *http.Client + apiKey string + apiBase string + maxTokensField string // Field name for max tokens (e.g., "max_completion_tokens" for o1/glm models) + httpClient *http.Client + supportPromptCache bool // Only send prompt_cache_key when true (OpenAI-specific feature) } func NewProvider(apiKey, apiBase, proxy string) *Provider { @@ -62,6 +63,12 @@ func NewProviderWithMaxTokensField(apiKey, apiBase, proxy, maxTokensField string } } +// SetSupportPromptCache enables or disables sending the prompt_cache_key field. +// Most providers accept or ignore this field, but Gemini rejects unknown fields. +func (p *Provider) SetSupportPromptCache(v bool) { + p.supportPromptCache = v +} + func (p *Provider) Chat( ctx context.Context, messages []Message, @@ -115,8 +122,11 @@ func (p *Provider) Chat( // with the same key and reuse prefix KV cache across calls. // The key is typically the agent ID — stable per agent, shared across requests. // See: https://platform.openai.com/docs/guides/prompt-caching - if cacheKey, ok := options["prompt_cache_key"].(string); ok && cacheKey != "" { - requestBody["prompt_cache_key"] = cacheKey + // Disabled for providers like Gemini that reject unknown fields in the request body. + if p.supportPromptCache { + if cacheKey, ok := options["prompt_cache_key"].(string); ok && cacheKey != "" { + requestBody["prompt_cache_key"] = cacheKey + } } jsonData, err := json.Marshal(requestBody)