Pi Agent 学习笔记:多个工具怎样并行执行

hello 我是逆境不可逃

模型一次可能提出多个工具请求。例如分析项目时,同时读取日志和配置;修改代码时,先编辑文件,再运行检查。执行这些请求,需要考虑它们之间有没有依赖,以及失败后还剩下什么状态。

Pi 的工具调度支持串行和并行。这篇从两个文件读取请求出发,梳理调度、结果排序和部分失败的处理思路。

哪些操作适合同时执行

假设读取日志需要 3 秒,读取配置需要 1 秒,两者都只读、互不依赖。这些时间只是用于说明流程。

text 复制代码
串行:读取日志 3 秒 → 读取配置 1 秒 → 约 4 秒完成
并行:两个读取同时进行 → 约 3 秒完成

并行减少了等待,但需要先确认操作可以独立进行。

如果任务变成"修改配置,再读取修改后的内容",读取就依赖修改结果。同时启动时,读取可能先完成,拿到旧配置。两个工具同时改同一份文件,也可能相互覆盖。

Pi 当前根据执行配置和工具声明选择模式:整体配置要求串行,或者同一批调用中有任意工具声明必须串行,整批就按顺序执行;否则走并行路径。

这段调度逻辑不会自动分析两个文件路径是否相同,也不会推断业务依赖。它依赖工具声明和任务安排表达约束。串行执行能保证先后顺序,但如果后一步的参数必须根据前一步结果才能确定,通常还需要把前一步结果交回模型,再生成下一次请求。

并行之前,还有依次准备

Pi 的并行路径并不是一收到调用列表,就立即启动所有工具。它会先按顺序为每次调用做准备,包括查找工具、准备并校验参数,以及执行前置钩子。前置钩子是允许其他逻辑检查或阻止调用的位置。

普通情况下,流程可以写成下面的简化伪代码:

text 复制代码
依次检查每个工具请求:
    如果检查失败或调用被阻断:
        保存错误结果
    否则:
        保存待执行操作

并行启动通过检查的操作
等待各操作结束
按原请求顺序整理结果

因此,如果 A 的执行前检查耗时 5 秒,即使 B 的检查很快,B 也要等准备阶段结束后才开始实际执行。这是当前流程的安排,并行带来的时间收益主要发生在后面的执行阶段。

依次准备让前置检查按固定顺序进行;代价是慢检查会延迟后续工具启动。不过,检查通过也不等于操作之间自动没有冲突,工具设计仍然需要考虑共享文件或外部状态。

先完成的工具,结果不一定先进入历史

继续看两个读取请求:

text 复制代码
模型请求顺序:A 读取日志 → B 读取配置
实际完成顺序:B → A
结果消息顺序:A → B

Pi 把执行结束通知与正式结果消息分开处理。工具完成后,可以发出结束通知;等这一批操作全部结束,再按原始请求顺序生成工具结果消息。

这样,界面订阅者可以先知道 B 已完成,下一轮模型则在整批结果收齐之后继续。结果排列不会随着某次网络或磁盘操作快慢而改变。

调用编号仍然不可缺少。顺序让记录稳定,编号明确每条结果属于哪次请求,尤其是多个请求使用同一个工具名称时。

整批等待也有成本。如果 A 要 10 秒,B 只要 1 秒,当前路径不会在第 1 秒就拿 B 的结果启动下一轮模型请求。A 会拖住这一批。要实现部分结果提前驱动下一轮,就需要另外设计未完成调用的管理和后续消息合并。

一个调用被阻断,其他调用怎么办

假设 A 在前置检查时被阻断,B 检查通过,并且没有取消整个任务。

Pi 会为 A 生成错误结果,让 B 继续执行。结果整理后,模型能看到两件事:A 没有实际执行,B 得到了什么结果。

text 复制代码
A:操作被阻断,错误结果
B:操作执行成功,成功结果

如果只返回 B,模型就缺少 A 的实际状态。明确反馈"执行前被阻断",也比笼统写成"失败"更有用,因为它与"执行了一部分后失败"有不同的恢复方式。

这里还要区分阻断与终止:阻断控制某次调用能否执行,终止控制是否继续模型循环。Pi 的工具批次只有在非空且所有结果都要求终止时,才通过这个条件结束后续循环;单个结果要求终止,不会自动取消同批其他调用。用户取消等其他停止条件则另行处理。

