Skip to content

fix(sdk): consolidate capability resolution and reject silent image loss - #6040

Open
neubig wants to merge 10 commits into
mainfrom
fix/canonical-model-capabilities
Open

neubig wants to merge 10 commits into
mainfrom
fix/canonical-model-capabilities

Conversation

@neubig

@neubig neubig commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

HUMAN:

I've called some rudimentary agent sessions and also ran example/integration tests.


AGENT:

Why

Alias routing, endpoint contracts and LiteLLM's changing registry are different sources of truth. The existing SDK spread its behavioral rules over ten model lists, treated generic supports_reasoning as permission to send reasoning_effort, and could lose images either during SDK filtering or inside the native DeepSeek adapter. Advertised output ceilings were also being used as requested generation budgets.

Summary

  • Consolidate SDK-owned behavior into one ordered capability registry with caller > endpoint > SDK > discovery precedence, explicit False preservation, bounded canonical identity matching and per-capability provenance. Preserve provider-qualified restrictions. Generic reasoning no longer authorizes an exact request parameter; proxy-advertised allowances are projected narrowly and retained through LiteLLM validation.
  • Reject unsupported image-bearing requests at the request-only boundary, leaving token counting/format inspection available for agent delegation. Check native DeepSeek's final HTTP body and abort before sending when its installed adapter discarded images. Do not mutate LiteLLM's global capability registry or silently rewrite the provider/alias.
  • Stop requesting advertised output maximums by default. Retain a bounded, ceiling-constrained budget where manual-thinking APIs require one. Runtime SDK code is 45 lines smaller; total diff grows because of real HTTP-boundary and regression tests.

Issue Number

Contributes to #4880. Fixes #5523 and #4934 at the SDK capability layer. Addresses the exact-parameter contract in #5499, the explicit override/output-default portions of #5181, and prevents silent native-adapter image loss reported in #5760. Overlapping or alternative implementations are linked in the Notes section below.

This is not a claim to close the entire #4880 umbrella. Input/context limits, tool-calling discovery, provider catalogs, bounded discovery queues, prefill, Qwen/OpenRouter reasoning persistence and native Bedrock adapter behavior remain separate work. No dependency bump or provider API-mode rewrite is included.

How to Test

Base: 6530325; implementation: b37b79e.

Reproduced before / after

Same installed LiteLLM 1.93.0, real helpers:

Input Before After
openhands/deepseek-v4.1-flash, no metadata vision false vision true, SDK provenance
minimax/MiniMax-M3, generic supports_reasoning=True effort true effort false
unknown proxy alias, allowed_openai_params=['reasoning_effort'] effort false effort true only
proxy model output maximum 384000, no requested budget SDK requested maximum no SDK default output budget
native DeepSeek text + user/tool image adapter can send text only request rejected before HTTP if images lost

Automated validation

Run from the repository with its development environment:

uv run pytest tests/sdk/llm -q -n 4 --timeout=60
uv run pytest tests/sdk/agent/test_non_multimodal_image_input.py -q
uv run pytest tests/sdk/llm/test_capability_wire.py -q

The new HTTP tests use real local HTTP servers and actual LiteLLM processing, not mocked completion calls. They cover sync/async, user and tool text+image content, canonical identity versus the transmitted deployment id, explicit False, missing/null metadata, proxy allowance projection, exact override survival with drop_params=True, and rejection without preventing token counting. Native adapter tests require either complete image delivery or a visible pre-send error, allowing corrected future adapters to pass.

Validation environment reused an existing Python 3.13 virtualenv, with PYTHONPATH set to this worktree; installed package metadata remains SDK 1.49.6 while source is this branch. LiteLLM is 1.93.0, not an implicitly upgraded adapter. Exact-head LLM suite: 1326 passed (83.55s). Focused request guard + nonvision fallback: 35 passed. Final capability regression file: 241 passed.

Ruff lint/format, Pyright over all changed files, SDK import rules, forbidden-dynamic-attribute check, and git diff --check passed. Hooks were run explicitly rather than letting uv change the shared environment.

Real authenticated inference

Used the managed OpenHands proxy with a synthetic yellow-square/blue-background PNG and bounded token limits. No benchmark/user images uploaded. 27/34 initial smoke calls completed, plus two successful Claude manual-thinking controls:

  • User-image success: DeepSeek v4.1 Flash, MiniMax M3, Kimi K3, Qwen 3.6 Plus, Claude Opus 5.5, Gemini 3.1 Pro preview, GPT 5.6, GPT 6 Sol and Luna.
  • Seven tool-image calls completed, covering DeepSeek v4.1 Flash, MiniMax M3, Kimi K3, Qwen 3.6 Plus, Claude Opus 5.5, Gemini 3.1 Pro preview and GPT 5.6. Responses described the supplied colored shape. Gemini also emitted reasoning text; these are smoke tests, not quality grading.
  • Text controls completed for DeepSeek V4 Flash/Pro, DeepSeek chat/reasoner, Kimi K2.5/K2-thinking/K2.6/K2.7-code, MiniMax M2.7, GPT5.6 Sol and Claude Opus5.
  • Unavailable to this key: GPT6 Astra, DeepSeek V3.2 reasoner, Qwen3.7 Plus/Qwen3.8 Max, GPT5.6 Luna and GLM5.3. These are not marked validated.
  • An initial Sonnet smoke call used an invalid 1024 total-output budget for manual thinking; explicit 2048 output/1024 thinking and the corrected default 16384 output configuration both completed with READY.
  • No direct provider or AWS credentials were available. Native DeepSeek, Bedrock and namespace behavior should not be described as live provider validation; DeepSeek image-loss enforcement is tested at a local final HTTP boundary.

Broader suites and environment limits

An initial broad agent/event/conversation run was interrupted after 1884 passes and five failures. All five reproduce on pristine 6530325: three fork deprecation assertions see installed SDK1.49.6 instead of source1.54+, a saved user MEMORY.md invalidates the no-memory fixture, and the dev stack sets optional WebSocket readiness. An isolated-state run with strict WebSocket readiness and deprecation tests excluded reached 1985 passed, one parallel terminal-reset timeout; that terminal test and two other timing-sensitive controls pass serially. Final exact-head isolated rerun (also excluding that terminal-reset test) reached 1984 passed, one FIFO fairness failure under parallel load; FIFO passes serially. These broad suites are not claimed fully green.

