Agent¶
你设计的 Agent 核心 ReAct 循环完整流程是什么?每一步你在代码里做了哪些容错处理?
好的,我想一下,我设计的Agent的核心ReAct循环的完整流程是:第一、通过cron队列将到期的任务注入消息历史,再收集已完成的任务并注入任务完成通知,然后每3轮提醒更新一下todo;第二,压缩过大的调用工具后的输出结果,截断旧的消息的头部和尾部,对较早的工具调用结果进行压缩,如果还是超过了context的limit上限则主动compact进行总结;第三,更新内存的记忆,已连接的mcp,并且重新组装工具池;第四,组装对LLM的System Prompt,并带重试调用LLM;第五,控制LLM的调用失败原因,如果超过最大token上下文,那么在第一次会进行重试,重试两次后就让LLM进行续写,如果没有超过最大tokens上下文则重置最大tokens上下文;第六,检查工具的调用,如果单次对话没有产生工具调用,则触发Stop Hooks然后结束循环返回,如果有产生工具调用,则遍历每一个tooluse block,第七,执行调用到的tooluse block,如果调用到的工具是压缩工具,那么执行上下文压缩然后回归循环,如果调用到的工具不是压缩,那么进行权限检查,如果hook返回了拦截,那么就是不能执行该工具(例如一些危险命令);如果需要调用后台执行工具,就会启动后台线程并返回占位符;如果不需要后台执行,那么就会找到对应的工具并执行工具,然后再进行hook的权限拦截确认,确认后进行工具调用。上面的一系列工具调用,如果没有触发更新todo,那么当三次后就会提醒更新。第八,上面工具调用的results都会组装起来加入到message,最后将当前对话产生的全部message发回给LLM。
你提到四层渐进式上下文压缩,分别是哪四层?各自解决什么场景下的上下文过长问题?
至于四层渐进式上下文压缩按照调用顺序分别是大工具输出持久化,解决的是工具调用的输出结果过大的情况;中间消息裁剪,解决的是对话轮数太多造成消息输出过长,裁剪动作会识别tooluse和toolresult的边界确保不会切分错误,因为一个tool_use对应一个result是整体;旧工具的调用结果使用占位,解决的是最近几个动作进行了太多的工具调用,就保留了太多的记录;全量总结压缩,解决的是经过了前面的操作后,还是很大,那就来个彻底的清理。因为全量总结压缩是需要LLM的,所以从节约成本上考虑,会将该动作放到最后,为什么说渐进式,就是从轻量到重量的操作,全量总结压缩是当实在没办法的时候进行的。
你在 ReAct 循环里区分了三种终止状态:maxtokens、endturn、tool_use,分别触发循环退出的逻辑有什么不同?各自对应的处理策略是什么?
好的,我想一下,我设计的Agent的核心ReAct循环的完整流程是:第一、通过cron队列将到期的任务注入消息历史,再收集已完成的任务并注入任务完成通知,然后每3轮提醒更新一下todo;第二,压缩过大的调用工具后的输出结果,截断旧的消息的头部和尾部,对较早的工具调用结果进行压缩,如果还是超过了context的limit上限则主动compact进行总结;第三,更新内存的记忆,已连接的mcp,并且重新组装工具池;第四,组装对LLM的System Prompt,并带重试调用LLM;第五,控制LLM的调用失败原因,如果超过最大token上下文,那么在第一次会进行重试,重试两次后就让LLM进行续写,如果没有超过最大tokens上下文则重置最大tokens上下文;第六,检查工具的调用,如果单次对话没有产生工具调用,则触发Stop Hooks然后结束循环返回,如果有产生工具调用,则遍历每一个tooluse block,第七,执行调用到的tooluse block,如果调用到的工具是压缩工具,那么执行上下文压缩然后回归循环,如果调用到的工具不是压缩,那么进行权限检查,如果hook返回了拦截,那么就是不能执行该工具(例如一些危险命令);如果需要调用后台执行工具,就会启动后台线程并返回占位符;如果不需要后台执行,那么就会找到对应的工具并执行工具,然后再进行hook的权限拦截确认,确认后进行工具调用。上面的一系列工具调用,如果没有触发更新todo,那么当三次后就会提醒更新。第八,上面工具调用的results都会组装起来加入到message,最后将当前对话产生的全部message发回给LLM。
你通过 BUILTINTOOLS + permissionhook 做工具权限隔离,举一个你实际做的危险操作拦截案例,permission hook 的执行时机是在工具调用前还是执行后?如果用户强行构造恶意 tool 请求,你有几层防护?
至于四层渐进式上下文压缩按照调用顺序分别是大工具输出持久化,解决的是工具调用的输出结果过大的情况;中间消息裁剪,解决的是对话轮数太多造成消息输出过长,裁剪动作会识别tooluse和toolresult的边界确保不会切分错误,因为一个tool_use对应一个result是整体;旧工具的调用结果使用占位,解决的是最近几个动作进行了太多的工具调用,就保留了太多的记录;全量总结压缩,解决的是经过了前面的操作后,还是很大,那就来个彻底的清理。因为全量总结压缩是需要LLM的,所以从节约成本上考虑,会将该动作放到最后,为什么说渐进式,就是从轻量到重量的操作,全量总结压缩是当实在没办法的时候进行的。
你实现了 spawnsubagent 子任务代理和 spawnteammatethread 多协作 Agent,说下两者的使用场景区别,以及你用 MessageBus 做跨 Agent 通信时,怎么解决多线程消息乱序、消息丢失问题? 子任务代理和多协作Agent其实是有区别的,首先,子代理是同步阻塞型的,只会在当前线程进行,而协作Agent是异步后台,通过独立的daemon线程进行;在任务的执行流程上,子代理是一步一步来的,主Agent等待子代理执行完后才到下一步,但是协作Agent不用让主Agent进行等待,各自独立执行,通过消息通信;在生命周期上,子代理执行完成后就退出了,返回执行的结果给主Agent,协作Agent可以长期运行,可以等待任务,轮询消息。而且两个的工具集也是不一样的范围的,子代理只有基础工具集,没有spawnsubagent的工具,但是协作agent有完整工具集,并且可以认领任务,同时实现了工作树的隔离。使用场景的话,子代理是主agent用于聚焦小型任务的执行,但是协作agent是将大型任务拆分,并行协作,例如将一个任务拆分给多个队友来做,或者是一些需要长时间后台执行的任务,通过消息通知。 MessageBus是为每个agent设计一个独立的“邮箱文件”,追加写入,读取就删除,通过追加写入和时间戳解决乱序;通过持久化和读即删除来解决消息丢失问题,另外,除了MessageBus本身,关键的共享数据例如后台任务执行的结果,cron任务队列,主agent的循环都使用了锁进行保护,防止并发修改引发混乱。
你把自研内置工具和 MCP 外部工具合并成统一 tool pool 供给 LLM 调用,MCP 服务是多实例的情况下,你做了哪些服务发现、生命周期管理逻辑?MCP 工具调用失败时怎么做降级容错?
MCP多实例通过字典维护服务的key,通过连接时候mcp工厂方法创建客户端,客户端自动拿到mcp的所有可用工具的定义,生命周期的维护的话,是让工具池每轮循环的时候重新构建,如果新增或者删除mcp连接那么下一轮的工具池就会失效,通过命名规则对不同mcp的同名工具进行区分,并且确保名称规范,避免字典key问题,mcp的完整生命周期从工厂连接服务器,创建mcp实例,将实例存进实例字典全局存活,每次agent_loop循环都会调用工具池创建,然后将全部mcp工具存入,如果和mcp断开的话,则从mcp实例字典进行移除。 如果发生了mcp的工具调用失败的话,首先,在mcp客户端内部会进行trycatch,如果工具未知则不崩溃,已知但执行异常则返回错误信息也不会造成崩溃;然后,因为客户端内部进行了处理,外部就不会崩溃,所有的结果result都会被返回给模型,并且对于高危动作的mcp工具也会进行权限hook,基本就是有问题就发回给模型进行处理。