History

fix(caching): ensure prompt-caching beta is always appended and add multi-turn cache control tests

Luis Pater committed 9 months agov6.7.34

Commit 7ff39361 parent f36a5f52 files · +53 −1Browse files

Unified · Split

2 files
  1. internal/runtime/executor/caching_verify_test.go +48 −0
  2. internal/runtime/executor/claude_executor.go +5 −1
internal/runtime/executor/caching_verify_test.go+48−0
@@ -165,6 +165,54 @@ func TestEnsureCacheControl(t *testing.T) { · 164 unmodified lines
165165 t.Errorf("system should have cache_control even with empty tools array")
166166 }
167167 })
168+
169+ // Test case 8: Messages caching for multi-turn (second-to-last user)
170+ t.Run("Messages Caching Second-To-Last User", func(t *testing.T) {
171+ input := []byte(`{
172+ "model": "claude-3-5-sonnet",
173+ "messages": [
174+ {"role": "user", "content": "First user"},
175+ {"role": "assistant", "content": "Assistant reply"},
176+ {"role": "user", "content": "Second user"},
177+ {"role": "assistant", "content": "Assistant reply 2"},
178+ {"role": "user", "content": "Third user"}
179+ ]
180+ }`)
181+ output := ensureCacheControl(input)
182+
183+ cacheType := gjson.GetBytes(output, "messages.2.content.0.cache_control.type")
184+ if cacheType.String() != "ephemeral" {
185+ t.Errorf("cache_control not found on second-to-last user turn. Output: %s", string(output))
186+ }
187+
188+ lastUserCache := gjson.GetBytes(output, "messages.4.content.0.cache_control")
189+ if lastUserCache.Exists() {
190+ t.Errorf("last user turn should NOT have cache_control")
191+ }
192+ })
193+
194+ // Test case 9: Existing message cache_control should skip injection
195+ t.Run("Messages Skip When Cache Control Exists", func(t *testing.T) {
196+ input := []byte(`{
197+ "model": "claude-3-5-sonnet",
198+ "messages": [
199+ {"role": "user", "content": [{"type": "text", "text": "First user"}]},
200+ {"role": "assistant", "content": [{"type": "text", "text": "Assistant reply", "cache_control": {"type": "ephemeral"}}]},
201+ {"role": "user", "content": [{"type": "text", "text": "Second user"}]}
202+ ]
203+ }`)
204+ output := ensureCacheControl(input)
205+
206+ userCache := gjson.GetBytes(output, "messages.0.content.0.cache_control")
207+ if userCache.Exists() {
208+ t.Errorf("cache_control should NOT be injected when a message already has cache_control")
209+ }
210+
211+ existingCache := gjson.GetBytes(output, "messages.1.content.0.cache_control.type")
212+ if existingCache.String() != "ephemeral" {
213+ t.Errorf("existing cache_control should be preserved. Output: %s", string(output))
214+ }
215+ })
168216 }
169217
170218 // TestCacheControlOrder verifies the correct order: tools -> system -> messages
internal/runtime/executor/claude_executor.go+5−1
@@ -642,13 +642,17 @@ func applyClaudeHeaders(r *http.Request, auth *cliproxyauth.Auth, apiKey string, · 641 unmodified lines
642642 ginHeaders = ginCtx.Request.Header
643643 }
644644
645− baseBetas := "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14,prompt-caching-2024-07-31"
645+ promptCachingBeta := "prompt-caching-2024-07-31"
646+ baseBetas := "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14," + promptCachingBeta
646647 if val := strings.TrimSpace(ginHeaders.Get("Anthropic-Beta")); val != "" {
647648 baseBetas = val
648649 if !strings.Contains(val, "oauth") {
649650 baseBetas += ",oauth-2025-04-20"
650651 }
651652 }
653+ if !strings.Contains(baseBetas, promptCachingBeta) {
654+ baseBetas += "," + promptCachingBeta
655+ }
652656
653657 // Merge extra betas from request body
654658 if len(extraBetas) > 0 {