Skip to content

tools: [] sent on automatic-compaction requests → HTTP 400 from OpenAI-compatible backends (vLLM); compaction never succeeds #194

Closed

Description

@SirTificate

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 against main @ 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

  1. The compaction request is built without a tools field:
    packages/local-runtime-v2/src/service/turn-system/compaction/execution/checkpoint-provider.ts L174–187, buildCheckpointRequest() returns { systemPrompt, messages: [...projected, control] }. The projected messages still contain the assistant tool calls / tool results of the history.

  2. 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.ts L548–550

    } else if (hasToolHistory(context.messages)) {
        // Anthropic (via LiteLLM/proxy) requires tools param when conversation has tool_calls/tool_results
        params.tools = [];
    }

    (hasToolHistory is 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-tools case in #3649 / #3650 but deliberately kept this hasToolHistory branch, so it is still present in the vendored v0.79.1 and on upstream main.

The combination (1) + (2) means every compaction call against an OpenAI-compatible backend carries "tools": [].

Reproduction

  1. mcode provider add --name local --base-url http://<vllm-or-proxy>/v1 --api-format openai-completions --model <model> --api-key-env KEY --use
  2. 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).
  3. 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's openai-completions-empty-tools.test.ts covers 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 receives tools in 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

  1. hetaoBackend commented on Sep 19, 2026

    @hetaoBackend
    Collaborator

    Thanks for the detailed trace. I reproduced the checkpoint-to-HTTP failure on main at 81b666a using a local OpenAI-compatible fixture that rejects tools: [], 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 tools alongside historical tool blocks.

    Validation at 237f0504324034dbb9be3447bc654f166967c7ae: all 7 focused real-SDK/local-HTTP regressions and all 14 gates of pnpm verify passed 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.

  2. added
    bugSomething isn't working
    needs-triageAwaiting maintainer assessment
    cliStandalone mcode: TUI, headless, ACP and source builds/tooling
    and removed
    needs-triageAwaiting maintainer assessment
    on Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcliStandalone mcode: TUI, headless, ACP and source builds/tooling

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions