Skip to content

Commit c6a7a6c

Browse files
committed
fix(client): resolve coroutine before gen-detection in _handle_chat
An `async def` chat_handler that returns an async (or sync) generator resolved to a coroutine wrapping the generator; isasyncgen/isgenerator ran against the raw coroutine (both False), and the single-shot path then stringified the generator into `"<async_generator object …>"` as the chat-response text. Await the coroutine up front so the gen detection sees the real streaming source.
1 parent 8b01aa0 commit c6a7a6c

1 file changed

Lines changed: 10 additions & 6 deletions

File tree

zhub/client.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,16 @@ async def _handle_chat(pub: ZhubPublication, ws, env: Envelope) -> None:
352352
try:
353353
result = pub.chat_handler(messages, options)
354354

355-
# Streaming first — before iscoroutine check, since some Python versions
356-
# treat sync generators ambiguously and `await` on a generator throws.
355+
# An `async def` chat_handler that itself returns an async generator
356+
# ends up as a coroutine wrapping the generator. Awaiting it first
357+
# unwraps the coroutine so the isasyncgen / isgenerator branches below
358+
# see the real streaming source instead of dropping to the single-shot
359+
# path (which would stringify the generator into ``"<async_generator …>"``).
360+
if inspect.iscoroutine(result):
361+
result = await result
362+
363+
# Streaming first — before the earlier iscoroutine check we could have
364+
# a plain sync generator, and `await` on a generator throws.
357365
# If the caller requested streaming, emit chat-chunk per yield. Otherwise
358366
# accumulate the generator output into a single chat-response so non-
359367
# streaming HTTP callers don't time out.
@@ -408,10 +416,6 @@ async def _handle_chat(pub: ZhubPublication, ws, env: Envelope) -> None:
408416
await ws.send(Envelope(type="chat-response", request_id=env.request_id, payload=payload).to_json())
409417
return
410418

411-
# Coroutine — await for the final value
412-
if inspect.iscoroutine(result):
413-
result = await result
414-
415419
# Single-shot
416420
if isinstance(result, str):
417421
payload = {"text": result, "finish_reason": "stop"}

0 commit comments

Comments
 (0)