fix: delay and vary spoken wait notices
Rebuild & Deploy / rebuild (push) Failing after 7s

This commit is contained in:
snowspeeder committed 2026-10-01 15:59:10 -04:00
1 parent 9cc6147c6f
commit c5f7bb39e7
3 files changed
+99 -16

No files matched your search

+74 -7
View File
@@ -17,7 +17,7 @@ from livekit.agents import llm as lk_llm
from livekit.agents.types import APIConnectOptions
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "agent"))
from hermes_llm import HermesLLM, HermesLLMError, WAIT_NOTICE
from hermes_llm import HermesLLM, HermesLLMError, WAIT_NOTICES, WAIT_NOTICE_SECONDS
def data(delta, **kwargs):
@@ -113,14 +113,14 @@ class HermesLLMTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(self.progress[-1]["request_id"], chunks[0].id)
self.assertTrue(fake.closed)
async def test_sse_crlf_unicode_multiline_comments_unknown_events_and_tool_notice_once(self):
async def test_sse_crlf_unicode_multiline_comments_unknown_events_and_quick_tools_silent(self):
tool = b'event: hermes.tool.progress\r\ndata: {"status":"started",\r\ndata: "tool_name":"search"}\r\n\r\n'
body = b":keepalive\r\n\r\nevent: unknown\r\ndata: not json\r\n\r\n" + tool + tool + data({"reasoning": "never speak"}) + data({"content": "caf\u00e9"}) + b"data: [DONE]\r\n\r\n"
fake = FakeSSE([body[i:i + 1] for i in range(len(body))])
bridge = self.bridge(fake)
result = await bridge.chat(chat_ctx=self.context()).collect()
self.assertEqual(result.text, "caf\u00e9")
self.assertEqual(self.notices, [WAIT_NOTICE])
self.assertEqual(self.notices, [])
self.assertEqual([event["status"] for event in self.progress], ["working", "working", "completed"])
self.assertTrue(all(event["type"] == "assistant_progress" for event in self.progress))
self.assertEqual(len({event["request_id"] for event in self.progress}), 1)
@@ -133,10 +133,11 @@ class HermesLLMTests(unittest.IsolatedAsyncioTestCase):
async def test_latency_notice_without_network_event(self):
fake = FakeSSE([], gate=asyncio.Event())
bridge = self.bridge(fake)
# Speed up only the bridge's three-second timer; network remains blocked.
# Speed up only the twelve-second timer; network remains blocked.
sleep = asyncio.sleep
async def fast_sleep(seconds):
self.assertEqual(seconds, 3.0)
self.assertEqual(seconds, 12.0)
self.assertEqual(seconds, WAIT_NOTICE_SECONDS)
await sleep(0)
with patch("hermes_llm.asyncio.sleep", side_effect=fast_sleep):
stream = bridge.chat(chat_ctx=self.context())
@@ -145,13 +146,79 @@ class HermesLLMTests(unittest.IsolatedAsyncioTestCase):
await sleep(0)
if self.notices:
break
self.assertEqual(self.notices, [WAIT_NOTICE])
self.assertEqual(len(self.notices), 1)
self.assertIn(self.notices[0], WAIT_NOTICES)
self.assertEqual(self.progress[0]["status"], "working")
self.assertEqual(self.progress[0]["message"], "Working on your request.")
await stream.aclose()
self.assertTrue(fake.closed)
self.assertEqual(self.progress[-1]["status"], "failed")
async def test_status_and_tools_wait_for_threshold_then_notice_once(self):
threshold = asyncio.Event()
noticed = asyncio.Event()
progress_seen = asyncio.Event()
tool = b'event: hermes.tool.progress\ndata: {"status":"started","tool_name":"search"}\n\n'
fake = FakeSSE([b'event: hermes.status\ndata: {"status":"working"}\n\n', tool, tool], gate=asyncio.Event())
bridge = self.bridge(fake)
async def timer_sleep(seconds):
self.assertEqual(seconds, 12.0)
await threshold.wait()
async def progress(event):
await self.progress_callback(event)
if len(self.progress) == 3:
progress_seen.set()
async def notice(text):
await self.notice_callback(text)
noticed.set()
bridge._on_progress = progress
bridge._on_wait_notice = notice
with patch("hermes_llm.asyncio.sleep", side_effect=timer_sleep):
stream = bridge.chat(chat_ctx=self.context())
try:
await asyncio.wait_for(progress_seen.wait(), timeout=1)
self.assertEqual(self.notices, [])
threshold.set()
await asyncio.wait_for(noticed.wait(), timeout=1)
await stream._notice() # repeated progress cannot duplicate it
self.assertEqual(len(self.notices), 1)
self.assertIn(self.notices[0], WAIT_NOTICES)
finally:
await stream.aclose()
async def test_delayed_notices_vary_across_turns_and_stay_out_of_history(self):
bridge = self.bridge(FakeSSE([], gate=asyncio.Event()))
sleep = asyncio.sleep
async def fast_sleep(seconds):
self.assertEqual(seconds, 12.0)
await sleep(0)
with patch("hermes_llm.asyncio.sleep", side_effect=fast_sleep):
for turn in range(8):
noticed = asyncio.Event()
async def notice(text):
await self.notice_callback(text)
noticed.set()
bridge._on_wait_notice = notice
context = self.context()
before = context.to_dict()
stream = bridge.chat(chat_ctx=context)
try:
await asyncio.wait_for(noticed.wait(), timeout=1)
self.assertEqual(len(self.notices), turn + 1)
self.assertIn(self.notices[-1], WAIT_NOTICES)
if turn:
self.assertNotEqual(self.notices[-1], self.notices[-2])
self.assertEqual(context.to_dict(), before)
messages = json.loads(self.requests[-1].content)["messages"]
self.assertFalse(any(m["content"] in WAIT_NOTICES for m in messages))
finally:
await stream.aclose()
async def test_timeout_closes_stream_and_never_retries(self):
fake = FakeSSE([b":keepalive\n\n"], gate=asyncio.Event())
stream = self.bridge(fake, timeout_seconds=0.02).chat(chat_ctx=self.context(), conn_options=APIConnectOptions(max_retry=10))
@@ -247,7 +314,7 @@ class HermesLLMTests(unittest.IsolatedAsyncioTestCase):
result = await self.bridge(fake).chat(chat_ctx=self.context()).collect()
self.assertEqual(result.text, "Done")
self.assertEqual(self.progress[0]["message"], "Working on your request.")
self.assertEqual(self.notices, [WAIT_NOTICE])
self.assertEqual(self.notices, [])
async def test_explicit_cancellation_closes_http_without_retry(self):
fake = FakeSSE([b":keepalive\n\n"], gate=asyncio.Event())