|
@@ -100,7 +100,8 @@ class FakeChatClient:
|
|
|
)
|
|
)
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
- assert any(message.role == "tool" for message in messages)
|
|
|
|
|
|
|
+ assert not any(message.role == "tool" for message in messages)
|
|
|
|
|
+ assert any(message.name == "event_agent" for message in messages)
|
|
|
yield StreamItem.message_delta("final answer")
|
|
yield StreamItem.message_delta("final answer")
|
|
|
|
|
|
|
|
|
|
|
|
@@ -170,6 +171,7 @@ class MultiEventChatClient:
|
|
|
class ToolCapturingChatClient:
|
|
class ToolCapturingChatClient:
|
|
|
def __init__(self) -> None:
|
|
def __init__(self) -> None:
|
|
|
self.tools: list[dict[str, Any]] = []
|
|
self.tools: list[dict[str, Any]] = []
|
|
|
|
|
+ self.messages: list[ChatMessage] = []
|
|
|
|
|
|
|
|
async def stream_chat(
|
|
async def stream_chat(
|
|
|
self,
|
|
self,
|
|
@@ -177,6 +179,7 @@ class ToolCapturingChatClient:
|
|
|
tools: list[dict],
|
|
tools: list[dict],
|
|
|
params: AgentParams,
|
|
params: AgentParams,
|
|
|
) -> AsyncIterator[StreamItem]:
|
|
) -> AsyncIterator[StreamItem]:
|
|
|
|
|
+ self.messages = list(messages)
|
|
|
self.tools = list(tools)
|
|
self.tools = list(tools)
|
|
|
yield StreamItem.message_delta("final answer")
|
|
yield StreamItem.message_delta("final answer")
|
|
|
|
|
|
|
@@ -185,6 +188,7 @@ class EventLoopLimitChatClient:
|
|
|
def __init__(self) -> None:
|
|
def __init__(self) -> None:
|
|
|
self.calls = 0
|
|
self.calls = 0
|
|
|
self.tools_by_call: list[list[dict[str, Any]]] = []
|
|
self.tools_by_call: list[list[dict[str, Any]]] = []
|
|
|
|
|
+ self.messages_by_call: list[list[ChatMessage]] = []
|
|
|
|
|
|
|
|
async def stream_chat(
|
|
async def stream_chat(
|
|
|
self,
|
|
self,
|
|
@@ -194,6 +198,7 @@ class EventLoopLimitChatClient:
|
|
|
) -> AsyncIterator[StreamItem]:
|
|
) -> AsyncIterator[StreamItem]:
|
|
|
self.calls += 1
|
|
self.calls += 1
|
|
|
self.tools_by_call.append(list(tools))
|
|
self.tools_by_call.append(list(tools))
|
|
|
|
|
+ self.messages_by_call.append(list(messages))
|
|
|
if self.calls == 1:
|
|
if self.calls == 1:
|
|
|
yield StreamItem.event(
|
|
yield StreamItem.event(
|
|
|
ToolCallEvent(
|
|
ToolCallEvent(
|
|
@@ -466,39 +471,20 @@ async def test_runtime_batches_round_events_before_continuing_chat_agent():
|
|
|
]
|
|
]
|
|
|
assert client.calls == 2
|
|
assert client.calls == 2
|
|
|
assert [message.role for message in client.second_call_messages] == [
|
|
assert [message.role for message in client.second_call_messages] == [
|
|
|
|
|
+ "system",
|
|
|
"user",
|
|
"user",
|
|
|
"assistant",
|
|
"assistant",
|
|
|
- "tool",
|
|
|
|
|
- "tool",
|
|
|
|
|
"user",
|
|
"user",
|
|
|
]
|
|
]
|
|
|
- assistant_message = client.second_call_messages[1]
|
|
|
|
|
|
|
+ assistant_message = client.second_call_messages[2]
|
|
|
assert assistant_message.content == "Checking events."
|
|
assert assistant_message.content == "Checking events."
|
|
|
- assert assistant_message.tool_calls == [
|
|
|
|
|
- {
|
|
|
|
|
- "id": "call_1",
|
|
|
|
|
- "type": "function",
|
|
|
|
|
- "function": {"name": "handoff_note", "arguments": "{}"},
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- "id": "call_2",
|
|
|
|
|
- "type": "function",
|
|
|
|
|
- "function": {"name": "audit_note", "arguments": "{}"},
|
|
|
|
|
- },
|
|
|
|
|
- ]
|
|
|
|
|
- assert [
|
|
|
|
|
- json.loads(message.content)
|
|
|
|
|
- for message in client.second_call_messages
|
|
|
|
|
- if message.role == "tool"
|
|
|
|
|
- ] == [
|
|
|
|
|
- {"tool": "handoff_note", "message": "Checking events."},
|
|
|
|
|
- {"tool": "audit_note", "message": "Checking events."},
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+ assert not any(message.role == "tool" for message in client.second_call_messages)
|
|
|
assert client.second_call_messages[-1].content == (
|
|
assert client.second_call_messages[-1].content == (
|
|
|
"EventAgent results:\n"
|
|
"EventAgent results:\n"
|
|
|
'{"tool": "handoff_note", "message": "Checking events."}\n'
|
|
'{"tool": "handoff_note", "message": "Checking events."}\n'
|
|
|
'{"tool": "audit_note", "message": "Checking events."}'
|
|
'{"tool": "audit_note", "message": "Checking events."}'
|
|
|
)
|
|
)
|
|
|
|
|
+ assert client.second_call_messages[-1].name == "event_agent"
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
@@ -545,8 +531,14 @@ async def test_runtime_finalizes_chat_after_reaching_event_loop_limit():
|
|
|
business_outputs = _without_audit(outputs)
|
|
business_outputs = _without_audit(outputs)
|
|
|
|
|
|
|
|
assert client.calls == 2
|
|
assert client.calls == 2
|
|
|
- assert client.tools_by_call[0][0]["function"]["name"] == "handoff_note"
|
|
|
|
|
|
|
+ assert client.tools_by_call[0] == []
|
|
|
assert client.tools_by_call[1] == []
|
|
assert client.tools_by_call[1] == []
|
|
|
|
|
+ assert "Available events:" in client.messages_by_call[0][0].content
|
|
|
|
|
+ assert not any(
|
|
|
|
|
+ "Available events:" in message.content
|
|
|
|
|
+ for message in client.messages_by_call[1]
|
|
|
|
|
+ if message.role == "system"
|
|
|
|
|
+ )
|
|
|
assert _message_types(outputs) == [
|
|
assert _message_types(outputs) == [
|
|
|
"session_started",
|
|
"session_started",
|
|
|
"event",
|
|
"event",
|
|
@@ -582,14 +574,15 @@ async def test_runtime_buffers_upstream_user_input_until_after_matching_tool_rep
|
|
|
|
|
|
|
|
assert remaining[-1] == {"type": "done"}
|
|
assert remaining[-1] == {"type": "done"}
|
|
|
assert [message.role for message in client.second_call_messages] == [
|
|
assert [message.role for message in client.second_call_messages] == [
|
|
|
|
|
+ "system",
|
|
|
"user",
|
|
"user",
|
|
|
"assistant",
|
|
"assistant",
|
|
|
- "tool",
|
|
|
|
|
"user",
|
|
"user",
|
|
|
"user",
|
|
"user",
|
|
|
]
|
|
]
|
|
|
- assert client.second_call_messages[2].tool_call_id == "call_1"
|
|
|
|
|
|
|
+ assert not any(message.role == "tool" for message in client.second_call_messages)
|
|
|
assert client.second_call_messages[3].content.startswith("EventAgent results:\n")
|
|
assert client.second_call_messages[3].content.startswith("EventAgent results:\n")
|
|
|
|
|
+ assert client.second_call_messages[3].name == "event_agent"
|
|
|
assert client.second_call_messages[4].content == "follow-up while tool runs"
|
|
assert client.second_call_messages[4].content == "follow-up while tool runs"
|
|
|
|
|
|
|
|
|
|
|
|
@@ -729,7 +722,7 @@ async def test_runtime_run_consumes_output_queue_in_stream_order():
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
-async def test_runtime_preserves_assistant_tool_calls_before_tool_reply():
|
|
|
|
|
|
|
+async def test_runtime_continues_with_event_summary_without_tool_call_history():
|
|
|
request = DebugRunRequest(
|
|
request = DebugRunRequest(
|
|
|
user_message="debug this",
|
|
user_message="debug this",
|
|
|
system_prompts=["You are a debugger."],
|
|
system_prompts=["You are a debugger."],
|
|
@@ -744,32 +737,23 @@ async def test_runtime_preserves_assistant_tool_calls_before_tool_reply():
|
|
|
|
|
|
|
|
assert client.calls == 2
|
|
assert client.calls == 2
|
|
|
assert [message.role for message in client.second_call_messages] == [
|
|
assert [message.role for message in client.second_call_messages] == [
|
|
|
|
|
+ "system",
|
|
|
"system",
|
|
"system",
|
|
|
"user",
|
|
"user",
|
|
|
"assistant",
|
|
"assistant",
|
|
|
- "tool",
|
|
|
|
|
"user",
|
|
"user",
|
|
|
]
|
|
]
|
|
|
- assistant_message = client.second_call_messages[2]
|
|
|
|
|
- tool_message = client.second_call_messages[3]
|
|
|
|
|
|
|
+ assistant_message = client.second_call_messages[3]
|
|
|
assert assistant_message.content == ""
|
|
assert assistant_message.content == ""
|
|
|
- assert assistant_message.tool_calls == [
|
|
|
|
|
- {
|
|
|
|
|
- "id": "call_1",
|
|
|
|
|
- "type": "function",
|
|
|
|
|
- "function": {
|
|
|
|
|
- "name": "handoff_note",
|
|
|
|
|
- "arguments": "{}",
|
|
|
|
|
- },
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
- assert tool_message.tool_call_id == "call_1"
|
|
|
|
|
|
|
+ assert "handoff_note" in client.second_call_messages[1].content
|
|
|
|
|
+ assert not any(message.role == "tool" for message in client.second_call_messages)
|
|
|
assert client.second_call_messages[4].content.startswith("EventAgent results:\n")
|
|
assert client.second_call_messages[4].content.startswith("EventAgent results:\n")
|
|
|
|
|
+ assert client.second_call_messages[4].name == "event_agent"
|
|
|
assert outputs[-1] == {"type": "done"}
|
|
assert outputs[-1] == {"type": "done"}
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
-async def test_runtime_passes_event_names_without_tool_parameters_to_chat_agent():
|
|
|
|
|
|
|
+async def test_runtime_passes_event_catalog_system_message_without_chat_tools():
|
|
|
registry = ToolRegistry(
|
|
registry = ToolRegistry(
|
|
|
[
|
|
[
|
|
|
ToolDefinition(
|
|
ToolDefinition(
|
|
@@ -800,20 +784,12 @@ async def test_runtime_passes_event_names_without_tool_parameters_to_chat_agent(
|
|
|
outputs = [message async for message in runtime.run(request)]
|
|
outputs = [message async for message in runtime.run(request)]
|
|
|
|
|
|
|
|
assert outputs[-1] == {"type": "done"}
|
|
assert outputs[-1] == {"type": "done"}
|
|
|
- assert client.tools == [
|
|
|
|
|
- {
|
|
|
|
|
- "type": "function",
|
|
|
|
|
- "function": {
|
|
|
|
|
- "name": "handoff_note",
|
|
|
|
|
- "description": "Registry-owned handoff tool.",
|
|
|
|
|
- "parameters": {
|
|
|
|
|
- "type": "object",
|
|
|
|
|
- "properties": {},
|
|
|
|
|
- "additionalProperties": False,
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+ assert client.tools == []
|
|
|
|
|
+ assert client.messages[0].role == "system"
|
|
|
|
|
+ assert "Available events:" in client.messages[0].content
|
|
|
|
|
+ assert "- handoff_note: Registry-owned handoff tool." in client.messages[0].content
|
|
|
|
|
+ assert "message" not in client.messages[0].content
|
|
|
|
|
+ assert "priority" not in client.messages[0].content
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|