Repository navigation
tools: [] sent on automatic-compaction requests → HTTP 400 from OpenAI-compatible backends (vLLM); compaction never succeeds #194
Description
Summary
When a session is run against an OpenAI-compatible backend (--api-format openai-completions) and the conversation contains tool calls, the automatic context compaction request is sent with "tools": []. vLLM rejects that with HTTP 400, so compaction never succeeds and is re-attempted on every following agent step. The main agent requests (which carry the real tool list) keep working, so the failure is easy to miss — but the context is never compacted and keeps growing until the model's window is exhausted.
Environment
- minimax-code:
0.4.12(code references below are againstmain@ 81b666a, 2026-09-19) - Provider: custom,
--api-format openai-completions - Backend: LiteLLM proxy (v1.101.0) in front of vLLM (OpenAI-compatible
/v1/chat/completions)
Observed
Error returned to the client:
400 - {"error":{"message":"`tools` must not be an empty array. Either provide at least one tool or omit the field entirely. (parameter=tools)","type":"BadRequestError","param":"tools","code":400}}
Request pattern once the context passes the compaction threshold: every successful main request (with tools) is accompanied by failing requests carrying roughly the same conversation minus the tool definitions (~30k tokens smaller) — i.e. the compaction checkpoint call. In one session this produced 41 failed requests in 4 minutes while the main turns continued normally. The compaction never completes.
Note that this is not vLLM being strict for its own sake: the OpenAI API itself rejects "tools": [] as well, so any provider that mirrors OpenAI validation will fail here.
Root cause
-
The compaction request is built without a
toolsfield:
packages/local-runtime-v2/src/service/turn-system/compaction/execution/checkpoint-provider.tsL174–187,buildCheckpointRequest()returns{ systemPrompt, messages: [...projected, control] }. The projected messages still contain the assistant tool calls / tool results of the history. -
The vendored pi-ai provider then injects an empty array whenever the history has tool calls but no tools are supplied:
third_party/pi-mono/packages/ai/src/providers/openai-completions.tsL548–550} else if (hasToolHistory(context.messages)) { // Anthropic (via LiteLLM/proxy) requires tools param when conversation has tool_calls/tool_results params.tools = []; }
(
hasToolHistoryis at L48.) This branch is a workaround for Anthropic-behind-a-proxy that came in upstream via earendil-works/pi [Bug]: POST /api/file/save cannot create new files — existing-path resolver 404s before the write #149 / [Bug]: Bash permission deny rules can be skipped via transparent wrappers while allow rules match the stripped form #150. Upstream later fixed the--no-toolscase in #3649 / #3650 but deliberately kept thishasToolHistorybranch, so it is still present in the vendored v0.79.1 and on upstreammain.
The combination (1) + (2) means every compaction call against an OpenAI-compatible backend carries "tools": [].
Reproduction
mcode provider add --name local --base-url http://<vllm-or-proxy>/v1 --api-format openai-completions --model <model> --api-key-env KEY --use- Work in a session that uses tools until the context reaches the automatic-compaction trigger (a few hundred k tokens of tool output gets there quickly).
- Watch the backend log: each agent step now logs one 200 (main request) and a 400 with the message above (compaction request).
Minimal backend-side check, independent of mcode:
curl -s http://<vllm>/v1/chat/completions -H 'Content-Type: application/json' \
-d '{"model":"<model>","messages":[{"role":"user","content":"hi"}],"tools":[]}'
# -> 400 "`tools` must not be an empty array"Suggested fix
Any of these would do; (a) is the smallest and matches the repo's vendor-patch workflow (third_party/pi-mono/MINIMAX_CHANGES.md):
- (a) In the vendored provider, drop the
else if (hasToolHistory(...))branch, or gate it so it only fires for Anthropic-style compat (e.g.compat.cacheControlFormat === "anthropic"). Upstream'sopenai-completions-empty-tools.test.tscovers the surrounding cases and would need its third case inverted. - (b) In
transformLocalTurnPayload(local-turn-payload-transform.ts), which already runs for both main and compaction requests:if (Array.isArray(payload.tools) && payload.tools.length === 0) delete payload.tools; - (c) Pass the tool list through in
buildCheckpointRequest— the compactor already receivestoolsin its input — though (a)/(b) are cleaner since the checkpoint call is not supposed to call tools.
Workaround
--api-format anthropic-messages or --api-format openai-responses: both provider paths only set tools when the list is non-empty, so the compaction request goes through.
Activity
hetaoBackend commentedon Sep 19, 2026
Thanks for the detailed trace. I reproduced the checkpoint-to-HTTP failure on
mainat81b666ausing a local OpenAI-compatible fixture that rejectstools: [], and opened #199 with a fix.The checkpoint builder preserves historical tool calls/results but supplies no tool definitions. The vendored OpenAI Completions adapter then injected an empty array solely because of that history. The fix removes that injection: absent or empty tool definitions now omit
tools; nonempty definitions are unchanged.After checking the API documentation, the PR is now limited to this omission, with no compatibility setting or additional retry. Anthropic's February 27, 2025 release notes explicitly removed the requirement to supply
toolsalongside historical tool blocks.Validation at
237f0504324034dbb9be3447bc654f166967c7ae: all 7 focused real-SDK/local-HTTP regressions and all 14 gates ofpnpm verifypassed on macOS arm64 / Node 26.4.0. The tests verify successful checkpoint replacement, preserved history, and the serialized request body.This remains a PR, not a released fix. Live LiteLLM/vLLM acceptance is unverified. The regression invokes compaction directly with synthetic admission sizing rather than driving an interactive session to its automatic threshold.