Fix tool_use message format for Anthropic API
All checks were successful
Build Nanobot OAuth / build (push) Successful in 1m59s
All checks were successful
Build Nanobot OAuth / build (push) Successful in 1m59s
The agent loop produces messages in OpenAI format (role:tool, tool_calls array) but the Anthropic API expects its own format (tool_use content blocks in assistant messages, tool_result blocks in user messages). This caused 400 errors whenever the bot tried to use tools like web_search, because the follow-up message with tool results was malformed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -68,21 +68,87 @@ class AnthropicOAuthProvider(LLMProvider):
|
||||
self,
|
||||
messages: list[dict[str, Any]]
|
||||
) -> tuple[str | None, list[dict[str, Any]]]:
|
||||
"""Prepare messages, extracting system prompt and adding Claude Code identity.
|
||||
"""Prepare messages: extract system prompt and convert OpenAI format to Anthropic.
|
||||
|
||||
Returns (system_prompt, messages_without_system)
|
||||
The agent loop produces messages in OpenAI format:
|
||||
- assistant msgs with tool_calls [{type:"function", function:{name, arguments}}]
|
||||
- tool role msgs with tool_call_id, name, content
|
||||
|
||||
Anthropic API expects:
|
||||
- assistant msgs with content blocks [{type:"tool_use", id, name, input}]
|
||||
- user msgs with content blocks [{type:"tool_result", tool_use_id, content}]
|
||||
|
||||
Returns (system_prompt, anthropic_messages)
|
||||
"""
|
||||
system_parts = [get_claude_code_system_prefix()]
|
||||
filtered_messages = []
|
||||
converted: list[dict[str, Any]] = []
|
||||
|
||||
for msg in messages:
|
||||
if msg.get("role") == "system":
|
||||
role = msg.get("role")
|
||||
|
||||
if role == "system":
|
||||
system_parts.append(msg.get("content", ""))
|
||||
else:
|
||||
filtered_messages.append(msg)
|
||||
continue
|
||||
|
||||
if role == "assistant" and msg.get("tool_calls"):
|
||||
# Convert OpenAI tool_calls to Anthropic content blocks
|
||||
content_blocks: list[dict[str, Any]] = []
|
||||
text = msg.get("content")
|
||||
if text:
|
||||
content_blocks.append({"type": "text", "text": text})
|
||||
for tc in msg["tool_calls"]:
|
||||
func = tc.get("function", {})
|
||||
args = func.get("arguments", "{}")
|
||||
if isinstance(args, str):
|
||||
try:
|
||||
args = json.loads(args)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
args = {}
|
||||
content_blocks.append({
|
||||
"type": "tool_use",
|
||||
"id": tc.get("id", ""),
|
||||
"name": func.get("name", ""),
|
||||
"input": args,
|
||||
})
|
||||
converted.append({"role": "assistant", "content": content_blocks})
|
||||
continue
|
||||
|
||||
if role == "tool":
|
||||
# Convert tool result to Anthropic user message with tool_result block
|
||||
tool_result_block = {
|
||||
"type": "tool_result",
|
||||
"tool_use_id": msg.get("tool_call_id", ""),
|
||||
"content": msg.get("content", ""),
|
||||
}
|
||||
# Merge into previous user message if it already has tool_result blocks
|
||||
if converted and converted[-1].get("role") == "user":
|
||||
prev_content = converted[-1].get("content")
|
||||
if isinstance(prev_content, list):
|
||||
prev_content.append(tool_result_block)
|
||||
continue
|
||||
converted.append({"role": "user", "content": [tool_result_block]})
|
||||
continue
|
||||
|
||||
if role == "user":
|
||||
content = msg.get("content", "")
|
||||
# Merge text into previous user message if it has tool_result blocks
|
||||
# (handles the "Reflect on the results" interleaved message)
|
||||
if converted and converted[-1].get("role") == "user":
|
||||
prev_content = converted[-1].get("content")
|
||||
if isinstance(prev_content, list):
|
||||
if isinstance(content, str):
|
||||
prev_content.append({"type": "text", "text": content})
|
||||
elif isinstance(content, list):
|
||||
prev_content.extend(content)
|
||||
continue
|
||||
converted.append({"role": role, "content": content})
|
||||
continue
|
||||
|
||||
# Pass through other messages (assistant without tool_calls, etc.)
|
||||
converted.append(msg)
|
||||
|
||||
system_prompt = "\n\n".join(system_parts)
|
||||
return system_prompt, filtered_messages
|
||||
return system_prompt, converted
|
||||
|
||||
def _convert_tools_to_anthropic(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user