144 lines
4.4 KiB
Python
144 lines
4.4 KiB
Python
import pytest
|
|
|
|
from src.llm.backend import CompletionResult, ToolCallResult
|
|
from src.llm.history_adapters import (
|
|
AnthropicHistoryAdapter,
|
|
GeminiHistoryAdapter,
|
|
OpenAIHistoryAdapter,
|
|
)
|
|
|
|
|
|
def test_anthropic_history_adapter_preserves_thinking_blocks() -> None:
|
|
adapter = AnthropicHistoryAdapter()
|
|
result = CompletionResult(
|
|
content="Done",
|
|
thinking_blocks=[
|
|
{
|
|
"type": "thinking",
|
|
"thinking": "private reasoning",
|
|
"signature": "sig_123",
|
|
}
|
|
],
|
|
tool_calls=[
|
|
ToolCallResult(id="tool_1", name="search", input={"query": "honcho"})
|
|
],
|
|
)
|
|
|
|
message = adapter.format_assistant_tool_message(result)
|
|
|
|
assert message["role"] == "assistant"
|
|
assert message["content"][0]["type"] == "thinking"
|
|
assert message["content"][1] == {"type": "text", "text": "Done"}
|
|
assert message["content"][2]["type"] == "tool_use"
|
|
|
|
|
|
def test_gemini_history_adapter_preserves_thought_signature() -> None:
|
|
adapter = GeminiHistoryAdapter()
|
|
result = CompletionResult(
|
|
content="Calling a tool",
|
|
tool_calls=[
|
|
ToolCallResult(
|
|
id="tool_1",
|
|
name="search",
|
|
input={"query": "honcho"},
|
|
thought_signature="sig_abc",
|
|
)
|
|
],
|
|
)
|
|
|
|
message = adapter.format_assistant_tool_message(result)
|
|
|
|
assert message["role"] == "model"
|
|
assert message["parts"][1]["thought_signature"] == "sig_abc"
|
|
|
|
|
|
def test_openai_history_adapter_preserves_reasoning_details() -> None:
|
|
adapter = OpenAIHistoryAdapter()
|
|
result = CompletionResult(
|
|
content="Calling a tool",
|
|
reasoning_details=[{"type": "reasoning", "content": "step 1"}],
|
|
tool_calls=[
|
|
ToolCallResult(id="tool_1", name="search", input={"query": "honcho"})
|
|
],
|
|
)
|
|
|
|
message = adapter.format_assistant_tool_message(result)
|
|
|
|
assert message["role"] == "assistant"
|
|
assert message["reasoning_details"] == [{"type": "reasoning", "content": "step 1"}]
|
|
assert message["tool_calls"][0]["function"]["name"] == "search"
|
|
|
|
|
|
def test_openai_history_adapter_preserves_thinking_content() -> None:
|
|
adapter = OpenAIHistoryAdapter()
|
|
result = CompletionResult(
|
|
content="Calling a tool",
|
|
thinking_content="step 1",
|
|
tool_calls=[
|
|
ToolCallResult(id="tool_1", name="search", input={"query": "honcho"})
|
|
],
|
|
)
|
|
|
|
message = adapter.format_assistant_tool_message(result)
|
|
|
|
assert message["reasoning_content"] == "step 1"
|
|
assert "reasoning_details" not in message
|
|
|
|
|
|
def test_openai_history_adapter_prefers_reasoning_details() -> None:
|
|
adapter = OpenAIHistoryAdapter()
|
|
reasoning_details = [{"type": "reasoning", "content": "step 1"}]
|
|
result = CompletionResult(
|
|
content="Calling a tool",
|
|
thinking_content="duplicate step 1",
|
|
reasoning_details=reasoning_details,
|
|
)
|
|
|
|
message = adapter.format_assistant_tool_message(result)
|
|
|
|
assert message["reasoning_details"] == reasoning_details
|
|
assert "reasoning_content" not in message
|
|
|
|
|
|
@pytest.mark.parametrize("thinking_content", [None, ""])
|
|
def test_openai_history_adapter_omits_empty_thinking_content(
|
|
thinking_content: str | None,
|
|
) -> None:
|
|
adapter = OpenAIHistoryAdapter()
|
|
result = CompletionResult(
|
|
content="Calling a tool",
|
|
thinking_content=thinking_content,
|
|
)
|
|
|
|
message = adapter.format_assistant_tool_message(result)
|
|
|
|
assert "reasoning_content" not in message
|
|
|
|
|
|
def test_openai_history_adapter_preserves_null_content_on_tool_call_turns() -> None:
|
|
adapter = OpenAIHistoryAdapter()
|
|
reasoning_details = [
|
|
{
|
|
"type": "reasoning.encrypted",
|
|
"data": "opaque",
|
|
"format": "openai-responses-v1",
|
|
"id": "binding",
|
|
"index": 0,
|
|
}
|
|
]
|
|
result = CompletionResult(
|
|
content=None,
|
|
reasoning_details=reasoning_details,
|
|
tool_calls=[
|
|
ToolCallResult(id="call_probe", name="search", input={"query": "honcho"})
|
|
],
|
|
)
|
|
|
|
message = adapter.format_assistant_tool_message(result)
|
|
|
|
assert message["content"] is None
|
|
assert message["reasoning_details"] == reasoning_details
|
|
assert message["tool_calls"][0]["id"] == "call_probe"
|
|
assert message["tool_calls"][0]["function"]["name"] == "search"
|
|
assert message["tool_calls"][0]["function"]["arguments"] == '{"query": "honcho"}'
|