Skip to content

feat(sdk): add litellm.systemone and trim litellm.decisions to the OpenAI shape - #45846

Merged
kerrylu-berri merged 7 commits into
mainfrom
litellm_systemone_sdk
Oct 10, 2026
Merged

kerrylu-berri merged 7 commits into
mainfrom
litellm_systemone_sdk

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • litellm.decisions accepted both the OpenAI Decisions shape and the System One shape
  • Callers had to know which kwargs flip the wire format, and errors were pydantic dumps
  • /v1/systemone traffic was logged as call type adecisions, indistinguishable from /v1/decisions

How it solves it:

  • New litellm.systemone / litellm.asystemone take state plus a questions mapping
  • litellm.decisions / litellm.adecisions document only input, a questions list, safety_identifier
  • litellm.decisions(state=...) keeps working for existing Jev callers, it sends the System One format and emits a UserWarning pointing at litellm.systemone
  • litellm.systemone(input=...) raises BadRequestError pointing at litellm.decisions
  • Shared provider lookup, translation and HTTP send live once in litellm/decisions/call.py
  • New CallTypes.systemone / asystemone, router.systemone / router.asystemone, /v1/systemone runs as asystemone
  • Evaluation health checks call litellm.asystemone

Intentional product change: spend logs and the Logs page show asystemone for /v1/systemone requests instead of adecisions

User Flow

Before: a developer sending the System One shape through the SDK has to pass it to litellm.decisions, and a wrong field mix gets a pydantic validation dump

  1. They call litellm.decisions(model="typesafe/jev-latest", state="...", questions={"is_refund_request": {...}}) and get the System One response back
  2. They try litellm.systemone(...) and get AttributeError: module 'litellm' has no attribute 'systemone'
  3. They pass state with an OpenAI question list and get Invalid Decisions request: 1 validation error for DecisionsRequestBody ...
  4. They POST /v1/systemone on the proxy and https://litellm-domain/ui/?page=logs shows the row as adecisions

After: the same developer picks the function by the request shape they write

  1. They call litellm.systemone(model="typesafe/jev-latest", state="...", questions={"is_refund_request": {...}}) and get the System One response back
  2. litellm.decisions(model=..., input="...", questions=[{...}]) keeps returning the OpenAI response unchanged
  3. litellm.decisions(state=...) still returns the System One answers and prints UserWarning: litellm.decisions(state=...) is deprecated, call litellm.systemone for the System One request format, and litellm.systemone(input=...) raises BadRequestError pointing at litellm.decisions
  4. They POST /v1/systemone on the proxy and https://litellm-domain/ui/?page=logs shows the row as asystemone, while /v1/decisions rows stay adecisions

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/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
  • 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: source /tmp/qa.env exports TYPESAFE_API_KEY (1Password Shared vault), OPENAI_API_KEY from the environment. Proxy config at /tmp/proof_config.yaml:

model_list:
  - model_name: jev-latest
    litellm_params:
      model: typesafe/jev-latest
      api_key: os.environ/TYPESAFE_API_KEY
  - model_name: gpt-6-luna
    litellm_params:
      model: openai/gpt-6-luna
      api_key: os.environ/OPENAI_API_KEY
general_settings:
  master_key: sk-proof-1234

Proxy started with python litellm/proxy/proxy_cli.py --config /tmp/proof_config.yaml --port 4000 --use_v2_migration_resolver

Before (290ac3d, main at capture time)

SDK: litellm.systemone

  1. python -c 'import litellm; litellm.systemone(model="typesafe/jev-latest", state="Customer wrote: I was charged twice for order #4411 and want a refund today.", questions={"is_refund_request": {"type": "noul", "instructions": "Is the customer asking for a refund?"}})'
  2. AttributeError: module 'litellm' has no attribute 'systemone'

SDK: litellm.decisions with state

  1. python -c 'import litellm; litellm.decisions(model="openai/gpt-6-luna", state="hello", questions=[{"type": "predicate", "name": "q", "instructions": "x"}])'
  2. BadRequestError 400 litellm.BadRequestError: Invalid Decisions request: 1 validation error for DecisionsRequestBody (a pydantic dump, nothing tells the caller which function to use)

Proxy: /v1/systemone call type in spend logs

  1. curl -s -D /tmp/h localhost:4000/v1/systemone -H "Authorization: Bearer sk-proof-1234" -H "Content-Type: application/json" -d '{"model":"jev-latest","state":"Customer wrote: I was charged twice for order #4411 and want a refund today.","questions":{"is_refund_request":{"type":"noul","instructions":"Is the customer asking for a refund?"}}}'
  2. {"model":"jev-1.13.0","answers":{"is_refund_request":{"type":"noul","noul":0.99}},"usage":{"input_tokens":294,"output_tokens":23}}
  3. curl -s "localhost:4000/spend/logs?request_id=$(grep -i ^x-litellm-call-id /tmp/h | awk '{print $2}' | tr -d '\r')" -H "Authorization: Bearer sk-proof-1234"
  4. {'call_type': 'adecisions', 'model': 'typesafe/jev-latest', 'spend': 1.2348e-05, 'status': 'success'}

