mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 10:15:48 +00:00
* feat(providers): explicit prompt cache for DashScope/Qwen Extend OpenAI-compat path with Anthropic-style cache_control:ephemeral inline blocks for Alibaba DashScope endpoints (provider_type=bailian or URL contains "dashscope"). Verified live: qwen3.6-plus / qwen3.5-plus / qwen3-coder-plus all return 99.7% cache hit rate on 6K-token prefix, yielding ~89% token cost reduction on cached prefix (90% Alibaba discount). - isDashScope(): 3-source detection (URL + providerType + name) handles reverse-proxied endpoints; covers both "dashscope" and "bailian" - buildRequestBody wraps system content with SplitSystemPromptForCache (exported from anthropic_request.go) using <!-- GOCLAW_CACHE_BOUNDARY --> - Tool prefix cache: cache_control on last tool definition, with 4-marker budget guard - Parse cache_creation_input_tokens from prompt_tokens_details into Usage.CacheCreationTokens; propagates via existing span metadata writers - Runtime escape hatch: GOCLAW_DISABLE_DASHSCOPE_CACHE=true - Live integration smoke test (build tag integration, env-gated) * chore(providers): polish per PR #1127 review - Use strings.Repeat instead of custom repeat() helper in smoke test - Clarify BuildRequestBodyForTest is test-only, not public API * feat(providers): enable thinking for Qwen 3.7/3.6, observe cache in smoke test Qwen3.7-plus and qwen3.6-plus support deep thinking but were missing from dashscopeThinkingModels, so enable_thinking/thinking_budget was silently skipped. Add both to the whitelist and test. Add qwen3.7-plus to the cache smoke test as cache-optional: Alibaba's context-cache doc does not yet list 3.7/3.6 for explicit cache, and the cache wrap is a safe no-op when unsupported, so a no-cache result is logged rather than failed to avoid a flaky live assertion. Claude-Session: https://claude.ai/code/session_01X3jkrc7N3ar8ZGzUJWyNS5 * fix(providers): preserve DashScope detection for proxy routes
58 lines
2.3 KiB
Go
58 lines
2.3 KiB
Go
package providers
|
|
|
|
import "testing"
|
|
|
|
func TestOpenAIProvider_isDashScope(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
apiBase string
|
|
ptype string
|
|
pname string
|
|
want bool
|
|
}{
|
|
{"coding-intl URL", "https://coding-intl.dashscope.aliyuncs.com/v1", "openai_compat", "qwen-richard", true},
|
|
{"providerType=bailian", "https://custom-proxy.example.com/v1", "bailian", "internal-qwen", true},
|
|
{"providerType=dashscope", "https://proxy.example.com/v1", "dashscope", "x", true},
|
|
{"name contains dashscope", "https://proxy.com/v1", "openai_compat", "my-dashscope-mirror", true},
|
|
{"name contains bailian", "https://proxy.com/v1", "openai_compat", "company-bailian-relay", true},
|
|
{"openai native", "https://api.openai.com/v1", "openai", "gpt", false},
|
|
{"anthropic", "https://api.anthropic.com", "anthropic", "claude", false},
|
|
{"openrouter", "https://openrouter.ai/api/v1", "openai_compat", "openrouter", false},
|
|
{"empty", "", "", "", false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
p := &OpenAIProvider{apiBase: tc.apiBase, providerType: tc.ptype, name: tc.pname}
|
|
if got := p.isDashScope(); got != tc.want {
|
|
t.Errorf("isDashScope() = %v, want %v (apiBase=%q ptype=%q name=%q)",
|
|
got, tc.want, tc.apiBase, tc.ptype, tc.pname)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOpenAIProvider_dashScopePassthroughKeys(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
apiBase string
|
|
ptype string
|
|
pname string
|
|
want bool
|
|
}{
|
|
{"dashscope URL", "https://coding-intl.dashscope.aliyuncs.com/v1", "openai_compat", "qwen", true},
|
|
{"providerType=dashscope", "https://proxy.example.com/v1", "dashscope", "qwen", true},
|
|
{"providerType=bailian", "https://proxy.example.com/v1", "bailian", "qwen", true},
|
|
{"name contains dashscope", "https://proxy.example.com/v1", "openai_compat", "team-dashscope", true},
|
|
{"name contains bailian", "https://proxy.example.com/v1", "openai_compat", "team-bailian", true},
|
|
{"openrouter", "https://openrouter.ai/api/v1", "openai_compat", "openrouter", false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
p := &OpenAIProvider{apiBase: tc.apiBase, providerType: tc.ptype, name: tc.pname}
|
|
if got := p.dashScopePassthroughKeys(); got != tc.want {
|
|
t.Errorf("dashScopePassthroughKeys() = %v, want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|