Skip to content

fix: keep content: null on an assistant tool-call turn - #38048

Open
likalight wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
likalight:fix/preserve-null-content-on-tool-call-turn
Open

likalight wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
likalight:fix/preserve-null-content-on-tool-call-turn

Conversation

@likalight

@likalight likalight commented Aug 24, 2026

Copy link
Copy Markdown

Fixes #37711.

Root cause

I traced the reporter's repro to cleanup_none_field_in_message in
litellm/utils.py:

new_message: Final = message.copy()
return {k: v for k, v in new_message.items() if v is not None}

It drops every key whose value is None. For an assistant message carrying
tool_calls, content: null is exactly the shape the OpenAI spec prescribes
for a tool-call-only turn, so the key is removed and providers that require it
to be present reject the request.

The helper's own docstring says the intent is removing stray keys like
{"function": None} that trip provider validation — content was collateral.

It is reached via validate_and_fix_openai_messages, which is why this
reproduces with a plain litellm.completion() call: no proxy, no provider, no
API key.

The fix

Exempt content when the assistant message carries tool_calls or a legacy
function_call. Everything else is stripped exactly as before — including a
None content on an assistant message that has neither, so the original
behaviour is preserved wherever it was not the bug.

Verification

Six tests in tests/test_litellm/test_cleanup_none_field_in_message.py:

  • content: null survives on a tool_calls turn, and on a legacy
    function_call turn
  • other None fields on that same turn (function, name) are still
    stripped — the helper's original purpose is intact
  • content: null is still stripped from an assistant message with no tool call
  • a user message is unaffected
  • an end-to-end case through validate_and_fix_openai_messages, the caller
    that was actually dropping the field

4 of the 6 fail without the source change, and the reporter's own repro script
now prints content key present: True.

Regression check. I ran the provider suites most likely to depend on the old
behaviour — tests/test_litellm/llms/{openai,anthropic,databricks,mistral}
2169 passed. There are 54 failures in that set, but the identical 54 fail on a
clean checkout with nothing applied (verified by stashing and re-running:
54 failed, 268 passed on both), so none are caused by this change.

  • ruff format --check clean on both touched files
  • ruff check litellm/utils.py: same findings as base, none added
  • Type-discipline checker: identical counts to base on every rule

On the red code-quality check: it is not from this PR. It fails on every
PR against litellm_internal_staging right now, including ones that touch no
workflow files, because three unit shards in .github/workflows/test-unit.yml
cap the job below the startup-safety invariant. Fixed independently in #38046;
this PR needs no change for it.

`cleanup_none_field_in_message` dropped every key whose value was None,
so an assistant message carrying `tool_calls` lost its `content` key
entirely. That is the shape the OpenAI spec prescribes for a tool-call-only
turn, and providers that require the key to be present reject the request.

The helper's purpose, per its own docstring, is removing stray keys like
`{"function": None}` that trip provider validation — not `content`. Exempt
`content` when the assistant message carries `tool_calls` or a legacy
`function_call`; everything else still gets stripped, including a None
`content` on an assistant message with neither.

Reproduces with a plain `litellm.completion()` call, no proxy and no
credentials, via the intercept in the issue.

Fixes BerriAI#37711

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adjusts OpenAI-message normalization so assistant tool-call and legacy function-call turns retain an explicit content: null, while other null fields continue to be removed.

  • Adds focused coverage for tool calls, legacy function calls, unrelated null fields, non-tool assistant messages, user messages, and the full validation path.
  • The implementation is narrowly scoped to assistant turns carrying a truthy tool-call payload.

Confidence Score: 4/5

The PR appears safe to merge functionally, with only non-blocking repository-convention cleanup needed for the new tests.

The normalization change is narrowly guarded and the added cases cover its intended behavior; the remaining concern is test organization and disallowed commentary rather than runtime correctness.

Files Needing Attention: tests/test_litellm/test_cleanup_none_field_in_message.py

Important Files Changed

Filename Overview
litellm/utils.py Preserves null content for assistant tool/function-call turns without changing cleanup of other null-valued fields.
tests/test_litellm/test_cleanup_none_field_in_message.py Adds comprehensive regression coverage, but does so in a new test file and includes an ordinary explanatory comment contrary to repository conventions.

Reviews (1): Last reviewed commit: "fix: keep content: null on an assistant ..." | Re-trigger Greptile

@@ -0,0 +1,68 @@
"""`content: null` is the OpenAI-prescribed shape for an assistant tool-call turn."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Regression tests are misplaced

This bug fix creates a standalone test module for an existing helper and adds an ordinary explanatory comment at line 34, fragmenting the helper’s regression coverage and violating the repository’s test-placement and source-comment conventions. Move these cases into the existing mapped test module and remove the redundant comment.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@codspeed

codspeed Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing likalight:fix/preserve-null-content-on-tool-call-turn (9e7c25d) with litellm_internal_staging (d447be1)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (f005afa) during the generation of this report, so d447be1 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

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]: content: null is stripped from assistant messages with tool_calls before the provider call

2 participants