Skip to content

fix(sdk): normalize blank Chat Completions content across roles - #4981

Open
BORAN002 wants to merge 3 commits into
OpenHands:mainfrom
BORAN002:fix/chat-empty-content
Open

BORAN002 wants to merge 3 commits into
OpenHands:mainfrom
BORAN002:fix/chat-empty-content

Conversation

@BORAN002

@BORAN002 BORAN002 commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

HUMAN:

I asked Codex to investigate contribution opportunities and prepare this fix for blank Chat Completions content. Codex implemented and tested the change and drafted this note with my authorization. The validation below was performed by the agent; I am submitting the change for maintainer review.


AGENT:

Why

Blank text blocks and empty content arrays can cause strict Chat Completions endpoints to reject a conversation with HTTP 400.

Summary

  • Filter blank text blocks during Chat Completions serialization.
  • Normalize empty ordinary-message content to a string while preserving assistant tool calls and stored messages.
  • Preserve tool-level prompt-cache markers before filtering, and retain trailing cache breakpoints on preceding content for other roles.
  • Cover automatic SDK caching through LiteLLM's Anthropic request conversion, including wholly blank tool results.

Issue Number

Fixes #4965.

How to Test

From the repository root after make build:

  • .venv/bin/python .pr/repro_chat_content.py: 4/4 SDK → LiteLLM → loopback HTTP cases pass, preserving the original HTTP 400 → 200 correction.
  • .venv/bin/python .pr/repro_prompt_cache.py: 6/6 SDK completion calls retain cache markers in the actual Anthropic-compatible HTTP request. Covers empty and whitespace-only trailing blocks, mixed/wholly blank tool results, and mixed user content; input messages remain unchanged.
  • uv run pytest tests/sdk/llm -q: 1037 passed, 17 warnings. The focused serialization and caching subset passes 136 tests. All ten added cache-regression cases failed before the correction.
  • uv run pre-commit run --all-files --show-diff-on-failure: passed. Merged upstream 76e9e250 (fix(sdk): generate titles with Responses and subscription streaming #4968), resolving the previously documented baseline dynamic-attribute violations.

Reproduction and validation · original before · original after · cache validation log

Video/Screenshots

CLI evidence is linked above; this change has no UI.

Design Doc

Small bug fix; reproduction and compatibility notes are linked above.

Type

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

Notes

The cache regression identified in review is fixed in 86199bdf. These local HTTP checks validate serialization, not live cache hits, cost, latency, or task performance. No real provider or benchmark was run; maintainer-triggered lightweight evals and human review remain outstanding. New upstream CI requires maintainer approval. No public API or persisted-schema change.

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

Copy link
Copy Markdown
Contributor

📁 PR Artifacts Notice

This PR contains a .pr/ directory with temporary PR-specific documents. Because this is a fork PR, the workflow will open or update a cleanup PR against main after merge.

@BORAN002
BORAN002 marked this pull request as ready for review September 12, 2026 06:10
@enyst

enyst commented Sep 13, 2026

Copy link
Copy Markdown
Member

Thank you for the contribution @BORAN002 ! My agent seems to have found a risk of regression, do you think you could look into it?

AGENT:

P2 regression: skipping blank text blocks⁠ can discard the Anthropic prompt-cache breakpoint.

The SDK automatically marks the last user/tool content block for caching. If a tool result ends with a blank block, this new continue skips the code that preserves its cache marker. I reproduced this through SDK serialization and LiteLLM’s Anthropic request conversion: base retains cache_control; PR loses it. That can reduce cache reuse and increase cost/latency. Preserve the tool-level marker before filtering, and cover this case.

Otherwise, the intended fix checks out:

  • 120 focused tests pass.
  • Four local HTTP cases fail on base and pass on the PR.
  • No live-provider or benchmark validation performed.

This changes LLM message serialization, so benchmark runs can encounter it regardless of which tools are registered. The repository’s review guide calls for lightweight evals and human review for this scope; I’d address the cache regression first.

BORAN002 and others added 2 commits September 13, 2026 10:41
Co-authored-by: openhands <openhands@all-hands.dev>
Retain tool-level cache markers before filtering and carry trailing blank block markers onto preceding content for other roles. Cover automatic SDK caching through Anthropic request conversion and loopback HTTP.

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

BORAN002 commented Sep 13, 2026 via email

Copy link
Copy Markdown
Contributor Author

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]: to_chat_dict emits blank text blocks and empty content arrays that strict OpenAI-compatible providers reject

2 participants