After (a95b48a)

SDK: litellm.systemone

  1. same command as Before
  2. systemone -> {'type': 'noul', 'noul': 0.99} {'input_tokens': 294, 'output_tokens': 23}

SDK: litellm.decisions with state (backwards compatible)

  1. same command as Before, run as is, UserWarning shows by default
  2. decisions(state=) -> {'type': 'noul', 'noul': 0.99} | warning: ['litellm.decisions(state=...) is deprecated, call litellm.systemone for the System One request format']
  3. adecisions(state=) -> {'type': 'noul', 'noul': 0.99} | warning: ['litellm.decisions(state=...) is deprecated, call litellm.systemone for the System One request format']

SDK: litellm.decisions with input (unchanged)

  1. python -c 'import litellm; r = litellm.decisions(model="openai/gpt-6-luna", input="Customer wrote: I was charged twice for order #4411 and want a refund today.", questions=[{"type": "predicate", "name": "is_refund_request", "instructions": "Is the customer asking for a refund?"}]); print(r.answers[0].model_dump(mode="json"))'
  2. {'type': 'predicate', 'name': 'is_refund_request', 'probability': 1.0}

Proxy: /v1/systemone call type in spend logs

  1. same two curls as Before
  2. {"model":"jev-1.13.0","answers":{"is_refund_request":{"type":"noul","noul":0.99}},"usage":{"input_tokens":294,"output_tokens":23}}
  3. {'call_type': 'asystemone', 'model': 'typesafe/jev-latest', 'spend': 1.2348e-05, 'status': 'success'}

Proxy: /v1/decisions call type unchanged

  1. curl -s -D /tmp/h2 localhost:4000/v1/decisions -H "Authorization: Bearer sk-proof-1234" -H "Content-Type: application/json" -d '{"model":"jev-latest","input":"Customer wrote: I was charged twice for order #4411 and want a refund today.","questions":[{"type":"predicate","name":"is_refund_request","instructions":"Is the customer asking for a refund?"}]}'
  2. {"model":"jev-1.13.0","answers":[{"type":"predicate","name":"is_refund_request","probability":0.99}],"usage":{"input_tokens":294,...,"total_tokens":317}}
  3. {'call_type': 'adecisions', 'model': 'typesafe/jev-latest', 'spend': 1.2348e-05, 'status': 'success'}

Provider matrix at a95b48a, both request shapes against every provider we hold a key for (TypeSafe, OpenAI, Databricks, OpenRouter, Perplexity, Cloudflare)

Same four calls per provider, litellm.systemone(state=..., questions={...}) and litellm.decisions(input=..., questions=[...]), state "Hi, I want my money back for order 1234.", one is_refund_request question. Keys from the Shared 1Password vault, real spend

provider / model litellm.systemone litellm.decisions
typesafe/jev-latest {'type': 'noul', 'noul': 0.99} {'type': 'predicate', 'name': 'is_refund_request', 'probability': 0.99}
openai/gpt-6-luna {'type': 'noul', 'noul': 1.0} {'type': 'predicate', 'name': 'is_refund_request', 'probability': 1.0}
databricks/databricks-openjev-qwen35-4b {'type': 'noul', 'noul': 0.8807970842359877} {'type': 'predicate', 'name': 'is_refund_request', 'probability': 0.8807970842359877}
openrouter/typesafe/jev-1.13 {'type': 'noul', 'noul': 0.99} {'type': 'predicate', 'name': 'is_refund_request', 'probability': 0.99}
perplexity/pplx-decider-v1-27b {'type': 'noul', 'noul': 0.9997092001918163} {'type': 'predicate', 'name': 'is_refund_request', 'probability': 0.9997092001918163}
cloudflare/clef {'type': 'noul', 'noul': 0.9944} {'type': 'predicate', 'name': 'is_refund_request', 'probability': 0.9944}
azure_ai/Microsoft-Decision-1 DeploymentNotFound on all 4 Foundry resources in the vault, no Decision-1 deployment exists to test against same
strands_decider/... (Bedrock AgentCore) no deployed AgentCore runtime available, unit and wire tests only same

So the System One shape reaches OpenAI (systemone -> openai/gpt-6-luna) and the OpenAI shape reaches Jev (decisions -> typesafe/jev-latest) through the shared IR, and each provider's own shape round-trips too

