Skip to content

fix(llm): match LiteLLM proxy model_info by litellm_params.model too#3429

Draft
juanmichelini wants to merge 1 commit into
mainfrom
fix/proxy-model-info-litellm-params
Draft

fix(llm): match LiteLLM proxy model_info by litellm_params.model too#3429
juanmichelini wants to merge 1 commit into
mainfrom
fix/proxy-model-info-litellm-params

Conversation

@juanmichelini
Copy link
Copy Markdown
Collaborator

@juanmichelini juanmichelini commented May 29, 2026

  • A human has tested these changes.

Why

Even after All-Hands-AI/infra#1309 merged and deployed (which sets model_info.supports_vision: true on the claude-opus-4-8 proxy entry), the vision integration test is still skipped:

t08_image_file_viewing: ⊘ - This test requires a vision-capable LLM model.
Please use a model that supports image input.

See run #26608680950 on commit 45697f5.

Root cause

openhands.sdk.llm.utils.model_info._get_model_info_from_litellm_proxy only matched proxy entries by model_name:

current = next(
    (info for info in data
     if info["model_name"] == model.removeprefix("litellm_proxy/")),
    None,
)

Our LiteLLM proxy config registers the new model with a short public alias:

- model_name: "claude-opus-4-8"            # alias
  litellm_params:
    model: "anthropic/claude-opus-4-8"     # underlying provider id
  model_info:
    supports_vision: true

The SDK is configured with model = "litellm_proxy/anthropic/claude-opus-4-8". After stripping the litellm_proxy/ prefix it looks for model_name == "anthropic/claude-opus-4-8" — a miss. get_litellm_model_info then falls back to litellm.get_model_info("claude-opus-4-8").

  • For claude-opus-4-7 this fallback succeeds because LiteLLM upstream already knows the id, so the bug has been silently masked.
  • For claude-opus-4-8 LiteLLM upstream does not know the id yet, the fallback also returns None, LLM._model_info ends up None, and LLM._supports_vision() returns False. The infra override is never read.

This is a general bug, not specific to one model: any time the proxy uses an alias that differs from the provider id the SDK uses (the recommended LiteLLM pattern), model_info overrides set in proxy config are invisible to the SDK.

What

Extend the matcher in _get_model_info_from_litellm_proxy to also try litellm_params.model — the same field the proxy itself accepts for incoming requests:

stripped = model.removeprefix("litellm_proxy/")
current = next(
    (info for info in data
     if info.get("model_name") == stripped
     or info.get("litellm_params", {}).get("model") == stripped),
    None,
)

The original model_name match path is unchanged, so callers that address the proxy by its alias still hit the same entry. The new path only fires when model_name does not match — exactly today's silent-miss case.

Tests

New tests/sdk/llm/test_model_info_proxy_lookup.py covers:

  • test_lookup_matches_by_model_name_alias — backward-compat.
  • test_lookup_matches_by_litellm_params_model — the case that broke claude-opus-4-8.
  • test_lookup_returns_none_for_unknown_model — negative.
  • test_get_litellm_model_info_uses_proxy_match_for_provider_prefixed_id — end-to-end through get_litellm_model_info, asserting supports_vision: true flows through.

Issue Number

Fixes #3428

How to Test

uv run pytest tests/sdk/llm/test_model_info_proxy_lookup.py -v
uv run pytest tests/sdk/llm/ -k "model_info or vision or supports_vision" -q
uv run pre-commit run --files \
  openhands-sdk/openhands/sdk/llm/utils/model_info.py \
  tests/sdk/llm/test_model_info_proxy_lookup.py

Local results from this branch:

  • New tests: 4 passed.
  • Targeted regression sweep: 28 passed, 0 failed.
  • Pre-commit (ruff format/lint, pycodestyle, pyright, import-rule, tool-registration): all hooks passed.

After merge, re-running the Run Integration Tests vision claude-opus-4-8 workflow on main is expected to actually execute t08_image_file_viewing instead of skipping it.

Type

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

Related

  • All-Hands-AI/infra#1305 — registered claude-opus-4-8 on the proxy
  • All-Hands-AI/infra#1309 — added model_info.supports_vision: true (correct on the proxy side, but invisible to the SDK without this fix)
  • Register claude-opus-4-8 in SDK model lists #3424 — SDK model registration

This PR was opened by an AI agent (OpenHands) on behalf of @juanmichelini.

