Skip to content

fix(proxy): coerce non-str x-litellm-* header values to avoid httpx TypeError (#27458) - #27504

Merged
oss-pr-review-agent-shin[bot] merged 1 commit into
BerriAI:shin_agent_oss_staging_05_09_2026from
Anai-Guo:fix/llm-headers-coerce-non-str-27458
May 9, 2026
Merged

fix(proxy): coerce non-str x-litellm-* header values to avoid httpx TypeError (#27458)#27504
oss-pr-review-agent-shin[bot] merged 1 commit into
BerriAI:shin_agent_oss_staging_05_09_2026from
Anai-Guo:fix/llm-headers-coerce-non-str-27458

Conversation

@Anai-Guo

@Anai-Guo Anai-Guo commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #27458 — when add_user_information_to_llm_headers: true, the proxy injects x-litellm-* headers from get_sanitized_user_information_from_key(), but several fields in StandardLoggingUserAPIKeyMetadata are non-string by definition:

field type
user_api_key_spend Optional[float]
user_api_key_max_budget Optional[float]
user_api_key_auth_metadata Optional[Dict[str, str]]

These got assigned directly into returned_headers and then passed to httpx.AsyncClient.build_request(), which raises:

TypeError: Header value must be str or bytes, not <class 'float'>

The reporter's traceback hits httpx/_models.py:_normalize_header_value exactly at the float branch, which matches user_api_key_spend = 0.0 even on a fresh key.

Fix

In add_headers_to_llm_call, coerce values before assigning into headers:

  • dict/listjson.dumps(v)
  • str/bytes → unchanged
  • everything else (float, int, bool, …) → str(v)

Test

tests/proxy_unit_tests/test_proxy_utils.py::test_foward_litellm_user_info_to_backend_llm_call previously expected a float and a dict in the output map (it never actually fed the result to httpx, so the regression slipped through). Updated to:

  1. Assert every emitted header value is str/bytes — would have caught this regression.
  2. Update the expected map to the coerced shape ("0.0", "{}").

🤖 Generated with Claude Code

…headers (BerriAI#27458)

StandardLoggingUserAPIKeyMetadata mixes typed fields (user_api_key_spend / user_api_key_max_budget are floats, user_api_key_auth_metadata is a dict) and these were dropped into outgoing request headers without coercion. httpx then raises 'Header value must be str or bytes, not <class 'float'>' when add_user_information_to_llm_headers=True.

Coerce numbers/bools via str(); JSON-encode dict/list. Existing test now also asserts every emitted header value is str/bytes.
@greptile-apps

greptile-apps Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a TypeError raised by httpx when add_user_information_to_llm_headers: true is enabled — several fields in StandardLoggingUserAPIKeyMetadata (e.g. user_api_key_spend: float, user_api_key_auth_metadata: dict) were being written into headers without coercion, and httpx requires all header values to be str or bytes.

  • litellm_pre_call_utils.py: In add_headers_to_llm_call, non-None values are now coerced before insertion: dict/listjson.dumps, str/bytes → unchanged, everything else → str(v).
  • test_proxy_utils.py: The existing test was tightened with an explicit isinstance(v, (str, bytes)) assertion on every emitted header value, and expected values updated to reflect the coerced strings ("0.0", "{}"). This assertion would have caught the original regression.

Confidence Score: 4/5

Safe to merge — the coercion logic is correct and targeted, the test change strengthens rather than weakens coverage, and the fix addresses a real crash in an opt-in feature path.

The production change is minimal and correct: a well-ordered type dispatch replaces a raw assignment. The test update properly reflects the new coerced output and adds a type guard that would have caught this regression earlier. The only loose end is that litellm.add_user_information_to_llm_headers is left set globally after the test, which could silently affect other tests sharing the same process state.

No files require special attention beyond the minor test teardown gap in test_proxy_utils.py.

Important Files Changed

Filename Overview
litellm/proxy/litellm_pre_call_utils.py Adds type-coercion logic before assigning x-litellm-* header values — dicts/lists → JSON string, str/bytes → unchanged, everything else → str(v). Fixes the httpx TypeError for float and dict fields.
tests/proxy_unit_tests/test_proxy_utils.py Test updated to assert all emitted header values are str/bytes (new regression guard) and expected map updated to coerced shapes. Global test state litellm.add_user_information_to_llm_headers is set but not restored after the test.

Comments Outside Diff (1)

  1. tests/proxy_unit_tests/test_proxy_utils.py, line 573-576 (link)

    P2 The test sets litellm.add_user_information_to_llm_headers = True globally but never resets it. If a later test in the same session expects the default (False or None), it can pick up this state and produce unexpected results. Wrapping the assignment in a try/finally (or using a fixture) keeps tests isolated.

Reviews (1): Last reviewed commit: "fix(proxy): coerce non-str values when i..." | Re-trigger Greptile

@codecov

codecov Bot commented May 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@oss-pr-review-agent-shin
oss-pr-review-agent-shin Bot changed the base branch from litellm_internal_staging to shin_agent_oss_staging_05_09_2026 May 9, 2026 20:32
@oss-pr-review-agent-shin
oss-pr-review-agent-shin Bot merged commit 8044529 into BerriAI:shin_agent_oss_staging_05_09_2026 May 9, 2026
42 checks passed
@oss-pr-review-agent-shin

Copy link
Copy Markdown
Contributor

🤖 litellm-agent: Squash-merged into staging branch shin_agent_oss_staging_05_09_2026. Staging PR: #27549


Triage Summary
Gathered PR data only — the triage LLM step did not produce a valid report, so failing-check classification and prior-signal reconciliation were skipped. 25 line(s) across 2 file(s) (+22/-3).

25 lines across 2 files (+22 / -3)

Merge Confidence: 5/5 ✅ READY
Ready to ship.

All checks green. Greptile 4/5, no blocking pattern findings, no CircleCI runs (OSS-typical).

fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…ypeError (BerriAI#27458) (BerriAI#27504)

Squash-merged by litellm-agent from Anai-Guo's PR.
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]: Header value must be str or bytes, not float

2 participants