Skip to content

Commit 927962c

Browse files
committed
test(brains/anthropic): cover tool_use surfacing and finish mapping
Stream a tool_use turn (content_block_start opener, input_json_delta argument fragments, stop_reason=tool_use) and assert the tool_call delta opener carries id + name, fragments concatenate to valid JSON under one block index, and the finish maps to tool_calls. Also assert tools are forwarded into the body and omitted when none. Drop the now-unused ChatChunk import.
1 parent 0e60819 commit 927962c

1 file changed

Lines changed: 49 additions & 1 deletion

File tree

tests/test_brains_anthropic.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import httpx
66
import pytest
77

8-
from zhub.brains.base import ChatChunk
98
from zhub.brains.anthropic import AnthropicAdapter
109

1110

@@ -98,6 +97,55 @@ async def test_stream_parses_anthropic_sse():
9897
assert headers["anthropic-version"]
9998

10099

100+
@pytest.mark.asyncio
101+
async def test_stream_surfaces_tool_use_as_deltas():
102+
"""A tool_use turn opens with content_block_start (id + name), streams its
103+
arguments as input_json_delta fragments, and ends stop_reason=tool_use.
104+
The adapter must surface a tool_call_delta opener, the concatenable argument
105+
fragments under the same block index, and map the finish to 'tool_calls' so
106+
the hub's auto-resolution fires."""
107+
lines = [
108+
'data: {"type":"message_start","message":{"id":"msg_x"}}',
109+
'data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_42","name":"get_weather","input":{}}}',
110+
'data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\\"city\\":"}}',
111+
'data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\\"Paris\\"}"}}',
112+
'data: {"type":"message_delta","delta":{"stop_reason":"tool_use"}}',
113+
'data: {"type":"message_stop"}',
114+
]
115+
fake = _FakeAsyncClient(lines)
116+
adapter = AnthropicAdapter(api_key="sk-ant-xyz", model="claude-sonnet-4-5", http=fake)
117+
out = [c async for c in adapter.stream(
118+
[{"role": "user", "content": "weather?"}],
119+
tools=[{"type": "function", "function": {"name": "get_weather"}}],
120+
)]
121+
122+
# tools forwarded into the request body
123+
assert fake.last_call["json"]["tools"][0]["function"]["name"] == "get_weather"
124+
125+
tcds = [c.tool_call_delta for c in out if c.tool_call_delta]
126+
# opener carries id + name under the block index
127+
assert tcds[0]["index"] == 1
128+
assert tcds[0]["id"] == "toolu_42"
129+
assert tcds[0]["function"]["name"] == "get_weather"
130+
# argument fragments concatenate to valid JSON for the hub
131+
args = "".join(t["function"].get("arguments", "") for t in tcds)
132+
assert args == '{"city":"Paris"}'
133+
assert all(t["index"] == 1 for t in tcds)
134+
# finish mapped from anthropic's "tool_use" to the hub's "tool_calls"
135+
assert out[-1].done is True
136+
assert out[-1].finish_reason == "tool_calls"
137+
138+
139+
@pytest.mark.asyncio
140+
async def test_stream_omits_tools_when_none():
141+
fake = _FakeAsyncClient([
142+
'data: {"type":"message_stop"}',
143+
])
144+
adapter = AnthropicAdapter(api_key="sk-ant-xyz", model="claude-sonnet-4-5", http=fake)
145+
[c async for c in adapter.stream([{"role": "user", "content": "hi"}])]
146+
assert "tools" not in fake.last_call["json"]
147+
148+
101149
def test_anthropic_in_default_registry():
102150
from zhub.brains import REGISTRY
103151
names = [c.name for c in REGISTRY]

0 commit comments

Comments
 (0)