Skip to content

Commit 7f2d1c5

Browse files
docs: add detailed code pattern analysis showing exact bug patterns
Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com>
1 parent ac0a9c8 commit 7f2d1c5

1 file changed

Lines changed: 391 additions & 0 deletions

File tree

CODE_PATTERNS_ANALYSIS.md

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
# Code Pattern Analysis: Opus 4.7 Breaking Haiku 4.5
2+
3+
This document shows the specific code patterns from the Opus 4.7 PRs that likely caused the Haiku 4.5 regression.
4+
5+
---
6+
7+
## Pattern 1: Broad Substring Matching
8+
9+
### Problem Code (likely in PR #25867/#25876)
10+
11+
```python
12+
# File: litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py
13+
14+
def _supports_extended_thinking_on_bedrock(model: str) -> bool:
15+
"""Check if model supports extended thinking on Bedrock"""
16+
17+
# ❌ PROBLEM: This catches BOTH Haiku 4.5 AND Haiku 4.7+
18+
if "claude-haiku-4" in model:
19+
return True
20+
21+
if "claude-opus-4" in model:
22+
return True
23+
24+
if "claude-sonnet-4" in model:
25+
return True
26+
27+
return False
28+
```
29+
30+
### Why This Breaks
31+
32+
```python
33+
# These all match the pattern "claude-haiku-4":
34+
"anthropic.claude-haiku-4-5-20251001-v1:0" # ❌ Haiku 4.5 - doesn't support extended thinking!
35+
"anthropic.claude-haiku-4-7-20260416-v1:0" # ✅ Haiku 4.7 - supports it
36+
37+
# Result: Both get extended thinking metadata → Haiku 4.5 requests fail
38+
```
39+
40+
### What Should Happen
41+
42+
```python
43+
def _supports_extended_thinking_on_bedrock(model: str) -> bool:
44+
"""Check if model supports extended thinking on Bedrock"""
45+
46+
# ✅ CORRECT: Explicit version checks
47+
haiku_patterns = ["haiku-4-6", "haiku_4_6", "haiku-4.6",
48+
"haiku-4-7", "haiku_4_7", "haiku-4.7"]
49+
50+
# Explicitly exclude 4.5
51+
if any(p in model.lower() for p in ["haiku-4-5", "haiku_4_5", "haiku-4.5"]):
52+
return False
53+
54+
# Only match 4.6+
55+
if any(p in model.lower() for p in haiku_patterns):
56+
return True
57+
58+
# Similar for Opus and Sonnet...
59+
return False
60+
```
61+
62+
---
63+
64+
## Pattern 2: Misleading Function Scope
65+
66+
### Problem Code (from PR #25876)
67+
68+
```python
69+
# File: litellm/llms/bedrock/common_utils.py
70+
# Lines 565-597
71+
72+
def is_claude_4_5_on_bedrock(model: str) -> bool:
73+
"""
74+
Claude 4.5 models support prompt caching with '5m' and '1h' TTL
75+
76+
Returns:
77+
bool: True if model is Claude 4.5 on Bedrock
78+
"""
79+
model_lower = model.lower()
80+
81+
claude_4_5_patterns = [
82+
# Original 4.5 patterns
83+
"sonnet-4.5", "sonnet_4.5", "sonnet-4-5", "sonnet_4_5",
84+
"haiku-4.5", "haiku_4.5", "haiku-4-5", "haiku_4_5",
85+
"opus-4.5", "opus_4.5", "opus-4-5", "opus_4_5",
86+
87+
# 4.6 patterns added later
88+
"sonnet-4.6", "sonnet_4.6", "sonnet-4-6", "sonnet_4_6",
89+
"opus-4.6", "opus_4.6", "opus-4-6", "opus_4_6",
90+
91+
# 🚨 NEW in Opus 4.7 PR: Now includes 4.7!
92+
"opus-4.7", "opus_4.7", "opus-4-7", "opus_4_7",
93+
]
94+
95+
return any(pattern in model_lower for pattern in claude_4_5_patterns)
96+
```
97+
98+
### Why This Breaks
99+
100+
**Before Opus 4.7 PR:**
101+
```python
102+
is_claude_4_5_on_bedrock("anthropic.claude-haiku-4-5-...") # True
103+
is_claude_4_5_on_bedrock("anthropic.claude-opus-4-6-...") # True
104+
is_claude_4_5_on_bedrock("anthropic.claude-opus-4-7-...") # False
105+
```
106+
107+
**After Opus 4.7 PR:**
108+
```python
109+
is_claude_4_5_on_bedrock("anthropic.claude-haiku-4-5-...") # True
110+
is_claude_4_5_on_bedrock("anthropic.claude-opus-4-6-...") # True
111+
is_claude_4_5_on_bedrock("anthropic.claude-opus-4-7-...") # ✅ True (NEW!)
112+
```
113+
114+
**Impact on Callsites:**
115+
116+
If any code does this:
117+
```python
118+
if is_claude_4_5_on_bedrock(model):
119+
# Apply 4.5-specific logic
120+
enable_prompt_caching()
121+
set_thinking_mode("adaptive") # ← This might have been 4.5-specific!
122+
```
123+
124+
Now Opus 4.7 triggers that code path too! If that path includes logic that only works for 4.5/4.6, it might accidentally affect other models.
125+
126+
### What Should Happen
127+
128+
```python
129+
# Option A: Rename function to reflect actual scope
130+
def is_modern_claude_on_bedrock(model: str) -> bool:
131+
"""
132+
Claude 4.5+ models support advanced features like prompt caching
133+
134+
Returns:
135+
bool: True if model is Claude 4.5, 4.6, or 4.7 on Bedrock
136+
"""
137+
# Clear that it covers multiple versions
138+
pass
139+
140+
# Option B: Use semantic version checks
141+
def supports_prompt_caching_on_bedrock(model: str) -> bool:
142+
"""Check if model supports prompt caching via JSON lookup"""
143+
return get_model_capability(model, "supports_prompt_caching")
144+
```
145+
146+
---
147+
148+
## Pattern 3: Duplicate/Redundant Checks
149+
150+
### Problem Code (from Greptile review of PR #25876)
151+
152+
```python
153+
# File: litellm/llms/anthropic/chat/transformation.py
154+
# Lines 237-248
155+
156+
def get_supported_openai_params(model: str, custom_llm_provider: str):
157+
supported_params = ["temperature", "top_p", "max_tokens"]
158+
159+
# ❌ REDUNDANT: Check added for 4.7
160+
if _is_claude_4_7_model(model):
161+
supported_params.extend(["thinking", "reasoning_effort"])
162+
# ✅ This already covers 4.7 because the JSON has supports_reasoning: true!
163+
elif supports_reasoning(model, custom_llm_provider):
164+
supported_params.extend(["thinking", "reasoning_effort"])
165+
166+
return supported_params
167+
```
168+
169+
### Why This Matters
170+
171+
From Greptile review:
172+
> "`claude-opus-4-7` has `supports_reasoning: true` in `model_prices_and_context_window.json`, so the `or supports_reasoning(...)` branch already covers it. The explicit `_is_claude_4_7_model` guard is dead code under any recognised 4.7 model name."
173+
174+
**The deeper problem:** If the explicit check runs FIRST, it shortcuts the `supports_reasoning()` call. This means:
175+
1. The JSON-based system is bypassed
176+
2. Any bugs in `supports_reasoning()` are hidden
177+
3. Future model additions can't use the JSON system because the hardcoded checks shadow it
178+
179+
---
180+
181+
## Pattern 4: The Root Cause - Provider Name Mismatch
182+
183+
### The Real Bug (from PR #24053 analysis)
184+
185+
```python
186+
# File: litellm/utils.py (simplified)
187+
188+
def supports_reasoning(model: str, custom_llm_provider: str) -> bool:
189+
"""Check if model supports reasoning from JSON"""
190+
model_info = _get_model_info_helper(
191+
model=model,
192+
custom_llm_provider=custom_llm_provider # ← "bedrock"
193+
)
194+
return model_info.get("supports_reasoning", False)
195+
196+
def _get_model_info_helper(model: str, custom_llm_provider: str):
197+
"""Look up model in cost map"""
198+
# Looks for entry with matching litellm_provider
199+
200+
# Problem: JSON has litellm_provider: "bedrock_converse"
201+
# But we're searching with custom_llm_provider: "bedrock"
202+
#
203+
# Mismatch! Returns None → supports_reasoning = False
204+
pass
205+
```
206+
207+
### Model Entry in JSON
208+
209+
```json
210+
{
211+
"anthropic.claude-haiku-4-5-20251001-v1:0": {
212+
"max_tokens": 64000,
213+
"max_input_tokens": 200000,
214+
"input_cost_per_token": 0.0000004,
215+
"output_cost_per_token": 0.000002,
216+
"litellm_provider": "bedrock_converse", // ← Note: "bedrock_converse"
217+
"mode": "chat",
218+
"supports_vision": true,
219+
"supports_reasoning": false // ← Haiku 4.5 doesn't support reasoning!
220+
},
221+
"anthropic.claude-opus-4-7-...": {
222+
"litellm_provider": "bedrock_converse",
223+
"supports_reasoning": true, // ← Opus 4.7 DOES support reasoning
224+
"supports_xhigh_reasoning_effort": true
225+
}
226+
}
227+
```
228+
229+
### Why Hardcoded Checks Were Added
230+
231+
Because `supports_reasoning()` doesn't work for Bedrock (returns `False` even when JSON says `True`), developers added hardcoded checks as workarounds:
232+
233+
```python
234+
# Workaround pattern seen throughout codebase:
235+
if _is_claude_4_6_model(model) or _is_claude_4_7_model(model):
236+
# Hardcoded: We know these support reasoning
237+
add_reasoning_params()
238+
elif supports_reasoning(model, provider):
239+
# This branch never executes for Bedrock! ❌
240+
add_reasoning_params()
241+
```
242+
243+
**The cascade effect:**
244+
1. `supports_reasoning()` is broken for Bedrock
245+
2. Developers add hardcoded checks as workarounds
246+
3. Hardcoded checks use broad patterns (`"claude-haiku-4"`)
247+
4. Broad patterns catch unintended models (Haiku 4.5)
248+
5. Wrong behavior applied to wrong models
249+
6. Production regression
250+
251+
---
252+
253+
## How This Affected Haiku 4.5
254+
255+
### Request Flow (Before Opus 4.7)
256+
257+
```
258+
User request → Haiku 4.5 on Bedrock
259+
260+
Check: is_claude_4_5_on_bedrock() → True (4.5 pattern matches)
261+
262+
Check: _supports_extended_thinking_on_bedrock() → False (no "haiku-4" check)
263+
264+
Request sent WITHOUT thinking metadata
265+
266+
✅ Bedrock accepts request → Normal cost
267+
```
268+
269+
### Request Flow (After Opus 4.7)
270+
271+
```
272+
User request → Haiku 4.5 on Bedrock
273+
274+
Check: is_claude_4_5_on_bedrock() → True (4.5 pattern matches)
275+
276+
Check: _supports_extended_thinking_on_bedrock() → True ❌ (NEW "haiku-4" check matches!)
277+
278+
Request sent WITH thinking metadata ❌
279+
280+
❌ Bedrock rejects: "unsupported thinking metadata"
281+
282+
LiteLLM retries (3x default?) ❌
283+
284+
💰💰💰 3x normal cost!
285+
```
286+
287+
---
288+
289+
## The Complete Picture
290+
291+
```mermaid
292+
graph TD
293+
A[Opus 4.7 Announced] --> B[Day 0 Launch PR #25867]
294+
B --> C[Add _is_opus_4_7_model checks]
295+
C --> D[Extend is_claude_4_5_on_bedrock]
296+
C --> E[Add broad pattern: 'claude-haiku-4']
297+
298+
E --> F[Haiku 4.5 model name matches]
299+
D --> F
300+
301+
F --> G[Thinking metadata added to request]
302+
G --> H[Bedrock API error]
303+
H --> I[Retry storm]
304+
I --> J[Cost spike reported]
305+
306+
style F fill:#f99
307+
style G fill:#f99
308+
style H fill:#f99
309+
style I fill:#f99
310+
style J fill:#f99
311+
```
312+
313+
---
314+
315+
## Fix Strategy
316+
317+
### 1. Immediate Hotfix
318+
319+
```python
320+
# File: litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py
321+
322+
def _supports_extended_thinking_on_bedrock(model: str) -> bool:
323+
"""Check if model supports extended thinking on Bedrock"""
324+
model_lower = model.lower()
325+
326+
# ✅ EXPLICIT EXCLUSION: Haiku 4.5 does NOT support extended thinking
327+
haiku_45_patterns = ["haiku-4-5", "haiku_4_5", "haiku-4.5", "haiku_4.5"]
328+
if any(p in model_lower for p in haiku_45_patterns):
329+
return False
330+
331+
# Now safe to check broader patterns for 4.6+
332+
if "claude-haiku-4" in model_lower: # Catches 4.6, 4.7, etc.
333+
return True
334+
335+
if "claude-opus-4" in model_lower:
336+
return True
337+
338+
if "claude-sonnet-4" in model_lower:
339+
return True
340+
341+
return False
342+
```
343+
344+
### 2. Architectural Fix
345+
346+
```python
347+
# Fix the provider name mismatch in _get_model_info_helper
348+
349+
def _get_model_info_helper(model: str, custom_llm_provider: str):
350+
"""Look up model in cost map with provider normalization"""
351+
352+
# ✅ NORMALIZE Bedrock provider names
353+
if custom_llm_provider in ["bedrock", "bedrock_converse"]:
354+
# Try both variants
355+
info = _lookup(model, "bedrock_converse")
356+
if not info:
357+
info = _lookup(model, "bedrock")
358+
return info
359+
360+
return _lookup(model, custom_llm_provider)
361+
362+
# Then REMOVE all hardcoded model checks and use:
363+
if supports_reasoning(model, provider):
364+
add_reasoning_params()
365+
```
366+
367+
---
368+
369+
## Lessons Learned
370+
371+
1. **Broad substring matching is dangerous**
372+
- `"claude-haiku-4"` matches 4.0, 4.5, 4.6, 4.7, 4.99...
373+
- Use explicit version checks or semantic versioning
374+
375+
2. **Function names must match scope**
376+
- `is_claude_4_5_on_bedrock()` covering 4.5/4.6/4.7 is misleading
377+
- Rename or refactor when scope expands
378+
379+
3. **Hardcoded checks hide architectural bugs**
380+
- `supports_reasoning()` being broken led to workarounds
381+
- Workarounds created new bugs (this regression)
382+
- Fix root cause instead of adding workarounds
383+
384+
4. **CI/CD is critical**
385+
- Opus 4.7 skipped full CI/CD
386+
- Integration tests would have caught Haiku 4.5 regression
387+
388+
5. **JSON-based configuration only works if it actually works**
389+
- LiteLLM has great design: model capabilities in JSON
390+
- But implementation is broken for Bedrock provider
391+
- Document says "just add JSON entry" but reality requires code changes

0 commit comments

Comments
 (0)