feat(chat): stop an in-flight answer — Send becomes Stop, the partial is kept and persisted, the model stream is torn down
This commit is contained in:
+22
-10
@@ -246,12 +246,19 @@ async def run_agent(
|
||||
rounds = 0
|
||||
while True:
|
||||
calls: list[ToolCallPiece] = []
|
||||
async for piece in llm.chat_stream(
|
||||
cast("list[dict[str, str]]", messages), tools=tools
|
||||
):
|
||||
if isinstance(piece, ToolCallPiece):
|
||||
calls.append(piece)
|
||||
yield piece
|
||||
# Phase 48: bind the round's stream so a consumer abandon
|
||||
# (GeneratorExit into the yield below) tears down the in-flight
|
||||
# model stream deterministically — not GC-dependent. Awaiting
|
||||
# ``aclose()`` in the ``finally`` is safe because it does not
|
||||
# yield; on a fully consumed round it is a quiet no-op.
|
||||
stream = llm.chat_stream(cast("list[dict[str, str]]", messages), tools=tools)
|
||||
try:
|
||||
async for piece in stream:
|
||||
if isinstance(piece, ToolCallPiece):
|
||||
calls.append(piece)
|
||||
yield piece
|
||||
finally:
|
||||
await stream.aclose()
|
||||
if not calls:
|
||||
return # the answer was streamed
|
||||
call = calls[0] # a stream can carry several calls; run the first
|
||||
@@ -287,8 +294,13 @@ async def run_agent(
|
||||
"no-tools answer",
|
||||
rounds,
|
||||
)
|
||||
async for piece in llm.chat_stream(
|
||||
cast("list[dict[str, str]]", messages), tools=None
|
||||
):
|
||||
yield piece
|
||||
# Phase 48: the forced final answer gets the same explicit
|
||||
# teardown as the loop rounds (consumer abandon mid-final
|
||||
# answer must still close the model's stream).
|
||||
final = llm.chat_stream(cast("list[dict[str, str]]", messages), tools=None)
|
||||
try:
|
||||
async for piece in final:
|
||||
yield piece
|
||||
finally:
|
||||
await final.aclose()
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user