取消并复用智能体#
关闭被中断的流,清理扩展状态,并完成一次新的请求。
运行#
在仓库根目录下执行:
uv run python docs/_examples/cancellation.py
输出包含:
Cancelled request; extension state cleared
这个程序在需要时使用确定性的离线模型和临时存储。它不需要 API 密钥,并由文档测试套件执行。
离开时关闭流#
对于可能提前停止的流,请用aclosing包起来。该示例在第一个文本增量处跳出,退出上下文管理器,然后复用客户端完成一次完整的请求:
async def main() -> None:
tracker = WorkTracker()
client = await create_agent(StreamingModel(), extensions=[tracker])
# stream() is annotated AsyncIterator but is an async generator, which aclosing needs.
async with aclosing(client.stream("Start")) as events: # type: ignore[type-var]
async for event in events:
if event.type == AgentEventType.TEXT_DELTA:
break
assert client.agent.state.phase == AgentPhase.CANCELLED and not tracker.active
print("Cancelled request; extension state cleared")
print((await client.run("Try again")).content)
assert client.agent.state.phase == AgentPhase.COMPLETED and not tracker.active
如果扩展保留了请求级状态,那么在取消时也要像成功和出错时一样清理它。WorkTracker 演示了这三种路径。关闭流不会回滚已经完成的工具操作;设计有副作用的工具时请留意这一区别。
完整源码#
1"""Close a streaming request and then safely reuse the same Agent."""
2
3import asyncio
4from collections.abc import AsyncIterator
5from contextlib import aclosing
6
7from zett_agent.agent import AgentRunContext
8from zett_agent.client import create_agent
9from zett_agent.events import (
10 AgentEventType,
11 AgentPhase,
12)
13from zett_agent.extensions.base import AgentExtension
14from zett_agent.extensions.events import (
15 ExtensionEvent,
16 RunCancelledEvent,
17)
18from zett_agent.messages import AssistantMessage
19from zett_agent.model import (
20 ModelEvent,
21 ModelRequest,
22 ModelResponse,
23 RetryOptions,
24)
25
26
27class WorkTracker(AgentExtension):
28 def __init__(self) -> None:
29 self.active: set[AgentRunContext] = set()
30
31 async def before_run(self, context: AgentRunContext) -> None:
32 self.active.add(context)
33
34 async def on_event(self, context: AgentRunContext, event: ExtensionEvent) -> None:
35 if isinstance(event, RunCancelledEvent):
36 self.active.discard(context)
37
38 async def on_error(self, context: AgentRunContext, error: Exception) -> None:
39 self.active.discard(context)
40
41 async def on_success(self, context: AgentRunContext, result: AssistantMessage) -> None:
42 self.active.discard(context)
43
44
45class StreamingModel:
46 retry = RetryOptions(max_retries=0)
47
48 async def stream(self, request: ModelRequest) -> AsyncIterator[ModelEvent]:
49 yield ModelEvent.text("Partial")
50 yield ModelEvent.text(" answer")
51 yield ModelEvent.completed(ModelResponse(AssistantMessage(content="Partial answer")))
52
53
54async def main() -> None:
55 tracker = WorkTracker()
56 client = await create_agent(StreamingModel(), extensions=[tracker])
57 # stream() is annotated AsyncIterator but is an async generator, which aclosing needs.
58 async with aclosing(client.stream("Start")) as events: # type: ignore[type-var]
59 async for event in events:
60 if event.type == AgentEventType.TEXT_DELTA:
61 break
62 assert client.agent.state.phase == AgentPhase.CANCELLED and not tracker.active
63 print("Cancelled request; extension state cleared")
64 print((await client.run("Try again")).content)
65 assert client.agent.state.phase == AgentPhase.COMPLETED and not tracker.active
66
67
68if __name__ == "__main__":
69 asyncio.run(main())
下一步:消费端的处理模式见把流式输出接入终端或界面,扩展的清理见状态、并发与清理。