跳转至

运行 Agent

Runner 接收一个 Agent 和一份输入,并执行一次运行。它本身不保存状态;单次运行的全部状态 都由其启动的运行循环管理。Runner 只提供三个入口,区别仅在于调用方如何获取运行结果。

from lovia import Runner

result = await Runner.run(agent, "写一段发布说明。")      # 等待运行完成
result = Runner.run_sync(agent, "总结这个文件。")         # 脚本 / REPL
handle = Runner.stream(agent, "解释上下文压缩。")         # 边运行边消费事件

agent.run(...)agent.run_sync(...)agent.stream(...) 是对应的实例方法写法。

三个入口

Runner.run(agent, input, **options) -> RunResult:等待运行完成并返回最终结果。 失败会抛异常(GuardrailTrippedBudgetExceededProviderError 等,见 错误清单)。

Runner.run_sync(...):做同样的事,但用 asyncio.run() 包起来,适合尚未使用 async 的代码。如果在已经运行的事件循环里调用,会抛 UserError,hint 里会告诉你 改用 await Runner.run(...)

Runner.stream(...) -> RunHandle:启动运行,并返回一个 handle。它既是 异步可迭代对象(产生类型化事件),也是 awaitable(得到最终结果):

handle = Runner.stream(agent, "分析这些日志。")

async for ev in handle:          # 运行失败不会从这里抛出
    ...

result = await handle.result()   # 返回 RunResult,或抛出运行错误

迭代是一次性的;第二次 async for 会抛 RuntimeError。每个流都会以且仅以一个 终止事件结束:RunCompletedRunFailedawait handleawait handle.result() 的简写;如果尚未开始遍历事件流,result() 会自行驱动运行直至 完成。handle.cancel() 可以在没有预先传 CancelToken 的情况下请求协作式取消; handle.approvals审批通道。 事件本身见流式输出

运行选项

三个入口都接受同一组关键字:

选项 默认值 作用
context None 你的依赖对象,会作为 ctx.deps 暴露(见 Agent
output_type None 本次运行覆盖 agent 的输出类型
extra_instructions None 本次运行追加到 system prompt 的内容,渲染在 agent 自身 instructions 后;handoff 到的每个 agent 都会重新应用
max_turns 50 模型轮次的硬上限;超过会抛 MaxTurnsExceeded
budget None 限制本次运行可消耗资源的 RunBudget(见预算
cancel_token None 预先接入的协作式取消(见取消
mailbox None 运行中追加指令的通道(见运行中追加指令
retry agent 的配置 本次调用覆盖 provider 重试策略
context_policy agent 的配置 本次调用覆盖上下文策略
session + session_id None 对话持久化(见 Session 与 Checkpoint
checkpoint None 崩溃恢复和幂等运行(见 Session 与 Checkpoint
tracer None 本次运行的链路追踪(见可观测性

retrycontext_policy 是两个应对策略覆盖项。它们默认使用 agent 配置,而且 初始 agent 的应对策略会贯穿整个运行,handoff 后也一样。其余选项是限制和外部接入点, agent 侧没有对应项。

输入

input 可以是字符串(一条用户消息),也可以是 Message 列表,用来以多条消息开始:

from lovia import Runner, system, user

result = await Runner.run(
    agent,
    [
        system("用海盗口吻回答。"),   # 额外 system 消息,会保存在 transcript 中
        user("我们驶向哪里?"),
    ],
)

图片和文件

消息内容除了字符串,也可以是类型化 part 列表:TextPartImagePartFilePart。 provider 会把它们转换成自己的请求格式:

from lovia import ImagePart, Runner, TextPart, user

result = await Runner.run(
    agent,
    [
        user(
            [
                TextPart("这张截图里有什么?"),
                ImagePart.from_path("shot.png"),
            ]
        )
    ],
)

user(...) 也接受普通字符串或单个 part;但 part 列表必须包含类型化 part, 列表里的普通 str 不会被自动转换。)

  • ImagePart(url=...)ImagePart(data=..., mime_type=...)url / data 必须且只能选一个;base64 data 需要 mime_typeImagePart.from_path() 会读取并编码本地文件,根据后缀推断 MIME 类型。可选 detail="low"|"high"|"auto"
  • FilePart:同样的形状,再加 filename;构造器有 from_pathfrom_bytesfrom_base64from_url。URL part 是 provider 原生引用,lovia 不会替你下载。

结果

RunResult 字段 含义
output 最终答案:str,或本次运行 output_type 校验后的实例
entries 本次运行自己的 transcript:本次输入及其产生的所有内容,跨 handoff 也会包含
messages entries 派生出的 chat 格式视图(有损)
final_agent 产出最终答案的 agent;handoff 后可能和初始 agent 不同
usage 累计 token:input_tokensoutput_tokenscache_read_tokenscache_write_tokenstotal_tokens;agent-as-tool 子运行也计入
turns 本次运行用了多少个模型轮次
finish_reason 最后一轮 provider 报告的结束原因;检查 "stop""length" 可发现被 max_tokens 截断的答案

entries 有意不包含 system prompt 和之前的 session 历史,因此无论运行是刚刚完成, 还是从 checkpoint 重建出来,它都一致。要看完整对话,可以在 hook 里读取 ctx.entries,或运行结束后调用 session.load()

注意事项

  • RunResult.entries 不是完整 transcript:它只是本次运行的增量。用它渲染 “整段对话”的代码会不小心丢掉之前的历史;请用 session。
  • finish_reason 可能是 None:provider 没报时如此;从已完成 checkpoint 重放结果时也如此,因为 snapshot 不持久化它。
  • 模型回复既没有内容也没有工具调用时,运行会完成,输出为空字符串(会记录 warning 日志)。 这几乎总是 provider 抖动或 max_tokens 截断;在相信空答案之前,先检查 finish_reason
  • run_sync 拥有事件循环:它拒绝在现有事件循环里运行。notebook 里如果已经有 正在运行的事件循环,请用 await Runner.run(...)

延伸阅读