Type

🆕 New Feature
🧹 Refactoring

Caveats (if any)

Severe

  • litellm.decisions(state=...) now emits a UserWarning on every call, callers rename to litellm.systemone to silence it
  • /v1/systemone call type changes from adecisions to asystemone in spend logs and the Logs page

Low

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/7fa4332c8dd6470c89614432409b9174
Open in Devin Desktop: https://app.devin.ai/desktop/session/7fa4332c8dd6470c89614432409b9174?variant=devin
Requested by: @kerry-berri


Note

Devin errored when opening this Pull Request as kerry-berri.
As a fallback, Devin opened this PR as itself.

…enAI shape

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

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • 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.


kerry seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 3/5

[Medium impact] Adds a new decisions API variant alongside existing decisions.

Fix the incomplete SDK test migration and incorrect call-type assertions before merging.

Findings

  1. P1 Sync SDK tests now fail ▶
  2. P1 Tests expect wrong call types ▶

Summary

Adds litellm.systemone and litellm.asystemone, while keeping decisions and adecisions on the OpenAI request shape.

  • System One and OpenAI Decisions requests use separate SDK functions.
  • System One proxy requests have their own call type in logs.

Acknowledged intentional changes from devin-ai-integration[bot]: decisions(state=...) now returns a 400 directing callers to systemone; System One spend logs now use asystemone. These compatibility changes were not reported as defects.

Reviews (1) · Last reviewed commit: "feat(sdk): add litellm.systemone and tri..." · Reviewed by Greptile

model=provider.model, state=_STATE, questions=_SDK_QUESTIONS, api_base=handle.api_base(), api_key=_API_KEY
)
asynchronous: Final = await litellm.adecisions(
asynchronous: Final = await litellm.asystemone(

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.

P1 Sync SDK tests now fail

The sync side still calls litellm.decisions(..., state=...), which now raises BadRequestError before reaching the upstream. Renaming only the async call leaves this test broken. The connection-error test and the signed Strands test also retain the old sync call.

Rename those remaining sync calls to litellm.systemone so the tests reach the upstream and check the intended behavior.

row["api_base"],
_number(row["spend"]),
) == ("success", "adecisions", "databricks", deployment, f"{handle.api_base()}/{_ENDPOINT}/invocations", 0.0), (
) == ("success", "asystemone", "databricks", deployment, f"{handle.api_base()}/{_ENDPOINT}/invocations", 0.0), (

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.

P1 Tests expect wrong call types

This test posts to /v1/decisions, which still logs adecisions, but the changed assertion expects asystemone. It will fail even when the proxy records the correct call type. The Strands test makes the same mistake by expecting asystemone for all four routes, including /decisions and /v1/decisions.

Keep adecisions here and choose the Strands expectation by route.

@codspeed

codspeed Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚠️ 1 benchmark spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 31 untouched benchmarks


Comparing litellm_systemone_sdk (fb81050) with main (58808f5)1

Open in CodSpeed

Footnotes

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

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot requested a review from a team October 10, 2026 19:32
…onWarning

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

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.51244% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/decisions/call.py 96.62% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

kerry and others added 3 commits October 10, 2026 20:04
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…UserWarning

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@kerrylu-berri
kerrylu-berri enabled auto-merge (squash) October 10, 2026 22:00
@kerrylu-berri
kerrylu-berri merged commit 25d9b05 into main Oct 10, 2026
102 checks passed
@kerrylu-berri
kerrylu-berri deleted the litellm_systemone_sdk branch October 10, 2026 22:16
devin-ai-integration Bot added a commit that referenced this pull request Oct 11, 2026
…ers to litellm.decisions.call

The decision model guardrail backport (#45894) landed on the line after this branch
forked and imports supported_decisions_providers from litellm.decisions.main, which
#45846 replaces here. Match main by exposing it from litellm.decisions.call and point
the guardrail endpoint and its test at that module. schema.d.ts and the lazy OpenAPI
snapshot are regenerated on the merged tree

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
DeoLeung pushed a commit to aiyou178/litellm that referenced this pull request Oct 11, 2026
…enAI shape (BerriAI#45846)

* feat(sdk): add litellm.systemone and trim litellm.decisions to the OpenAI shape

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

* ci: run tests/unit/systemone in the core-utils unit shard

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

* feat(sdk): keep litellm.decisions(state=...) working with a DeprecationWarning

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

* test: make tests/unit/systemone a package

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

* feat(sdk): surface the litellm.decisions(state=...) deprecation as a UserWarning

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

---------

Co-authored-by: kerry <kerry@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 25d9b05)
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.

2 participants