Repository navigation
feat(providers): add CoralBricks as a native provider - #44700
Conversation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
Mubashir Osmani 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. |
|
Merging this PR will not alter performance
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
… streaming spend Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
bugbot run |
…r cell and the README row
|
bugbot run |
…rovider_wt Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> # Conflicts: # tests/e2e/ui/tests/integrationCritical/expected.json # ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx
…, always release held upstream Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 similar comment
…d of a mutable list Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
bugbot run |
…pseek-v4.1-flash-fast-fp4 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
bugbot run |
…UI rate from the catalog The retry cells now set num_retries on the deployment, since the CI rig posts router_settings.num_retries 0 after readiness and the router default of 2 no longer applies there. The SIGKILL chaos cell waits for the respawned worker's startup before the owned proxy is stopped and derives its timeout from the harness budget. The Add Model spec waits for the catalog the form loads, reads the expected rate from it, and finds the deployment through /v1/model/info, which does not paginate
|
Thanks for picking this up. Two catalog changes since the rows were read on 2026-10-06, for whenever you next touch it:
Machine-readable source: https://www.coralbricks.ai/api/public/models. Its |
…input rate CoralBricks renamed glm-5.3-fp4 to glm-5.3-fast and deepseek-v4.1-flash-fast-fp4 to deepseek-v4.1-flash-fast (the fp4 names stay as deprecated aliases), and cut DeepSeek V4.1 Flash input from $0.30 to $0.01 per 1M on 2026-10-08. Rates read from https://www.coralbricks.ai/api/public/models on 2026-10-10; a live cache write billed 6014 tokens at 9e-08 each, so the cache-write rate stays input plus cacheWritePerM. The test config builders now build new dicts instead of editing the parsed YAML, and the cost-map test locals carry Final.
|
@monotykamary thanks, cb1a23d switches to glm-5.3-fast and deepseek-v4.1-flash-fast with the $0.01 input rate, checked against the live catalog and a billed call |
|
bugbot run |
There was a problem hiding this comment.
Devin Review found 3 potential issues.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| "max_input_tokens": 1048576, | ||
| "max_tokens": 1048576, | ||
| "mode": "chat", | ||
| "output_cost_per_token": 4.4e-06, |
There was a problem hiding this comment.
🔴 Reasoning-heavy calls inflate CoralBricks spend
When reasoning tokens exceed completion tokens, coralbricks bills every reasoning token at the new output rate. generic_cost_per_token adds that count without limiting it to reported output. Non-streaming spend and budget deductions exceed the vendor's cost
Learn more
CoralBricks can return a chat usage block where completion_tokens_details.reasoning_tokens exceeds completion_tokens. The new pricing rows send that usage through generic_cost_per_token, which bills the whole reasoning count separately at the output rate even when it exceeds the output total. The same calculation feeds the response-cost header and spend logs. Streaming usage follows a different normalization path, so identical requests can have different bills.
Example: A response reporting 10 completion tokens and 12 reasoning tokens bills 12 output tokens, rather than the vendor's 10, even before any text tokens are counted
Recommended fix: Normalize CoralBricks reasoning details against completion_tokens before pricing non-stream responses, without changing other providers' accounting. Add a regression with reasoning tokens greater than completion tokens and compare the billed amount to the vendor cost
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
CoralBricks nests reasoning inside its output count (35 of 39 tokens in the proof). The arithmetic is the shared calculator every OpenAI-style provider uses
| COHERE_CHAT: "cohere_chat", | ||
| COMETAPI: "cometapi", | ||
| COMPACTIFAI: "compactifai", | ||
| CORALBRICKS: "coralbricks", |
There was a problem hiding this comment.
🟡 Packaged Add Model picker lacks CoralBricks
Default proxy installs serve the tracked packaged UI, not the updated dashboard source. Its unrebuilt assets lack CORALBRICKS, so those installs still cannot pick CoralBricks in Add Model
Learn more
The dashboard's provider mapping is compiled into static assets. The default proxy serves packaged UI assets from litellm/proxy/_experimental/out. This PR changes the dashboard source but does not update those tracked assets. Consequently the updated backend provider metadata reaches an older Add Model UI that lacks this provider's mapping, and the UI does not offer the new provider in the default packaged installation. The browser test uses a freshly built dashboard instead of the packaged copy.
Example: Install the wheel built from this tree and open Add Model without setting LITELLM_UI_PATH; the bundled picker lacks CoralBricks even though /public/providers/fields lists it
Recommended fix: Include the regenerated packaged dashboard artifacts in the same release or ensure the release build regenerates them before publishing. Test the packaged UI path as well as the source-built dashboard
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
The packaged dashboard is rebuilt by the weekly "update Next.js build artifacts" commit before each release. No provider PR commits it (tencent, Sail, TypeSafe)
…derive the test's input limit from the row
|
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 d5bbab5. Configure here.
TLDR
Problem this solves:
coralbricks/<model>is not a known provider, so every call 400sHow it solves it:
coralbricksas a JSON OpenAI-compatible provider athttps://inference.coralbricks.ai/v1/v1/chat/completions,/v1/responsesand/v1/messages, so nothing is translatedglm-5.3-fastanddeepseek-v4.1-flash-fastwith cache-write pricing and free cached reads, rates from the live catalog (GET https://www.coralbricks.ai/api/public/models, read 2026-10-12; the pricing page at https://www.coralbricks.ai/pricing renders client side)glm-5.3,glm-5.3-fp4,deepseek-v4.1-flash, anddeepseek-v4.1-flash-fast-fp4slugs CoralBricks still serves, at the same rates, so a call on an old name is billed rather than logged at $0glm-5.3-flash-fp4, which CoralBricks retired on 2026-10-07 (every call answers 404model_retired)get_valid_modelsknows them and acoralbricks/*wildcard deployment expands to them on/v1/modelsUser Flow
Before: a proxy admin who configures a CoralBricks model gets a 400 on every endpoint and cannot list or pick its models
model: coralbricks/glm-5.3-fastwithCORALBRICKS_API_KEYto the proxy config/v1/responses,/v1/messages) with that modelThere are no healthy deployments for this modelcoralbricks/*; it answers 500not live in this pod's router after the reload, and GET http://localhost:4000/v1/models lists nocoralbricks/modelCoralin Provider showsNo providers foundAfter: the same calls go straight to CoralBricks, are billed at its rates, and the catalog shows up everywhere
model: coralbricks/glm-5.3-fastwithCORALBRICKS_API_KEYto the proxy config/v1/responses,/v1/messages) with that modelx-litellm-response-costmatches thecostCoralBricks reports, and a repeated prompt comes back withcached_tokensthat add $0 to spendcoralbricks/*answers 200 with a deployment id, and GET http://localhost:4000/v1/models lists every CoralBricks catalog modelCoralin Provider offers CoralBricks, which lists its models and showshttps://inference.coralbricks.ai/v1as the API base placeholder (left empty, the provider's base applies)Relevant issues
Fixes #44678
Pre-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
/qa at d5bbab5 against 29f48e6, the merge base before main was merged in as 93c5048
Last updated: d5bbab5
Two proxies, one per leg, each booted with
--num_workers 2from its own worktree on a random port with its own Postgres database,STORE_MODEL_IN_DB=True,LITELLM_LOCAL_MODEL_COST_MAP=True, the same config (coralbricks/glm-5.3-fast,coralbricks/deepseek-v4.1-flash-fast, and the deprecatedcoralbricks/glm-5.3-fp4, each withapi_key: os.environ/CORALBRICKS_API_KEY, plus anopenai/gpt-5.4-minicontrol,drop_params: true), and the real CoralBricks key. The same script ran against both;$KEYis the proxy master key. Run on 2026-10-12, the before leg at 17:50Z and the after leg at 18:56Z on the staged tree 113351dece, every product file identical to d5bbab5 (committed at 19:27Z)Before (29f48e6): the router drops every CoralBricks deployment at boot, so each call is a 400 and the wildcard create is a 500
Proxy log at boot, once per deployment and worker:
On http://localhost:3000/models-and-endpoints, Add Model, typing
Coralinto Provider:After (d5bbab5): chat, responses, and messages answer 200 on both models, billed at the cost-map rates,
coralbricks/*lists the catalog, and the deprecated slugs bill at their current names' ratesThe same Add Model flow: the Provider search offers CoralBricks, the picker lists its catalog, and API Base shows
https://inference.coralbricks.ai/v1as its placeholderObserved along the way, none changed here:
/v1/messagesreportsinput_tokens0, all undercache_creation_input_tokenscache_read_input_token_costis 0.0 on every CoralBricks rowusage.costequalsx-litellm-response-coston every case/v1/modelsrepeats a name both configured and wildcard-expanded (shared concat)/live-pr-risk at a4fb718, then merged with main as 9f355f016a on the rig (the branch's own merge of main is 93c5048)
Verdict: pass. The diff since 18ffd76 is additive: two cost-map rows (
coralbricks/glm-5.3-fast,coralbricks/deepseek-v4.1-flash-fast), a repriced row (coralbricks/deepseek-v4.1-flash-fast-fp4input 3e-07 to 1e-08), two placeholder strings, andcoralbricks_modelsjoining the provider model sets inlitellm/__init__.py(_populate_provider_model_sets's parameter is now typedMapping[str, Mapping[str, object]], an annotation with no runtime change). The readers of the new set areget_valid_models(explicit provider, and env-inferred throughCORALBRICKS_API_KEY),get_provider_modelsbehind wildcard expansion on/v1/models,/model/info, and key model lists,pick_cheapest_chat_models_from_llm_providerbehind the wildcard health probe, andlitellm.model_list, so this run re-checked every row of the 18ffd76 table at the tip and added the rows those readers reach. Both sides ran 2 uvicorn workers on their own Postgres, no Redis,STORE_MODEL_IN_DB=True,LITELLM_LOCAL_MODEL_COST_MAP=True, on the GCE lane: the merge base 29f48e6 on 127.0.0.1:33079 and the tip on 127.0.0.1:45539, each with a forwarding recorder in front ofhttps://inference.coralbricks.aiso the vendor wire could be diffed.origin/main(7af7919) is 272 commits past the base and movespyproject.toml,uv.lock, and the Rust tree, so the tip was merged with it (clean, 9f355f016a), the merged tree resynced, its Rust bridge rebuilt, and the proxy restarted on the same port, where every cell below answered the same as the tip (/public/providers169 and/public/providers/fields132 there, main having added two providers of its own), with one main-side difference: anopenai/*-scoped key no longer lists the coralbricks catalog on/v1/models(0 rows across 4 tries after both workers reloaded) where the tip listed it, while thecoralbricks/*-scoped key and the master key still list the 4 rows, so a wildcard-scoped key now expands only its own provider, a change main made between the base and 7af7919 and not this PR'sBreaking: none. Backward incompatible: none. Every pre-existing path answers identically on both sides, and the public registries differ only by the added entry:
/public/providers166 to 167,/public/providers/fields129 to 130 with the CoralBricks placeholdercoralbricks/glm-5.3-fast,/model/settings166 to 167,/public/endpointslists coralbricks under chat_completions, messages, and responsesUntested dependents driven live, the same request on both sides (a base cell answers 400 invalid model because no coralbricks deployment can go live there):
/model/new, bareglm-5.3-fast+ api_base, then chat/credentials+/model/newby name, then chatresponse_formatjson_object{"ok": true}drop_paramsonstream: truewithinclude_usage/v1/audio/speech/v1/embeddings/health?model=<deployment>/model/newcoralbricks/*, then/v1/models/model/infothrough the wildcard/v1/model/info,/model_group/infomodels: ["coralbricks/*"], then/v1/modelsopenai/*key no longer gets them, see above)coralbricks/deepseek-v4.1-flash-fast-fp4through the wildcardcoralbricks/not-a-modelthrough the wildcard/v1/messagesdeepseek,/v1/responsesglm through the wildcard/healthwith the wildcard presentget_valid_models()withCORALBRICKS_API_KEYsetLITELLM_MODEL_COST_MAP_URLat the tip's map/model/infoand a control chat bill at the map's openai rates (0.000036),get_model_infoprices the 4 coralbricks rows, butcoralbricksis still not inprovider_list,get_valid_modelsis empty with the pre-existingnot a valid LlmProviderswarning,/model/newcoralbricks still 500 not live, and acoralbricks/*row a new pod wrote logsError upserting deployment ... LLM Provider NOT providedon every reload of the old pod, a warning onlyThe api_base-inferred chat's spend row names provider coralbricks and model glm-5.3-fast with spend 0.00033824 (18 cache-write tokens at 1.68e-06 and 70 output tokens at 4.4e-06); the credential-backed deepseek chat's row is 0.0000057 (10 cache-write tokens at 9e-08 and 4 output tokens at 1.2e-06), the deprecated fp4 slug through the wildcard bills the same 0.0000057 for the same usage at the new rate, and the streaming glm chat's row is 0.00030096 (22 cache-write, 60 output). The recorder in front of the vendor logged 17 requests on the head and 0 on the base, one vendor request per proxy request with no retry behind either 404, the same header set per client (the OpenAI SDK headers on chat and speech, plain httpx on
/v1/responses,anthropic-versionon/v1/messages). On the head tree with the local cost map,get_valid_models(custom_llm_provider="coralbricks")returns the four rows (empty at 18ffd76),litellm.models_by_provider["coralbricks"]andlitellm.model_listcarry them,pick_cheapest_chat_models_from_llm_provider("coralbricks", 3)picks the fp4 slug, deepseek-v4.1-flash-fast, and glm-5.3-fast, andget_model_infoon each row matches the cost map (glm 1.12e-06 in, 4.4e-06 out, 1.68e-06 cache write, vision false; both deepseek slugs 1e-08 in, 1.2e-06 out, 9e-08 cache write, vision true; cache read 0 everywhere)Dependents already tested through the change and not re-driven here: chat, responses, and messages sync, async, streaming, and raw (the wire suite and the /qa leg above), 4xx, 5xx, and unreachable error mapping (the chaos suite), key scoping, fallback, the add-model fields and the dashboard picker (the browser cell and the /qa screenshots), config versus env credential precedence (the env wire suite), the catalog behind a wildcard (
test_coralbricks_rows_are_listed_for_wildcard_expansionand the wire testtest_wildcard_deployment_lists_every_coralbricks_catalog_row), the deprecated slug's price (test_coralbricks_deprecated_slug_bills_like_its_current_slug), and every cost-map provider having a registry entry (test_every_cost_map_provider_is_registered). The 13 unit files that readmodels_by_provider,model_list, or the cost map without naming coralbricks (test_utils,test_model_checks,test_health_check_helpers,test_get_llm_provider_logic,test_get_model_cost_map,test_constants,test_cost_calculator,test_router_model_cost_isolation,test_model_prices_schema, the two MCP sampling files, the soniox registration file, and this PR's unit file) ran on the merged tree: 1373 passed, 1 skipped, none pins a provider or model count. Legacy suites: no hit intests/llm_translationortests/local_testing; thetests/integrationhits are this PR's own three files, run under the audit roundsNot verified, each the generic path every JSON provider already takes:
/v1/images/generationsand the assistants routes with a coralbricks model (same dispatch class as speech, the vendor serves neither), websearch interception with coralbricks as an enabled provider,/config/cost_discount_configor/config/cost_margin_confignaming coralbricks, and the rendered add-model form with the new placeholder (its API row,/public/providers/fields, is in the table)Observations, each reproduced on the base and so pre-existing, none for this PR to fix: a key scoped to a provider wildcard (
anthropic/*,openai/*, orcoralbricks/*) lists every proxy model on/v1/modelsand can call any configured deployment (ananthropic/*key calledcontrol-openaiandopenai/gpt-5.4-minithrough anopenai/*deployment on both sides, andcoralbricks/glm-5.3-faston the head), worth its own ticket; a vendor 404 puts a single deployment into cooldown for the minute (an openai control answered one 404 then 429 on both sides, per worker without Redis), which is why the first head pass saw embeddings and streaming answer 429 after the speech cell and the table reports the re-run on a fresh deployment ordered before speech; a chat with a just-deleted key answered 200 once on both sides (the other worker's key cache); and an api_base-inferred deployment shows null provider and prices in/model/infowhile its spend row bills at the cost-map rate, as at 18ffd76. No vendor 429 or quota error; every 429 above was the proxy's own cooldown/live-pr-risk at d5bbab5, then merged with main as 5e2e004dca on the rig
Verdict: pass. The delta since a4fb718 is two cost-map rows,
coralbricks/glm-5.3(a copy ofcoralbricks/glm-5.3-fp4) andcoralbricks/deepseek-v4.1-flash(a copy ofcoralbricks/deepseek-v4.1-flash-fast-fp4), inmodel_prices_and_context_window.jsonand the backup map, plus test-only edits; the merge commit 93c5048 brings main f516668 in and touches no symbol the PR owns (its 8 commits are decisions,supports_video_input, other providers' rows, one endpoint flag). Every consumer of the map that the two rows reach was driven on a real proxy against the real CoralBricks API, base f516668 against the tip, two uvicorn workers each with Postgres and Redis, a forwarding recorder in front of the vendor on every legBreaking: none. Backward incompatible: none. The public registries differ only by the rows:
/public/litellm_model_cost_map4115 entries with no coralbricks rows on base, 4129 with the 6 coralbricks rows on the tip;/public/providers168 to 169 and/public/providers/fields131 to 132 with the placeholder stillcoralbricks/glm-5.3-fast, both unchanged by this deltaUntested dependents of the two rows driven live, the same request on both sides (a base cell answers 400 invalid model because no coralbricks deployment can go live there):
/public/litellm_model_cost_map, catalog and full/uiAdd Model, provider CoralBricks, model list/model/newcoralbricks/*, then/v1/modelsx4[]/model/info,/v1/model/info[]/model_group/info?model_group=coralbricks/*get_valid_models,model_list,models_by_providerin-process, 3 hash seedsnot a valid LlmProviderswarningpick_cheapest_chat_models_from_llm_provider("coralbricks", 3), 6 seeds[]/health?model=coralbricks/*x4deepseek-v4.1-flash-fast-fp4x3 anddeepseek-v4.1-flashx1coralbricks/glm-5.3,coralbricks/deepseek-v4.1-flashthrough the wildcardstream: truewithinclude_usage/v1/messagesdeepseek,/v1/responsesglmmax_budget 0.0001oncoralbricks/*, glm chat as that key, then 2 moremodel_max_budgetdeepseek 1e-06 per day, chat as that key, then 2 moremodels: ["coralbricks/*"],/v1/modelsx2 and/model/info[]models: ["coralbricks/glm-5.3"]: glm chat, deepseek chat,/v1/modelsThe spend rows reproduce the copied prices: the key-budget glm chat billed 28 prompt tokens as cache-write at 1.68e-06 plus 37 output tokens at 4.4e-06 (0.00020984), the model-budget deepseek chat 20 cache-write tokens at 9e-08 plus 4 output tokens at 1.2e-06 (0.0000066), and the first glm chat 17 cached tokens at the row's cache-read price 0.0, 1 cache-write token, and 87 output tokens (0.00038448). The vendor serves both deprecated slugs directly at the same rates, and its
/v1/modelslists only the two-fastnames. The 422 and 403 cells produced no vendor request, and no cell produced more than oneMerge-ref check: the ctl leg ran the clean merge 5e2e004dca (d5bbab5 with main a86ad9e, the map +333/-2 with zero coralbricks lines) through the same cells and answered as the tip on every row above, with the health probes landing on
deepseek-v4.1-flash-fastx2 anddeepseek-v4.1-flashx2 (the three deepseek rows tie at the cheapest price and the set order is per process), the key budget tripping at 0.00065048, and 13 vendor requests. Main has since moved 3 commits (to 932a0d6: MCP OAuth scopes, Admin UI session failure rows, an e2e rename) that touch no file the PR owns and carry no coralbricks lineDependents already proved at a4fb718 and untouched by this delta (no provider code moved): api_base inference, stored credentials,
response_formatanddrop_params, speech and embeddings dispatch, the deprecated fp4 slug, a non-catalog name, env-inferred credentials, and the hosted-map upgrade of a base proxy, all in the section aboveNot verified: the hosted-map upgrade re-pointed at the tip's map (the a4fb718 cell proved the mechanism and the delta only adds rows to it),
/v1/images/generationsand the assistants routes with a coralbricks model, websearch interception with coralbricks enabled, and the cost discount and margin config naming coralbricks, each the generic path every JSON provider already takesObservations, none for this delta to fix: a request carrying two
Authorizationheaders is authenticated as the first one (the rig's helper sent the master key ahead of the virtual key at first, so the budget and scoped-key cells above are the re-runs with one header, and the first attempt billed the master key); the Redis response cache answers an identical chat with spend 0.0 and no budget consumption, so the budget cells carry a nonce; with no vendor key resolvable (the ctl leg's first boot),/v1/chat/completionsfails before the wire with a 500 AuthenticationError while/v1/messagesand/v1/responsessend the request upstream without anAuthorizationheader and relay the vendor's 401, which is the provider's handling from before this delta and the same on every leg that lacks the keyAudit round 3 of 3 (at d5bbab5)
/audit run at the tip d5bbab5 (the branch's merge of main 93c5048 plus the cost-map rows
coralbricks/glm-5.3andcoralbricks/deepseek-v4.1-flash, two unit cases, and the test files below) against the merge base f516668. The 53 deterministic cells, the controls, and the browser cell ran on the synced tree 113351dece (every product file identical to the tip); the driver then rewrotetest_coralbricks_provider_chaos.pyfor the TQ010 gate (tests only, tree 17b0284724, every product file and every other test file unchanged), so its 4 cells re-ran on both legs at that tree, twice on head, and every other row keeps its result, per the audit skill's rerun rule. One proxy per leg fromtests/integration/proxy_config.yamlwith 2 uvicorn workers (--num_workers 2), its own Postgres database, Redis,STORE_MODEL_IN_DB=True,LITELLM_LOCAL_MODEL_COST_MAP=True, andLITELLM_UI_PATHat the leg's ownui/litellm-dashboard/outbuild, on a GCE lane (the merge base on 127.0.0.1:48325, the tip on 127.0.0.1:58825 through an ssh tunnel). Every deterministic cell's provider is a real HTTP peer the test owns (tests/integration/_support/wire.py), so each cell asserts what the caller received, what the provider received, and the Postgres spend row by response id. The live rows reach the real CoralBricks API (https://inference.coralbricks.ai, real spend) through the same two proxies booted withCORALBRICKS_API_KEYfrom.env, the master key in$KEYCells: 53 node ids under
tests/integration/providers/(test_coralbricks_provider_wire.py38,test_coralbricks_provider_env_wire.py11,test_coralbricks_provider_chaos.py4, sharing_coralbricks.py) plus the browser celltests/e2e/ui/tests/integrationCritical/addModelCoralBricks.spec.ts, registered inexpected.json. New this round:test_wildcard_deployment_lists_every_coralbricks_catalog_row(E9), which derives the catalog from the leg's own cost map and asserts everycoralbricks/row on/v1/modelsand/model/info. Leg command, the same 53 ids on both legs:Verdict: PASS. Every inventory row (H1 to H15, S1 to S9, E1 to E9, M1 to M4 with the secrecy cell, C1 to C4, U1, L1 to L4) is VERIFIED: red on the merge base for the reason its base cell names, green on the tip twice with the same seeds, the controls green on both legs, the live rows reaching the real vendor on the tip only
Before (f516668)
/v1/chat/completions
coralbricks/deepseek-v4.1-flash-fastorcoralbricks/glm-5.3-fastdeployment throughPOST /model/newPOST /model/newfora3-rec-glmd5b5(coralbricks/glm-5.3-fast, real key) anda3-rec-dsd5b5(coralbricks/deepseek-v4.1-flash-fast) answers 500 the same way (live/d5bbab56a5/base/new_a3-rec-glmd5b5.body), andPOST /v1/chat/completionson them 400 "Invalid model name passed in", the stream too, 0 chunks (L1_chat.body,L1_stream.body)/v1/responses
/model/new;POST /v1/responseson the live deployment 400 "Invalid model name passed in" (L1_responses.body)/v1/messages
/model/new;POST /v1/messageson the live deployment 400 the same way (L1_messages.body)Credentials and provider inference
CORALBRICKS_API_KEYandCORALBRICKS_API_BASEset and a config modelcoralbricks-env-config(coralbricks/deepseek-v4.1-flash-fast) with no credentials/model/infohas no row for it, and the/model/newcells (M2 to M4) 500 at setupPOST /model/newwith only the env key, catalog base) and L3 (unprefixedglm-5.3-fastwithapi_base: https://inference.coralbricks.ai/v1): both 500 at/model/new(new_a3-envcred-glmd5b5.body,new_a3-inferred-glmd5b5.body), the chats on them 400 "Invalid model name passed in" (L2_chat.body,L3_chat.body)/model/new500, the chat 400 before any vendor call (L4_chat.body)Sad paths and chaos
coralbricks/deployment first/model/new500; C3 and C4 "State did not converge: 0", the burst never reached the wire); the chaos rerun at tree 17b0284724 is red the same way (C1 and C2/model/new500 in under a second, C3 and C4 "State did not converge: 0" after 129 s each,chaos-base4 collected, 0 passed, pytest 212 s)/health/liveliness200, green on this leg too (controls)Listing
/model/info,/v1/models,/model_group/infofor the coralbricks rowscoralbricks/deepseek-v4.1-flash-fast(no such row in the merge base cost map), E5 finds no CORALBRICKS entry on/public/providers/fields, E9 500 at/model/newfor thecoralbricks/*deploymentAdmin UI Add Model
/ui/?page=add-model, typesCoralBricksinto the Provider comboboxlocator.clicktimes out after 15 s waiting forgetByRole('option', { name: /CoralBricks$/ })withCoralBrickstyped into the expanded Provider combobox, the picker shows "No providers found"), the cell fails at the provider step (phase12/d5bbab56a5/ui-base)Full matrix
After (d5bbab5)
/v1/chat/completions
/model/new200, each chat 200 with the scripted id, the provider receivedPOST /v1/chat/completionswithAuthorization: Bearer <deployment key>and the model prefix stripped, one spend row per response id withcustom_llm_provider = coralbricks, 4357 prompt (4352 cached, 5 cache write) and 20 completion tokens, spend 2.445e-05 ondeepseek-v4.1-flash-fastand 9.64e-05 onglm-5.3-fast(the usage frame of the stream bills the same)/model/new200 fora3-rec-glm20f6(coralbricks/glm-5.3-fast) anda3-rec-ds20f6(coralbricks/deepseek-v4.1-flash-fast),POST /v1/chat/completionson the glm deployment 200 (req_9d10dcc9...), 20 prompt and 30 completion tokens,x-litellm-response-cost 0.0001656(30 x 4.4e-06 output plus the 20 prompt tokens the vendor reports as cache creation at 1.68e-06), the spend rowcustom_llm_provider coralbricks,api_base https://inference.coralbricks.ai/v1/, spend 0.0001656 (live/d5bbab56a5/head/L1_chat.{hdr,body,spend}); the streamed chat on the deepseek deployment 200, 5 chunks ending in[DONE], its row 12 prompt and 7 completion tokens, spend 9.48e-06 (L1_stream.*)/v1/responses
POST /v1/responsesnatively (no chat bridge) with the input intact, the stream's created, delta and completed events all forwarded, one row per response id (call_type aresponses)POST /v1/responses200 (resp_zeZA..., statusincompleteatmax_output_tokens 30, the vendor's reasoning used the budget), cost 0.0001876, the rowcall_type aresponses,api_base .../v1/responses(L1_responses.*)/v1/messages
msg_id, the provider receivedPOST /v1/messageswith the bearer key andanthropic-version, the full Anthropic SSE forwarded, one row (call_type anthropic_messages)POST /v1/messages200 (msg_ebf6e949...), cost 0.0001656, the rowcall_type anthropic_messages,api_base .../v1/messages(L1_messages.*)Credentials and provider inference
api_baseandapi_keybeat the env pair, a missing or null or emptyapi_keyorapi_basefalls through to the env value, and/model/infonever echoes the env key/model/newwith onlyCORALBRICKS_API_KEYset and noapi_base200, chat 200, spend rowapi_base https://inference.coralbricks.ai/v1/, cost 0.0001744 (L2_chat.*)glm-5.3-fastwith the catalogapi_base200, spend rowcustom_llm_provider coralbricks,model glm-5.3-fast, cost 0.00017 (L3_chat.*)x-litellm-response-cost 0(L4_chat.body)Sad paths and chaos
num_retries2) with the provider message in the bodyapi_baseanswers 5xx with the connection error in the body and the proxy serves a control request after; S8: no key anywhere fails before the provider, naming the missing key, 0 provider requestsowned.replacing_workerandowned.begin_shutdown): C1: 30 concurrent requests across the three endpoints while the provider is down,/health?model=unhealthy during the outage and healthy after the restart on the same port, everyx-litellm-call-idlands exactly once; C2: 20 paused streams complete with one row each; C3: a SIGKILLed worker's sibling keeps serving; C4: the SIGTERMed proxy's 200s land once, the rebooted proxy serves the deploy; green twice at that tree with the same seeds (chaos-head-r1,chaos-head-r2), collected and passed selections identicalListing
/model/inforeports the cost-map rates and limits of the deployment; E3:/v1/modelsand/model_group/infocarry it; E5:/public/providers/fieldsnames the CoralBricks credential fields; E7: acoralbricks/*deployment routes the requested model; E8: a refused primary falls back onto coralbrickscoralbricks/*deployment lists all 6 catalog rows (glm-5.3-fast,glm-5.3-fp4,glm-5.3,deepseek-v4.1-flash-fast,deepseek-v4.1-flash-fast-fp4,deepseek-v4.1-flash) on/v1/models, and/model/infoexpands it into one entry per row withlitellm_params.modelequal to the row andlitellm_provider coralbricks, the catalog read from the leg's own cost map rather than a literal countAdmin UI Add Model
/model/newreceivesmodel: deepseek-v4.1-flash-fast,custom_llm_provider: coralbricks, the api_base and the api_key; a chat on the new deployment 200; its spend row hascustom_llm_provider coralbricks, 20 prompt and 20 completion tokens, spend 2.42e-05 (20 x 1e-08 + 20 x 1.2e-06, the rates read back from/model/info); green twice against the tip's own dashboard build (phase12/d5bbab56a5/ui-head-r114.2 s,ui-head-r223.7 s)Full matrix
execution.json:hypothesis_seed 141,order_seed 141,complete true)complete true), run 2 4 passed, pytest 245 s, collected and passed selections identical to run 1; the base leg at the same tree 4 collected, 0 passed, pytest 212 s; the control lane (3 S9 ids on the ctl leg): 3 passed in 43 stests/integration/providers/test_coralbricks_provider_wire.py::test_openai_sdk_chat_reaches_coralbricks_and_bills_the_cost_map_row[deepseek-v4.1-flash-fast]test_coralbricks_provider_wire.py::test_openai_sdk_chat_reaches_coralbricks_and_bills_the_cost_map_row[glm-5.3-fast]test_coralbricks_provider_wire.py::test_async_openai_sdk_chat_reaches_coralbrickstest_coralbricks_provider_wire.py::test_openai_sdk_chat_stream_is_forwarded_and_billed_from_the_usage_frametest_coralbricks_provider_wire.py::test_async_openai_sdk_chat_stream_is_forwarded_and_billedtest_coralbricks_provider_wire.py::test_raw_chat_request_carries_the_call_id_and_lands_oncetest_coralbricks_provider_wire.py::test_openai_sdk_responses_go_natively_to_coralbricks_responsestest_coralbricks_provider_wire.py::test_async_openai_sdk_responses_go_natively_to_coralbricks_responsestest_coralbricks_provider_wire.py::test_openai_sdk_responses_stream_is_forwarded_natively_and_billed_oncetest_coralbricks_provider_wire.py::test_async_openai_sdk_responses_stream_is_forwarded_natively_and_billed_oncetest_coralbricks_provider_wire.py::test_raw_responses_request_lands_oncetest_coralbricks_provider_wire.py::test_anthropic_sdk_messages_go_natively_to_coralbricks_messagestest_coralbricks_provider_wire.py::test_async_anthropic_sdk_messages_go_natively_to_coralbricks_messagestest_coralbricks_provider_wire.py::test_anthropic_sdk_messages_stream_is_forwarded_natively_and_billed_oncetest_coralbricks_provider_wire.py::test_async_anthropic_sdk_messages_stream_is_forwarded_natively_and_billed_oncetest_coralbricks_provider_wire.py::test_raw_messages_request_lands_oncetest_coralbricks_provider_wire.py::test_provider_client_error_reaches_the_caller_after_one_attempt[/v1/chat/completions-401]test_coralbricks_provider_wire.py::test_provider_client_error_reaches_the_caller_after_one_attempt[/v1/chat/completions-404]test_coralbricks_provider_wire.py::test_provider_client_error_reaches_the_caller_after_one_attempt[/v1/responses-401]test_coralbricks_provider_wire.py::test_provider_client_error_reaches_the_caller_after_one_attempt[/v1/messages-401]test_coralbricks_provider_wire.py::test_provider_server_error_is_retried_then_reaches_the_caller[429]test_coralbricks_provider_wire.py::test_provider_server_error_is_retried_then_reaches_the_caller[500]test_coralbricks_provider_wire.py::test_unreachable_api_base_reports_the_connection_error_and_leaves_the_proxy_servingtest_coralbricks_provider_wire.py::test_deployment_without_a_key_and_no_env_key_fails_before_the_provider[missing]test_coralbricks_provider_wire.py::test_deployment_without_a_key_and_no_env_key_fails_before_the_provider[null]test_coralbricks_provider_wire.py::test_deployment_without_a_key_and_no_env_key_fails_before_the_provider[empty]test_coralbricks_provider_wire.py::test_hostile_model_field_is_rejected_without_crashing[int]test_coralbricks_provider_wire.py::test_hostile_model_field_is_rejected_without_crashing[list]test_coralbricks_provider_wire.py::test_hostile_model_field_is_rejected_without_crashing[5kb]test_coralbricks_provider_wire.py::test_identical_uncached_chats_are_each_forwarded_and_billedtest_coralbricks_provider_wire.py::test_model_info_reports_the_coralbricks_cost_map_rowtest_coralbricks_provider_wire.py::test_models_listing_and_model_group_info_carry_the_coralbricks_deploymenttest_coralbricks_provider_wire.py::test_wildcard_deployment_lists_every_coralbricks_catalog_rowtest_coralbricks_provider_wire.py::test_health_probe_reaches_coralbricks_with_the_deployment_keytest_coralbricks_provider_wire.py::test_add_model_form_fields_name_the_coralbricks_credentialstest_coralbricks_provider_wire.py::test_key_scoped_to_the_coralbricks_deployment_is_charged_its_spendtest_coralbricks_provider_wire.py::test_wildcard_coralbricks_deployment_routes_the_requested_modeltest_coralbricks_provider_wire.py::test_fallback_from_a_refused_primary_lands_on_coralbrickstest_coralbricks_provider_env_wire.py::test_config_model_without_credentials_uses_the_env_key_and_base[/v1/chat/completions-acompletion]test_coralbricks_provider_env_wire.py::test_config_model_without_credentials_uses_the_env_key_and_base[/v1/responses-aresponses]test_coralbricks_provider_env_wire.py::test_config_model_without_credentials_uses_the_env_key_and_base[/v1/messages-anthropic_messages]test_coralbricks_provider_env_wire.py::test_explicit_credentials_beat_the_env_credentialstest_coralbricks_provider_env_wire.py::test_deployment_without_a_key_uses_the_env_key[missing]test_coralbricks_provider_env_wire.py::test_deployment_without_a_key_uses_the_env_key[null]test_coralbricks_provider_env_wire.py::test_deployment_without_a_key_uses_the_env_key[empty]test_coralbricks_provider_env_wire.py::test_deployment_without_a_base_uses_the_env_base[missing]test_coralbricks_provider_env_wire.py::test_deployment_without_a_base_uses_the_env_base[null]test_coralbricks_provider_env_wire.py::test_deployment_without_a_base_uses_the_env_base[empty]test_coralbricks_provider_env_wire.py::test_env_credentials_are_not_echoed_by_model_infotest_coralbricks_provider_chaos.py::test_provider_outage_mid_burst_fails_fast_reports_unhealthy_and_recoverstest_coralbricks_provider_chaos.py::test_slow_provider_streams_are_forwarded_once_under_concurrencytest_coralbricks_provider_chaos.py::test_worker_sigkill_mid_burst_leaves_the_sibling_serving_coralbrickstest_coralbricks_provider_chaos.py::test_proxy_sigterm_mid_burst_then_reboot_serves_coralbricks_againtests/e2e/ui/tests/integrationCritical/addModelCoralBricks.spec.ts::the Add Model form offers CoralBricks and the deployment it creates serves and bills a chat(browser, each leg's own dashboard build)Type
🆕 New Feature
Caveats (if any)
Medium
A
litellm.completion(stream=True)call with nostream_optionslogs usage 0/0 andresponse_cost 0.0for CoralBricks in the SDK; the proxy is unaffectedstream_options: {"include_usage": true}, litellm injects that only for OpenAI-hosted api_bases (get_stream_options,litellm/llms/openai/openai.py:1233-1241), andCustomStreamWrapperstamps a zeroedUsageinto the last chunk's hidden params when the vendor sent none, so the chunk builder's token-count fallback (calculate_usagecounts only when the per-chunk counts are None) never runs. The same mocked usage-less stream throughhosted_vllm/with a custom base logs the same zeros, so this is the shared streaming wrapper's handling of every OpenAI-compatible provider whose vendor omits usage, not changed here and tracked on its own ticketinclude_usageupstream for every stream (litellm/proxy/common_request_processing.py:542-554): on the tip astream: truechat with nostream_optionslogged 33 prompt and 20 completion tokens and spend 0.00002697 (req_7146e491..., the vendor's usage with 33 cache-write tokens), so key, team, and budget spend are right. SDK callers passstream_options: {"include_usage": true}to get the vendor's usage and costA reply whose
reasoning_tokensexceedcompletion_tokensis overbilled by the differencemax_tokenscut (60 completion, 61 reasoning, finish_reasonlength): the vendor charged 0.00029256 and litellm 0.00029696, one output token more; an earlier finished non-stream call showed the same two tokens overtext_tokens_without_nested_reasoning, not changed here, so it is bounded at (reasoning minus completion) tokens per reply; a clamp there changes reasoning billing for every provider that nests reasoning, so the shared calculator fix is tracked on its own ticketThe four deprecated alias rows are listed as models of their own
coralbricks/*deployment listsglm-5.3,glm-5.3-fp4,deepseek-v4.1-flash, anddeepseek-v4.1-flash-fast-fp4next to the two current names on/v1/models,/model/info,get_valid_models, and the Add Model picker (six entries for two models), and the wildcard health probe picks the fp4 slug as the cheapest row, which serves at the same pricedeprecation_date, so/model/deprecationscannot warn before CoralBricks drops an alias. The catalog publishesdeprecatedSlugswith no end date (GET https://www.coralbricks.ai/api/public/models, 2026-10-12) and/model/deprecationscounts the days until a real date, so none can be set truthfully; litellm lists every priced row of a provider (litellm/__init__.py:1016), so keeping the aliases billable (the P1 review thread) means listing them. Add the date once the vendor announces onelitellm/__init__.py:1016buildscoralbricks_modelsfrom everycoralbricksrow), so the rows list until the vendor stops serving the aliases; an operator who wants only the two current names configures them by nameLow
openai/<model>plusapi_baseworkaround from the earlier PR stays unpriced:get_model_info("glm-5.3-fp4", custom_llm_provider="openai")raisesModelNotMappedErroron the tip because the rows are keyedcoralbricks/, so those operators switch the prefix tocoralbricks/to get billinglitellm/proxy/_experimental/out) is not rebuilt here, so a pip install serves an Add Model picker without CoralBricks until the periodic "chore: update Next.js build artifacts" rebuild lands after the merge/public/providers/fields,/model/newwithcustom_llm_provider: coralbricks) works on the tip; the browser cell runs againstui/litellm-dashboard/outbuilt from the branch, the layout CI usesinput_cost_per_tokenis a rate no request pays: the vendor bills every uncached prompt token as a cache write (cost_details.input0.0,cache_write_tokens=prompt_tokenson every call), so pre-call estimates andpick_cheapest_chat_models_from_llm_provider, which rank on input plus output cost, read 1.5x (glm) to 9x (deepseek) below what the spend log records; spend rows match the vendor's owncostGET /api/public/modelsreportscacheWritePerM0.56 and 0.08 as a surcharge on top of input, so the rows' 1.68e-06 and 9e-08 are input plus surcharge; billed calls confirm the rows, and the next rate update should not "fix" themglm-5.3,glm-5.3-fp4,deepseek-v4.1-flash, anddeepseek-v4.1-flash-fast-fp4rows stay priced at the current names' rates because CoralBricks still serves all four (a chat on each answered 200 on 2026-10-12, billed at those rates by the vendor's owncostfield); they can go once the vendor stops serving themglm-5.3-flash-fp4, but CoralBricks retired it on 2026-10-07 (every call now 404s and it is gone from/v1/models), so 18ffd76 drops its row and the tests use the currentdeepseek-v4.1-flash-fastapi_keyon the deployment norCORALBRICKS_API_KEYset, a request falls through toOPENAI_API_KEY, the way every OpenAI-compatible provider does (speech()resolves it explicitly, chat through the SDK's env read); not changed hereOPENAI_COMPATIBLE_PROVIDERSexception-mapping list (litellm-rust/crates/core-utils/src/exception_mapping_utils/original.rs) does not namecoralbricks, nor cortecs, prism, sail, or edenai; it only picks the exception-mapping branch there, left alonemax_tokensis set to the 1,048,576 context length because the catalog publishes no output limit (max_output_tokens: nullon 2026-10-06), solitellm.get_max_tokensquotes 1M output for these models and a request asking formax_tokens: 1048576gets the vendor's 400tests/integration/providers/test_responses_minted_reasoning_replay_wire.py) lists nine JSON providers for native/v1/responsesand notcoralbricks; the PR's own wire suite covers chat, responses, and messages, so this is coverage parity only/v1/model/infoshowslitellm_provider: nulland no prices for a deployment that names the bare model with the CoralBricksapi_base(provider inferred from the base), while chat through it bills at the cost-map rateapi_keyis theos.environ/CORALBRICKS_API_KEYliteral is sent to CoralBricks verbatim and answers 401QA runbook
coralbricksdeployment, and a chat on it is served and billed from the cost mapSTORE_MODEL_IN_DB=True, a PostgresDATABASE_URL, and the integration upstream onINTEGRATION_UPSTREAM_URL(default http://127.0.0.1:8190), then sign in at http://localhost:4000/ui/login withadminand the master keyCoralBricksinto Provider and pick it, chooseCustom Model Name (Enter below)under Select models, and typedeepseek-v4.1-flash-fast<upstream>/v1and API Key withcb_ui_integration_key, and click Add Model; expect thePOST /model/newbody to carrymodel_name= the alias,litellm_params.model=deepseek-v4.1-flash-fast,custom_llm_provider=coralbricks, that api_base and that api_key, and a "created successfully" toastPOST /v1/chat/completionswith the alias and the master key until it answers 200, read itsid, andGET /v2/model/infofor the alias'sinput_cost_per_tokenandoutput_cost_per_token(both finite and above 0)GET /spend/logs?request_id=<id>and expectcustom_llm_provider=coralbricks,model=deepseek-v4.1-flash-fast,model_group= the alias, 20 prompt and 20 completion tokens (the upstream's scripted usage), andspend= 20 x input rate + 20 x output rateFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/cc554ddbbcad40cd834ab4a99655d5bb
Open in Devin Desktop: https://app.devin.ai/desktop/session/cc554ddbbcad40cd834ab4a99655d5bb?variant=devin
Requested by: @mubashir1osmani
Note
Low Risk
Changes are additive registry, pricing, and UI metadata with no modifications to existing provider behavior; risk is mainly misconfigured pricing or routing for the new provider only.
Overview
Adds CoralBricks (
coralbricks) as a first-class OpenAI-compatible provider athttps://inference.coralbricks.ai/v1, with env varsCORALBRICKS_API_KEY/CORALBRICKS_API_BASEand native support for/v1/chat/completions,/v1/responses, and/v1/messages.Registers the provider across LiteLLM and proxy surfaces:
openai_compatible_providers, endpoint support matrices, README provider table, proxy Add Model credential fields, and dashboard provider enum/placeholder (logoless fallback).Adds cost-map rows for
coralbricks/glm-5.3-fastandcoralbricks/deepseek-v4.1-flash-fast(including cache-write pricing and free cached reads) so routing,x-litellm-response-cost, and spend logs can bill CoralBricks usage.Ships broad coverage: unit tests for resolution and costing, integration wire/env/chaos suites, and a Playwright Add Model flow that creates a deployment and verifies chat + spend.
Reviewed by Cursor Bugbot for commit cb1a23d. Bugbot is set up for automated code reviews on this repo. Configure here.