Skip to content

fix(streaming): don't split tool-call arg deltas into separate calls (kimi-coding) - #12

Closed
Fede654 wants to merge 10 commits into
nicoechaniz:mainfrom
Fede654:fix/streaming-toolcall-fragmentation
Closed

fix(streaming): don't split tool-call arg deltas into separate calls (kimi-coding)#12
Fede654 wants to merge 10 commits into
nicoechaniz:mainfrom
Fede654:fix/streaming-toolcall-fragmentation

Conversation

@Fede654

@Fede654 Fede654 commented Jun 15, 2026

Copy link
Copy Markdown

Problem

On a kimi-coding deployment, every tool-using turn (web_search, web_extract, read_file, …) failed; the user saw the misleading "Response truncated due to output length limit" (a 48-char error reply). It is not an output-length / max_tokens issue (failing turns were ~4–9 s, tiny output) and not a platform message-length limit (Telegram/WhatsApp both chunk correctly).

Root cause

The chat_completions streaming accumulator in agent/chat_completion_helpers.py merges tool-call argument deltas by index. Its "Ollama fix" redirects to a fresh slot whenever a delta reuses a raw index with a different tool_call id (handles Ollama emitting a back-to-back second tool call at the same index).

kimi-coding sends a changing id on every argument-continuation delta, so each streamed JSON fragment ({", query, ":, oro, …) tripped the new-slot heuristic and became its own bogus, unparseable tool call (name ?). Downstream sanitization replaced each with {} ("Unrepairable tool_call arguments"), so the tool ran with empty args and the agent looped, finally surfacing the truncation error. Overlaps with NousResearch#26425.

Fix

A genuine new tool call always opens with a function.name; argument-continuation deltas carry only function.arguments. Gate the new-slot redirect (and the _last_id_at_idx tracking) on the delta also carrying a name. Provider-neutral — no hardcoded models; preserves the Ollama same-index/new-id-with-name case, fixes the kimi-coding fragmentation.

Second commit (fix(truncation)): detects a tool call whose JSON args open but don't parse as a genuine length-truncation and routes it into the existing retry/boost path instead of silently sanitizing to {}.

Validation

  • Live-validated on a kimi-k2.7 deployment: web_search→searxng and web_extract→firecrawl both work end-to-end, zero Unrepairable tool_call arguments warnings (vs. a flood before).
  • tests/gateway/test_stream_consumer*.py119 passed, no regression.
  • py_compile clean on all touched files.

Refs NousResearch#26425.

🤖 Generated with Claude Code

nicoechaniz and others added 8 commits June 3, 2026 08:15
# Conflicts:
#	gateway/run.py
#	hermes_cli/kanban.py
#	model_tools.py
# Conflicts:
#	agent/conversation_loop.py
#	cli.py
#	gateway/run.py
#	hermes_cli/main.py
Add browser automation via Kimi WebBridge daemon (local HTTP on :10086).
Unlike Playwright-based browser tools, this controls the user's REAL browser
with their actual login sessions.

Tools:
- kimi_webbridge_navigate, find_tab, snapshot, click, fill, evaluate
- kimi_webbridge_screenshot, save_screenshot, save_pdf
- kimi_webbridge_list_tabs, close_tab, close_session

Config: providers.kimi_webbridge.base_url (defaults to 127.0.0.1:10086)
Off by default (requires separate extension + daemon install).

Includes 26 tests with mocked daemon responses.
Extract the true AutoResearch functionality from the archived feat/autoresearch branch without provider/docs churn from Fede's broader fork.
The Kimi Coding Plan picker (hermes_cli/model_setup_flows.py:1800) had a
hardcoded model_list that included kimi-k2.6, k2.5, kimi-for-coding, k2-thinking,
k2-thinking-turbo, but NOT kimi-k2.7-code (the new coding model Moonshot
released 2026-06-12, available on Allegro tier). The general catalog
(hermes_cli/models.py:60) had it, but the curated Coding Plan picker filtered
it out. Added kimi-k2.7-code as the first option, alongside the same edit in
the deprecated copy in hermes_cli/main.py and the kimi-coding curated list
in hermes_cli/models.py:282.

OAuth detection: resolve_kimi_coding_runtime_credentials() already reads
~/.kimi/credentials/kimi-code.json, the runtime_provider auto-routes to
api.kimi.com/coding/v1, and the X-Msh-* headers are applied — k2.7-code
just needs to be selectable. Now it is.
The info command showed a hardcoded default of '60', but the actual
code default from _get_env_config is 180. This mismatch confused
users into thinking the timeout was 60s when it was actually 180s.
Adds the v0.16.0 release entry to the project CHANGELOG with:
- TL;DR for team members on older agents (5 bullet upgrade steps)
- Detailed changelog of all 4 changes that landed on top of upstream sync
- Verification commands (hermes --version, hermes tools, hermes model, OAuth test)
- List of what did not make it (video-gen-minimax PR, tui-history-nav fix)
- DaemonCraft preservation note
- Source-of-truth references (MEMORY.md, branch-stewardship note, HMK ch 61)

The release note is also mirrored to:
- ~/wiki/projects/hermes-agent/notes/release-v0.16.0-2026-06-14.md (wiki; propagated via compaii-state sync)
- HMK chapter 62 (library; queryable by future CompAII instances)

Together these give CompAII agents on older hermes-agent forks a self-service upgrade path.
@Fede654
Fede654 force-pushed the fix/streaming-toolcall-fragmentation branch from 68c5a05 to c0e1fb4 Compare June 15, 2026 18:22
Fede654 added 2 commits June 15, 2026 19:18
…arate calls

The chat_completions streaming accumulator's Ollama new-slot heuristic
fired whenever a delta reused a raw index with a different tool_call id.
kimi-coding sends a *changing* id on every argument-continuation delta, so
each JSON fragment ('{"', 'query', '":', 'oro', ...) was redirected to a
fresh slot and surfaced as its own bogus, unparseable tool call (name '?'),
making every web_search/web_extract/read_file fail.

Gate the new-slot redirect (and the _last_id tracking) on the delta also
carrying a function.name — a genuine new tool call always opens with a
name, continuation deltas never do. Preserves the Ollama same-index/new-id
case; fixes kimi-coding fragmentation.
…ation

kimi-coding (and other chat_completions providers) can return a tool call
whose JSON arguments were cut off mid-generation (e.g. '{"') while
under-reporting finish_reason as 'stop'/'tool_calls' instead of 'length'.
The existing truncated-tool-call retry/boost path only fired on
finish_reason=='length', so these were silently sanitized to '{}' and the
agent looped on an empty tool call (surfacing the misleading 'Response
truncated due to output length limit').

Add _has_truncated_tool_call_args() and upgrade finish_reason to 'length'
when a chat_completions tool call has args that open as a JSON object/array
but fail to parse, routing it into the existing retry/boost recovery.

Refs NousResearch#26425.
@Fede654
Fede654 force-pushed the fix/streaming-toolcall-fragmentation branch from c0e1fb4 to cbffe7a Compare June 15, 2026 19:19
nicoechaniz added a commit that referenced this pull request Jul 7, 2026
Tests the two changes cherry-picked from Fede654 PR #12:

- _has_truncated_tool_call_args: 16 cases covering api_mode guard,
  empty/None/non-string args, valid JSON (object + array), truncated
  JSON (object + array + garbage), dict-shaped tool_calls,
  multi-tool-call batches, whitespace handling.

- Tool-call delta fragmentation gate: asserts that argument-continuation
  deltas (no function.name) at a reused index do NOT redirect to a new
  slot (kimi-coding case), while same-index + new-id + with-name still
  redirects (Ollama case must keep working).

Refs #12 (cherry-picked: 7c50ae9, cbffe7a).
@Fede654 Fede654 closed this Aug 10, 2026
Fede654 added a commit to Fede654/hermes-agent that referenced this pull request Aug 10, 2026
Merged nicoechaniz/main (d5812ea) into the canonical integration and
switched the fork to consume-only.

- Operating principles now say "consume from Nico, don't push upstream";
  the delta stays clean for cheap merges, not for upstreamability.
- "Upstream collaboration" retired: PRs nicoechaniz#11/nicoechaniz#12/nicoechaniz#13 closed 2026-08-10 after
  ~2 months open while his main moved 8k commits (and nicoechaniz#11's target, the
  autoresearch core, had been dropped from his fork entirely).
- PR-hygiene/CI section replaced with sync-time failure triage: reproduce on
  pristine nicoechaniz/main and on the previous canonical, disable
  pytest-randomly before believing a failure, and pip install -e . first so
  missing acp/multipart deps don't read as mass breakage.
- Track the kimi OAuth routing bug doc, which Nico's 68932e3 /
  5e3034b now plausibly fix.
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.

2 participants