|
@@ -1,5 +1,6 @@
|
|
|
import asyncio
|
|
import asyncio
|
|
|
import importlib
|
|
import importlib
|
|
|
|
|
+import json
|
|
|
from collections.abc import AsyncIterator
|
|
from collections.abc import AsyncIterator
|
|
|
from typing import Any
|
|
from typing import Any
|
|
|
|
|
|
|
@@ -17,6 +18,10 @@ def _runtime_queues_class():
|
|
|
return module.RuntimeQueues
|
|
return module.RuntimeQueues
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+async def _collect_outputs(stream: AsyncIterator[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
|
|
+ return [message async for message in stream]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
class RecordingQueue(asyncio.Queue):
|
|
class RecordingQueue(asyncio.Queue):
|
|
|
def __init__(self, name: str, log: list[tuple[str, str, str]]) -> None:
|
|
def __init__(self, name: str, log: list[tuple[str, str, str]]) -> None:
|
|
|
super().__init__()
|
|
super().__init__()
|
|
@@ -261,6 +266,50 @@ async def test_runtime_buffers_upstream_user_input_until_after_matching_tool_rep
|
|
|
assert client.second_call_messages[3].content == "follow-up while tool runs"
|
|
assert client.second_call_messages[3].content == "follow-up while tool runs"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_runtime_continues_when_event_agent_tool_handler_raises():
|
|
|
|
|
+ def fail_tool(event: ToolCallEvent) -> dict[str, Any]:
|
|
|
|
|
+ raise RuntimeError("boom")
|
|
|
|
|
+
|
|
|
|
|
+ registry = ToolRegistry(
|
|
|
|
|
+ [
|
|
|
|
|
+ ToolDefinition(
|
|
|
|
|
+ name="handoff_note",
|
|
|
|
|
+ description="Broken handoff tool.",
|
|
|
|
|
+ parameters={"type": "object"},
|
|
|
|
|
+ handler=fail_tool,
|
|
|
|
|
+ )
|
|
|
|
|
+ ]
|
|
|
|
|
+ )
|
|
|
|
|
+ request = DebugRunRequest(
|
|
|
|
|
+ user_message="debug this",
|
|
|
|
|
+ system_prompts=[],
|
|
|
|
|
+ pre_messages=[],
|
|
|
|
|
+ chat_agent=AgentParams(model="fake-model", temperature=0.1, max_tokens=200),
|
|
|
|
|
+ event_agent=EventAgentParams(enabled_tools=["handoff_note"], max_event_loops=2),
|
|
|
|
|
+ )
|
|
|
|
|
+ runtime = DebugRuntime(FakeChatClient(), registry=registry)
|
|
|
|
|
+
|
|
|
|
|
+ outputs = await asyncio.wait_for(
|
|
|
|
|
+ _collect_outputs(runtime.run(request)),
|
|
|
|
|
+ timeout=1,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ assert [message["type"] for message in outputs] == [
|
|
|
|
|
+ "session_started",
|
|
|
|
|
+ "event",
|
|
|
|
|
+ "tool_result",
|
|
|
|
|
+ "round_stats",
|
|
|
|
|
+ "message_delta",
|
|
|
|
|
+ "round_stats",
|
|
|
|
|
+ "done",
|
|
|
|
|
+ ]
|
|
|
|
|
+ assert json.loads(outputs[2]["message"]["content"]) == {
|
|
|
|
|
+ "tool": "handoff_note",
|
|
|
|
|
+ "error": "tool handler failed: boom",
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
async def test_runtime_uses_event_and_input_queues_for_event_agent_handoff():
|
|
async def test_runtime_uses_event_and_input_queues_for_event_agent_handoff():
|
|
|
RuntimeQueues = _runtime_queues_class()
|
|
RuntimeQueues = _runtime_queues_class()
|