Skip to content

Accept sanitised Kimi tool-call ids and return the canonical form - #2282

Open
Bdwg222 wants to merge 1 commit into
exo-explore:mainfrom
Bdwg222:fix/kimi-tool-call-ids
Open

Accept sanitised Kimi tool-call ids and return the canonical form#2282
Bdwg222 wants to merge 1 commit into
exo-explore:mainfrom
Bdwg222:fix/kimi-tool-call-ids

Conversation

@Bdwg222

@Bdwg222 Bdwg222 commented Aug 31, 2026

Copy link
Copy Markdown

Motivation

Fixes #2281 (part 1 of 2).
_parse_kimi_tool_calls requires the id immediately before <|tool_call_argument_begin|> to match functions.<name>:<n>. Kimi K2 models also emit functions_<name>_<n> — they do so whenever a client has echoed a sanitised id back in the conversation history, which is common (OpenAI-style clients restrict ids to [A-Za-z0-9_-]). That produces a hard parse failure, surfaced to the client as an ErrorChunk whose message is the raw tool-call markup (streaming) or a bare 500 (non-streaming). In practice the second tool call in any agent session fails deterministically.

Changes

  • src/exo/worker/engines/mlx/utils_mlx.py_parse_kimi_tool_calls: the id regex now accepts . or _ after functions, and : or _ before the index. The returned id is always the canonical functions.<name>:<n> regardless of which form the model emitted.
  • src/exo/worker/tests/unittests/test_runner/test_kimi_tool_parser.py (new) — unit tests for the canonical form, the sanitised form, underscores inside function names, a missing functions. prefix, multiple calls with mixed forms, and the still-rejected no-index case.

Why It Works

The trailing [:_]\d+ is unambiguous even when the function name contains underscores (functions_read_file_3read_file, 3): the lazy name group can only stop where separator + digits + the argument marker follow, so it cannot split the name early.
Returning the canonical id also breaks the feedback loop that causes the bug — the client now receives functions.<name>:<n>, echoes that back in the history, and the model keeps emitting the form it was trained on.

Test Plan

Manual Testing

Hardware: two Mac Studio M3 Ultra 512GB, two-node MLX ring, serving moonshotai/Kimi-K2.7-Code.

Automated Testing

  • New test_kimi_tool_parser.py covers the cases listed under Changes.
  • Existing test_parse_tool_calls.py unaffected; behaviour on parse failure is unchanged in this PR.

Not in this PR

Part 2 of #2281 — having parse_tool_calls degrade to plain text on a parse failure instead of emitting finish_reason="error" — is a deliberate behaviour change (the current behaviour is asserted in test_failed_parse_yields_text), so it is kept out of this PR for discussion on the issue first.

Fixes exo-explore#2281 (part 1). Kimi K2 emits functions_<name>_<n> when a client has
echoed a sanitised id back in the history; the parser only accepted
functions.<name>:<n> and failed hard. Accept both, always return canonical.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kimi K2 tool-call parsing fails when the model emits functions_<name>_<n> ids; failure surfaces as an error chunk containing the raw tool-call markup

1 participant