Repository navigation
Move litellm to 1.95.0 (GHSA-g5ff-637f-6q2m) and accept it in the tool pilot's Gemini patch - #252
Conversation
`uv lock --upgrade-package litellm==1.95.0`. Dependabot alert #96 covers every litellm below 1.95.0, and no 1.88.x release after 1.88.6 exists. No other package moves; pyproject.toml is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6K3qkpWxSr7Tw1Lc2m
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Ready for hub review Head: Alert and how this PR resolves it
Request bodies: none change. 635 captured requests are identical in method, URL and body under 1.88.6 and 1.95.0, with litellm's remote cost map. That covers every Tests
Judgment calls
Generated by Claude Code |
The lock moved to litellm 1.95.0, which the Gemini search-tool gate refused. Offline captures of every pilot arm (Gemini, Claude and GPT across pe, web and web+pe, plus Claude's deferred server results) match 1.88.6's request bodies except in one place: Gemini web+pe continuations now return each server-side search part's thoughtSignature on its own part, where 1.88.x sent the call's signature on both. - Add 1.95.0 to GEMINI_SEARCH_PATCH_LITELLM and say what changed. - Record, per checked release, which tools litellm keeps unpatched and which signatures it re-sends, and require one entry per gated release. - Run the Gemini web+pe arm end to end through litellm's own Gemini code with canned replies, and pin the turn-0 tools and the turn-1 signatures for the installed release. - Name 1.95.0 beside 1.88.x where the docs say how litellm reports the Gemini model and re-sends server results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review This is an independent review by a Claude Opus 5.5 cloud session for the PolicyBench owner session. Verdict: REQUEST_CHANGES at The PR head on GitHub moved to The code and tests hold up. I'm asking for changes because of one description claim about scored rows that my run contradicts. The fix is text only. The other findings are non-blocking. Setup: a clone at the head, and Blocking1. Empty
|
The test comment now cites the tool-combination guide, which asks a client that keeps its own history to send back each tool call's and result's signature; the thought-signatures page covers only the Interactions API. The litellm comment adds that the pilot's function-calling turns carry no grounding metadata, so on 1.95.0 any toolUsePromptTokenCount Gemini reports there adds to prompt tokens and cost, while Google bills Google Search per query. The patch docstring says only a test run on 1.88.x exercises the patch body. Comments and one docstring only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review This is an independent review by a Claude Opus 5.5 cloud session for the PolicyBench owner session. Verdict: APPROVE at The PR head on GitHub is still Setup: a clone at the head, and BlockingNone. Non-blocking1. The probe the comment and description cite is in
|
The comment on GEMINI_SEARCH_PATCH_LITELLM pointed at bill_searches, but the live probe that found no grounding metadata on function-calling turns sits in turn_searches's docstring; bill_searches calls turn_searches. The change touches only a comment, so the module's AST stays the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The round 2 reviewer (#252 (comment)) approved |
This PR moves litellm from 1.88.6 to 1.95.0 in
uv.lockfor Dependabot alert #96 (high, GHSA-g5ff-637f-6q2m, "internal_user_viewer arbitrary local file read viavertex_ai_credentials"). It also adds 1.95.0 to the litellm releases that the tool pilot's Gemini search patch accepts, since the lock bump alone trips the patch's version gate. It stays separate from #251 (pyasn1, tornado, filelock) because litellm builds every synchronous benchmark request. The two PRs touch different lock entries, so either can land first.The board's requests don't change. Under 1.88.6 and 1.95.0, litellm builds the same method, URL and JSON body for all 635 requests we captured from
policybench.config.MODELS, and only theuser-agentheader differs. In the tool pilot, 10 of 11 captured provider-and-arm cases send identical bodies. The Gemini web+pe continuation turn changes one field: 1.95.0 sends each Google Search part's ownthoughtSignatureback on that part, where 1.88.6 sent one signature on both parts.pyproject.tomldoesn't change.policyengine,policyengine-us,policyengine-ukandpolicyengine-corekeep their exact lock entries frommain, so no reference answer moves.The advisory and the version
litellm[caching]>=1.30)No 1.88.x release clears the alert. PyPI lists 1.88.6 as the last 1.88.x release, and the advisory covers every release below 1.95.0, so no 1.89–1.94 release clears it either.
uv lock --upgrade-package litellmalone picked 1.106.6, so we ranuv lock --upgrade-package 'litellm==1.95.0'.No other package moves. litellm 1.95.0's lock entry lists the same dependencies as 1.88.6's. Compared field by field, the two lockfiles differ only in litellm's
version,sdistandwheels, and their lock metadata matches.1.88.6 already carries the fix commit. The advisory points to BerriAI/litellm 065faf6 (#34189). That commit changes seven library files:
proxy/auth/auth_utils.pyaddsvertex_ai_credentialsto the request-body blocklist and checks fallback targets for banned parameters and URL destinations;proxy/auth/user_api_key_auth.pyvalidates fallbacks through the new fallback helper;litellm_core_utils/url_utils.pyaddsprovider_url_destination_candidates, andproxy/litellm_pre_call_utils.pychecks URL destinations with it;http://orhttps://prefix, andhuggingface/embedding/transformation.pydrops its ownget_api_base.The installed 1.88.6 and 1.95.0 packages both contain every one of those changes. In clean installs of
litellm[proxy]1.88.6, 1.94.3 and 1.95.0,is_request_body_safe(request_body={"model": "gemini-pro", "vertex_ai_credentials": "/etc/passwd"}, ...)raisesRejected Request: vertex_ai_credentials is not allowed in request bodyon all three. Dependabot flags any release below 1.95.0 all the same, and landing 1.95.0 clears the alert.1.95.0 has no macOS wheel. PyPI lists manylinux and Windows wheels and the sdist, and litellm 1.95.0 includes a Rust extension, so
uv syncon a Mac builds it from source with maturin. On a Mac with Python 3.14 it builtcp314-cp314-macosx_11_0_arm64. litellm 1.95.1 and later ship macOS wheels.PolicyBench doesn't run the litellm proxy.
git grepfinds nolitellm.proxyimport or proxy launch inpolicybench/,scripts/orpyproject.toml. The vulnerable endpoint (/utils/transform_request) belongs to the proxy server.What changes in litellm on PolicyBench's request path
We installed 1.88.6 (from
main's lock) and 1.95.0 (from this lock) into separate virtual environments. We sorted each file as byte-identical, format-only (its Python syntax trees match, since 1.95.0 rewrapped much of the code at a longer line length) or changed.litellm/main.pyresponses/utils.pyllms/anthropic/llms/openai/llms/gemini/llms/vertex_ai/gemini/litellm_core_utils/prompt_templates/factory.pylitellm_core_utils/llm_response_utils/To find the other modules on the request path, we traced every litellm file whose code ran during the request captures below: 90 files under 1.95.0 and 84 under 1.88.6. Code changed in 77 files across both lists, among them
__init__.py,cost_calculator.py,exceptions.py,caching/caching.pyandcaching_handler.py,litellm_core_utils/exception_mapping_utils.py,get_llm_provider_logic.py,completion_timeout.py,llms/custom_httpx/llm_http_handler.py,http_handler.pyandaiohttp_transport.py, and thexai,deepseekandopenrouterchat transformations. Six files first appear in 1.95.0, among themlitellm_core_utils/request_timeout_resolver.py,litellm_core_utils/fallback_generalizations.pyandllms/xai/oauth.py.1.95.0 restructures
completion()inmain.py: it adds a dispatch context and an agentic-loop hook, and callsAnthropicCacheControlHook.maybe_seed_default_injection_pointsfrom bothcompletion()andacompletion(). We read the diffs and ran both versions against faked provider responses and errors.Unchanged for PolicyBench's calls
timeout=_request_timeout_seconds(model_id)(policybench/eval_no_tools.py), which takes priority over the new global-timeout fallback incompletion_timeout.pyandrequest_timeout_resolver.py.insufficient_quota), 500, 502, 503, 504 and 529, a context-window 400, and connect, read-timeout and protocol errors, both versions raise the same exception class, message,status_codeandllm_provider. PolicyBench's_should_retry,_is_model_fatal_errorand_is_retryable_provider_errorreturn the same results.exceptions.pyonly adds attributes toRateLimitError.policybench/cache.pybuilds identical keys, and 1.95.0 reads entries 1.88.6 wrote as cache hits.tool_calls,function_call, finish reason,id,system_fingerprint,model, reasoning and cached-token counts, and_hidden_params["cache_hit"].model_response.model = model,llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py:2451). This PR names 1.95.0 beside 1.88.x in the two notes that record it, inpolicybench/config.py(gemini-3.7-flash entry) anddocs/runbook.md:211.integrations/anthropic_cache_control_hook.pyreturns[]unlesslitellm.enable_anthropic_prompt_caching is True. litellm reads that setting fromLITELLM_ENABLE_ANTHROPIC_PROMPT_CACHING,"false"when unset, and nothing inpolicybench/orscripts/sets it or passescache_control_injection_points.Changed, and reached by PolicyBench only in edge cases
"choices": [], 1.88.6 rejected only aNonechoicesand returned the empty list, so PolicyBench'sresponse.choices[0]raisedIndexError. 1.95.0 raisesAPIError(status_code=500, "LiteLLM: provider returned a response with no 'choices'...")(litellm_core_utils/llm_response_utils/convert_dict_to_response.py:631), and the board handles the household differently. Under 1.88.6,run_no_tools_evalrecorded a failed row with theIndexError, and sinceis_infrastructure_error_textreturns False for that error, the row scored as a model miss. Under 1.95.0,_is_retryable_provider_errorreturns True for theAPIError, as it does for a provider's HTTP 500, so the run saves a checkpoint and raises (policybench/eval_no_tools.py:3597). That ends theeval-no-toolscall, so the models after this one in--modelsdon't run. A supervised run starts oneeval-no-toolsworker per household, each with one--model(policybench/supervisor.py:689-711), and a resume retries the household. No committed result row carries anIndexErrorerror.LITELLM_LOCAL_MODEL_COST_MAPtells it to, and PolicyBench sets that variable only in two tests. 1.95.0's bundled map addsxai/grok-4.5,claude-opus-5andclaude-sonnet-5. On the bundled map, 1.88.6 can't routeclaude-opus-5orclaude-sonnet-5("LLM Provider NOT provided") and can't pricexai/grok-4.5, while 1.95.0 routes and prices all three. Both versions fetch the remote map from the same URL at import, and on it they price all three the same (litellm.cost_per_token).policybench/case_reference_explanations.py,litellm.acompletion) goes throughllms/custom_httpx/aiohttp_transport.py, where 1.95.0 raiseshttpx.ReadErrorwhen a connection closes mid-body; 1.88.6 returned the partial body. We did not test this.cache_write_tokens/cache_creation_tokens,service_tieranditerationsfields. PolicyBench readscache_write_tokensfirst, falls back tocache_creation_tokens, and extracts the same value. 1.95.0 dropsmessage.provider_specific_fields["reasoning_content"]and keepsmessage.reasoning_content; PolicyBench reads neither.Changed, behind settings PolicyBench doesn't use
1.95.0 rewrites
reasoning_effortfor OpenRouter ("max"→"xhigh") and DeepSeek ("none"→thinking: disabled), and PolicyBench'sREASONING_EFFORT_OVERRIDESholds no entries. It raises a Responsesmax_output_tokensbelow 16 to 16. Its Anthropic adaptive-thinking and native-structured-output checks now read the cost map, and only a request withthinking,reasoning_effortor ajson_schemaresponse format reaches them. Adrop_params=argument toresponses()now takes effect. xAI OAuth (llms/xai/oauth.py) runs only withuse_xai_oauthand no key. 1.95.0 also changes streaming code, and PolicyBench doesn't stream.Board request bodies
We patched
httpx.Client.sendandhttpx.AsyncClient.sendafter importing litellm and PolicyBench, so the import-time model-map fetch went out normally. Each patchedsendrecorded the request and raised, so nothing reached a provider. With dummy API keys andPOLICYBENCH_MAX_ATTEMPTS=1, we drove PolicyBench's own functions:run_single_no_toolsfor every one of the 52MODELSentries, on a US household (TX, joint, two children) and a UK household (London, one adult), each withinclude_explanations=TrueandFalse. The capture includes the board's chunking: a model whose explanation contract chunks the outputs sends several requests._request_predictions_once(..., repair=True)with and without explanations, and_request_explanations_once, for every model.case_reference_explanations._generate_one, which asksclaude-haiku-4-5withtemperature: 0.0andmax_tokens: 500; both versions forwardtemperature.Each version sent 635 requests in 365 groups, to
api.anthropic.com/v1/messages,api.openai.com/v1/responses,generativelanguage.googleapis.com(v1alphaandv1betagenerateContent),api.x.ai,api.deepseek.com/betaandopenrouter.ai.user-agentheader differs.LITELLM_LOCAL_MODEL_COST_MAP=True), 1.88.6 builds no request forclaude-opus-5orclaude-sonnet-5, as above, and 1.95.0 builds the same 51 requests for them that both versions build with the remote map. The other 584 match.We checked only the bodies litellm builds from PolicyBench's arguments. The board never sends a provider's reply back in a later request. The tool pilot does, so we captured its conversations separately. The batch path (
policybench/batch_eval.py) calls provider SDKs directly, so litellm never touches it.The tool pilot
allow_gemini_search_with_functionsinscripts/tool_pilot_argon.pyreplaces twoVertexGeminiConfigmethods,_resolve_search_tool_conflictand_drop_search_tools_mixed_with_functions, so the pilot's Gemini web arms keep Google Search beside their function tools. It refuses any litellm outsideGEMINI_SEARCH_PATCH_LITELLM, and this PR adds 1.95.0 to that set.The patched methods
1.95.0 rewraps the lines of both patched methods, and their syntax trees match 1.88.6's. It adds
_apply_include_server_side_tool_invocations, whichmap_openai_paramscalls before it maps any parameter. Its docstring reads "map_openai_params iterates non_default_params in request order; if tools appear before this flag, _resolve_search_tool_conflict would drop search tools before the flag is applied." The pilot's web arms always set that flag, so 1.95.0 keeps the search tool by itself. The test's own call,get_optional_params(model="gemini-4-argon", tools=[{"googleSearch": {}}, calculate_tool], tool_choice="required", include_server_side_tool_invocations=True), returns these tools:function_declarationsfunction_declarations,googleSearchfunction_declarations,googleSearchfunction_declarations,googleSearchWithout the flag, unpatched 1.95.0 still drops search.
Pilot request bodies
We drove the pilot's own
run_householdthrough litellm'scompletionandresponsesunder each version, withhttpx.Client.sendpatched to record each request and answer with a canned provider reply. The 11 cases cover Gemini 4 Argon, Claude Opus 5.5 and GPT-6 Sol on the pe, web and web+pe arms, plus Claude's two web arms with the search result arriving in the next response. In 10 cases every request body matches between versions. Our canned Claude answers missed the pilot's JSON answer format, so those runs also went through its two repair prompts and ended incomplete under both versions, with matching bodies request for request.In the Gemini web+pe case, the continuation turn re-sends the first reply's Google Search parts with different signatures:
SIG_CALL, resultSIG_RESPSIG_CALLon bothSIG_CALLon the call,SIG_RESPon the resultSIG_RESPonlySIG_RESPon bothSIG_RESPon the resultGoogle's guide to combining built-in tools with function calling (ai.google.dev/gemini-api/docs/tool-combination) says every built-in tool call and result carries its own signature, and asks a client that keeps its own history to send each one back. 1.95.0 does that.
Two other changes reach the pilot:
toolUsePromptTokenCounttoprompt_tokensunless the response carries search grounding (groundingMetadata.webSearchQueries). In a canned Gemini web+pe run, for a reply that reported 1,000 prompt tokens and 150 tool-use tokens without grounding, the pilot recorded 1,150 prompt tokens under 1.95.0 and 1,000 under 1.88.6. The pilot's live probe found no grounding metadata on function-calling turns (docstring ofturn_searches), so on the web arms 1.95.0 adds the tool-use tokens on every turn, search turns included. If live Gemini reports tool-use tokens on those turns, a 1.95.0 run prices them at the input rate on top of the per-query search fee. Google's guide says it doesn't charge for Google Search's tool tokens, since it prices each search per query (ai.google.dev/gemini-api/docs/tool-combination). 1.95.0 also stops counting thoughts twice incompletion_tokenswhencandidatesTokenCountalready includes them and a response reports tool-use tokens. A 1.95.0 web-arm run's token costs don't compare with a 1.88.x run's. Each run's manifest records its litellm version._attach_deferred_results, no later request in either version carries it; with it, both versions carry it from the third request on. This PR names 1.95.0 beside 1.88.x in that method's docstring.Tests
UNPATCHED_GEMINI_TOOLSandRESENT_SEARCH_SIGNATURESrecord, for each gated release, the tools litellm keeps without the patch and the signatures it sends back. Two tests require an entry for every release inGEMINI_SEARCH_PATCH_LITELLM, so the next bump has to record both.test_the_gemini_patch_keeps_search_beside_functionschecks the installed release's unpatched tools against its entry, then checks that the patch keeps search.test_the_gemini_web_and_tool_arm_runs_through_the_locked_litellmruns the web+pe arm through litellm's own Gemini code with canned replies. It checks that turn 0 sendsgoogleSearchbesidefunction_declarationswithincludeServerSideToolInvocationsset, and that turn 1 sends the installed release's signatures on the search parts andSIG_FNon the function call.Each of these mutants fails a test:
Running that arm without the patch on 1.95.0 passes, since 1.95.0 keeps the search tool by itself, so CI, which runs the locked 1.95.0, can't catch a broken patch body. Only a test run on 1.88.x exercises it, as the patch's docstring says.
Verification
77ebb5e: lint, test (7,010 passed, 90 skipped), app, paper and the Vercel preview pass.77ebb5echanges one word in a comment (bill_searches→turn_searches), andscripts/tool_pilot_argon.py's syntax tree matchesdc43c51's.dc43c51: lint, test (7,010 passed, 90 skipped), app, paper and the Vercel preview pass.081727bmergesmainagain, bringing in Merge batch repair rounds cell by cell, like sync #180, Record PolicyEngine provenance without importing policyengine #186, Pin the reference adversary's leaderboard impact to the pass's inputs #205 and Patch pyasn1, tornado and filelock in uv.lock (16 Dependabot alerts) #251;git show --remerge-diffprints nothing for it.dc43c51changes only comments and the patch's docstring: the two files' syntax trees match apart from that docstring.d9f99ab: lint, test (6,903 passed, 90 skipped), app, paper and the Vercel preview pass.d9f99abmergesmain, which brings in Count only real request timeouts in the supervisor's concurrency backoff #183 and Probe card-less models under the card the onboarding gauntlet derives #189. Count only real request timeouts in the supervisor's concurrency backoff #183's supervisor finds request timeouts in worker logs withlog_shows_request_timeout, and its tests use log lines that workers wrote under litellm 1.88.1. On 1.95.0,log_shows_request_timeoutstill matches a printedlitellm.Timeoutand passes over litellm's warning that its cost-map fetch timed out.c55e34a, the lock bump alone: lint, app, paper and the Vercel preview passed. The test job failed only the two gate tests,test_the_gemini_patch_keeps_search_beside_functionsandtest_the_locked_litellm_is_one_the_gemini_patch_was_checked_against(assert '1.95.0' in frozenset({'1.88.1', '1.88.6'})), with 6,781 passed and 90 skipped.tests/test_tool_pilot_argon.pygives 150 passed and 1 skipped on 1.95.0 (Python 3.14.0) atd9f99aband atdc43c51, and the 9 tests whose names matchgemini,checked_litellmorsignaturespass on 1.88.6 too.ruff checkandruff format --checkpass on the changed files.Invariants
policyengine,policyengine-us,policyengine-ukandpolicyengine-corekeep their exact lock entries frommain.uv sync --lockedaccepts the lock, anduv lock --checkpasses under uv 0.11.32, 0.13.4 and 0.13.6. A newer uv rewrote the lockfile'srevision = 3header andpexpect'sptyprocessmarker; both lines now matchmain's.axiom: n/a: dependency security bump, no policy logic🤖 Generated with Claude Code