Repository navigation
feat(decisions): serve System One format at /v1/systemone and OpenAI format at /v1/decisions - #45184
Conversation
…format at /v1/decisions The System One request format moves to /v1/systemone and /systemone. /v1/decisions and /decisions now accept the OpenAI Decisions API format, translate it into a System One request, route it through the same pipeline, and translate the answers back.
|
Merging this PR will not alter performance
Comparing |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
/v1/decisions accepted up to 200 questions, but the System One request it translates to takes at most 128, so 129 to 200 questions failed with an internal validation error. Both now share MAX_DECISION_QUESTIONS.
|
@greptileai please re-review: non-JSON bodies now return 400, OpenAI questions are capped at 128, and integration tests use /v1/systemone |
|
bugbot run |
…point # Conflicts: # litellm/proxy/decisions_endpoints/endpoints.py
…point # Conflicts: # litellm/types/decisions.py
|
bugbot run |
…ns when requested
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f360214. Configure here.
…and the openai Decisions provider to rc/1.105.0 (#44236, #45184, #45214) (#45189) * feat(decisions): add unified /v1/decisions endpoint for Jev-compatible providers (#44236) Backport of #44236 to rc/1.105.0. Cherry-picked from 8b1990b (main). * feat(decisions): serve System One format at /v1/systemone and OpenAI format at /v1/decisions Backport of #45184 to rc/1.105.0. Cherry-picked from 3339469 (litellm_systemone_endpoint). The playground System One tab is not on this line, so its UI hunks are dropped. * fix(decisions): import assert_never from typing_extensions for Python 3.10 Backport of the #45184 follow-up to rc/1.105.0. Cherry-picked from 29e7154 (litellm_systemone_endpoint). * fix(decisions): cap OpenAI-format questions at the System One limit Backport of the #45184 follow-up to rc/1.105.0. Cherry-picked from 7ef1329 (litellm_systemone_endpoint). * fix(decisions): return 400 for bodies that are not JSON Backport of the #45184 follow-up to rc/1.105.0. Cherry-picked from b6bb885 (litellm_systemone_endpoint). * test(decisions): send System One bodies to /v1/systemone in integration tests Backport of the #45184 follow-up to rc/1.105.0. Cherry-picked from 77872f6 (litellm_systemone_endpoint). * feat(decisions): add OpenAI as a Decisions provider System One requests to openai/ models are translated to OpenAI's Decisions shape on the way out and OpenAI's answers are translated back, so both /v1/systemone and /v1/decisions can route to gpt-6-luna. (cherry picked from commit b94bbbc) * refactor(decisions): move the OpenAI wire translation into llms/openai (cherry picked from commit d209a85) * feat(decisions): route both request formats through a shared decisions IR System One and OpenAI-format bodies now convert to one internal representation, and each provider translates it to its own wire format. OpenAI deployments receive the caller's messages, images, names, typed choice values, level descriptions and safety_identifier unchanged. System One providers return a 400 for image input. Cached and cache-write tokens are priced on both response shapes, and provider response extras survive the round trip. (cherry picked from commit 0dd207f) * fix(decisions): keep provider fields inside System One answers (cherry picked from commit 1348de8) * fix(decisions): load on Python 3.10 and 3.11 and bill cached tokens once under custom pricing Decisions IR dataclasses used a mappingproxy default, which is only hashable from Python 3.12, so import litellm failed on 3.10 and 3.11. Decisions usage now reaches cost_per_token as a Usage with prompt_tokens_details, so custom pricing no longer adds cached and cache-write tokens on top of input_tokens that already include them (cherry picked from commit 39012f1) * fix(decisions): honor litellm OpenAI key and base settings for the openai provider OpenAI decisions now resolve the key from litellm.api_key, litellm.openai_key, then OPENAI_API_KEY, and the base from litellm.api_base, OPENAI_BASE_URL, then OPENAI_API_BASE, matching other OpenAI calls. Providers own their configured key and base lookup through the endpoint config. (cherry picked from commit e465919) * test(decisions): clear every OpenAI base setting in the proxy OpenAI deployment test (cherry picked from commit a2951b3) * fix(decisions): send OpenAI instructions for System One questions without them and reject single-option questions (cherry picked from commit e4081f7) * fix(decisions): return guardrail_information on OpenAI-format decisions when requested Backport of the #45184 and #45214 guardrail fix. Cherry-picked from 855f0bb (litellm_systemone_endpoint) as merged into #45214 at c19ada6 (litellm_openai_decisions_provider). --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…04.2) (#374) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.104.0` → `v1.104.2` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.104.2`](https://github.com/BerriAI/litellm/releases/tag/v1.104.2) [Compare Source](BerriAI/litellm@v1.104.1...v1.104.2) #### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.104.2 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.104.2/cosign.pub \ ghcr.io/berriai/litellm:v1.104.2 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** #### What's Changed - feat(decisions): backport /v1/systemone, OpenAI-format /v1/decisions and the OpenAI Decisions provider to stable/1.104.x for v1.104.2 ([#​44236](BerriAI/litellm#44236), [#​45184](BerriAI/litellm#45184), [#​45214](BerriAI/litellm#45214)) by [@​mateo-berri](https://github.com/mateo-berri) in [#​45190](BerriAI/litellm#45190) **Full Changelog**: <BerriAI/litellm@v1.104.1...v1.104.2> ### [`v1.104.1`](https://github.com/BerriAI/litellm/releases/tag/v1.104.1) [Compare Source](BerriAI/litellm@v1.104.0...v1.104.1) #### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.104.1 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.104.1/cosign.pub \ ghcr.io/berriai/litellm:v1.104.1 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** #### What's Changed - chore(release): refresh dependencies on stable/1.104.x and cut 1.104.1 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​44777](BerriAI/litellm#44777) - chore(docker): bump pgbouncer to 1.26.0 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​45012](BerriAI/litellm#45012) **Full Changelog**: <BerriAI/litellm@v1.104.0...v1.104.1> </details> --- ### Configuration 📅 **Schedule**: (in timezone America/New_York) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMzMuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjEzMy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.greyrock.io/todd/home-ops/pulls/374
…format at /v1/decisions Backport of BerriAI#45184 to stable/1.104.x. Cherry-picked from 3339469 (litellm_systemone_endpoint, BerriAI#45184 is not merged yet). The playground System One tab and managed agent authorization are not on this line, so those hunks are dropped, and the lazy-disabled precedence test is dropped as in the BerriAI#44236 pick.
TLDR
Problem this solves:
/v1/decisionsonly spoke the System One formatHow it solves it:
/v1/systemoneand/systemone/v1/decisionsand/decisionsnow take OpenAI's Decisions API format/v1/systemoneguardrail_informationwhen the caller setsinclude_guardrail_responseIntentional product change: callers sending System One bodies to
/v1/decisionsmust switch to/v1/systemone, since/v1/decisionsnow expects OpenAI's shape and returns 400 otherwiseUser Flow
Before: a developer using OpenAI's Decisions API format gets a 400 from the proxy
inputand aquestionslist (predicate, choice, score)Invalid Decisions requestbecause it expectsstateand aquestionsmapAfter: the same OpenAI-format request returns OpenAI-format answers, and System One callers use their own route
answerslist of typed predicate, choice and score answers plus OpenAI-styleusagex-litellm-response-costheaderPre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/unit/<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)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
Proxy started with
python litellm/proxy/proxy_cli.py --config decisions_config.yaml --port 4000, calling Perplexity for realsystemone_body.json:{ "model": "decider", "state": "Customer wrote: I was charged twice for my order #4411 and want one of the charges refunded today.", "questions": { "is_refund_request": {"type": "noul", "instructions": "Is the customer asking for a refund?"}, "urgency": {"type": "choice", "instructions": "How urgent is this?", "criteria": {"low": "can wait a week", "high": "needs action today"}}, "frustration": {"type": "score", "instructions": "How frustrated is the customer?", "criteria": ["calm", "mildly annoyed", "angry"]} } }openai_body.json:{ "model": "decider", "input": [{"role": "user", "type": "message", "content": [{"type": "input_text", "text": "Customer wrote: I was charged twice for my order #4411 and want one of the charges refunded today."}]}], "questions": [ {"type": "predicate", "name": "is_refund_request", "instructions": "Is the customer asking for a refund?"}, {"type": "choice", "name": "needs_human", "instructions": "Should a human agent take over?", "choices": [{"value": true, "description": "billing dispute that needs a person"}, {"value": false}]}, {"type": "score", "name": "frustration", "instructions": "How frustrated is the customer?", "levels": [{"label": "calm"}, {"label": "mildly annoyed"}, {"label": "angry", "description": "uses hostile language"}]} ] }Before (df23f11)
System One format at /v1/systemone
curl -sS -i localhost:4000/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' -d @systemone_body.jsonOpenAI format at /v1/decisions
curl -sS -i localhost:4000/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' -d @openai_body.jsonAfter (77872f6)
System One format at /v1/systemone
curl -sS -i localhost:4000/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' -d @systemone_body.jsonOpenAI format at /v1/decisions
curl -sS -i localhost:4000/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' -d @openai_body.jsonAfter, Jev direct from TypeSafe (77872f6)
Same proxy and request bodies with
modelset tojev, using this config so the calls go straight to TypeSafe's own APISystem One format at /v1/systemone
curl -sS -i localhost:4000/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' -d @jev_systemone_body.jsonOpenAI format at /v1/decisions
curl -sS -i localhost:4000/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' -d @jev_openai_body.jsonType
New Feature
Caveats (if any)
Severe
/v1/decisionsnow get a 400/v1/systemoneLow
input_imageparts return 400 since System One is text onlysafety_identifieris accepted but not forwarded upstreamrefusalanswersmodelis the upstream model name, same as/v1/systemone/v1/decisionsstill takes precedencetests/integrationwere not run locally because they need Postgres and RedisFinal Attestation
Note
Medium Risk
Breaking route contract for
/v1/decisionsaffects existing System One clients; translation and answer-mapping logic must stay correct for predicate/choice/score edge cases.Overview
Splits decision APIs by wire format: System One (
state+ keyedquestions) moves to/v1/systemoneand/systemone, while/v1/decisionsand/decisionsnow accept OpenAI’s Decisions shape (input, listquestions, typed answers).OpenAI requests are validated with new Pydantic types, translated to System One via
openai_transformation(predicate→noul, choice/score mapping, textstatefrom messages), run through the sameadecisionsproxy path, then mapped back to OpenAI answers (includingrefusalwhen upstream data is missing).include_guardrail_responseis honored on the OpenAI response path. Invalid or non-JSON bodies return 400 before upstream calls; OpenAI question count is capped at 128.Routing/auth/OpenAPI/allowlists and the playground System One tab are updated to the new paths. Breaking: clients sending System One JSON to
/v1/decisionsmust switch to/v1/systemone.Reviewed by Cursor Bugbot for commit f360214. Bugbot is set up for automated code reviews on this repo. Configure here.