Repository navigation
feat(xai): add native xAI batches and files support - #42812
Conversation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…on star import 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. |
|
bugbot run |
|
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…_key Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… Python 3.10 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The batches test package collided with litellm/batches under pytest prepend, so the All Other Providers shard could not collect the new tests.
|
bugbot run |
|
Live QA at c7cd26e against real xAI, viewed in the Admin UI at http://localhost:4000/ui/logs/. xAI file upload, batch create, retrieve, results, list and cancel all show up as request logs with the batch cost tracked Request Logs, xAI Batch and file rows Batch detail: provider xai, 2 successful and 0 failed requests, cost 0.00010998 File upload detail hitting https://api.x.ai/v1/files |
…s, and add 200k batch tier rates
| """ | ||
| xAI Batches API handler: create, retrieve, list, cancel and result download. | ||
|
|
||
| Every operation is one or more plain HTTP calls with the same auth header, so the sync and async | ||
| paths share the URL builders and response mapping in ``transformation.py`` and differ only in the client. | ||
| """ |
There was a problem hiding this comment.
Routine source prose
This module comment describes ordinary HTTP-client structure, which the repository's source-comment rule disallows. Similar prose in both transformation modules also needs removal before merging
Context Used: AGENTS.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!
There was a problem hiding this comment.
These docstrings explain xAI quirks the code cannot show (no status, batch id as output file, bare REST dates), which AGENTS.md's complex-logic exception allows
There was a problem hiding this comment.
You're right that the transformation docstrings explain provider-specific behavior. The handler docstring only describes implementation structure, so removing it remains appropriate.
|
bugbot run |
1 similar comment
|
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 dc0e29c. Configure here.
TLDR
Problem this solves:
provider=xaion/v1/filesand/v1/batchesHow it solves it:
pagination_tokennum_pending,num_success, ...) to OpenAI batchstatusandrequest_counts/v1/files/{batch_id}/contentmode: batchxAI deployment no longer reports an OpenAI 401Decisions made here rather than by accident:
output_file_idis the batch id because xAI has no output file object (the alternatives were a syntheticfile_id that nothing could retrieve, or leaving it null and breaking every OpenAI client that reads results by it);created_atandcancelled_atare passed through at xAI's day resolution instead of being replaced by the proxy's clock; the caller'sendpointis echoed back but not sent to xAI, which routes each JSONL line by its ownurlUser Flow
Before: a developer who wants to run a batch of chat completions on xAI through the proxy is rejected at the first step
purpose=batchand a JSONL fileLiteLLM doesn't support xai for 'create_file'. Only ['openai', 'azure', 'vertex_ai', 'manus', 'anthropic'] are supported.input_file_idand get a 400 withLiteLLM doesn't support custom_llm_provider=xai for 'create_batch'After: the same developer runs the whole OpenAI style batch lifecycle against xAI
purpose=batchand a JSONL file and get back a file object with an id likefile_eb1ce109-a8d8-43a8-8831-d7afa27b2017input_file_idand get back a batch object with an id likebatch_fdc779da-139c-4390-b086-b9715a6b2f38andstatus: validatingstatus: in_progresswithrequest_counts.completedclimbing untilstatus: completed,request_counts.completedequal to the number of lines andoutput_file_idsetcustom_idand the chat completion bodyspendequals the sum of xAI's per-linecost_in_usd_ticksstatus: cancelled, DELETE https://litellm-domain/v1/files/{file_id}?provider=xai returnsdeleted: trueand a later GET of that file is a 404Relevant issues
Fixes #20424
Linear ticket
Resolves LIT-8499
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)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
Both legs ran the same flow through the same client: two proxy processes, each
proxy_cli.py --config config.yaml --port <random free port> --num_workers 2, sharing one fresh Postgres database, with the steps alternating between the two processes so what one wrote is read back through the other.LITELLM_LOCAL_MODEL_COST_MAP=Trueso the branch's own price map is what bills the batch. The before leg booted from a worktree at the merge base 170eb7f (ports 45170 and 45527, databaselitellm_lit8499_qa_before); the after leg booted from the PR tip dc0e29c (ports 22959 and 24000, databaselitellm_lit8499_qa).XAI_API_KEYandLITELLM_MASTER_KEYcome from the environmentconfig.yaml:batch_input.jsonl:{"custom_id": "req-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "grok-4.3", "messages": [{"role": "user", "content": "What is the capital of France? Answer in one word."}], "max_tokens": 40}} {"custom_id": "req-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "grok-4.3", "messages": [{"role": "user", "content": "How many legs does a spider have? Answer with just the number."}], "max_tokens": 40}} {"custom_id": "req-3", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "grok-4.3", "messages": [{"role": "user", "content": "What color is the sky on a clear day? One word."}], "max_tokens": 40}}Before (170eb7f)
Upload through proxy A:
Create a batch through proxy B:
The issue reporter's client, the litellm Python SDK, with
litellmimported from the merge-base worktree:Nothing succeeded, so there was nothing further to chain: every xAI files and batches call is rejected before it reaches xAI, on both proxy processes and in the SDK
After (dc0e29c)
Upload through proxy A (22959):
Create the batch through proxy B (24000) from the file proxy A uploaded:
Poll every 15 s, alternating A and B (
curl -s -i "http://127.0.0.1:$PORT/v1/batches/batch_fdc779da-139c-4390-b086-b9715a6b2f38?provider=xai" -H "Authorization: Bearer $LITELLM_MASTER_KEY"), printingstatus request_counts output_file_idper read. xAI's queue held the third line for about seven minutes and both processes reported its counters as they moved:The final read, on B:
Results through B, one OpenAI style JSONL line per request (usage trimmed to what the billing below uses):
Spend for that completed read, on A, by the call id from step 3:
The row is what the branch's batch prices for
xai/grok-4.3give: input(615 - 448) x 1e-06 + 448 x 1.6e-07 = 0.00023868, output(3 + 446) x 2e-06 = 0.000898, total0.00113668, which is exactly xAI's own11366800cost ticks summed over the three linesList through A with no provider filter:
A file that does not exist, through B:
Second batch from the same file: created on A, cancelled on B, re-read on A:
Delete the input file on A, re-read it on B:
Files list across xAI's page boundary (xAI pages at 100 by
pagination_token). With 9 files on the account, A, B and xAI direct all list 9. Then 100 one-line probe files were uploaded through A until xAI's first page filled and carried apagination_token, and the list was read back through both processes, through the SDK, and directly from xAI:The 100 probe files were then deleted alternating A and B (
deleted 100 of 100) and A listed 9 files againThe reporter's client, the litellm Python SDK (
litellmimported from the tip worktree), running the same lifecycle:Health check of a
mode: batchxAI deployment (grok-4.3-batch = xai/grok-4.3withmodel_info.mode: batch), which an earlier commit of this branch had turned into an OpenAI401 No API key providedand c29a1ff fixed:The same check at the merge base was also healthy, so this is a regression guard rather than a before-fails leg
Mutation check for the tests: reverting
bool(page.pagination_token)tois not Nonefailstest_list_batches_treats_empty_pagination_token_as_last_pageandtest_file_content_stops_paging_on_empty_pagination_token; dropping thelitellm.xai_keyfallback failstest_retrieve_batch_falls_back_to_litellm_xai_key; making the files list stop after the first page failstest_list_files_walks_every_page_by_pagination_tokenObserved next to the fix, none caused by this PR:
modelis empty;batch_modelscarries grok-4.3completed_atstays null on a completed batchafteron batches, honors it on filesfailedType
🆕 New Feature
Caveats (if any)
Medium
output_file_idis the xAI batch id; xAI has no output file, so results are synthesized on read. Deliberate, see the decisions in the TLDRGET /v1/batches?provider=xaithrough managed batches rejects theproviderfilter with a 400; list without it. Pre-existing for every provider (same 400 at the merge base), tracked as a follow-upLow
url, so a file can mix chat, image, and video. Retrieve, list, and cancel report/v1/chat/completionsbecause xAI stores no batch-level endpointbatch_result.response(chat_get_completion,responses,image_generation,video_generation, and so on) and puts that object in the output line'sbody. That is enough to return an image or video payload. xAI says those media URLs expire after 1 hour, and this PR does not download or persist the mediavalidating/finalizingstates; status is derived from counters pluscancel_time, and a cancelled batch reports its still-queued lines asfailed, mirroring xAI's countersstatus: failedwith the xAI message inerrorscreated_at,cancelled_atandexpires_atcome from xAI's date fields, which are day resolution, andcompleted_atstays null because xAI reports no completion time. Passed through as xAI states them rather than replaced by the proxy's clockpurpose: batchand the list'spurposefilter is ignored; xAI stores no purposepagination_tokenup to the shared 10,000-file cap and returns the whole set; the proxy'slimitandafterquery params never reach a provider file list today for any provider, so xAI-specific handling of them in the shared handler would be scope creep hereFinal Attestation
Note
Medium Risk
Touches batch/files routing, synthesized batch output downloads (in-memory pagination), and cost accounting for xAI batches; incorrect status mapping or usage folding could affect operations and billing but does not change core auth paths.
Overview
Adds native xAI support for the OpenAI-style files and batches lifecycle so
custom_llm_provider=xaiworks end-to-end instead of being rejected.New
XAIBatchesHandlerand transformation layer call xAI’s batch APIs (create, retrieve, list, cancel), derive OpenAI-likestatusandrequest_countsfrom xAI counters, and setoutput_file_idto the batch id when complete. Batch results are fetched by pagingGET …/batches/{id}/results, converted to OpenAI JSONL (including chat, responses, image, and video result shapes), and exposed viafile_contentwhen the id is abatch_*id. Files useXAIFilesConfigfor upload, list (with pagination), retrieve, delete, and raw/contentdownload.Wiring updates route xAI through these handlers in
batches/mainandfiles/main, register xAI in file provider literals andProviderConfigManager, extend allowed batch endpoints (image/video routes in types), and fix batch health checks to passcustom_llm_providerintoalist_batches. Billing adds xAI batch token rates (including above-200k batch tiers), folds xAI reasoning tokens into completion tokens when aggregating batch usage, and exposes the new pricing keys in logging/types/schema.Reviewed by Cursor Bugbot for commit dc0e29c. Bugbot is set up for automated code reviews on this repo. Configure here.
Link to Devin session: https://app.devin.ai/sessions/419a69dd7d2948c38360ef2dacaa592a
Open in Devin Desktop: https://app.devin.ai/desktop/session/419a69dd7d2948c38360ef2dacaa592a?variant=devin
Requested by: @mubashir1osmani