部分失败后,不能直接整批重跑

工具批次通常没有数据库事务那样的整体回滚能力。Pi 的这段调度不会因为 B 失败,就自动撤销 A 已经产生的修改。

例如:

text 复制代码
A:追加一条配置记录,成功
B:读取另一个文件,返回"文件不存在"

模型找到正确路径后,可以重新发起 B。把 A、B 全部再执行一次,可能会重复追加配置。

因此,恢复前要查看各次调用的实际结果。对失败调用也不能只凭"报错了"就认定它毫无影响:读取失败与写入一半后失败,需要不同的处理方式。

如果业务要求两个修改一起成功,就需要额外设计,例如暂存修改并统一提交,或者为已经完成的操作提供补偿。是否能这样做,取决于工具背后的文件系统或业务服务,不能由并行调度自动保证。

取消不等于撤销

假设 A 已经修改文件,B 仍在执行,此时用户取消任务。A 的修改已经发生,发送取消请求不会让文件自动恢复。

Pi 会把取消信号传给工具,并在相关调度位置检查取消状态。正在执行的工具能否及时停止,还取决于具体实现是否处理这个信号,以及操作进行到了哪一步。

这里应分别看待"请求取消""确认停止"和"撤销修改"。例如,一个外部请求可能已经成功,只是响应还没回来;此时仅凭本地取消,无法确认远端操作也被撤销。

从结果反馈的设计上,应尽量说明哪些操作已完成、哪些已停止、哪些状态还不确定。只显示"任务已取消,文件没有变化",会掩盖已经发生的修改。

用两个小测试观察行为

本地运行了仓库已有的两个定向测试,使用模拟模型和简单的回显工具,不访问真实模型服务。两个测试均通过。

第一个测试让第一个调用等待,第二个调用先完成,再检查事件与结果消息。通过的断言对应以下顺序:

text 复制代码
执行结束通知:第二个 → 第一个
工具结果消息:第一个 → 第二个
本轮结束携带的结果:第一个 → 第二个

这验证了完成顺序和结果顺序可以不同。测试检查的是实际事件和消息,而不仅是最终文本有没有出现。

第二个测试在前置钩子中阻断第一个调用,并设置终止标记;第二个调用正常执行。通过的断言表明:

text 复制代码
实际执行的调用:只有第二个
模型请求次数:2 次

由于并非所有工具结果都要求终止,循环继续请求模型。这也说明某个调用的终止标记不能当作整批任务的取消开关。

这两个测试验证了排序与局部阻断。实际文件写入后的回滚、外部服务的取消与补偿,则需要针对具体工具另外验证。

理解 Pi 的工具并行,可以沿着一条顺序看:先判断操作是否独立,再看准备和执行如何安排,然后检查结果如何配对,最后确认部分失败或取消后留下了什么状态。这样才能判断并行究竟节省了哪些等待,又需要承担哪些恢复成本。


相关推荐
Wang's Blog17 分钟前
Java框架 SpringCloud 快速入门: 实现 Feign 最佳实践(抽取方式)
java·spring cloud
MandalaO_O1 小时前
IDEA 开发(快捷键 + 调试 + 序列化)
java·ide·intellij-idea
xiaoqiMikko2 小时前
JVM 线上排查实战(七):jps 看不见它、jstack 连不上它,可它明明活得好好的
java·jvm
狼爷2 小时前
Rust/Go/Java/Python/PHP 大比拼:负载下后端框架到底差多少?
java·后端·编程语言
念何架构之路3 小时前
zap扩展生态与总结
java·前端·数据库
第七页独白3 小时前
汽车零件厂如何通过 QMS 真正落地 IATF 16949——QMS软件系统:品质检验-内审稽核-8d客诉管理:全星质量管理软件系统
java·前端·数据库
QCoding4 小时前
Spring AI Alibaba Graph实战:从ReAct Agent到Workflow,企业AI复杂流程该如何编排?
java·人工智能
用户094248568034 小时前
第25章:Java虚拟线程(Project Loom)实战与调度协作
java·jvm
砚底藏山河4 小时前
量化实战:截面因子有效性检验(IC 分析与分层回测)
java·python·金融·maven
wuminyu4 小时前
ForkJoinPool内部WorkQueue的Lock-Free数组操作以及并发任务窃取原理剖析
java·linux·c语言·jvm·c++