Skip to content

fix(proxy): keep SpendLogs and callback session ids in sync when the request has none - #39450

Merged
yassin-berriai merged 3 commits into
litellm_internal_stagingfrom
litellm_missing_session_id_policy
Sep 3, 2026
Merged

yassin-berriai merged 3 commits into
litellm_internal_stagingfrom
litellm_missing_session_id_policy

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Requests with no session id get a random session id in SpendLogs
  • Langfuse gets no session id for the same request
  • Correlating the two by session id silently fails

How it solves it:

  • New general_settings.missing_session_id: generate | reject
  • generate stamps one id into the request before callbacks run
  • SpendLogs and Langfuse then carry the same session id
  • reject returns 400 when the request has no session id
  • Unset keeps today's behavior
  • Generated ids are marked, so affinity code ignores them
    • Fireworks x-session-affinity, router session affinity, complexity router
    • They change per request, pinning on them would defeat caching

User Flow

Before: a platform team correlating a request between the Logs page and Langfuse by session id finds nothing on the Langfuse side

  1. A developer sends POST https://litellm-domain/v1/chat/completions with a normal chat body and no x-litellm-session-id header
  2. They get a 200 with "id": "chatcmpl-..."
  3. The team opens https://litellm-domain/ui/?page=logs and sees the request with a session id like 85603c20-c2d3-4483-9729-f4d4a9b733a4
  4. They search that session id in Langfuse and get nothing, because the Langfuse trace for the request has no session at all

After: the same lookup lands on the same session in both places

  1. The proxy admin sets general_settings.missing_session_id: generate and restarts the proxy
  2. A developer sends POST https://litellm-domain/v1/chat/completions with a normal chat body and no x-litellm-session-id header
  3. They get a 200 with "id": "chatcmpl-..."
  4. The team opens https://litellm-domain/ui/?page=logs and sees the request with a session id like 1c58430d-d8f3-4020-9f34-3a04267883ca
  5. They search that session id in Langfuse and find the trace, whose session is that same id
  6. Sending x-litellm-session-id: my-client-session-42 still works as before: both sides show my-client-session-42
  7. With missing_session_id: reject instead, step 2 returns 400 with Request has no session id. Send an x-litellm-session-id header or metadata.session_id

Relevant issues

Linear ticket

Resolves LIT-6694

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Setup shared by both runs. Langfuse is a local receiver on 127.0.0.1:3010 that prints the id and sessionId of every trace it gets (no Langfuse credentials on this box), the LLM call is a real OpenAI call, and SpendLogs is a local Postgres

model_list:
  - model_name: gpt-5.6
    litellm_params:
      model: openai/gpt-5.6
      api_key: os.environ/OPENAI_API_KEY

litellm_settings:
  success_callback: ["langfuse"]

general_settings:
  master_key: sk-1234
  # missing_session_id: generate   <- only in the After runs
LANGFUSE_HOST=http://127.0.0.1:3010 LANGFUSE_PUBLIC_KEY=pk-lf-stub LANGFUSE_SECRET_KEY=sk-lf-stub \
  python litellm/proxy/proxy_cli.py --config config.yaml --port 4000

Before (708c396)

  1. Request with no session header

    curl -s http://localhost:4000/v1/chat/completions -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
      -d '{"model":"gpt-5.6","messages":[{"role":"user","content":"Say hi in one word"}]}'
    {"id":"chatcmpl-EJp...","choices":[{"message":{"role":"assistant","content":"Hi"}, ...
    
  2. SpendLogs has a session id

    psql "$DATABASE_URL" -At -c 'select session_id from "LiteLLM_SpendLogs" order by "startTime" desc limit 1'
    85603c20-c2d3-4483-9729-f4d4a9b733a4
    
  3. Langfuse got the trace with no session

    LANGFUSE trace id=85603c20-c2d3-4483-9729-f4d4a9b733a4 session_id=None
    

After (4bc7887)

generate, no session header

  1. Same curl as Before

    id= chatcmpl-EJq2DbmwTd8ffm8B0KwjiA1DuLndd | content= Hi
    
  2. SpendLogs

    chatcmpl-EJq2DbmwTd8ffm8B0KwjiA1DuLndd|1c58430d-d8f3-4020-9f34-3a04267883ca
    
  3. Langfuse, same id on both sides

    LANGFUSE trace id=1c58430d-d8f3-4020-9f34-3a04267883ca session_id=1c58430d-d8f3-4020-9f34-3a04267883ca
    

generate, client sends x-litellm-session-id

  1. Same curl plus -H "x-litellm-session-id: my-client-session-44"

    id= chatcmpl-EJq2EVDPLTRmkiubfNXaMtj5LpHq5
    
  2. SpendLogs

    chatcmpl-EJq2EVDPLTRmkiubfNXaMtj5LpHq5|my-client-session-44
    
  3. Langfuse

    LANGFUSE trace id=my-client-session-44 session_id=my-client-session-44
    

reject, no session header

  1. Proxy restarted with missing_session_id: reject, same curl as Before with -w "\nHTTP %{http_code}\n"

    {"error":{"message":"Request has no session id. Send an `x-litellm-session-id` header or `metadata.session_id`. Required by `general_settings.missing_session_id: reject`.","type":"bad_request_error","param":"session_id","code":"400"}}
    HTTP 400
    

reject, client sends x-litellm-session-id

  1. Same curl plus -H "x-litellm-session-id: my-client-session-45"

    id= chatcmpl-EJq4jzRvUz4LYUkXC85WmdQWa89tP
    HTTP 200
    

Type

🐛 Bug Fix

Caveats (if any)

Low

  • Opt in only: unset keeps the old SpendLogs random fallback
  • MCP routes are skipped, they have their own session handling
  • Affinity guards are unit tested only, no Fireworks credentials on this box
  • Docs PR in litellm-docs adds the setting to config_settings.md

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/08e66d54121744149f600a9ebf31022e
Open in Devin Desktop: https://app.devin.ai/desktop/session/08e66d54121744149f600a9ebf31022e?variant=devin
Requested by: @yassin-berriai

…request has none

Add general_settings.missing_session_id (generate | reject). In generate mode one id is
stamped into litellm_session_id, litellm_trace_id and metadata.session_id before callbacks
run, so LiteLLM_SpendLogs.session_id and the Langfuse session id match. In reject mode such
requests get a 400. Unset keeps the legacy behavior. MCP routes are not affected

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

PR #39450 labeled risk:high (3 files / +219, but it inserts a new policy step with a 400-reject path into the core proxy pre-call pipeline add_litellm_data_to_request). No enterprise label, so Linear routing was skipped.

@codspeed

codspeed Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_missing_session_id_policy (4bc7887) with litellm_internal_staging (ff1f21a)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds configurable handling for missing session IDs and keeps generated request IDs out of affinity decisions

  • Adds generate and reject policies for LLM requests without session IDs
  • Marks proxy-generated IDs and excludes them from Fireworks, deployment, and complexity-router affinity
  • Adds regression coverage and updates the generated dashboard schema

Confidence Score: 5/5

The PR appears safe to merge

No blocking failure remains

Important Files Changed

Filename Overview
litellm/proxy/litellm_pre_call_utils.py Adds route-aware generation or rejection of missing session IDs before callback processing
litellm/llms/fireworks_ai/common_utils.py Prevents proxy-generated session IDs from becoming Fireworks affinity headers
litellm/router_utils/pre_call_checks/deployment_affinity_check.py Excludes generated request IDs from deployment-affinity cache keys
litellm/router_strategy/complexity_router/complexity_router.py Excludes generated request IDs from complexity-router session pins
litellm/proxy/_types.py Adds the typed missing_session_id general setting
litellm/constants.py Defines the shared metadata marker for generated session IDs

Reviews (2): Last reviewed commit: "fix(proxy): mark generated session ids s..." | Re-trigger Greptile

Comment thread litellm/proxy/litellm_pre_call_utils.py
Comment thread tests/test_litellm/proxy/test_litellm_pre_call_utils.py
@codecov

codecov Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.29730% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/litellm_pre_call_utils.py 96.29% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

yassin-berriai and others added 2 commits September 3, 2026 00:44
…or ruff format

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…in on them

Fireworks x-session-affinity, the router session_affinity pre-call check and the
complexity router session pin all read metadata.session_id as a caller-chosen
stable key. A missing_session_id: generate id is fresh per request, so it now
carries metadata.litellm_session_id_generated and those consumers skip it

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review. Generated session ids are now marked so Fireworks, router and complexity router affinity ignore them; test params are typed

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Fixed in 4bc7887: generated ids carry a metadata marker and get_fireworks_session_id, router affinity and complexity router skip them

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Both parameters are typed on the current tip: dict[str, object] for the helper and pytest.MonkeyPatch for the fixture

@yassin-berriai
yassin-berriai merged commit 8441dd6 into litellm_internal_staging Sep 3, 2026
81 checks passed
@yassin-berriai
yassin-berriai deleted the litellm_missing_session_id_policy branch September 3, 2026 01:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Sensitive, broad, or high-risk change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants