Skip to content

fix(sdk): prevent double provider-prefix strip for namespaced model IDs - #4438

Draft
santhiprakash wants to merge 1 commit into
OpenHands:mainfrom
santhiprakash:fix/llm-double-prefix-strip
Draft

fix(sdk): prevent double provider-prefix strip for namespaced model IDs#4438
santhiprakash wants to merge 1 commit into
OpenHands:mainfrom
santhiprakash:fix/llm-double-prefix-strip

Conversation

@santhiprakash

Copy link
Copy Markdown

HUMAN:


AGENT:

Why

LLMProvider.from_model() parses a model string like openai/openai/example-model into model="openai/example-model" + provider="openai". When as_litellm_call_kwargs() forwards that as model="openai/example-model" + custom_llm_provider="openai", LiteLLM's Responses API path re-parses and strips the openai/ prefix again, sending example-model upstream instead of openai/example-model — which returns 400 Model not found.

This affects any namespaced model ID where the inner segment is also a LiteLLM provider name (e.g. openai/openai/o3-mini, mistral/mistral/large). The OpenRouter nested case (openrouter/anthropic/claude-sonnet-4) is unaffected because LiteLLM has a dedicated OpenRouter special-case that preserves the inner provider/model form.

Summary

  • Re-prefix the model in as_litellm_call_kwargs() when the parsed model still starts with the provider prefix, so LiteLLM strips exactly one prefix and lands on the correct parsed model.
  • Added regression test test_llm_provider_preserves_namespaced_model_after_litellm_reparse covering openai/openai/example-model.

Issue Number

Fixes OpenHands/OpenHands#16365

How to Test

uv run pytest tests/sdk/llm/test_litellm_provider.py -v

Result (12 passed):

tests/sdk/llm/test_litellm_provider.py::test_llm_provider_parses_nested_openrouter_model PASSED
tests/sdk/llm/test_litellm_provider.py::test_llm_provider_parses_bedrock_model PASSED
tests/sdk/llm/test_litellm_provider.py::test_llm_provider_strips_api_key_for_bedrock_calls PASSED
tests/sdk/llm/test_litellm_provider.py::test_llm_provider_handles_unknown_model_without_provider PASSED
tests/sdk/llm/test_litellm_provider.py::test_llm_provider_keeps_requested_api_base_verbatim PASSED
tests/sdk/llm/test_litellm_provider.py::test_llm_provider_preserves_namespaced_model_after_litellm_reparse PASSED
tests/sdk/llm/test_litellm_provider.py::test_split_kwargs_equivalent_to_full_model_string[...] PASSED (6 cases)
============================== 12 passed in 0.06s ==============================

Pre-commit verification:

uv run pre-commit run --files openhands-sdk/openhands/sdk/llm/utils/litellm_provider.py tests/sdk/llm/test_litellm_provider.py

All checks passed (Ruff format, Ruff lint, PEP8, pyright, import dependency rules, tool subclass registration).

Reproduction proof (before fix vs after fix):

import litellm
# Before: double-strip
litellm.get_llm_provider(model="openai/example-model", custom_llm_provider="openai", ...)
# -> ('example-model', 'openai')  wrong, 400 Model not found

# After: re-prefix prevents double-strip
litellm.get_llm_provider(model="openai/openai/example-model", custom_llm_provider="openai", ...)
# -> ('openai/example-model', 'openai')  correct

Video/Screenshots

N/A — backend correctness fix, no visual surface.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

The fix is minimal and only triggers when self.model.startswith(f"{self.name}/") — the exact condition that causes the double-strip. All existing test cases (openrouter nested, bedrock, mistral, litellm_proxy, openai local, unknown) are unaffected because their parsed models do not start with the provider prefix.

LLMProvider.from_model() parses a model string like "openai/openai/example-
model" into model="openai/example-model" + provider="openai". When
as_litellm_call_kwargs() forwards that as model="openai/example-model" +
custom_llm_provider="openai", LiteLLM's Responses API path re-parses and
strips the "openai/" prefix again, sending "example-model" upstream instead
of "openai/example-model" (400 Model not found).

Re-prefix the model when it still starts with the provider prefix so LiteLLM
strips exactly one prefix and lands on the parsed model.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@santhiprakash
santhiprakash force-pushed the fix/llm-double-prefix-strip branch from ebb59ad to f92b151 Compare August 10, 2026 10:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OpenAI provider prefix is stripped twice for namespaced model IDs in Responses API

1 participant