fix(openai_compat): gate prompt_cache_key to OpenAI endpoints
This commit is contained in:
parent
8581d46eaa
commit
3d80078db7
2 changed files with 100 additions and 2 deletions
|
|
@ -155,9 +155,9 @@ func (p *Provider) Chat(
|
||||||
// The key is typically the agent ID — stable per agent, shared across requests.
|
// The key is typically the agent ID — stable per agent, shared across requests.
|
||||||
// See: https://platform.openai.com/docs/guides/prompt-caching
|
// See: https://platform.openai.com/docs/guides/prompt-caching
|
||||||
// Prompt caching is only supported by OpenAI-native endpoints.
|
// Prompt caching is only supported by OpenAI-native endpoints.
|
||||||
// Gemini and other providers reject unknown fields, so skip for non-OpenAI APIs.
|
// Other OpenAI-compatible providers may reject unknown fields.
|
||||||
if cacheKey, ok := options["prompt_cache_key"].(string); ok && cacheKey != "" {
|
if cacheKey, ok := options["prompt_cache_key"].(string); ok && cacheKey != "" {
|
||||||
if !strings.Contains(p.apiBase, "generativelanguage.googleapis.com") {
|
if supportsPromptCacheKey(p.apiBase) {
|
||||||
requestBody["prompt_cache_key"] = cacheKey
|
requestBody["prompt_cache_key"] = cacheKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -195,6 +195,18 @@ func (p *Provider) Chat(
|
||||||
return parseResponse(body)
|
return parseResponse(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func supportsPromptCacheKey(apiBase string) bool {
|
||||||
|
host := ""
|
||||||
|
if parsed, err := url.Parse(apiBase); err == nil {
|
||||||
|
host = parsed.Hostname()
|
||||||
|
}
|
||||||
|
if host == "" {
|
||||||
|
host = apiBase
|
||||||
|
}
|
||||||
|
host = strings.ToLower(strings.TrimSpace(host))
|
||||||
|
return host == "openai.com" || strings.HasSuffix(host, ".openai.com")
|
||||||
|
}
|
||||||
|
|
||||||
func parseResponse(body []byte) (*LLMResponse, error) {
|
func parseResponse(body []byte) (*LLMResponse, error) {
|
||||||
var apiResponse struct {
|
var apiResponse struct {
|
||||||
Choices []struct {
|
Choices []struct {
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,92 @@ func TestProviderChat_AcceptsNumericOptionTypes(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProviderChat_SkipsPromptCacheKeyForNonOpenAIEndpoint(t *testing.T) {
|
||||||
|
var requestBody map[string]any
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp := map[string]any{
|
||||||
|
"choices": []map[string]any{
|
||||||
|
{
|
||||||
|
"message": map[string]any{"content": "ok"},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
p := NewProvider("key", server.URL, "")
|
||||||
|
_, err := p.Chat(
|
||||||
|
t.Context(),
|
||||||
|
[]Message{{Role: "user", Content: "hi"}},
|
||||||
|
nil,
|
||||||
|
"gpt-4o",
|
||||||
|
map[string]any{"prompt_cache_key": "agent-123"},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Chat() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := requestBody["prompt_cache_key"]; ok {
|
||||||
|
t.Fatalf("did not expect prompt_cache_key for non-OpenAI endpoint")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupportsPromptCacheKey(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
apiBase string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "openai endpoint",
|
||||||
|
apiBase: "https://api.openai.com/v1",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "openai apex endpoint",
|
||||||
|
apiBase: "https://openai.com/v1",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nvidia endpoint",
|
||||||
|
apiBase: "https://integrate.api.nvidia.com/v1",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gemini endpoint",
|
||||||
|
apiBase: "https://generativelanguage.googleapis.com/v1beta",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "openrouter endpoint",
|
||||||
|
apiBase: "https://openrouter.ai/api/v1",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "local endpoint",
|
||||||
|
apiBase: "http://localhost:11434/v1",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := supportsPromptCacheKey(tt.apiBase)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("supportsPromptCacheKey(%q) = %v, want %v", tt.apiBase, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNormalizeModel_UsesAPIBase(t *testing.T) {
|
func TestNormalizeModel_UsesAPIBase(t *testing.T) {
|
||||||
if got := normalizeModel("deepseek/deepseek-chat", "https://api.deepseek.com/v1"); got != "deepseek-chat" {
|
if got := normalizeModel("deepseek/deepseek-chat", "https://api.deepseek.com/v1"); got != "deepseek-chat" {
|
||||||
t.Fatalf("normalizeModel(deepseek) = %q, want %q", got, "deepseek-chat")
|
t.Fatalf("normalizeModel(deepseek) = %q, want %q", got, "deepseek-chat")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue