Repository navigation
fix(jwt,otel): backport session conversation id and JWT team header selection to rc/1.103.0 - #43343
Conversation
…on v2 LLM spans (#42486) * feat(otel): emit gen_ai.conversation.id from the caller's session id on v2 LLM spans Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(otel): keep the caller's header session under missing_session_id: generate and read replayed payload session ids Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(otel): drop only the proxy-minted session id so a caller id on the other metadata key survives Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(otel): keep a replayed session id hidden when it only echoes the payload trace id Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(otel): keep a replayed session id even when the payload trace id fell back to it Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(otel): stop reading the replayed payload's session id, the generated marker does not survive replay Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): audit gen_ai.conversation.id on otel v2 spans through a real proxy, sink and postgres Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): keep otel conversation rigs alive for the whole session so shuffled shards do not reboot the proxy per test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(otel): stop the audit rig proxies from probing sibling test peers for model info Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(otel): record accepted OTLP batches in the sink instead of mutating the collector Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(otel): guard the accepted batch deque so snapshots cannot race sink appends Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: mrinal <mrinal@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: yucheng <yucheng@berri.ai> (cherry picked from commit a319690)
* fix(jwt): accept a team alias in x-litellm-team-id The header only matched canonical team ids, so a JWT caller selecting one of their teams by its alias got a 403 even though they belonged to it. The header value is now resolved through the existing alias lookup before the JWT allowed-team check and the DB membership fallback, while a value that is already a team id never costs an alias lookup and denials keep naming the value the caller sent Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(jwt): only alias a header team id the database provably lacks Under fallback_to_db_teams a header value whose team row read fails for any reason other than TeamNotFoundError now keeps the membership denial instead of falling through to the alias lookup, so a degraded read cannot select a different team that carries the value as an alias. Drops the HeaderTeam docstring that only restated its fields Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: ryan <ryan@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> (cherry picked from commit 071cb49)
#42495) * fix(jwt): say x-litellm-team-id matched no team id or alias in the 403 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(jwt): tell the caller when x-litellm-team-id names an alias shared by several teams Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(jwt): deny a shared x-litellm-team-id alias exactly like an unknown value A distinct 403 for an alias several teams share was raised before the allowed-teams check, so any JWT could probe which aliases exist. The alias lookup now treats the duplicate as a miss, and both denials say the value does not resolve to a team id or a unique team alias, which is true for unknown, unauthorized and duplicate values alike Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: ryan <ryan@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> (cherry picked from commit 08639fc)
…oken also carries a team claim (#43206) * fix(jwt): let x-litellm-team-id select DB membership teams when the token also carries a team claim Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * docs(jwt): describe header team selection under fallback_to_db_teams Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: yassin <yassin@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> (cherry picked from commit 7b4fd47)
|
|
|
| import yaml | ||
| from integration._support.client import Gateway, eventually, gateway_from_environment | ||
| from integration._support.database import read_rows | ||
| from integration._support.process import OwnedProxy, owned_proxy_process |
There was a problem hiding this comment.
Observability tests cannot load
When this test file is collected, its import of OwnedProxy and owned_proxy_process fails because integration._support.process defines neither name. None of the new conversation-ID tests can run. The existing owned_proxy helper returns a Gateway, not the process wrapper these tests expect.
| await get_team_object( | ||
| team_id=header_value, | ||
| prisma_client=prisma_client, | ||
| user_api_key_cache=user_api_key_cache, | ||
| parent_otel_span=parent_otel_span, | ||
| proxy_logging_obj=proxy_logging_obj, | ||
| team_id_upsert=False, |
There was a problem hiding this comment.
Alias headers repeatedly query database
With fallback_to_db_teams enabled, a team alias outside the JWT's allowed IDs is first looked up as a team ID. A missing ID raises before that result is cached, so every request with the alias repeats the database query even if the alias itself is cached. This violates the repository directive to avoid new database requests in the critical request path; the requirement must be satisfied before merging.
Rule Used: What: Avoid creating new database requests or Router objects in the critical request path. Why: Creating these objects on every request causes performance degradation and unnecessary resource consumption. (source)
TLDR
Problem this solves:
gen_ai.conversation.idHow it solves it:
gen_ai.conversation.idx-litellm-team-idaccepts a team alias as well as a team idfallback_to_db_teamson, the header can pick any team the caller belongs toIntentional product change: with
fallback_to_db_teams: true, a header naming a team the caller is not in now gets a 403 reading "among your team memberships" instead of listing the JWT's team idsUser Flow
Before: on rc/1.103.0 a JWT developer and an OTel user each hit a gap that main already fixed
"litellm_session_id": "conv-1", and their OTel backend shows the span with nogen_ai.conversation.idteam-engsendsx-litellm-team-id: engineering, that team's alias, and gets 403appidto a team sendsx-litellm-team-id: team-db-1, another team they belong to, withfallback_to_db_teamson, and gets 403After: the same requests behave the way they do on main
gen_ai.conversation.id: "conv-1", and a request with no session id still has noneteam-engteam-db-1, while a team they are not in still gets 403Relevant issues
Backport of #42486, #42445, #42495 and #43206
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
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@greptileaito re-request a review after pushing changes)tests/test_litellm/proxy/authandtests/test_litellm/integrations/otelpass on this branch (3067 tests with the local cost map). With the remote cost map, one unrelated imagen pricing test also fails on the rc tip. Each pick's own tests were also run with its source change reverted, and they fail there: #42486 22 failures, #42445 an import error, #42495 4, #43206 3Adaptations from the main versions:
tests/integration/contracts.jsonconflicted because main's copy has moved on, so only this PR's 23 test entries were addedpatch()calls carry atest-quality-okreason, since rc/1.103.0 is already over its TQ008 budgetType
🆕 New Feature
🐛 Bug Fix
Caveats (if any)
Severe
fallback_to_db_teams: truex-litellm-team-idnow also accepts team aliases on every JWT deploymentMedium