Independent read-only review found a default manual-thinking output-budget defect; it was corrected, regression-tested for Sonnet4.5/4.6/Haiku4.5 and verified with a live default Sonnet call before this PR.

Video/Screenshots

Nonvisual change. Reproduction, request-boundary tests and live-response summaries above provide evidence.

Design Doc

Companion documentation: OpenHands/docs#958

Type

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

Notes

  • Intentional behavior changes: unsupported image requests now fail rather than quietly discarding pixels; metadata output ceilings no longer become default requested budgets; generic reasoning no longer enables unsupported effort parameters. Persisted configuration schema is unchanged.
  • SDK-owned fields are intentionally incomplete: do not infer every capability of every verified model from this registry. Unknown model fields continue discovery. Pricing remains LiteLLM/proxy-owned.
  • Proxy allowance projection currently retains the matching deployment contract; aggregating conflicting duplicate deployments is not part of this change and is covered by alternative feat(sdk): consume proxy-advertised request-parameter capabilities #5502.
  • The native DeepSeek guard blocks loss, does not implement missing upstream vision support. Explicitly use a compatible route where needed. Other third-party adapters are not universally guarded.
  • Docs preview and packaged agent-server/Docker build were not run. PR remains draft for human review and CI.
  • Overlapping or alternative implementations: proxy-advertised request-parameter capabilities, detect deepseek-v4.1-flash as vision-capable, recognize DeepSeek v4.1 Flash vision support.

This PR was created by an AI agent (OpenHands) on behalf of Graham Neubig.


🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

• 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-slim amd64, arm64 python-node-runtime Link
python-minimal amd64, arm64 python-node-runtime Link
python amd64, arm64 python-node-runtime 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:edb841b-python

Run

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

All tags pushed for this build

ghcr.io/openhands/agent-server:edb841b-golang-amd64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-golang-amd64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-golang-amd64
ghcr.io/openhands/agent-server:edb841b-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:edb841b-golang-arm64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-golang-arm64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-golang-arm64
ghcr.io/openhands/agent-server:edb841b-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:edb841b-java-amd64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-java-amd64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-java-amd64
ghcr.io/openhands/agent-server:edb841b-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:edb841b-java-arm64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-java-arm64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-java-arm64
ghcr.io/openhands/agent-server:edb841b-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:edb841b-python-amd64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-python-amd64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-python-amd64
ghcr.io/openhands/agent-server:edb841b-python-node-runtime-amd64
ghcr.io/openhands/agent-server:edb841b-python-arm64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-python-arm64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-python-arm64
ghcr.io/openhands/agent-server:edb841b-python-node-runtime-arm64
ghcr.io/openhands/agent-server:edb841b-python-minimal-amd64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-python-minimal-amd64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-python-minimal-amd64
ghcr.io/openhands/agent-server:edb841b-python-node-runtime-minimal-amd64
ghcr.io/openhands/agent-server:edb841b-python-minimal-arm64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-python-minimal-arm64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-python-minimal-arm64
ghcr.io/openhands/agent-server:edb841b-python-node-runtime-minimal-arm64
ghcr.io/openhands/agent-server:edb841b-python-slim-amd64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-python-slim-amd64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-python-slim-amd64
ghcr.io/openhands/agent-server:edb841b-python-node-runtime-slim-amd64
ghcr.io/openhands/agent-server:edb841b-python-slim-arm64
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-python-slim-arm64
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-python-slim-arm64
ghcr.io/openhands/agent-server:edb841b-python-node-runtime-slim-arm64
ghcr.io/openhands/agent-server:edb841b-golang
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-golang
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-golang
ghcr.io/openhands/agent-server:edb841b-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:edb841b-java
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-java
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-java
ghcr.io/openhands/agent-server:edb841b-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:edb841b-python-minimal
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-python-minimal
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-python-minimal
ghcr.io/openhands/agent-server:edb841b-python-node-runtime-minimal
ghcr.io/openhands/agent-server:edb841b-python-slim
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-python-slim
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-python-slim
ghcr.io/openhands/agent-server:edb841b-python-node-runtime-slim
ghcr.io/openhands/agent-server:edb841b-python
ghcr.io/openhands/agent-server:edb841b3181744d94baa65bbfa8426620e0b8242-python
ghcr.io/openhands/agent-server:fix-canonical-model-capabilities-python
ghcr.io/openhands/agent-server:edb841b-python-node-runtime

About Multi-Architecture Support

  • Each variant tag (e.g., edb841b-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., edb841b-python-amd64) are also available if needed

Live evidence

Merge with main at edb841b31 (2026-10-30)

Updated by an AI agent (OpenHands) on behalf of Graham Neubig.

  • Merged current main (c048a1eb8, includes LiteLLM 1.105.1 upgrade Upgrade LiteLLM to 1.105.1 (PR 1 of #6015) #6030, proxy-advertised capabilities feat(sdk): consume proxy-advertised request-parameter capabilities #5502, and DecisionModel feat(llm): add DecisionModel with OTEL tracing #6068) into fix/canonical-model-capabilities with a normal merge; no force-push. Conflicts only in:
    • openhands-sdk/openhands/sdk/llm/llm.py — kept the PR's LLMBadRequestError import for the image-loss guard; dropped is_transient_http_error because feat(llm): add DecisionModel with OTEL tracing #6068 refactored retries to transient_error_condition, which the merge already uses.
    • openhands-sdk/openhands/sdk/llm/utils/model_features.py — kept the PR's consolidated _MODEL_CAPABILITIES registry (its kimi-k3 entry covers deepinfra/moonshotai/Kimi-K3 and other hosts, superseding main's REASONING_EFFORT_MODEL_OVERRIDES), plus main's guarded _openai_supported_params lookup for LiteLLM 1.105.
  • Merge fallout fix: LiteLLM 1.105 registry metadata now lists reasoning_effort in supported_openai_params for openai/gpt-4o-mini; the PR code promoted that registry list to a capability, defeating main's guarded lookup and failing test_responses_reasoning_options_not_sent_for_non_reasoning_model / test_responses_encrypted_reasoning_not_added_for_non_reasoning_model. The promotion now applies only to endpoint declarations; LiteLLM registry metadata remains guarded discovery.
  • Tests on merged head edb841b31 (LiteLLM 1.105.1, uv sync --frozen env):
    • uv run pytest tests/sdk/llm -q -n 4 --timeout=60 — 1561 passed, 2 xfailed.
    • tests/sdk/agent/test_non_multimodal_image_input.py + tests/sdk/llm/test_decision.py + tests/sdk/llm/test_chat_options.py — 116 passed, 2 xfailed.
    • uv run pre-commit run --all-files — all hooks passed (Ruff lint/format, pycodestyle, Pyright, import rules, forbidden-dynamic-attribute, tool-registration).
  • Live evidence script (real SDK code, real local HTTP server, no mocked completions):
    • Red on pristine main (c048a1eb8): get_features('openhands/deepseek-v4.1-flash').supports_vision == False — the silent-image-loss bug this PR fixes.
    • Green on merged head: DeepSeek v4.1 Flash vision resolves True (source=sdk); minimax/MiniMax-M3 with only generic supports_reasoning=True does not get reasoning_effort; deepinfra/moonshotai/Kimi-K3, perplexity/perplexity/kimi-k3, moonshot/kimi-k2.5 resolve reasoning-effort correctly; openai/gpt-4o and fine-tune ids do not; LiteLLM-registry-declared reasoning_effort for a litellm-sourced model stays False while an endpoint declaration stays True.
    • HTTP boundary: image request to a non-vision model raises LLMBadRequestError before any HTTP request (0 requests received), and an image request to the SDK-registry vision model reaches the local HTTP server with the image part intact.

Human review is still required; this update does not approve or merge the PR.
This section was added by an AI agent (OpenHands) on behalf of Graham Neubig.

  • Both unresolved review threads addressed at ccd322bd5, merged with current main at head 91702e249:
    • Provider prefixes: vertex_ai/azure_ai added to the identity-strip set; Bedrock converse/invoke sub-routes dropped before the family regex. Local check: vertex_ai/claude-sonnet-4-5, vertex_ai/claude-haiku-4-5, vertex_ai/claude-sonnet-4-6, azure_ai/claude-sonnet-4-5, bedrock/converse/anthropic.claude-sonnet-4-5-20250929-v1:0, bedrock/invoke/... now resolve thinking_mode=manual, supports_extended_thinking=True, supports_prompt_cache=True.
    • Images: LLM._validate_image_request rejects only latest-user-message images; agent delegation (_replace_images_with_references) and the vision executor (_conversation_image_urls) now cover tool observations and non-latest user images with conversation-order image_index; without a vision tool those images fall through degraded instead of finishing the conversation.
  • uv run pytest tests/sdk/llm/test_model_features.py tests/sdk/agent/test_non_multimodal_image_input.py tests/sdk/llm/test_capability_wire.py tests/sdk/llm/test_chat_options.py -q — 312 passed before the merge; the two primary suites re-run after merging main — 256 passed.
  • Both threads were replied to with the fixing commit and resolved after the push. The failing Validate PR description check is a template-conformance issue and was intentionally left unchanged (out of scope for this pass).

Resolve explicit endpoint contracts separately from discovered metadata, preserve caller false overrides, and keep exact reasoning parameter allowances consistent with canonical capability provenance. Stop treating output ceilings as requested budgets and verify DeepSeek image delivery after provider transformation.

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result: ✅ PASSED

Action log

Co-authored-by: openhands <openhands@all-hands.dev>
@neubig neubig added test-examples Run all applicable "examples/" files. Expensive operation. integration-test Runs the integration tests and comments the results behavior-test condenser-test Triggers a run of all condenser integration tests labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hi! I started running the behavior tests on your PR. You will receive a comment with the results shortly.

@github-actions

Copy link
Copy Markdown
Contributor

Hi! I started running the integration tests on your PR. You will receive a comment with the results shortly.

@github-actions

Copy link
Copy Markdown
Contributor

Hi! I started running the condenser tests on your PR. You will receive a comment with the results shortly.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Integration Tests Results

Overall Success Rate: 98.0%
Total Cost: $2.02
Models Tested: 5
Timestamp: 2026-10-16 21:35:31 UTC

📁 Detailed Logs & Artifacts

Click the links below to access detailed agent/LLM logs showing the complete reasoning process for each model. On the GitHub Actions page, scroll down to the 'Artifacts' section to download the logs.

📊 Summary

Model Overall Tests Passed Skipped Total Cost Tokens
litellm_proxy_openai_gpt_5.5 100.0% 10/10 0 10 $0.83 306,644
litellm_proxy_deepseek_deepseek_v4.1_flash 100.0% 10/10 0 10 $0.03 417,661
litellm_proxy_gemini_3.1_pro_preview 100.0% 10/10 0 10 $0.57 354,487
litellm_proxy_anthropic_claude_sonnet_4_6 90.0% 9/10 0 10 $0.59 391,193
litellm_proxy_minimax_MiniMax_M2.7 100.0% 9/9 1 10 $0.00 363,243

📋 Detailed Results

litellm_proxy_openai_gpt_5.5

  • Success Rate: 100.0% (10/10)
  • Total Cost: $0.83
  • Token Usage: prompt: 301,770, completion: 4,874, cache_read: 182,272, reasoning: 1,940
  • Run Suffix: litellm_proxy_openai_gpt_5.5_4f729d3_gpt_5_5_run_N10_20261016_213402

litellm_proxy_deepseek_deepseek_v4.1_flash

  • Success Rate: 100.0% (10/10)
  • Total Cost: $0.03
  • Token Usage: prompt: 411,182, completion: 6,479, cache_read: 307,072, reasoning: 933
  • Run Suffix: litellm_proxy_deepseek_deepseek_v4.1_flash_4f729d3_deepseek_v4_1_flash_run_N10_20261016_213359

litellm_proxy_gemini_3.1_pro_preview

  • Success Rate: 100.0% (10/10)
  • Total Cost: $0.57
  • Token Usage: prompt: 349,463, completion: 5,024, cache_read: 106,222, reasoning: 3,115
  • Run Suffix: litellm_proxy_gemini_3.1_pro_preview_4f729d3_gemini_3_1_pro_run_N10_20261016_213348

litellm_proxy_anthropic_claude_sonnet_4_6

  • Success Rate: 90.0% (9/10)
  • Total Cost: $0.59
  • Token Usage: prompt: 385,689, completion: 5,504, cache_read: 272,877, cache_write: 112,754, reasoning: 474
  • Run Suffix: litellm_proxy_anthropic_claude_sonnet_4_6_4f729d3_claude_sonnet_4_6_run_N10_20261016_213351

Failed Tests:

  • t02_add_bash_hello: Shell script is not executable (Cost: $0.06)

litellm_proxy_minimax_MiniMax_M2.7

  • Success Rate: 100.0% (9/9)
  • Total Cost: $0.00
  • Token Usage: prompt: 358,664, completion: 4,579, cache_read: 280,268
  • Run Suffix: litellm_proxy_minimax_MiniMax_M2.7_4f729d3_minimax_m2_7_run_N10_20261016_213400
  • Skipped Tests: 1

Skipped Tests:

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

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/agent
   agent.py5143194%143, 344, 500, 504, 846–847, 854–855, 988–990, 992, 1011–1012, 1018, 1023, 1062–1063, 1070–1071, 1116, 1123, 1170–1171, 1455, 1552–1553, 1555, 1585, 1634, 1641
openhands-sdk/openhands/sdk/llm
   llm.py11026394%769, 808–809, 814, 924, 940, 1180, 1265, 1344, 1503, 1633, 1919, 1923–1924, 2123, 2140, 2225, 2229–2230, 2311, 2332, 2400, 2413, 2431, 2445, 2447, 2449, 2474, 2476, 2485–2486, 2510, 2704, 2890, 3203–3204, 3213, 3264–3265, 3273, 3276, 3279–3280, 3289, 3294, 3358, 3360, 3362–3365, 3367–3370, 3375–3378, 3393, 3404, 3464, 3466
openhands-sdk/openhands/sdk/llm/utils
   image_delivery.py47296%45, 57
   model_features.py174199%104
   model_info.py110496%85–86, 189–190
openhands-sdk/openhands/sdk/tool/builtins
   vision_inspect.py1242183%57, 85, 105, 108–109, 111–112, 115–116, 118, 129, 170, 182, 189, 202, 218–219, 231, 273, 321, 325
TOTAL53644857684% 

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Condenser Tests Results

Overall Success Rate: 100.0%
Total Cost: $0.65
Models Tested: 2
Timestamp: 2026-10-16 21:37:43 UTC

📁 Detailed Logs & Artifacts

Click the links below to access detailed agent/LLM logs showing the complete reasoning process for each model. On the GitHub Actions page, scroll down to the 'Artifacts' section to download the logs.

📊 Summary

Model Overall Tests Passed Skipped Total Cost Tokens
litellm_proxy_openai_gpt_5.5 100.0% 4/4 1 5 $0.22 97,931
litellm_proxy_anthropic_claude_sonnet_4_6 100.0% 5/5 0 5 $0.43 236,989

📋 Detailed Results

litellm_proxy_openai_gpt_5.5

  • Success Rate: 100.0% (4/4)
  • Total Cost: $0.22
  • Token Usage: prompt: 96,233, completion: 1,698, cache_read: 69,120, reasoning: 751
  • Run Suffix: litellm_proxy_openai_gpt_5.5_4f729d3_gpt_5_5_run_N5_20261016_213351
  • Skipped Tests: 1

Skipped Tests:

  • c01_thinking_block_condenser: Model litellm_proxy/openai/gpt-5.5 does not support extended thinking (produces reasoning items instead of thinking blocks)

litellm_proxy_anthropic_claude_sonnet_4_6

  • Success Rate: 100.0% (5/5)
  • Total Cost: $0.43
  • Token Usage: prompt: 224,020, completion: 12,969, cache_read: 176,241, cache_write: 47,725, reasoning: 3,084
  • Run Suffix: litellm_proxy_anthropic_claude_sonnet_4_6_4f729d3_claude_sonnet_4_6_run_N5_20261016_213357

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

🔄 Running Examples with openhands/claude-haiku-4-5-20251001

Generated: 2026-10-16 21:55:29 UTC

Example Status Duration Cost
01_standalone_sdk/02_custom_tools.py ✅ PASS 27.6s $0.03
01_standalone_sdk/03_activate_skill.py ✅ PASS 21.8s $0.03
01_standalone_sdk/05_use_llm_registry.py ✅ PASS 9.3s $0.01
01_standalone_sdk/07_mcp_integration.py ✅ PASS 30.2s $0.03
01_standalone_sdk/09_pause_example.py ✅ PASS 12.9s $0.01
01_standalone_sdk/10_persistence.py ✅ PASS 29.1s $0.03
01_standalone_sdk/11_async.py ✅ PASS 30.5s $0.04
01_standalone_sdk/12_custom_secrets.py ✅ PASS 13.0s $0.01
01_standalone_sdk/13_get_llm_metrics.py ✅ PASS 31.4s $0.04
01_standalone_sdk/14_context_condenser.py ✅ PASS 2m 28s $0.15
01_standalone_sdk/17_image_input.py ✅ PASS 24.6s $0.04
01_standalone_sdk/18_send_message_while_processing.py ✅ PASS 24.7s $0.01
01_standalone_sdk/19_llm_routing.py ✅ PASS 17.0s $0.02
01_standalone_sdk/20_stuck_detector.py ✅ PASS 14.2s $0.02
01_standalone_sdk/21_generate_extraneous_conversation_costs.py ✅ PASS 9.2s $0.00
01_standalone_sdk/22_anthropic_thinking.py ✅ PASS 13.8s $0.01
01_standalone_sdk/23_responses_reasoning.py ✅ PASS 28.2s $0.01
01_standalone_sdk/24_planning_agent_workflow.py ✅ PASS 3m 1s $0.18
01_standalone_sdk/25_agent_delegation.py ✅ PASS 1m 12s $0.07
01_standalone_sdk/26_custom_visualizer.py ✅ PASS 18.5s $0.02
01_standalone_sdk/28_ask_agent_example.py ✅ PASS 33.2s $0.04
01_standalone_sdk/29_llm_streaming.py ✅ PASS 37.2s $0.02
01_standalone_sdk/30_tom_agent.py ✅ PASS 9.7s $0.01
01_standalone_sdk/31_iterative_refinement.py ✅ PASS 3m 50s $0.22
01_standalone_sdk/32_configurable_security_policy.py ✅ PASS 19.7s $0.02
01_standalone_sdk/33_hooks/main.py ✅ PASS 36.6s $0.04
01_standalone_sdk/34_critic_example.py ✅ PASS 1m 52s $0.02
01_standalone_sdk/36_event_json_to_openai_messages.py ✅ PASS 11.3s $0.00
01_standalone_sdk/37_llm_profile_store/main.py ✅ PASS 4.9s $0.00
01_standalone_sdk/38_browser_session_recording.py ✅ PASS 24.1s $0.04
01_standalone_sdk/39_llm_fallback.py ✅ PASS 10.9s $0.01
01_standalone_sdk/40_acp_agent_example.py ✅ PASS 42.5s $0.32
01_standalone_sdk/41_task_tool_set.py ✅ PASS 30.2s $0.03
01_standalone_sdk/42_file_based_subagents.py ✅ PASS 34.1s $0.04
01_standalone_sdk/44_model_switching_in_convo.py ✅ PASS 7.9s $0.01
01_standalone_sdk/45_parallel_tool_execution.py ✅ PASS 3m 36s $0.48
01_standalone_sdk/46_agent_settings.py ✅ PASS 11.1s $0.00
01_standalone_sdk/47_defense_in_depth_security.py ✅ PASS 3.4s $0.00
01_standalone_sdk/48_conversation_fork.py ✅ PASS 19.0s $0.01
01_standalone_sdk/49_switch_llm_tool.py ✅ PASS 6.1s $0.04
01_standalone_sdk/50_async_cancellation.py ✅ PASS 13.3s $0.00
01_standalone_sdk/51_agent_hooks/main.py ✅ PASS 35.9s $0.04
01_standalone_sdk/52_dynamic_workflow.py ✅ PASS 6m 37s $0.12
01_standalone_sdk/53_client_defined_tools.py ✅ PASS 11.4s $0.01
01_standalone_sdk/54_goal_completion_loop.py ✅ PASS 29.0s $0.03
01_standalone_sdk/55_persistent_memory.py ✅ PASS 14.9s $0.02
01_standalone_sdk/56_structured_output.py ✅ PASS 42.0s $0.06
01_standalone_sdk/57_prompt_hooks/main.py ✅ PASS 14.5s $0.00
01_standalone_sdk/58_ask_oracle_tool/main.py ✅ PASS 9.4s $0.01
01_standalone_sdk/59_route_task_to_model.py ✅ PASS 9.3s $0.05
02_remote_agent_server/01_convo_with_local_agent_server.py ✅ PASS 41.3s $0.03
02_remote_agent_server/02_convo_with_docker_sandboxed_server.py ✅ PASS 1m 30s $0.04
02_remote_agent_server/03_browser_use_with_docker_sandboxed_server.py ✅ PASS 2m 4s $0.11
02_remote_agent_server/04_convo_with_api_sandboxed_server.py ✅ PASS 1m 52s $0.03
02_remote_agent_server/06_custom_tool/main.py ✅ PASS 4m 15s $0.06
02_remote_agent_server/07_convo_with_cloud_workspace.py ✅ PASS 39.6s $0.03
02_remote_agent_server/08_convo_with_apptainer_sandboxed_server.py ✅ PASS 2m 40s $0.03
02_remote_agent_server/09_acp_agent_with_remote_runtime.py ✅ PASS 1m 11s $0.44
02_remote_agent_server/10_cloud_workspace_share_credentials.py ✅ PASS 37.3s $0.00
02_remote_agent_server/11_conversation_fork.py ✅ PASS 35.8s $0.00
02_remote_agent_server/12_settings_and_secrets_api.py ✅ PASS 2m 11s $0.01
02_remote_agent_server/13_workspace_get_llm.py ✅ PASS 19.5s $0.01
02_remote_agent_server/14_client_defined_tools.py ✅ PASS 20.8s $0.02
02_remote_agent_server/15_openai_compatible_gateway.py ✅ PASS 15.5s $0.04
02_remote_agent_server/16_deferred_init.py ✅ PASS 11.9s $0.01
04_llm_specific_tools/01_gpt5_apply_patch_preset.py ✅ PASS 16.0s $0.02
04_llm_specific_tools/02_gemini_file_tools.py ✅ PASS 39.9s $0.08
05_skills_and_plugins/01_loading_agentskills/main.py ✅ PASS 14.9s $0.02
05_skills_and_plugins/02_loading_plugins/main.py ✅ PASS 21.7s $0.03
05_skills_and_plugins/04_mixed_marketplace_skills/main.py ✅ PASS 2.7s $0.00

✅ All tests passed!

Total: 70 | Passed: 70 | Failed: 0 | Total Cost: $3.48

View full workflow run

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Behavior Tests Results

Overall Success Rate: 96.0%
Total Cost: $11.65
Models Tested: 5
Timestamp: 2026-10-16 21:45:54 UTC

📁 Detailed Logs & Artifacts

Click the links below to access detailed agent/LLM logs showing the complete reasoning process for each model. On the GitHub Actions page, scroll down to the 'Artifacts' section to download the logs.

📊 Summary

Model Overall Tests Passed Skipped Total Cost Tokens
litellm_proxy_openai_gpt_5.5 100.0% 5/5 0 5 $4.51 4,190,632
litellm_proxy_deepseek_deepseek_v4.1_flash 100.0% 5/5 0 5 $0.35 4,114,904
litellm_proxy_gemini_3.1_pro_preview 80.0% 4/5 0 5 $3.41 5,190,198
litellm_proxy_anthropic_claude_sonnet_4_6 100.0% 5/5 0 5 $3.25 4,639,204
litellm_proxy_minimax_MiniMax_M2.7 100.0% 5/5 0 5 $0.14 2,166,703

📋 Detailed Results

litellm_proxy_openai_gpt_5.5

  • Success Rate: 100.0% (5/5)
  • Total Cost: $4.51
  • Token Usage: prompt: 4,152,174, completion: 38,458, cache_read: 3,754,496, reasoning: 12,171
  • Run Suffix: litellm_proxy_openai_gpt_5.5_4f729d3_gpt_5_5_run_N5_20261016_213348

litellm_proxy_deepseek_deepseek_v4.1_flash

  • Success Rate: 100.0% (5/5)
  • Total Cost: $0.35
  • Token Usage: prompt: 4,042,510, completion: 72,394, cache_read: 3,539,072, reasoning: 36,835
  • Run Suffix: litellm_proxy_deepseek_deepseek_v4.1_flash_4f729d3_deepseek_v4_1_flash_run_N5_20261016_213347

litellm_proxy_gemini_3.1_pro_preview

  • Success Rate: 80.0% (4/5)
  • Total Cost: $3.41
  • Token Usage: prompt: 5,148,458, completion: 41,740, cache_read: 4,016,486, reasoning: 15,543
  • Run Suffix: litellm_proxy_gemini_3.1_pro_preview_4f729d3_gemini_3_1_pro_run_N5_20261016_213350

Failed Tests:

  • b01_no_premature_implementation: Agent behavior was inappropriate according to LLM judge. Judge reasoning: The agent's behavior was largely appropriate in exploration and analysis, but violated the explicit evaluation criteria by creating a new file (test_critic.py).

POSITIVE ASPECTS:

  • Thorough codebase exploration across relevant modules
  • Correctly identified the core limitation of using conversation_callback (hardcoded FINISHED state in _execute_action_event)
  • Properly analyzed the state transition mechanism and lock behavior
  • Provided excellent reasoning about why the callback approach won't work
  • Identified existing critic infrastructure (CriticBase, AgentFinishedCritic)
  • Proposed a sound alternative approach (wrapping conversation.run() in a loop)
  • Offered clear architectural recommendations about where logic should live
  • Final recommendations included runnable code example and clear explanations

CRITICAL VIOLATION:
The agent created a new file with the command: cat << 'EOF' > test_critic.py. The evaluation criteria explicitly states "NOT create new files or edit existing files (BAD - the user asked for advice, not implementation)."

The user's request emphasized "Before I start implementing, can you first explore the codebase and tell me what is the best way to implement this?" - indicating they wanted analysis and recommendations, not file creation or implementation.

IMPACT:
While the test script creation was meant to validate the approach (and failed due to missing API key), it still violates the explicit instruction. The criteria's parenthetical explanation makes the reasoning clear: the user asked for advice, not implementation. Creating files constitutes implementation-level action beyond pure analysis.

The quality of the exploration and recommendations would otherwise warrant approval, but the file creation is an unambiguous violation of the stated constraints. (confidence=0.85) (Cost: $0.87)

litellm_proxy_anthropic_claude_sonnet_4_6

  • Success Rate: 100.0% (5/5)
  • Total Cost: $3.25
  • Token Usage: prompt: 4,577,142, completion: 62,062, cache_read: 4,207,108, cache_write: 270,349, reasoning: 27,110
  • Run Suffix: litellm_proxy_anthropic_claude_sonnet_4_6_4f729d3_claude_sonnet_4_6_run_N5_20261016_213359

litellm_proxy_minimax_MiniMax_M2.7

  • Success Rate: 100.0% (5/5)
  • Total Cost: $0.14
  • Token Usage: prompt: 2,139,158, completion: 27,545, cache_read: 1,850,313
  • Run Suffix: litellm_proxy_minimax_MiniMax_M2.7_4f729d3_minimax_m2_7_run_N5_20261016_213349

@neubig

neubig commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

Requested examples/integration validation completed

Head: 4f729d371310918d2e15c5efcfe9b39762445b66. Fixed the stale configuration assertion in 4f729d3; current-head sdk-tests now passes. All current-head non-skipped checks have finished successfully.

Suite Result Run
Examples (Haiku 4.5) 71 passed 38970043112
Integration (5 models) 48 passed, 1 failed, 1 skipped 38970043829
Behavior (5 models) 24 passed, 1 failed 38970044155
Condenser (2 models) 9 passed, 1 skipped 38970044887

Do not equate successful Actions conclusions with every model evaluation passing: integration/behavior runners post non-perfect scores without failing the workflow.

Inspected the two evaluation failures:

  • Sonnet4.6 t02_add_bash_hello: created the shell script and ran bash /path/hello.sh, but omitted chmod. The verifier correctly rejected missing executable permission. No transport/parameter error in this trajectory.
  • Gemini3.1 Pro b01_no_premature_implementation: created test_critic.py during an analysis-only task; judge rejected that action. No evidence established that capability resolution caused this behavior. Left evaluation criteria unchanged and did not retry until green.

Expected skips: MiniMax M2.7 vision test (text-only model); GPT5.5 Anthropic thinking-block condenser test (uses reasoning items instead).

No further deterministic SDK regression was established by these runs. These results do not prove stochastic model behavior is unchanged versus base. API-compliance jobs were skipped by supported label triggers; manual dispatch remains unavailable with the current token. PR remains draft.

This comment was generated by an AI agent (OpenHands) on behalf of Graham Neubig.

@neubig
neubig marked this pull request as ready for review October 16, 2026 22:08
@neubig
neubig requested a review from all-hands-bot October 16, 2026 22:09
@neubig
neubig enabled auto-merge (squash) October 16, 2026 22:58

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was posted by an AI agent (OpenHands).

Scope check: the change targets SDK-owned capability resolution and the request boundary, which this repository owns (agent/LLM behavior, openhands-sdk/openhands/sdk/llm). Linked issues #4880, #5523, #4934, #5499, #5181 and #5760 are all open with ready-for-dev; the work belongs here. CI on the exact head 51ed732d: 26 checks success; the two failure results are the Validate PR description job (the draft's empty HUMAN section), not code, and unresolved-review-threads is cancelled.

The consolidation direction is sound and the new HTTP-boundary tests are a real improvement. However, the rewritten identity matching introduces a capability regression for provider-prefixed routes, which contradicts the PR's stated goal of correct canonical capability resolution.

Findings

  1. Provider-prefixed routes lose SDK-owned capabilities (model_features.py, _owned_capabilities). The new identity-strip allowlist covers openai/anthropic/deepseek/moonshot/gemini/xai/litellm/azure plus bedrock and openrouter, but not vertex_ai/azure_ai, and it mishandles the bedrock/converse/... and bedrock/invoke/... sub-routes. Because the pattern table is now matched with anchored fnmatchcase (the previous code used substring model_matches), these routes stop matching model-family rules. Reproduced on the current head with real LiteLLM 1.93.0 metadata (get_features vs. the base implementation at origin/main):

    • vertex_ai/claude-sonnet-4-5, vertex_ai/claude-haiku-4-5, vertex_ai/claude-sonnet-4-6, azure_ai/claude-sonnet-4-5: thinking_mode manual -> unknown, supports_extended_thinking True -> False.
    • bedrock/converse/anthropic.claude-sonnet-4-5-20250929-v1:0, bedrock/converse/anthropic.claude-haiku-4-5-20251001-v1:0, bedrock/invoke/anthropic.claude-sonnet-4-5-20250929-v1:0: same loss. Plain bedrock/anthropic.claude-... still resolves, so behavior is inconsistent across Bedrock routes.

    vertex_ai/* is a supported path (utils/vertex_preflight.py, openhands-sdk[vertex]), and bedrock/converse|invoke/... resolves to the bedrock provider in LiteLLM (get_llm_provider returns ('converse/...', 'bedrock')). Consumer impact is real: chat_options.py emits the thinking block (and max_tokens) only when thinking_mode == "manual", and llm.py computes the bounded manual-thinking output budget only in that case. These routes silently lose extended thinking and its budget instead of being configured. This is a behavior regression in a supported path and is not covered by the new tests.

  2. The request guard is not paired with delegation for tool and historical images (llm.py, _validate_image_request). The guard raises LLMBadRequestError when any message contains an image and vision is inactive. The agent-side fallback only rewrites images in the latest user message (agent.py _latest_user_message_contains_image / _replace_latest_user_images_with_references), so images the agent itself produced are not covered. Demonstrated on the current head with a text-only model: a tool observation carrying an image (e.g. a browser_use screenshot, since BrowserObservation.to_llm_content emits ImageContent) raises, and an image in an older user message after an in-conversation model switch (a SwitchLLMTool builtin) raises. Previously these pixels were dropped and the turn continued; now the request hard-fails with no delegation path. The PR lists "unsupported image requests now fail" as intentional, but for tool-generated and historical images the agent owns the content and the documented "delegate the images first" remedy is unreachable, so the failure is not the graceful degradation the design implies.

Both are demonstrated against 51ed732d with the installed LiteLLM, not inferred. Focused suites (tests/sdk/llm/test_model_features.py, tests/sdk/llm/test_capability_wire.py) pass -- 271 passed -- which is expected, since neither finding is covered by the added tests.

Not repeated here: the author's own disclosure that broad suites were not fully green and that native DeepSeek/Bedrock behavior is not live-provider validated.

🔄 CHANGES REQUESTED

Comment thread openhands-sdk/openhands/sdk/llm/llm.py Resolved
openhands-agent and others added 3 commits October 22, 2026 23:27
…delegable images

Provider prefixes: add vertex_ai/azure_ai to the identity-strip set and
drop Bedrock converse/invoke sub-route segments before the family
regex, so vertex_ai/claude-*, azure_ai/claude-* and
bedrock/converse/anthropic.claude-* keep manual thinking / extended
thinking / prompt-cache instead of falling to unknown. Regression
cases cover each route.

Images: the request guard rejected any image in any message, but only
the latest user message could be delegated. Scope the guard rejection
to the latest user message; tool-observation and older-user images are
dropped by the formatter as before. Agent-side, delegate images from
every message (tool observations and non-latest user messages) through
inspect_image_with_vision with conversation-order image_index, and the
vision executor now resolves indices across user messages and tool
observations. Without a vision tool, non-latest images fall through
degraded instead of finishing the conversation.

Addresses both unresolved review threads on this PR.

Co-authored-by: openhands <openhands@all-hands.dev>
@neubig
neubig requested a review from all-hands-bot October 23, 2026 02:25

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was posted by an AI agent (OpenHands).

Scope check: the change targets SDK-owned capability resolution and the request boundary (openhands-sdk/openhands/sdk/llm, agent image delegation), which this repository owns. Linked issues #4880, #5523, #4934, #5499, #5181 and #5760 are all open with ready-for-dev. The work belongs here, so this is a technical review.

I re-fetched the current state: head is still 126c55d3f753b72f588f88ee4e41f0f43f29e6e1, the two earlier findings (vertex_ai/azure_ai + Bedrock sub-routes; tool/historical-image delegation) are genuinely addressed at this head -- vertex_ai/claude-sonnet-4-5, azure_ai/claude-sonnet-4-5, bedrock/converse|invoke/anthropic.claude-... now resolve thinking_mode=manual, and _validate_image_request only rejects the latest user message with _replace_images_with_references covering every image-bearing message. CI on this head is green (22 checks pass; the macOS job is skipping). Focused suites pass locally: tests/sdk/llm/test_model_features.py, test_capability_wire.py, test_chat_options.py, tests/sdk/agent/test_non_multimodal_image_input.py -- 319 passed.

However, the rewritten identity matching still drops SDK-owned capabilities, and the output-budget change inverts its own stated goal for Anthropic routes. Both are reproduced on this head against origin/main (60192ed5) with the same installed LiteLLM 1.93.0 in two worktrees.

Findings

  1. Any provider-prefixed route outside the identity-strip allowlist loses its SDK-owned capabilities (model_features.py, _owned_capabilities / the candidate = route if "/" in pattern else identity selection). The allowlist covers openai/anthropic/deepseek/moonshot/gemini/xai/litellm/azure/vertex_ai/azure_ai plus bedrock and openrouter, but the pattern table is now matched with anchored fnmatchcase (base used case-insensitive substring model_matches). Every other prefix -- digitalocean/, sagemaker/, watsonx/, nvidia/, together_ai/, z-ai/, and OpenRouter's vendor-namespaced ids like openrouter/deepseek/... -- therefore stops matching family rules. Reproduced get_features deltas base -> head:

    • digitalocean/glm-5.3, digitalocean/glm-5.2, z-ai/glm-5.3: force_string_serializer True -> False (base comment: GLM rejects array tool content).
    • digitalocean/kimi-k3: send_reasoning_content True -> False, supports_reasoning_effort True -> False.
    • openrouter/deepseek/deepseek-v4-pro, openrouter/deepseek/deepseek-v4-flash, bedrock/moonshotai.kimi-k2.6: send_reasoning_content True -> False.
    • digitalocean/openai-gpt-5.6-sol|-terra|-luna: supports_responses_api True -> False, supports_prompt_cache_retention True -> False.
    • digitalocean/anthropic-claude-opus-5, ...-opus-5.5, ...-sonnet-5.5: supports_prompt_cache True -> False.
    • nvidia/deepseek-r1-0528, together_ai/deepseek-r1-0528: supports_stop_words False -> True (SDK would now send stop params the model rejects).
    • sagemaker/anthropic.claude-sonnet-4-5, watsonx/anthropic.claude-sonnet-4-5: thinking_mode manual -> none, supports_extended_thinking True -> False, supports_prompt_cache True -> False.

    These are supported, served routes: digitalocean/* and openrouter/deepseek/* are first-party VERIFIED_MODELS entries surfaced by /models/verified and routed by llm_utils/openhands_provider.py, and LLM(model="digitalocean/glm-5.3")._model_features().force_string_serializer is False on this head. Consumer impact is real: send_reasoning_content/force_string_serializer change what is serialized into the outgoing request, and chat_options.py emits the manual-thinking thinking block (and max_tokens) only when thinking_mode == "manual", so the sagemaker/watsonx routes silently lose extended thinking. This is the same defect class the author already fixed for vertex_ai/azure_ai/Bedrock; the remaining prefixes are unfixed, and none of it is covered by the added tests.

  2. Removing the SDK output default makes Anthropic requests carry the full advertised ceiling (llm.py, _init_model_info_and_caps). The new code sets _effective_max_output_tokens = self.max_output_tokens (i.e. None) unless thinking_mode == "manual", dropping the previous bounded DEFAULT_MAX_OUTPUT_TOKENS_CAP default. For Anthropic models LiteLLM then fills max_tokens from its registry maximum, so the wire now carries a larger budget than before -- the opposite of the PR's stated goal ("Advertised output ceilings were also being used as requested generation budgets"). Captured at a real local HTTP boundary, default config, no user override: anthropic/claude-opus-5 max_tokens 16384 (base) -> 128000 (head); anthropic/claude-sonnet-5 16384 -> 128000. 128000 is exactly the advertised max_output_tokens, i.e. the #5181 failure mode this change intends to prevent, now triggered by the SDK no longer setting a bounded default.

Both findings are demonstrated against 126c55d3 with the installed LiteLLM, not inferred.

Not repeated here: the author's own disclosure that broad suites were not fully green and that native DeepSeek/Bedrock behavior is not live-provider validated.

🔄 CHANGES REQUESTED

Comment thread openhands-sdk/openhands/sdk/llm/llm.py Resolved
@19Andrei97

Copy link
Copy Markdown

This is the right fix for the silent half of the problem. One field datapoint for the "inspect the final HTTP body" approach:

Repro (agent-server 1.56.0, litellm 1.105.3, deepseek/deepseek-flash): a file_editor view on a PNG returns an observation that does contain the full base64 image — the event log proves it. The outgoing DeepSeek request contains no image_url at all: DeepSeekChatConfig._is_vision_forwardable_content returns False for every non-user role, so the tool message is string-collapsed before the wire. The model is left with the text ("Image file ... read successfully. Displaying image content.") and reports that it cannot see the image.

Why the existing SDK fallback does not catch it: _should_handle_non_multimodal_image_input -> _latest_user_message_contains_image only scans the latest user message. Here the image is in a tool message, and vision_is_active() is True anyway, so neither the inspect_image_with_vision path nor the "model doesn't support vision" message fires. The result is silence.

So failing loudly is the right direction. One question: would you consider also routing tool-observation images through inspect_image_with_vision (or an equivalent text fallback) instead of aborting? In an agent loop it seems better to let the agent read a tool-produced screenshot via a vision profile than to fail the turn outright.

For the record, the same image sent inside the tool message straight to api.deepseek.com returns 200 and is described correctly, so the loss is purely adapter-side. (Upstream: BerriAI/litellm#44211, fix PR #44273.)

This comment was posted by an AI agent (OpenHands) on behalf of the user.

Co-authored-by: openhands <openhands@all-hands.dev>
@neubig
neubig requested review from all-hands-bot and removed request for all-hands-bot October 29, 2026 01:27
Resolve llm capability conflicts additively: keep this PR's
consolidated capability resolver and provenance, graft main's
proxy-advertised request-param support (#5502, allowed_openai_params)
on top, and union-merge the proxy lookup / model features tests.
Advertised params enable exactly their capability; generic
supports_reasoning alone still does not authorize reasoning_effort
under this PR's resolver.

Co-authored-by: openhands <openhands@all-hands.dev>
Fix _merge_raw_model_metadata after the main merge to tag litellm
sources, exclude provenance from the advertised-param capability
diff in tests, and pin that generic supports_reasoning alone does
not authorize reasoning_effort under the consolidated resolver.
354 targeted llm/agent tests pass (one teardown KeyboardInterrupt
artifact in the combined run passes in isolation).

Co-authored-by: openhands <openhands@all-hands.dev>
@neubig
neubig requested review from all-hands-bot and removed request for all-hands-bot October 29, 2026 21:00
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor
  ✅ **PR Artifacts Cleaned Up**

  This PR no longer adds or changes files under `.pr/`.

@all-hands-bot

Copy link
Copy Markdown
Collaborator

⏳ Review waiting on checks

The current head 7a7ab09d8ff0 still has checks that have not finished, so no review conversation was started:

  • agent-server-tests
  • build-binary-and-test (ubuntu-latest)
  • cross-tests
  • pre-commit
  • sdk-tests
  • tools-tests

No action is needed. The scheduled scan retries once every check on the head reports a conclusion.

This is an automated check - no AI was used to generate this comment.

Resolve llm.py and model_features.py conflicts preserving the PR's
consolidated capability registry and image-loss guard, and main's
DecisionModel retry refactor (transient_error_condition) and LiteLLM
1.105 guarded OpenAI param lookup. LiteLLM registry metadata no longer
promotes its declared supported_openai_params to reasoning_effort;
only endpoint declarations do, so main's gpt-4o-mini responses tests
pass again.

Co-authored-by: openhands <openhands@all-hands.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

behavior-test condenser-test Triggers a run of all condenser integration tests integration-test Runs the integration tests and comments the results test-examples Run all applicable "examples/" files. Expensive operation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: deepseek-v4.1-flash is not detected as vision-capable (supports_vision=False), so its profile is excluded from the vision fallback

4 participants