AI Agent 进阶:多工具编排与记忆系统——从单轮对话到自主决策的架构跃迁
一、当 LLM 只会说话不会做事:单轮对话的能力天花板
大语言模型在文本生成上表现出色,但本质上是一个"只会说话"的系统——它无法查询数据库、无法调用 API、无法操作文件系统。当用户问"帮我查一下今天的生产环境错误日志数量",LLM 只能编造一个看起来合理的数字,而非真正去执行查询。
AI Agent 的核心跃迁在于:将 LLM 从被动的文本生成器转变为主动的决策执行者。Agent 接收用户意图后,自主规划执行步骤、选择合适工具、解析执行结果、根据反馈调整策略,直到任务完成。这不再是单轮的"输入-输出",而是一个包含感知-规划-执行-反馈的闭环系统。
代码是人与机器的对话,而 Agent 更像是给这段对话赋予了行动力——不再只是纸上谈兵,而是真正下场做事。如同易经所言"变则通,通则久",Agent 的核心能力正是在变化中寻找通路,在执行中持续调整。
二、ReAct 循环与工具选择:Agent 的决策引擎
Agent 的决策过程遵循 ReAct(Reasoning + Acting)范式:在每一步中,模型先推理当前状态和下一步行动(Reasoning),然后执行具体工具调用(Acting),观察执行结果(Observation),再基于观察进行下一轮推理。
graph TB U[用户输入] --> P[意图解析与任务规划] P --> R1[推理: 当前需要做什么?] R1 --> T[工具选择与参数生成] T --> E[工具执行] E --> O[观察执行结果] O --> D{任务是否完成?} D -->|否| R2[推理: 根据结果调整策略] R2 --> T D -->|是| S[汇总结果返回用户] subgraph 记忆系统 M1[短期记忆: 当前对话上下文] M2[工作记忆: 中间执行结果] M3[长期记忆: 历史经验与知识] end R1 -.-> M1 R2 -.-> M2 T -.-> M3工具选择的关键挑战是:当可用工具数量增多时,LLM 需要准确理解每个工具的功能边界,避免选择错误的工具或传入不匹配的参数。解决方案是将工具描述结构化为 JSON Schema,包含功能说明、参数类型、约束条件和典型用例,让 LLM 在推理时拥有充分的决策依据。
记忆系统是 Agent 的另一个核心组件。短期记忆存储当前对话的上下文窗口;工作记忆保存当前任务的中间结果,如数据库查询返回的临时数据;长期记忆则持久化历史交互经验,使 Agent 能够从过去的成功和失败中学习。
三、生产级 Agent 框架与工具编排实现
以下代码实现了一个支持多工具编排、记忆管理和错误恢复的 Agent 框架:
import json import logging from typing import ( Any, Callable, Dict, List, Optional, TypedDict ) from dataclasses import dataclass, field from enum import Enum from abc import ABC, abstractmethod logger = logging.getLogger(__name__) class ToolParameter(TypedDict): """工具参数定义""" name: str type: str description: str required: bool enum: Optional[List[str]] @dataclass class ToolDefinition: """工具定义""" name: str description: str parameters: List[ToolParameter] handler: Callable[..., Any] timeout: float = 30.0 retry_count: int = 2 def to_schema(self) -> dict: """转换为 LLM 可理解的功能描述""" properties = {} required = [] for p in self.parameters: prop = {"type": p["type"], "description": p["description"]} if p.get("enum"): prop["enum"] = p["enum"] properties[p["name"]] = prop if p["required"]: required.append(p["name"]) return { "type": "function", "function": { "name": self.name, "description": self.description, "parameters": { "type": "object", "properties": properties, "required": required, }, }, } class AgentState(Enum): """Agent 状态""" IDLE = "idle" PLANNING = "planning" EXECUTING = "executing" OBSERVING = "observing" COMPLETED = "completed" FAILED = "failed" class MemoryStore(ABC): """记忆存储抽象接口""" @abstractmethod async def save(self, key: str, value: Any, ttl: Optional[int] = None) -> None: pass @abstractmethod async def load(self, key: str) -> Optional[Any]: pass @abstractmethod async def search(self, query: str, top_k: int = 5) -> List[Dict]: pass class InMemoryStore(MemoryStore): """基于内存的短期记忆实现""" def __init__(self, max_entries: int = 1000): self._store: Dict[str, Any] = {} self._max_entries = max_entries async def save(self, key: str, value: Any, ttl: Optional[int] = None) -> None: if len(self._store) >= self._max_entries: # LRU 淘汰:移除最早的一半条目 keys_to_remove = list(self._store.keys())[: self._max_entries // 2] for k in keys_to_remove: del self._store[k] self._store[key] = value async def load(self, key: str) -> Optional[Any]: return self._store.get(key) async def search(self, query: str, top_k: int = 5) -> List[Dict]: # 简单的关键词匹配,生产环境应替换为向量检索 results = [] query_lower = query.lower() for key, value in self._store.items(): if query_lower in str(key).lower() or query_lower in str(value).lower(): results.append({"key": key, "value": value}) if len(results) >= top_k: break return results class AgentExecutor: """Agent 执行器:管理 ReAct 循环""" def __init__( self, tools: List[ToolDefinition], memory: MemoryStore, max_iterations: int = 10, ): self._tools = {t.name: t for t in tools} self._tool_schemas = [t.to_schema() for t in tools] self._memory = memory self._max_iterations = max_iterations self._state = AgentState.IDLE self._execution_log: List[Dict] = [] def get_tool_schemas(self) -> List[dict]: """获取所有工具的 Schema 描述,供 LLM 推理使用""" return self._tool_schemas async def execute_tool( self, tool_name: str, arguments: Dict[str, Any] ) -> Any: """执行指定工具,带重试和超时""" if tool_name not in self._tools: raise ValueError( f"工具 '{tool_name}' 不存在," f"可用工具: {list(self._tools.keys())}" ) tool = self._tools[tool_name] # 参数校验 required_params = [ p["name"] for p in tool.parameters if p["required"] ] missing = [ p for p in required_params if p not in arguments ] if missing: raise ValueError( f"工具 '{tool_name}' 缺少必需参数: {missing}" ) last_error = None for attempt in range(tool.retry_count + 1): try: result = await tool.handler(**arguments) # 将执行结果存入工作记忆 await self._memory.save( f"tool_result:{tool_name}:{len(self._execution_log)}", result, ) self._execution_log.append({ "tool": tool_name, "arguments": arguments, "result": result, "status": "success", }) return result except Exception as e: last_error = e logger.warning( f"工具 '{tool_name}' 执行失败 " f"(第 {attempt + 1} 次): {e}" ) if attempt < tool.retry_count: import asyncio await asyncio.sleep(1.0 * (attempt + 1)) self._execution_log.append({ "tool": tool_name, "arguments": arguments, "error": str(last_error), "status": "failed", }) raise RuntimeError( f"工具 '{tool_name}' 执行失败," f"已重试 {tool.retry_count} 次: {last_error}" ) async def run( self, user_input: str, llm_client: Any, ) -> str: """执行完整的 Agent ReAct 循环""" self._state = AgentState.PLANNING self._execution_log = [] # 构建系统提示 system_prompt = self._build_system_prompt() # 对话历史 messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_input}, ] for iteration in range(self._max_iterations): self._state = AgentState.PLANNING # 调用 LLM 进行推理 response = await llm_client.chat( messages=messages, tools=self._tool_schemas, temperature=0.1, ) # 检查是否有工具调用 tool_calls = response.get("tool_calls", []) if not tool_calls: # LLM 认为任务已完成,返回最终回答 self._state = AgentState.COMPLETED return response.get("content", "") # 执行所有工具调用 self._state = AgentState.EXECUTING messages.append(response) for tool_call in tool_calls: tool_name = tool_call["function"]["name"] try: arguments = json.loads( tool_call["function"]["arguments"] ) except json.JSONDecodeError as e: logger.error( f"工具参数 JSON 解析失败: {e}" ) arguments = {} try: result = await self.execute_tool( tool_name, arguments ) # 将工具结果追加到对话历史 messages.append({ "role": "tool", "tool_call_id": tool_call["id"], "content": json.dumps( result, ensure_ascii=False, default=str ), }) except Exception as e: messages.append({ "role": "tool", "tool_call_id": tool_call["id"], "content": json.dumps({ "error": str(e), "suggestion": "请检查参数或尝试其他工具", }, ensure_ascii=False), }) self._state = AgentState.OBSERVING self._state = AgentState.FAILED return "任务执行超过最大迭代次数,请简化需求或分步执行" def _build_system_prompt(self) -> str: """构建 Agent 系统提示""" tool_descriptions = "\n".join( f"- {name}: {tool.description}" for name, tool in self._tools.items() ) return ( "你是一个智能助手,可以通过调用工具来完成任务。\n" "请按照 ReAct 模式工作:\n" "1. 思考当前需要做什么\n" "2. 选择合适的工具并调用\n" "3. 观察执行结果\n" "4. 根据结果决定下一步行动\n\n" f"可用工具:\n{tool_descriptions}\n\n" "注意事项:\n" "- 仔细阅读工具的参数要求,确保传入正确的参数\n" "- 如果工具执行失败,分析错误原因并调整策略\n" "- 任务完成后直接给出最终回答,不要再调用工具" ) def get_execution_log(self) -> List[Dict]: """获取执行日志""" return list(self._execution_log) def get_state(self) -> AgentState: """获取当前状态""" return self._state关键工程实践:工具定义使用 JSON Schema 标准化描述,确保 LLM 能准确理解参数要求;执行日志完整记录每一步的工具调用和结果,便于调试和审计;工具执行失败时将错误信息反馈给 LLM,让其自主调整策略而非直接终止。
四、Agent 架构的边界:自主决策的代价与风险
工具选择的可靠性问题:LLM 的工具选择并非 100% 准确。当可用工具超过 20 个时,选择准确率显著下降;参数类型复杂(如嵌套对象)时,生成的参数 JSON 格式错误率上升。实践中通常将工具按功能域分组,每次只暴露与当前任务相关的子集,降低选择难度。
执行成本与延迟:每次工具调用都是一次 I/O 操作,多步 Agent 的总延迟是各步之和。在实时对话场景中,用户等待超过 10 秒便会失去耐心。需要设置最大迭代次数限制,并在每步执行前估算剩余步骤,必要时提前返回中间结果。
安全与权限控制:Agent 拥有工具执行能力意味着它可能执行危险操作(如删除数据、修改配置)。必须在工具层面实现权限隔离:只读工具无需审批,写操作工具需要确认机制,危险操作工具需要二次验证。
记忆系统的扩展性瓶颈:长期记忆的检索质量直接影响 Agent 的决策水平。关键词匹配在数据量增大后召回率急剧下降,向量检索需要额外的 Embedding 计算和索引维护成本。在多轮对话中,记忆的时效性管理也是难题——过时的记忆可能误导决策,但自动判断哪些记忆已过时本身就是一个复杂问题。
禁用场景:对确定性要求极高的场景(如金融交易、医疗诊断),Agent 的非确定性决策过程不可接受;延迟敏感的在线服务,多步 Agent 的累积延迟无法满足 SLA;合规要求严格的领域,Agent 的自主决策链路难以通过审计。
五、总结
AI Agent 通过 ReAct 循环将 LLM 从文本生成器转变为自主决策执行者,核心架构包含工具定义与选择、记忆系统管理和执行循环控制。生产实践中需关注:工具描述的 Schema 标准化、工具数量对选择准确率的影响、执行日志的完整记录、权限隔离与安全控制、最大迭代次数限制。Agent 适用于需要多步推理和工具调用的复杂任务,但在确定性要求高、延迟敏感、合规严格的场景中需谨慎使用,必要时应回退到确定性的规则引擎方案。