在同步代码中使用#
并非每个程序都是异步的。create_agent_sync在同一个后台事件循环上运行相同的运行时,因此脚本、CLI 和 notebook 都可以从普通的def代码中调用它。模型行为、工具、扩展、会话历史、重试和事件类型都与异步 API 完全一致。
运行与流式输出#
from zett_agent.sync import create_agent_sync
with create_agent_sync(model) as agent:
reply = agent.run("Hello")
print(reply.content)
with agent.stream("Continue") as events:
for event in events:
print(event.type, event.delta)
如果你可能会提前停止,请把流当作上下文管理器使用;关闭它会取消未完成的请求并等待清理完成。同一会话上的后续请求可以正常运行。
异步对象的阻塞视图#
智能体、客户端、模型提供商、工具、存储和分发器都提供.sync(),它用阻塞调用映射它们各自的异步方法:
with existing_agent.sync() as agent:
agent.initialize(config=config)
reply = agent.run("Hello")
with read_file.sync() as read:
result = read({"path": "README.md"})
with storage.sync() as db:
session = db.load("session-42")
对于其他情况——自由函数、第三方客户端——请直接使用SyncRuntime:
with SyncRuntime() as runtime:
agent = runtime.call(Agent.create, model, config=config)
result = runtime.call(agent.run, "Hello")
with runtime.stream(agent.stream, "Continue") as events:
for event in events:
print(event)
对于应该配套使用的资源,请共享同一个运行时,并在离开之前关闭你自己拥有的部分。你显式传入的运行时在视图退出时仍保持打开。
回调与自定义模型#
扩展钩子和分发器回调仍然是async def;它们在后台循环上运行,并带有真实的请求上下文。GUI 应用应该通过自己的 UI 线程安排界面更新,而不是在回调里直接操作控件。
如果你的模型是一个阻塞式的def stream(request)生成器,请用SyncModelAdapter包装它。现有的@tool函数已经同时支持这两种风格。
向运行中的请求发送输入#
当同步流暂停在Ask User或 Plan Mode 事件上时,另一个线程可以用agent.emit_external_event(event, config=config)传递回复。不同会话可以并发运行;同一会话的重叠请求会被拒绝。
完整的离线示例#
"""Run, stream, and persist a conversation without an asynchronous entry point."""
from pathlib import Path
from tempfile import TemporaryDirectory
from zett_agent.agent import AgentRunConfig
from zett_agent.dispatcher import AgentEventDispatcher
from zett_agent.events import AgentEventType
from zett_agent.extensions.sqlite import SQLiteSessionExtension
from zett_agent.messages import (
AssistantMessage,
UserMessage,
)
from zett_agent.model import (
ModelEvent,
ModelResponse,
)
from zett_agent.sync import (
SyncModelAdapter,
create_agent_sync,
)
class Echo:
"""A synchronous model used only to make this example runnable offline."""
def stream(self, request):
users = [message for message in request.messages if isinstance(message, UserMessage)]
answer = f"Turn {len(users)}: {users[-1].text}"
yield ModelEvent.text(answer)
yield ModelEvent.completed(ModelResponse(AssistantMessage(content=answer)))
class Console(AgentEventDispatcher):
async def on_text_delta_event(self, event):
print(event.delta)
def main() -> None:
with TemporaryDirectory() as directory:
history = SQLiteSessionExtension(Path(directory) / "sessions.sqlite3")
try:
with create_agent_sync(
SyncModelAdapter(Echo()),
config=AgentRunConfig("sync-example"),
extensions=[history],
event_dispatcher=Console(),
) as agent:
assert agent.run("Hello").content == "Turn 1: Hello"
with agent.stream("Remember this") as events:
for event in events:
if event.type is AgentEventType.RUN_COMPLETED:
assert event.message is not None
assert event.message.content == "Turn 2: Remember this"
# Storage is asynchronous; a plain function reaches it through the sync view.
with history.storage.sync() as database:
records = database.list_raw_messages("sync-example")
assert [record.message.role for record in records] == [
"system",
"user",
"assistant",
"system",
"user",
"assistant",
]
print("Synchronous history persisted")
finally:
with history.storage.sync() as database:
database.close()
if __name__ == "__main__":
main()
下载示例。
参见
把流式输出接入终端或界面介绍事件处理,保存并恢复对话介绍持久化,生命周期钩子参考列出全部钩子。