@juanmichelini can click here to continue refining the PR


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:a5440ed-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-a5440ed-python \
  ghcr.io/openhands/agent-server:a5440ed-python

All tags pushed for this build

ghcr.io/openhands/agent-server:a5440ed-golang-amd64
ghcr.io/openhands/agent-server:a5440eda7f36b1a05ecfe4b3dfd3e329e9e37367-golang-amd64
ghcr.io/openhands/agent-server:fix-proxy-model-info-litellm-params-golang-amd64
ghcr.io/openhands/agent-server:a5440ed-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:a5440ed-golang-arm64
ghcr.io/openhands/agent-server:a5440eda7f36b1a05ecfe4b3dfd3e329e9e37367-golang-arm64
ghcr.io/openhands/agent-server:fix-proxy-model-info-litellm-params-golang-arm64
ghcr.io/openhands/agent-server:a5440ed-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:a5440ed-java-amd64
ghcr.io/openhands/agent-server:a5440eda7f36b1a05ecfe4b3dfd3e329e9e37367-java-amd64
ghcr.io/openhands/agent-server:fix-proxy-model-info-litellm-params-java-amd64
ghcr.io/openhands/agent-server:a5440ed-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:a5440ed-java-arm64
ghcr.io/openhands/agent-server:a5440eda7f36b1a05ecfe4b3dfd3e329e9e37367-java-arm64
ghcr.io/openhands/agent-server:fix-proxy-model-info-litellm-params-java-arm64
ghcr.io/openhands/agent-server:a5440ed-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:a5440ed-python-amd64
ghcr.io/openhands/agent-server:a5440eda7f36b1a05ecfe4b3dfd3e329e9e37367-python-amd64
ghcr.io/openhands/agent-server:fix-proxy-model-info-litellm-params-python-amd64
ghcr.io/openhands/agent-server:a5440ed-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:a5440ed-python-arm64
ghcr.io/openhands/agent-server:a5440eda7f36b1a05ecfe4b3dfd3e329e9e37367-python-arm64
ghcr.io/openhands/agent-server:fix-proxy-model-info-litellm-params-python-arm64
ghcr.io/openhands/agent-server:a5440ed-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:a5440ed-golang
ghcr.io/openhands/agent-server:a5440eda7f36b1a05ecfe4b3dfd3e329e9e37367-golang
ghcr.io/openhands/agent-server:fix-proxy-model-info-litellm-params-golang
ghcr.io/openhands/agent-server:a5440ed-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:a5440ed-java
ghcr.io/openhands/agent-server:a5440eda7f36b1a05ecfe4b3dfd3e329e9e37367-java
ghcr.io/openhands/agent-server:fix-proxy-model-info-litellm-params-java
ghcr.io/openhands/agent-server:a5440ed-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:a5440ed-python
ghcr.io/openhands/agent-server:a5440eda7f36b1a05ecfe4b3dfd3e329e9e37367-python
ghcr.io/openhands/agent-server:fix-proxy-model-info-litellm-params-python
ghcr.io/openhands/agent-server:a5440ed-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., a5440ed-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., a5440ed-python-amd64) are also available if needed

The proxy /v1/model/info matcher only checked `model_name` (the public
alias). When a proxy config exposes a model under a short alias
(`claude-opus-4-8`) for a provider id (`anthropic/claude-opus-4-8`),
clients addressing the model by its provider id (the typical SDK setup
of `litellm_proxy/anthropic/claude-opus-4-8`) missed the entry.

When that happens, `get_litellm_model_info` falls back to
`litellm.get_model_info` on the upstream catalog. For models LiteLLM
already knows the fallback succeeded and hid the bug. For new model ids
LiteLLM has not yet registered (Claude Opus 4.8 today), the fallback
also returns None, `_model_info` ends up None, and `_supports_vision`
returns False — so `model_info.supports_vision: true` overrides set on
the proxy never reach the SDK and t08_image_file_viewing is skipped.

Extend the matcher to also check `litellm_params.model`, mirroring how
the proxy itself accepts incoming requests. The existing alias-match
path is unchanged, so this is backward compatible.

Adds dedicated unit tests for both match paths (alias and provider id),
the negative case, and the end-to-end `get_litellm_model_info` path.

Fixes #3428

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/llm/utils
   model_info.py53786%52–53, 66–70
TOTAL28611829671% 

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.

LiteLLM proxy model_info lookup misses when proxy uses short aliases (claude-opus-4-8 vision still off)

2 participants