fix(providers): dedupe tool_use ids to prevent Anthropic 400s
Anthropic rejects any request where two tool_use blocks share an id
("messages.N.content.M: tool_use ids must be unique"). A mis-assembled
stream could surface the same tool_use block twice in one assistant turn;
the runner persisted it verbatim, so the malformed message was re-sent on
every subsequent turn and permanently bricked the session — the agent
silently stopped replying.
Fix at two layers:
- AnthropicProvider._parse_response: drop duplicate tool_use ids (keep
first) as the response enters nanobot, so corruption is never persisted.
- AgentRunner._dedup_tool_calls: a new context-governance pass that dedupes
assistant tool_calls and tool results by id before each send, healing any
history that was already corrupted.
Add regression tests covering both the dedup and the no-op fast path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
committed by
Xubin Ren
co-authored by
Claude Opus 4.8
parent
160cec2396
commit
6689e2d377
@@ -10,6 +10,8 @@ import string
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from nanobot.providers.base import (
|
||||
LLMProvider,
|
||||
LLMResponse,
|
||||
@@ -522,11 +524,19 @@ class AnthropicProvider(LLMProvider):
|
||||
content_parts: list[str] = []
|
||||
tool_calls: list[ToolCallRequest] = []
|
||||
thinking_blocks: list[dict[str, Any]] = []
|
||||
seen_tool_ids: set[str] = set()
|
||||
|
||||
for block in response.content:
|
||||
if block.type == "text":
|
||||
content_parts.append(block.text)
|
||||
elif block.type == "tool_use":
|
||||
# Anthropic requires every tool_use id to be unique. A mis-assembled
|
||||
# stream can occasionally surface the same block twice; keep the first
|
||||
# and drop the duplicate so it never poisons the persisted history.
|
||||
if block.id in seen_tool_ids:
|
||||
logger.warning("dropping duplicate tool_use id from response: {}", block.id)
|
||||
continue
|
||||
seen_tool_ids.add(block.id)
|
||||
tool_calls.append(ToolCallRequest(
|
||||
id=block.id,
|
||||
name=block.name,
|
||||
|
||||
Reference in New Issue
Block a user