
Agent架构设计:规划器、工具、记忆、评估器如何协同工作
说实话,我见过太多团队在搭Agent系统时踩的坑------上来就硬编一个"LLM+Tools"的玩具demo,然后期望它能搞定生产级任务。现实是,这种简单架构在面对复杂任务流时,会出现规划混乱、工具调用爆炸、记忆丢失、结果无法收敛等一系列问题。
真正工程化的Agent架构,绕不开四个核心组件:规划器(Planner)、工具(Tools)、记忆(Memory)、评估器(Evaluator)。今天聊聊它们各自负责什么,以及怎么让它们真正协同起来。
一、规划器:Agent的"大脑",决定做什么
规划器是Agent的核心决策层,负责理解任务目标、拆解执行步骤、决定下一步行动。
主流的规划策略有两种:
1.1 ReAct(Reasoning + Acting)
ReAct的思路很简单:思考 → 行动 → 观察 → 再思考。每一步都让LLM先推理一下当前状态,再决定做什么。
python
import json
class ReActPlanner:
def __init__(self, llm):
self.llm = llm
def plan(self, task: str, max_steps: int = 10):
history = []
observation = ""
for step in range(max_steps):
# 让LLM推理下一步
prompt = self._build_prompt(task, history, observation)
response = self.llm.generate(prompt)
# 解析出 action 和 action_input
action, action_input = self._parse_response(response)
if action == "finish":
return action_input
# 执行action
observation = self._execute(action, action_input)
history.append({
"step": step,
"action": action,
"action_input": action_input,
"observation": observation
})
return "规划失败:超过最大步数"
def _build_prompt(self, task, history, observation):
return f"""任务:{task}
执行历史:
{json.dumps(history, ensure_ascii=False)}
当前观察:{observation}
下一步行动(thinking + action + action_input):
"""
1.2 Chain-of-Thought(CoT)规划
CoT更适合复杂推理场景,让LLM显式输出推理链,逐步拆解任务。
python
def cot_plan(task: str, llm):
"""Chain-of-Thought 规划模式"""
prompt = f"""任务:{task}
请逐步推理出执行计划,每一步都要明确:
1. 当前要解决什么问题
2. 下一步具体做什么
3. 如何判断这一步完成
推理:
"""
reasoning = llm.generate(prompt)
# 将推理结果转换为可执行步骤
plan_prompt = f"""基于以下推理过程,列出具体执行步骤:
{reasoning}
执行步骤:
"""
steps = llm.generate(plan_prompt)
return steps
规划器设计的坑
坑1:规划器太"聪明" ,导致行动太空泛。比如LLM说"调用搜索API搜索相关信息",但没有指定关键词、时间范围、结果数量。工具调用参数必须精确,规划器要学会生成结构化的action_input。
坑2:没有停止条件 。很多实现忘了加finish action,导致规划陷入死循环。一定要设置max_steps,并且在每个循环里检查是否已达到目标。
二、工具层:Agent的"手脚"
工具是Agent与外部世界交互的接口。设计好工具系统,是Agent能真正干活的关键。
2.1 工具描述的写法
工具描述是LLM理解"能做什么"的唯一来源。很多团队随便写两句,结果LLM调用时参数乱七八糟。
好的工具描述:
python
tools = [
{
"name": "search_news",
"description": "搜索指定主题的最新新闻。
输入参数:
- keyword: str, 搜索关键词,必填
- days: int, 搜索最近N天的新闻,默认7
- limit: int, 返回结果数量,默认10,最大50
返回:新闻列表,每条包含标题、URL、发布时间、摘要",
"parameters": {
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "搜索关键词"},
"days": {"type": "integer", "default": 7},
"limit": {"type": "integer", "default": 10, "maximum": 50}
},
"required": ["keyword"]
}
},
{
"name": "send_email",
"description": "发送电子邮件。
输入参数:
- to: str, 收件人邮箱地址,必填
- subject: str, 邮件主题,必填
- body: str, 邮件正文,必填
返回:发送结果(成功/失败)",
"parameters": {
"type": "object",
"properties": {
"to": {"type": "string", "format": "email"},
"subject": {"type": "string"},
"body": {"type": "string"}
},
"required": ["to", "subject", "body"]
}
}
]
关键原则 :description要自包含,LLM看到description就知道能干什么、该传什么参数。不要写得太抽象,也不要写得太细节------给LLM足够的上下文来做出正确决策。
2.2 工具执行与错误处理
工具执行时要考虑:超时、重试、参数校验、结果解析。
python
import asyncio
from typing import Any, Callable
class ToolExecutor:
def __init__(self, max_retries: int = 2, timeout: float = 30.0):
self.max_retries = max_retries
self.timeout = timeout
async def execute(self, tool: Callable, params: dict) -> dict:
"""带重试和超时的工具执行"""
for attempt in range(self.max_retries + 1):
try:
result = await asyncio.wait_for(
tool(**params),
timeout=self.timeout
)
return {"status": "success", "result": result}
except asyncio.TimeoutError:
return {"status": "error", "error": f"超时({self.timeout}s)"}
except Exception as e:
if attempt == self.max_retries:
return {"status": "error", "error": str(e)}
# 重试前等待指数退避
await asyncio.sleep(2 ** attempt)
return {"status": "error", "error": "重试次数耗尽"}
2.3 工具选择的艺术
不是所有任务都需要调用工具。有时候LLM自己就能回答,有时候需要组合多个工具。工具选择的策略决定了Agent的效率。
python
def select_tools(task: str, available_tools: list, llm) -> list:
"""让LLM决定用哪些工具"""
tool_names = [t["name"] for t in available_tools]
prompt = f"""任务:{task}
可用工具:{tool_names}
请决定需要调用哪些工具(可多选)。直接列出工具名称,用逗号分隔。
如果不需要工具,返回"无需工具"。"""
response = llm.generate(prompt).strip()
if response == "无需工具":
return []
selected = [t.strip() for t in response.split(",")]
return [t for t in available_tools if t["name"] in selected]
三、记忆系统:Agent的"海马体"
没有记忆的Agent,每次任务都从零开始。有记忆的Agent,才能在多轮对话中保持上下文一致性,在长程任务中积累信息。
3.1 记忆的三层架构
用户输入 → 感官记忆(当前轮对话)
↓
工作记忆(短期记忆,当前任务上下文)
↓
长期记忆(向量数据库,跨任务知识)
感官记忆:直接存储用户当前输入,通常不做处理。
工作记忆:当前任务的执行上下文,包括:
- 已执行的步骤和结果
- 当前的中间目标和状态
- 最近的LLM输出
python
class WorkingMemory:
def __init__(self, max_history: int = 20):
self.max_history = max_history
self.history = []
self.context = {}
def add_turn(self, role: str, content: str):
self.history.append({"role": role, "content": content})
if len(self.history) > self.max_history:
self.history.pop(0)
def get_context(self) -> list:
return self.history[-self.max_history:]
def set_context(self, key: str, value: Any):
self.context[key] = value
def get_context_value(self, key: str) -> Any:
return self.context.get(key)
长期记忆:用向量数据库存储历史交互、领域知识、用户偏好。检索时用Embedding匹配相关记忆。
python
from datetime import datetime
class LongTermMemory:
def __init__(self, vector_store):
self.vector_store = vector_store # Chroma / Milvus / FAISS 等
def store(self, text: str, metadata: dict = None):
"""存储记忆到向量库"""
embedding = self._embed(text)
self.vector_store.add(
ids=[str(datetime.now().timestamp())],
embeddings=[embedding],
documents=[text],
metadatas=[metadata or {}]
)
def retrieve(self, query: str, top_k: int = 5) -> list:
"""基于语义检索记忆"""
query_embedding = self._embed(query)
results = self.vector_store.query(
query_embeddings=[query_embedding],
n_results=top_k
)
return results
def _embed(self, text: str) -> list:
# 用 embedding model 获取向量
return embedding_model.encode(text)
3.2 记忆何时写入?
这是个设计选择问题。太频繁写入会增加开销,太少则丢失关键信息。
推荐策略:
- 每轮对话结束时:写入用户query和Agent响应摘要
- 关键决策点:写入Agent做出重要判断的推理过程
- 任务完成时:写入任务整体执行摘要,供后续参考
python
class MemoryManager:
def __init__(self, working: WorkingMemory, long_term: LongTermMemory):
self.working = working
self.long_term = long_term
def on_turn_end(self, user_query: str, agent_response: str):
"""每轮结束时写入记忆"""
# 写入工作记忆
self.working.add_turn("user", user_query)
self.working.add_turn("assistant", agent_response)
# 关键内容写入长期记忆
if self._is_significant(user_query, agent_response):
summary = self._summarize(user_query, agent_response)
self.long_term.store(
text=summary,
metadata={"type": "turn_summary", "timestamp": datetime.now().isoformat()}
)
def _is_significant(self, query: str, response: str) -> bool:
"""判断是否是重要内容"""
# 例如:包含用户偏好、关键决策、错误修复等
keywords = ["喜欢", "偏好", "不要", "记住", "修复", "问题"]
return any(kw in query or kw in response for kw in keywords)
四、评估器:Agent的"质检员"
评估器负责判断当前状态是否已达到目标、是否需要调整策略。没有评估器的Agent,就像没有质检的工厂------可能在错误的道路上狂奔到底。
4.1 结果评估
最基础的评估:判断任务是否完成。
python
def evaluate_completion(task: str, result: str, llm) -> dict:
"""评估任务是否完成"""
prompt = f"""任务:{task}
执行结果:{result}
请判断任务是否成功完成:
- 如果结果完全满足任务要求,返回 {{"status": "success", "reason": "..."}}
- 如果结果不满足要求,返回 {{"status": "failed", "reason": "缺少XX,YY有问题"}}
- 如果结果部分满足,返回 {{"status": "partial", "reason": "..."}}"""
response = llm.generate(prompt)
return json.loads(response)
4.2 过程评估(Early Stopping)
不是所有任务都能在预设步数内完成,但要避免Agent在错误方向上浪费步数。过程评估在每步执行后判断:继续走下去是否有意义?
python
def evaluate_progress(task: str, history: list, llm) -> dict:
"""评估当前进度是否值得继续"""
prompt = f"""任务:{task}
执行历史:{json.dumps(history, ensure_ascii=False)}
请判断:
- 如果继续执行有望完成任务,返回 {{"continue": true, "reason": "..."}}
- 如果当前路径不太可能成功,返回 {{"continue": false, "reason": "应尝试XX方向"}}
"""
response = llm.generate(prompt)
return json.loads(response)
4.3 多维度评估
实际系统中,评估可能涉及多个维度:正确性、效率、成本、用户体验。
python
from dataclasses import dataclass
@dataclass
class EvaluationResult:
success: bool
correctness: float # 0-1,正确性评分
efficiency: float # 0-1,效率评分(用时/期望用时)
cost: float # 实际成本
feedback: str # 详细反馈
def multi_dimension_evaluate(
task: str,
result: str,
expected_time: float,
expected_cost: float,
llm
) -> EvaluationResult:
# ... 多维度评估逻辑
pass
五、四大组件如何协同
说了这么多独立组件,关键是它们怎么串联起来工作。
用户输入/任务
↓
规划器(Planner)→ 理解任务,拆解步骤,决定下一步action
↓
工具层(Tools)→ 执行action,获取结果
↓
评估器(Evaluator)→ 判断结果,决定是否继续/调整策略
↓
记忆(Memory)→ 写入执行记录,支持下一轮规划
↓
循环,直到任务完成或放弃
用一个完整的执行循环来串联:
python
class Agent:
def __init__(self, llm, tools: list, memory_manager, evaluator):
self.llm = llm
self.tools = {t["name"]: t for t in tools}
self.memory = memory_manager
self.evaluator = evaluator
async def run(self, task: str, max_steps: int = 10):
"""Agent主运行循环"""
self.memory.working.set_context("task", task)
history = []
for step in range(max_steps):
# 1. 规划:决定下一步
context = self.memory.working.get_context()
action = self.llm.plan(task, context, history)
if action["name"] == "finish":
return {"status": "success", "result": action["input"]}
# 2. 执行工具
if action["name"] in self.tools:
tool_result = await self.memory.tool_executor.execute(
self.tools[action["name"]],
action["input"]
)
observation = tool_result.get("result", tool_result.get("error"))
else:
observation = f"未知工具: {action['name']}"
history.append({
"step": step,
"action": action,
"observation": observation
})
# 3. 评估:判断是否继续
eval_result = self.evaluator.evaluate_progress(task, history)
if not eval_result["continue"]:
# 策略调整:让LLM重新规划
task = f"{task}(上条路径不可行,建议:{eval_result['reason']})"
# 4. 写入记忆
self.memory.on_step_complete(action, observation)
return {"status": "failed", "reason": "超过最大步数"}
写在最后
设计Agent架构,核心是让四个组件各司其职、协同工作。规划器做决策,工具执行动作,记忆存储上下文,评估器控制质量。
但我想泼一盆冷水:现在的Agent系统,远没有达到"智能"的水平。规划依赖LLM的推理能力,而LLM的推理是不稳定的;工具调用依赖准确的参数描述,但现实中的工具往往是遗留系统,描述质量参差不齐;记忆的检索和写入策略,没有统一标准,全靠团队经验。
真正的挑战不在于"能不能搭起来",而在于"搭起来之后能不能稳定跑、出了故障能不能排查、效果不好能不能优化"。这是工程问题,不是算法问题。
你目前在做的Agent项目,遇到的最大瓶颈是什么?规划失控、工具报错、还是记忆丢失?
