Skip to content

feat: add Y-API as a JSON-configured OpenAI-compatible provider - #41559

Open
jiweiyeah wants to merge 2 commits into
BerriAI:mainfrom
jiweiyeah:feat/add-y-api-provider
Open

jiweiyeah wants to merge 2 commits into
BerriAI:mainfrom
jiweiyeah:feat/add-y-api-provider

Conversation

@jiweiyeah

@jiweiyeah jiweiyeah commented Sep 17, 2026 •

Copy link
Copy Markdown

What

Adds Y-API as a JSON-configured OpenAI-compatible provider.

Y-API (https://y-api.bestvirtualgoods.com) is a relay that fronts DeepSeek / Z.ai / Moonshot / Tencent / Xiaomi / OpenAI models behind a single key, exposing the OpenAI, Anthropic Messages, and Responses APIs.

It fits the JSON-configured provider path, so this PR is configuration plus the registration points that path requires — no Python provider module.

import litellm

litellm.completion(
    model="y-api/deepseek/deepseek-v4-flash",
    messages=[{"role": "user", "content": "hi"}],
)

Model ids keep the upstream organization prefix, so they contain a slash of their own (y-api/deepseek/deepseek-v4-flash). Resolution splits on the first slash; the test below covers that case specifically.

Files

File Change
litellm/llms/openai_like/providers.json the provider entry
litellm/types/utils.py LlmProviders.Y_API
litellm/constants.py openai_compatible_endpoints, openai_compatible_providers, openai_text_completion_compatible_providers
provider_endpoints_support.json documented endpoint matrix
litellm/provider_endpoints_support_backup.json the runtime matrix behind GET /public/endpoints
tests/test_litellm/llms/openai_like/test_y_api.py new

Endpoints

Declared from live calls against the API rather than from the vendor's docs:

Endpoint Result
/v1/chat/completions 200
/v1/completions 200
/v1/responses 200
/v1/messages 200
/v1/embeddings 503 No available channel for model … under group y-api (distributor)

The relay serves no embedding model, so embeddings is advertised false. Everything else above is true.

param_mappings: max_tokens → max_completion_tokens. The relay accepts max_completion_tokens, and the four openai/* models it fronts require it — they reject the legacy name outright with Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.

LiteLLM's openai_gpt base class does not translate the parameter here, because these model ids are not in the OpenAI cost map, so a caller's max_tokens is forwarded verbatim. Captured by pointing the provider at a local echo server and reading the outbound body:

caller sends before after
max_tokens=16 {"max_tokens": 16} {"max_completion_tokens": 16}
max_completion_tokens=16 {"max_completion_tokens": 16} {"max_completion_tokens": 16}
neither neither key neither key

So the mapping repairs the four openai/* models without touching the other eleven: both caller spellings arrive under the name the relay expects, and a caller who sets no cap still sends none. It is the mirror of what the eighteen existing JSON providers declare — they rewrite the modern name down to the legacy one because their upstreams are older; this relay is the other way round.

Verification

Repo checks:

  • tests/test_litellm/llms/openai_like/test_y_api.py — 13 passed, including three that drive map_openai_params and assert the outgoing parameter name rather than the config
  • tests/code_coverage_tests/check_provider_folders_documented.py — passed (29 openai_like providers, 178 documented entries)
  • tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py — 15 passed (the Add Model dropdown drift test included)
  • tests/test_litellm/llms/openai_like/ — 187 passed, 17 failed. The 17 are pre-existing: test_model_info.py (15) and test_cognition_provider.py (2) fail identically on a clean checkout of this commit (verified by stashing the change and re-running).

Also exercised end to end through a local proxy started from this branch, with the real key against the real endpoint:

/v1/chat/completions  -> choices[0].message.content == "ok"
/v1/responses         -> output_text == "ok"
/v1/messages          -> content[0].text == "ok"   (Anthropic shape)
stream=True           -> 16 chunks
GET /public/endpoints -> y-api listed under chat_completions, messages and
                         responses, and absent from embeddings

Notes for the reviewer

  • Docs page. provider_endpoints_support.json points url at https://docs.litellm.ai/docs/providers/y-api. The docs site lives in BerriAI/litellm-docs, so that page is not in this PR — say the word and I'll open the companion PR there, or drop the url field (as chutes and poe do) if you'd rather keep the matrix link-free for now.
  • Backup matrix. I did update litellm/provider_endpoints_support_backup.json even though it is 29 providers behind provider_endpoints_support.json (missing meta, aihubmix, nvidia_riva, mongodb, and the most recent JSON provider empiriolabs). I added the entry because the omission is user-visible: without it GET /public/endpoints does not list y-api at all. Happy to drop that file from the PR if the backup is deliberately frozen.
  • openai_text_completion_compatible_providers. Included because /v1/completions returns 200 for the DeepSeek models. If you'd prefer to advertise the legacy completions route only for providers you've verified more broadly, this is the line to drop.

@jiweiyeah
jiweiyeah requested a review from a team September 17, 2026 03:59
@CLAassistant

CLAassistant commented Sep 17, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@jiweiyeah jiweiyeah changed the title (feat) add Y-API as a JSON-configured OpenAI-compatible provider feat: add Y-API as a JSON-configured OpenAI-compatible provider Sep 17, 2026
@codspeed

codspeed Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing jiweiyeah:feat/add-y-api-provider (31de22e) with main (5455152)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge with the earlier token-mapping and test-quality findings fully addressed

Findings

  1. P2 Tests inspect structure only ▶
  2. P2 Comments restate simple assertions ▶

Summary

Adds Y-API as a JSON-configured OpenAI-compatible provider

  • Registers provider identity, endpoint routing, credentials, and dashboard fields
  • Maps max_tokens to Y-API's required max_completion_tokens
  • Advertises supported chat, Messages, Responses, and legacy completion surfaces
  • Adds provider-resolution, parameter-mapping, endpoint, and dashboard coverage

Reviews (2) · Last reviewed commit: "feat(providers): add Y-API as a JSON-con..."

Comment thread litellm/llms/openai_like/providers.json
Comment on lines +24 to +34
assert "y-api" in litellm.provider_list

def test_y_api_json_config_exists(self):
"""Test that y-api is configured in providers.json"""
from litellm.llms.openai_like.json_loader import JSONProviderRegistry

assert JSONProviderRegistry.exists("y-api")

y_api = JSONProviderRegistry.get("y-api")
assert y_api is not None
assert y_api.base_url == BASE_URL

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.

P2 Tests inspect structure only

These assertions, along with the endpoint matrix checks, inspect static configuration instead of provider behavior, violating the repository's functional-testing requirement before merge

Context Used: CLAUDE.md (source)

Comment on lines +30 to +31
assert JSONProviderRegistry.exists("y-api")

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.

P2 Comments restate simple assertions

These comments restate configuration and vendor behavior, violating the repository rule that source comments must explain necessary complex logic or direct tools

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@jiweiyeah

Copy link
Copy Markdown
Author

Docs page opened: BerriAI/litellm-docs#1515

So the url field in this PR now resolves. That PR adds docs/providers/y-api.md and the
sidebars.js entry (between providers/xinference and providers/zai), so it should land
alongside this one. If you'd rather keep the matrix link-free, dropping url here (as
chutes and poe do) is equally fine — the docs PR can be closed independently.

@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@jiweiyeah
jiweiyeah force-pushed the feat/add-y-api-provider branch from a59a593 to 7a898ec Compare September 17, 2026 04:11
@jiweiyeah

Copy link
Copy Markdown
Author

Fixed the one real failure from the previous run. Pushed as a single amended commit (7a898ec)
so the branch stays one commit; the message is now standard Conventional Commits to match
main.

proxy-endpoints / Run tests — my bug, now fixed

1 failed, 10533 passed. The failure was
test_every_backend_provider_is_listed_in_add_model_or_frozen_as_unlisted:

Add Model dropdown drift: give the new provider an entry in
provider_create_fields.json rather than adding it to ADD_MODEL_UNLISTED_PROVIDERS
E   Extra items in the left set:
E   'y-api'

I had missed the third enforcement mechanism. provider_endpoints_support.json and
provider_endpoints_support_backup.json are checked by
tests/code_coverage_tests/check_provider_folders_documented.py (which passes here), but
there is a separate gate on the dashboard's Add Model form: every provider in LlmProviders
must either appear in litellm/proxy/public_endpoints/provider_create_fields.json or be
frozen into ADD_MODEL_UNLISTED_PROVIDERS. Without an entry, Y-API would have been silently
invisible in the UI.

Added the Y_API entry (api_key required, api_base optional with the default as the
placeholder, default_model_placeholder: y-api/deepseek/deepseek-v4-flash), inserted after
XINFERENCE to keep the list's alphabetical run intact, plus two tests — one on the JSON
entry itself, one asserting /public/providers/fields actually returns y-api.

Verification

Check Clean tree (a59a593) With fix (7a898ec)
test_every_backend_provider_is_listed_in_add_model_or_frozen_as_unlisted fail pass
tests/test_litellm/llms/openai_like/test_y_api.py 8 pass 10 pass
tests/code_coverage_tests/check_provider_folders_documented.py pass pass

I confirmed the delta by stashing the fix and re-running: the drift test is the only test
that changes state. The other five failures in that file
(test_autorouter_presets_*, test_fetch_remote_autorouter_presets_*) fail identically on
the clean tree and are unrelated to providers.

Not mine

Validate PR title shows red on the earlier commit, but that run was created at 03:59:41 with
the previous title — it logged "(feat) add Y-API...". A re-run at 04:00:49 against the
current title passed. The new commit triggers a fresh run either way.

@jiweiyeah
jiweiyeah force-pushed the feat/add-y-api-provider branch from 7a898ec to 162625f Compare September 17, 2026 05:00
@jiweiyeah

Copy link
Copy Markdown
Author

All three findings addressed in 162625f. The P1 was correct and I had it backwards in the PR description.

P1 — max_tokens (valid; fixed)

I had written that the openai_gpt base class sends max_completion_tokens. It does not. Pointing the provider at a local echo server and reading the outbound body:

caller sends body that left litellm
max_tokens=16 {"max_tokens": 16}
max_completion_tokens=16 {"max_completion_tokens": 16}

The reason is the one the finding implies: these model ids are not in the OpenAI cost map, so nothing translates the parameter on this path. The four openai/* models reject the legacy name outright —

Unsupported parameter: 'max_tokens' is not supported with this model.
Use 'max_completion_tokens' instead.

— so a caller using max_tokens got a 400 on 4 of the 15 models. Fixed by declaring the mapping the schema exists for:

"param_mappings": { "max_tokens": "max_completion_tokens" }

After: max_tokens=16 → {"max_completion_tokens": 16}, max_completion_tokens=16 unchanged, no cap → neither key present. The eleven non-openai/* models are unaffected, because both spellings now arrive under the one name the relay accepts.

This is the mirror of what the eighteen existing JSON providers declare (max_completion_tokens → max_tokens) — their upstreams are older; this relay is the other way round.

P2 — "tests inspect structure only" (addressed)

Fair, and worth noting a config-only assertion could not have caught the P1 above, because the config was the thing that was wrong. Added TestYApiTokenParamMapping, which drives map_openai_params and asserts the outgoing parameter name for all three cases. The rest of the file is structural by nature — provider resolution, base-URL override and the two endpoint matrices are configuration surfaces with no behaviour to exercise.

P2 — "comments restate simple assertions" (fixed)

Removed. The comment sitting above assert y_api.param_mappings == {} was also factually obsolete once the mapping was added, so it was the wrong thing to keep either way.

Verification

  • tests/test_litellm/llms/openai_like/test_y_api.py — 13 passed
  • tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py — 15 passed (drift test included)
  • tests/code_coverage_tests/check_provider_folders_documented.py — passed
  • tests/test_litellm/llms/openai_like/ — 187 passed, 17 failed; the 17 are test_model_info.py (15) and test_cognition_provider.py (2), unchanged from a clean checkout of this branch

@jiweiyeah

Copy link
Copy Markdown
Author

@greptileai review

The previous review was on a59a5935; the branch has since been amended to a single commit (162625f) that addresses all three findings:

  • P1 — max_tokens: param_mappings is now {"max_tokens": "max_completion_tokens"} in litellm/llms/openai_like/providers.json, and the test asserts that mapping rather than just its presence.
  • P2 — tests inspect structure only: test_y_api_provider_resolution_with_nested_model_id and test_y_api_api_base_override now exercise get_llm_provider() and assert the resolved model / provider / api_base / api_key.
  • P2 — comments restate assertions: the per-assertion comments were removed; what remains is a module docstring explaining the nested-model-id split and one-line test docstrings.

All CI on the current head is green.

…vider

Y-API is an OpenAI-compatible relay fronting DeepSeek, Z.ai, Moonshot,
Tencent, Xiaomi, Qwen and OpenAI models behind one key. It also serves the
Anthropic Messages API and the OpenAI Responses API.

Registered through the declarative JSON registry, so no Python provider
module is needed:

- litellm/llms/openai_like/providers.json: base URL, YAPI_API_KEY /
  YAPI_API_BASE, a max_tokens -> max_completion_tokens mapping, and the
  three supported endpoints.
- litellm/types/utils.py: LlmProviders.Y_API.
- litellm/constants.py: added to openai_compatible_providers,
  openai_compatible_endpoints and openai_text_completion_compatible_providers.
- provider_endpoints_support.json + the runtime backup: chat_completions,
  messages and responses true; embeddings false.
- provider_create_fields.json: an Add Model form entry, without which the
  provider would be invisible in the dashboard.
- tests: resolution, api_base override, router config, both endpoint
  matrices, dashboard registration, and the token-parameter rewrite.

Model ids keep the upstream organization prefix, so every model string has
two slashes (`y-api/deepseek/deepseek-v4-flash`). Provider resolution splits
on the first slash and forwards the rest unchanged; there is a test for it.

The `openai/*` models this relay fronts reject `max_tokens` outright and
require `max_completion_tokens`. LiteLLM does not translate the parameter for
this provider, so the mapping rewrites the caller's `max_tokens` to the name
the relay expects. Both spellings then arrive identically, and a caller who
sets no cap still sends none.
@jiweiyeah
jiweiyeah force-pushed the feat/add-y-api-provider branch from 162625f to 3c6a6c0 Compare September 27, 2026 10:21
Comment thread litellm/constants.py
"cognition",
"scx-ai",
"sail",
"y-api", # Y-API - JSON-configured provider

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.

Low: OpenAI credential forwarded to Y-API

Adding y-api here also adds it to OPENAI_AUDIO_TRANSCRIPTION_PROVIDERS and the speech dispatch. An authenticated caller can submit an audio request for a direct y-api/... model without a Y-API key; transcription() and speech() then fall back to OPENAI_API_KEY while retaining Y-API's base URL, sending the deployment's OpenAI credential to that third party. Keep Y-API out of the generic audio-capable set, or gate these transports using its declared supported_endpoints and never use OpenAI credential fallbacks for another provider.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced this against a loopback echo server (base_url pointed at 127.0.0.1, so nothing left the machine). The finding is real — with one correction on where the fix has to land.

Repro, as reported. YAPI_API_KEY unset, OPENAI_API_KEY set:

call what the echo server received
litellm.transcription("y-api/whisper-1", …) POST /v1/audio/transcriptions, Authorization: Bearer <OPENAI_API_KEY>
litellm.speech("y-api/tts-1", …) POST /v1/audio/speech, same header

Both resolved Y-API's own base URL from providers.json and attached the OpenAI credential, so the mechanism you describe is correct.

But deleting this line closes the audio surface, not the credential fallback. With "y-api" removed from openai_compatible_providers and the environment unchanged:

call outcome
transcription() ValueError: Unmapped provider passed in. — no request leaves
speech() Unable to map the custom llm provider=y-api to a known provider= — no request leaves
completion("y-api/deepseek/…") POST /v1/chat/completions, Authorization: Bearer <OPENAI_API_KEY>
responses("y-api/deepseek/…") POST /v1/responses reaches the third-party base with no Authorization header at all

So the fallback itself lives in the shared openai_like credential resolution, not in this list entry: a JSON provider with no configured key still sends whatever OPENAI_API_KEY holds to that provider's base on the chat path. Opting Y-API out of the audio set makes the flagged case go away; it does not make credential forwarding impossible.

Scope. Of the 30 entries in providers.json, 17 are in openai_compatible_providers, and 16 of those declare no audio endpoint in supported_endpoints yet still land in OPENAI_AUDIO_TRANSCRIPTION_PROVIDERS through the same derivation — publicai, helicone, cognition, chutes, poe, nano-gpt, darkbloom, libertai, tensormesh, parasail among them. Adding y-api to the list follows the existing convention rather than introducing a new one.

Where I think a real fix belongs, and I would rather send it separately so this PR stays a plain provider addition:

  1. Derive OPENAI_AUDIO_TRANSCRIPTION_PROVIDERS from each JSON provider's declared supported_endpoints instead of wholesale from openai_compatible_providers — that closes the audio surface for all 17 providers at once, and the providers.json data for it is already there.
  2. Skip the OPENAI_API_KEY fallback when the resolved base URL belongs to a different provider — this is the credential-forwarding step proper, and without it the chat path above still reproduces.

Say the word on which you want and I'll open it as its own PR. If you'd rather Y-API just not inherit the audio channel while (1) is pending, deleting line 1027 is a one-line change here — the four calls above show it costs nothing on the paths this provider declares (/v1/chat/completions, /v1/completions, /v1/responses, /v1/messages all still dispatched correctly).

@veria-ai

veria-ai Bot commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

PR overview

This pull request adds Y-API as a JSON-configured, OpenAI-compatible provider in LiteLLM, including provider registration and request dispatch integration.

One security issue remains open: an authenticated caller could route an audio request through Y-API and cause the deployment’s OpenAI API key to be sent to Y-API because of credential fallback behavior. Exploitation requires authenticated request access and use of the affected audio paths, but it could disclose a sensitive third-party credential.

Open issues (1)

Fixed/addressed: 0 · PR risk: 6/10

@jiweiyeah

Copy link
Copy Markdown
Author

Re: the open Low: OpenAI credential forwarded to Y-API finding — sent as a separate PR so this one stays a plain provider addition: #43784.

Three corrections to what I wrote on the inline thread here on 09-17, because two of them change where the fix has to land:

  1. speech() never consulted OPENAI_AUDIO_TRANSCRIPTION_PROVIDERS. It tested custom_llm_provider in litellm.openai_compatible_providers directly (litellm/main.py, the speech dispatch). So the option I floated — "just delete the y-api line from openai_compatible_providers" — would have closed transcription() and left speech() still attaching OPENAI_API_KEY to a third-party base. fix: derive OpenAI audio providers from declared supported_endpoints #43784 makes both verbs read the derived set.
  2. It is 17 of 17, not 16. Every JSON provider inside openai_compatible_providers declares no audio endpoint in its own supported_endpoints, so all 17 (apertis, chutes, cognition, darkbloom, helicone, libertai, meta, nano-gpt, parasail, pinstripes, poe, prism, publicai, sail, scx-ai, synthetic, tensormesh) were audio-capable by derivation alone. Adding y-api followed the existing convention rather than introducing a new one — which is exactly why the derivation, not the entry, is the thing to fix.
  3. My baseline numbers reference a path that no longer exists. I recorded tests/test_litellm/llms/openai_like/ as "187 passed, 17 failed". That directory is gone on current main; the equivalent suite is tests/unit/llms/openai_like/, which is 196/196 green on a clean checkout and 206/206 with fix: derive OpenAI audio providers from declared supported_endpoints #43784's tests added. Please treat the old line as obsolete rather than as a current baseline.

Scope of #43784: audio capability is derived from each provider's declared supported_endpoints; providers that omit the key are excluded (the conservative default — json_loader already defaults it to []). 71 audio-capable providers before, 54 after, zero added, the 53 Python-defined providers untouched. Verified by loopback capture: before, both transcription() and speech() sent Authorization: Bearer <OPENAI_API_KEY> to the third-party base with no provider key configured; after, 0 requests leave.

What #43784 deliberately does not change: the chat path. A JSON provider with no key of its own still resolves to OPENAI_API_KEY and sends it to that provider's base URL. That is the credential-forwarding step proper and it needs its own discussion about fallback semantics, so it is not bundled here.

Also noting one CI item that is not ours to fix: osv-scan is red on this PR and also red on unrelated open PRs (#43770, #43751), from advisories in the existing lockfile — no lockfile is touched by either PR.

# Conflicts:
#	litellm/constants.py
#	litellm/llms/openai_like/providers.json
#	litellm/types/utils.py
@jiweiyeah

jiweiyeah commented Oct 6, 2026 •

Copy link
Copy Markdown
Author

Rebased onto main (518 commits). Every conflict was the same shape — both sides appended a JSON-configured provider to the end of a list — resolved by keeping both entries (reka from main, then y-api):

  • litellm/constants.py (openai_compatible_providers)
  • litellm/types/utils.py (LlmProviders)
  • litellm/llms/openai_like/providers.json

Net diff against main is unchanged: 7 files, +318/−0.

Local verification, using the repo's own lockfile (uv sync, Python 3.12):

pytest tests/unit/llms/openai_like/test_y_api_provider.py       → 13 passed
pytest tests/unit/llms/openai_like/test_json_providers.py
       tests/unit/llms/openai_like/test_reka_provider.py        → passed

Two notes on things that are not from this PR, so a reviewer doesn't have to re-derive them:

  1. The documentation and code-quality jobs on this head both fail on the same step (tests/documentation_tests/test_env_keys.py) with Environment variables read under ./litellm but mentioned nowhere in the docs: ['DEFER_PYDANTIC_BUILD']. That variable is read at litellm/constants.py:8 on main and is documented nowhere in BerriAI/litellm-docs; the Documentation Validation workflow failed on the latest main pushes and on seven other unrelated PR branches in the same window. I re-ran the local tests with main's versions of the six touched files and got identical results, so this PR neither causes nor can fix it.
  2. provider_endpoints_support.json carries a duplicate charity_engine key on main. Left untouched rather than folded into this diff.

On the earlier security review: the credential fallback it flagged is still live on current main — OPENAI_AUDIO_TRANSCRIPTION_PROVIDERS remains {"openai"} ∪ openai_compatible_providers (litellm/constants.py:1052), which is exactly what #43784 addresses.

This branch has not been deployed

No deployments
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