引言
Agent Loop 是 CountBot 的核心引擎,实现了经典的 ReAct(Reasoning + Acting)循环模式。本文将深入分析其工程实现,探讨如何将学术概念转化为可靠的生产代码。
ReAct 循环原理
ReAct 模式的核心思想是让 LLM 交替进行"推理"和"行动":
用户提问 → LLM 推理 → 决定调用工具 → 执行工具 → 将结果反馈给 LLM → 继续推理 → ... → 最终回答
这个循环可能执行多轮,直到 LLM 认为已经收集到足够信息来回答用户问题。
AgentLoop 核心实现
初始化参数
python
class AgentLoop:
def __init__(
self,
provider, # LLM Provider 实例
workspace: Path, # 工作空间路径
tools, # ToolRegistry 实例
context_builder=None, # 上下文构建器
session_manager=None, # 会话管理器
subagent_manager=None, # 子代理管理器
model: str | None = None,
max_iterations: int = 25, # 最大循环次数(安全阀)
max_retries: int = 3, # LLM 调用重试次数
retry_delay: float = 1.0, # 重试延迟
temperature: float = 0.7,
max_tokens: int = 4096,
):
关键设计点:
max_iterations = 25作为安全阀,防止 LLM 陷入无限工具调用循环max_retries = 3处理 LLM API 的瞬时故障- 所有依赖通过构造函数注入,便于测试和替换
消息处理流程
python
async def process_message(
self, message, session_id, context=None,
media=None, channel=None, chat_id=None, cancel_token=None
) -> AsyncIterator[str]:
process_message 是一个异步生成器,通过 yield 逐步返回流式响应。这种设计使得调用方可以实时获取 LLM 的输出,而不必等待整个处理完成。
工具调用编排
当 LLM 返回 tool_call 时,AgentLoop 执行以下步骤:
- 解析工具调用 :从
StreamChunk.tool_call中提取工具名和参数 - 参数验证 :通过
Tool.validate_params()验证参数合法性 - 执行工具 :调用
ToolRegistry.execute()执行工具 - 结果注入 :将工具执行结果作为
tool角色消息注入对话历史 - 继续推理:将更新后的消息列表再次发送给 LLM
python
# 伪代码展示核心循环
for iteration in range(self.max_iterations):
async for chunk in self.provider.chat_stream(messages, tools):
if chunk.is_content:
yield chunk.content # 流式输出文本
if chunk.is_tool_call:
tool_calls.append(chunk.tool_call)
if not tool_calls:
break # LLM 没有请求工具调用,循环结束
for tc in tool_calls:
result = await self.tools.execute(tc.name, **tc.arguments)
messages.append({"role": "tool", "content": result, "tool_call_id": tc.id})
tool_calls.clear()
取消令牌机制
CountBot 实现了优雅的任务取消机制:
python
# 全局取消令牌管理
_session_cancel_tokens: dict[str, CancellationToken] = {}
def cancel_session(session_id: str) -> bool:
if session_id in _session_cancel_tokens:
_session_cancel_tokens[session_id].cancel()
return True
return False
在 AgentLoop 的每次迭代中检查取消令牌,确保用户可以随时中断长时间运行的任务。
错误处理策略
分层错误处理
- LLM 调用层:自动重试 + 指数退避
- 工具执行层:捕获异常并将错误信息作为工具结果返回给 LLM
- 循环层 :
max_iterations防止无限循环
工具执行错误的优雅降级
当工具执行失败时,错误信息会被格式化后返回给 LLM,让 LLM 自行决定如何处理:
python
try:
result = await tool.execute(**arguments)
except Exception as e:
result = f"工具执行失败: {str(e)}"
# 无论成功失败,都将结果反馈给 LLM
messages.append({"role": "tool", "content": result})
这种设计让 LLM 有机会尝试其他方案或向用户解释失败原因。
性能优化
流式响应
整个链路都是流式的:LLM 流式生成 → AgentLoop 流式 yield → WebSocket 流式推送。用户可以在 LLM 生成第一个 token 时就看到响应开始。
上下文窗口管理
通过 ContextBuilder 智能管理上下文窗口:
- 系统提示词(固定)
- 记忆摘要(压缩的历史信息)
- 最近 N 条对话历史(滑动窗口)
- 当前消息
与业界实践的对比
| 方面 | CountBot | LangChain Agent | AutoGPT |
|---|---|---|---|
| 循环控制 | max_iterations 硬限制 | 可配置 | 无限制 |
| 工具调用 | 原生 Function Calling | Tool/Agent 抽象 | 自定义协议 |
| 流式支持 | 全链路流式 | 部分支持 | 不支持 |
| 错误恢复 | 错误反馈给 LLM | 异常传播 | 自我修复 |
总结
CountBot 的 Agent Loop 实现展示了如何将 ReAct 理论优雅地工程化。通过异步生成器实现流式响应、通过安全阀防止失控、通过错误反馈实现自愈,这些设计模式值得在任何 AI Agent 项目中借鉴。