Skip to content
Merged

mega #47

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions app/adapters/anthropic_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import time
from typing import Any

from app.reasoning import extract_reasoning_text, map_reasoning_controls
from app.upstream_io import (StreamOutputBudget, merge_tool_call_delta, new_tool_state,
seal_tool_identities, tool_identity_complete)

Expand Down Expand Up @@ -93,6 +94,7 @@ def anthropic_request_to_chat(body: dict) -> dict:
raise ValueError("stop_sequences must be an array of strings")
chat["stop"] = stop_sequences

map_reasoning_controls(body, chat, protocol="messages")
return chat


Expand Down Expand Up @@ -124,6 +126,9 @@ def _convert_anthropic_message(msg: dict) -> list[dict]:

# Structured content blocks
blocks = content
if role != "assistant" and any(isinstance(block, dict) and block.get("type") in
("thinking", "redacted_thinking") for block in blocks):
raise ValueError("thinking requires an assistant message")

# User messages may contain tool results.
if role == "user":
Expand Down Expand Up @@ -157,11 +162,14 @@ def _convert_anthropic_message(msg: dict) -> list[dict]:
if role == "assistant":
content_out = _convert_content_blocks(blocks)
tool_calls: list[dict] = []
thoughts: list[str] = []
for block in blocks:
if not isinstance(block, dict):
continue
bt = block.get("type", "")
if bt == "tool_use":
thought = extract_reasoning_text(block)
if thought is not None:
thoughts.append(thought)
elif block.get("type") == "tool_use":
tc = {
"id": block.get("id", _rand_id("call_")),
"type": "function",
Expand All @@ -176,6 +184,8 @@ def _convert_anthropic_message(msg: dict) -> list[dict]:
msg_out["content"] = content_out if content_out or has_text else None
if tool_calls:
msg_out["tool_calls"] = tool_calls
if thoughts:
msg_out["reasoning_content"] = "".join(thoughts)
return [msg_out]

content_out = _convert_content_blocks(blocks)
Expand Down
14 changes: 7 additions & 7 deletions app/adapters/chat_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from fastapi import HTTPException

from app.reasoning import ReasoningInputError, extract_reasoning_text


_ANTHROPIC_BLOCKS = ("tool_use", "tool_result", "thinking", "redacted_thinking", "image")

Expand Down Expand Up @@ -116,17 +118,15 @@ def _convert_message(message, index, pending):
raise _invalid(location + ".tool_use_id", "tool_result must match one preceding, unanswered tool call")
result_ids.add(identifier)
results.append(result)
elif kind == "thinking":
elif kind in ("thinking", "redacted_thinking"):
if role != "assistant":
raise _invalid(location, "thinking requires an assistant message")
if message.get("reasoning_content") not in (None, ""):
raise _invalid(location, "thinking conflicts with existing reasoning_content")
if not isinstance(block.get("thinking"), str):
raise _invalid(location + ".thinking", "Thinking content must be a string")
# Anthropic signatures have no Chat equivalent and must not become visible text.
thoughts.append(block["thinking"])
elif kind == "redacted_thinking":
raise _invalid(location, "redacted_thinking cannot be converted to Chat; use the Messages protocol")
try:
thoughts.append(extract_reasoning_text(block))
except ReasoningInputError as error:
raise _invalid(location + ("." + error.field if error.field else ""), str(error)) from None
else:
parts.append(_content_part(block, location))
if results:
Expand Down
Loading
Loading