Here’s a concise, complete audit of how model name patterns are used in agent-sdk today, what they change in llm.py, how this interacts with LiteLLM, what still requires our own patterns/policies, and how to refactor to simplify.
1. Model name canonicalization and matching
• What
◦ normalize_model_name(model) (model_features.py)
▪︎ Lowercases, strips.
▪︎ If “/” present, uses basename after last “/”; drops Ollama-style “:tag”.
▪︎ If basename has a vendor prefix like “anthropic.”, drops it (only for specific vendor set).
▪︎ Drops “-gguf” suffix.
◦ model_matches(model, patterns)
▪︎ If pattern contains “/”, match against full raw model string (provider-qualified).
▪︎ Else match against normalized basename.
• Implications
◦ “anthropic.claude-*” becomes “claude-*”: will match basename patterns.
◦ “anthropic/claude-*” becomes “claude-*”: will match basename patterns.
◦ “bedrock/us.anthropic.claude-*” basename is “us.anthropic.claude-*” (NOT stripped): will NOT match “claude-*” patterns.
◦ Practical gap: Bedrock region-qualified ids often bypass our basename patterns.
2. Function calling (patterns → behavior)
• Patterns
◦ FUNCTION_CALLING_PATTERNS: families like claude-
, gpt-4o, gpt-5*, o1-2024-12-17, o3*, o4-mini, gemini-2.5-pro, kimi, qwen3.
• Where used
◦ llm.py::_init_model_info_and_caps(): _function_calling_active = (native_tool_calling if set) else features.supports_function_calling.
◦ NonNativeToolCallingMixin: if tools and not native FC → mock via prompt (convert messages).
◦ llm.py::completion(): passes tools to LiteLLM only when native FC is active; else uses mocked prompting.
• Params added/removed
◦ Native FC ON: kwargs["tools"] present (and tool_choice respected).
◦ Native FC OFF: kwargs["tools"] removed; kwargs["tool_choice"] removed; may add kwargs["stop"] (see item 5).
3. Reasoning effort (patterns → behavior)
• Patterns
◦ REASONING_EFFORT_PATTERNS: o1*, o3*, o4-mini*, gpt-5*, gemini-2.5-*.
• Where used
◦ options/chat_options.py::select_chat_options():
▪︎ Adds reasoning_effort if llm.reasoning_effort is set (defaults to “low” for gemini-2.5-pro if none).
▪︎ Removes temperature and top_p for these models (provider ignores them).
• Params added/removed
◦ Adds reasoning_effort.
◦ Removes temperature, top_p.
4. Extended thinking (Anthropic 4.5)
• Patterns
◦ EXTENDED_THINKING_PATTERNS: “claude-sonnet-4-5_”, “claude-haiku-4-5_”.
• Where used
◦ options/chat_options.py::select_chat_options():
▪︎ Adds thinking={"type":"enabled","budget_tokens": llm.extended_thinking_budget}.
▪︎ Adds extra_headers={"anthropic-beta":"interleaved-thinking-2025-05-14"}.
▪︎ Forces out["max_tokens"] = llm.max_output_tokens.
▪︎ Removes temperature and top_p.
• Params added/removed
◦ Adds thinking + extra_headers; sets max_tokens; removes temp/top_p.
5. Stop words in mocked FC (denylist patterns)
• Patterns
◦ SUPPORTS_STOP_WORDS_FALSE_PATTERNS: “o1_”, “grok-4-0709”, “grok-code-fast-1”, “deepseek-r1-0528_”.
• Where used
◦ NonNativeToolCallingMixin::pre_request_prompt_mock():
▪︎ If not denied and not disabled, adds kwargs["stop"] = STOP_WORDS during mocked FC.
• Params added/removed
◦ Adds stop only in mocked FC and only if allowed.
6. Prompt caching (Anthropic)
• Patterns
◦ PROMPT_CACHE_PATTERNS: various claude-* families.
• Where used
◦ llm.py::is_caching_prompt_active(): True only if llm.caching_prompt and pattern matches.
◦ llm.py::format_messages_for_llm():
▪︎ If active, _apply_prompt_caching() sets Anthropic-style cache breakpoints (system first chunk; last user/tool chunk).
• Params added/removed
◦ No request param; modifies message content flags (Anthropic-specific behavior).
7. Responses API routing (policy patterns)
• Patterns
◦ RESPONSES_API_PATTERNS: “gpt-5*”.
• Where used
◦ llm.py::uses_responses_api(): returns True for gpt-5*.
◦ openhands.sdk.agent.Agent.step(): chooses llm.responses() when True, otherwise llm.completion().
• Params added/removed (responses_options.py)
◦ Forces temperature=1.0, tool_choice="auto".
◦ store defaults to False; when store=False, add "reasoning.encrypted_content" to include.
◦ Adds reasoning={"effort": llm.reasoning_effort or "high", "summary":"detailed"}.
8. Provider-specific tweaks (string checks, not in pattern table)
• Where used
◦ llm.py::_coerce_inputs():
▪︎ Azure: default api_version if none.
▪︎ huggingface*: if top_p==1.0 → set to 0.9.
▪︎ openhands/
→ rewrite to litellm_proxy/ and set base_url.
▪︎ Default reasoning_effort: “high” unless model contains gemini-2.5-pro or claude-4.5 models.
◦ llm.py::format_messages_for_llm():
▪︎ deepseek or kimi-k2-instruct on groq → message.force_string_serializer = True.
9. Anthropic max_output_tokens override
• Where used
◦ llm.py::_init_model_info_and_caps():
▪︎ If max_output_tokens unset AND model in a specific Anthropic family, sets max_output_tokens = 64000; else takes from _model_info if available.
10. Vision support detection (mixed sources)
• Where used
◦ llm.py::_supports_vision():
▪︎ uses supports_vision(model) OR supports_vision(basename) OR _model_info.supports_vision.
◦ Rationale: historical LiteLLM bug for prefixed names; fallback to model_info.
11. How llm.py gets and uses LiteLLM model_info
• Sources (llm.py::_init_model_info_and_caps())
◦ openrouter/* → get_model_info(self.model)
◦ litellm_proxy/* → GET {base_url}/v1/model/info then take that model’s “model_info”
◦ Fallback → get_model_info(self.model.split(":")[0]) or get_model_info(self.model.split("/")[-1])
• Uses
◦ Sets max_input_tokens and max_output_tokens if unset.
◦ Used as vision fallback (supports_vision from model_info).
12. How LiteLLM computes supports_* / model_info
• LiteLLM logic
◦ get_llm_provider() canonicalizes provider+model (handles prefixes properly).
◦ _get_model_info_helper() returns model_info (costs, token limits, supports_* flags).
◦ supports_* APIs check model_info or provider overrides; robust to prefixed names.
13. What patterns/policies we still need in agent-sdk (beyond model_info)
• Keep (SDK policy or not exposed by LiteLLM):
◦ Extended thinking (Anthropic 4.5): add thinking + Anthropic beta header.
◦ Responses API default policy (“gpt-5*” or config flag).
◦ Stop words denylist (for mocked FC).
◦ huggingface top_p quirk (set 0.9 when 1.0).
◦ Azure default api_version.
◦ Serializer toggles for deepseek and kimi+groq.
◦ openhands/
→ litellm_proxy/ rewrite.
◦ Possibly Anthropic 64k override (prefer model_info if present).
• Delegate to LiteLLM (remove our patterns):
◦ supports_function_calling → supports_function_calling()
◦ supports_prompt_cache → supports_prompt_caching()
◦ supports_reasoning → supports_reasoning()
◦ supports_vision → supports_vision() (keep fallback for now)
14. Gaps/risks in current code
• Bedrock “us.anthropic.*” models fail basename matches; features erroneously off.
• Duplication with LiteLLM (patterns drift over time).
• Vision detection workaround may be obsolete when LiteLLM fixes prefixed-name support.
• Anthropic max_output_tokens override may diverge from real caps.
15. Refactor alternatives
• A) LiteLLM-first adapter
◦ Build a capabilities adapter that uses supports_* from LiteLLM, then layer only SDK-specific overrides (extended thinking, stop denylist, responses policy).
• B) Explicit policy + supports_*
◦ Keep a small file for our policy defaults (Responses, reasoning default, HF/azure quirks), delegate capabilities to supports_*.
• C) Provider-qualified-only patterns
◦ If patterns remain, require “/”-qualified matches to avoid basename ambiguity (still inferior to supports_*).
16. Recommended plan (phased)
• Phase 1: Add a get_capabilities() that:
◦ Canonicalizes model/provider via get_llm_provider().
◦ Resolves supports_* via LiteLLM.
◦ Adds SDK-only overrides (extended thinking, stop denylist, responses policy).
• Phase 2: Replace get_features() usage with the adapter internally; drop FUNCTION_CALLING, PROMPT_CACHE, REASONING_EFFORT patterns.
• Phase 3: Make prefer_responses_api configurable (auto|true|false); keep HF/azure quirks and serializer toggles; constrain 64k override or remove if model_info provides caps.
• Phase 4: Tests for anthropic/claude across: anthropic.
, anthropic/, bedrock/us.anthropic., openrouter/, litellm_proxy/; document the decision flow.
Direct answer to the core question
• Which model names/patterns do we still need in agent-sdk (beyond LiteLLM model_info)?
◦ Only those implementing SDK policy or features not present in LiteLLM’s model_info:
▪︎ Extended thinking (Anthropic 4.5) → adds thinking + anthropic-beta header.
▪︎ Responses API default routing (currently gpt-5*; could become config).
▪︎ Stop words denylist for mocked tool-calling (o1*, grok*, deepseek-r1).
▪︎ Provider quirks (HF top_p=0.9, Azure api_version default).
▪︎ Serializer toggles for deepseek/kimi-on-groq.
▪︎ openhands/
routing to litellm_proxy/.
▪︎ Optional Anthropic 64k cap override (prefer model_info when available).
• Everything else (function-calling, reasoning capability, prompt caching, and vision) should rely on LiteLLM supports_* and model_info instead of our long-lived pattern tables. This reduces drift, fixes bedrock/us.anthropic.*, and simplifies the code.
No code changes were made in this analysis. If you want, I can implement the “LiteLLM-first capabilities adapter” and replace the pattern tables in a branch, then open a PR. Would you like me to proceed with that, or keep the plan as documentation for now?
<sub>
View full conversation</sub>