Agent进化:nanoAgent记忆与规划双突破
nanoAgent进化版:agent-plus
之前说过这个精简版的agent还缺少了几样功能:缺少的功能
作者也认识到了这个问题,于是就有了 nanoAgent第二个版本:agent-plus.py,它多了几十行代码,却解决了两个根本问题:1. agent有了记忆,2. 让agent能够规划
如果不知道讲什么请看 上一篇: 百行代码认识Agent:nanoAgent解读
看看多了什么?
| 能力 | agent | agent-plus | 是否变化 |
|---|---|---|---|
| 工具调用 | 3个工具 | 3个工具 | ❌无变化 |
| 单任务执行 | 是 | 是 | ❌无变化 |
| 跨会话记忆 | 每次运行都失忆 | 记忆文件 | ✔️有变化 |
| 任务规划 | 无规划 | 先拆解再执行 | ✔️有变化 |
| 多步串联 | 无 | 任务步骤间共享messages context | ✔️有变化 |
记忆系统:最朴素的实现方案
记忆的存储:使用markdown文件
MEMORY_FILE = "agent_memory.md"
def save_memory(task, result):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
entry = f"\n## {timestamp}\n**Task:** {task}\n**Result:** {result}\n"
try:
with open(MEMORY_FILE, 'a') as f:
f.write(entry)
except:
pass
agent-plus使用了最简单朴素的方案:把历史任务和结果追加写入到一个markdown文件中。没有存入数据库,也没有使用向量索引,就是纯文本。每次任务执行完成,agent就把 “何时、做了什么、得到的结果” 追加到文件尾端。
记忆的恢复:滑动窗口机制
def load_memory():
if not os.path.exists(MEMORY_FILE):
return ""
try:
with open(MEMORY_FILE, 'r') as f:
content = f.read()
lines = content.split('\n')
return '\n'.join(lines[-50:]) if len(lines) > 50 else content
except:
return ""
agent-plus 只取最近50条历史记录。这是一个简单的滑动窗口策略,因为记忆是无限增长的,但LLM的上下文窗口是有限制的,所以获取最近的N条记忆。
记忆的延申:将其注入 system prompt
def run_agent_plus(task, use_plan=False):
memory = load_memory()
system_prompt = "You are a helpful assistant that can interact with the system. Be concise."
if memory:
system_prompt += f"\n\nPrevious context:\n{memory}"
messages = [{"role": "system", "content": system_prompt}]
恢复出来的记忆被拼接到 system prompt末尾,以 Previous context形式注入到新的prompt,这样LLM在处理新任务时就能"感知"到之前发生了什么。
记忆的本质
这个方案看起来原始粗糙,但揭示了一个根本性问题:LLM本身没有持久记忆,所有“记忆”是通过在prompt中注入历史消息来实现的。 目前的大模型都遵从这种模式,只是在 存在哪、怎么找、找多少上有不同的实现方式。
规划系统:让agent先think再do
为什么需要规划?
想象如果把整个任务丢给 LLM,让它在循环中自行摸索。简单任务没问题,但面对复杂任务(比如"重构整个项目"),LLM 容易迷失在细节中,忘记全局目标。
agent-plus引入了一个可选项-规划
def create_plan(task):
print("[Planning] Breaking down task...")
response = client.chat.completions.create(
model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
messages=[
{"role": "system", "content": "Break down the task into 3-5 simple, actionable steps. Return as JSON array of strings."},
{"role": "user", "content": f"Task: {task}"}
],
response_format={"type": "json_object"}
)
try:
plan_data = json.loads(response.choices[0].message.content)
if isinstance(plan_data, dict):
steps = plan_data.get("steps", [task])
elif isinstance(plan_data, list):
steps = plan_data
else:
steps = [task]
print(f"[Plan] {len(steps)} steps created")
for i, step in enumerate(steps, 1):
print(f" {i}. {step}")
return steps
except:
return [task]
规划的设计细节
- 使用LLM做规划:让LLM把任务拆解为 3~5个可执行的步骤,以json array的形式返回
- 结构化输出:response_format={“type”: “json_object”} 强制LLM返回json格式
- 优雅降级:如果json解析失败,则将整个任务作为一个步骤执行,这里使用了防御性编程,这在agent开发中很重要。
agent和agent-plus两种执行范式对比
多步执行:步骤之间上下文传递
def run_agent_step(task, messages, max_iterations=5):
messages.append({"role": "user", "content": task})
actions = []
for _ in range(max_iterations):
response = client.chat.completions.create(
model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
messages=messages,
tools=tools
)
message = response.choices[0].message
messages.append(message)
if not message.tool_calls:
return message.content, actions, messages
for tool_call in message.tool_calls:
function_payload = getattr(tool_call, "function", None)
if function_payload is None:
continue
function_name = str(getattr(function_payload, "name", ""))
raw_arguments = str(getattr(function_payload, "arguments", ""))
function_args = parse_tool_arguments(raw_arguments)
print(f"[Tool] {function_name}({function_args})")
function_impl = available_functions.get(function_name)
if function_impl is None:
function_response = f"Error: Unknown tool '{function_name}'"
elif "_argument_error" in function_args:
function_response = f"Error: {function_args['_argument_error']}"
else:
function_response = function_impl(**function_args)
actions.append({"tool": function_name, "args": function_args})
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": function_response})
return "Max iterations reached", actions, messages
- 变化1:messages从内部创建变为外部传入,这样多个步骤间就可以共享同一个对话历史。
- 变化2:返回值中包含messages;函数把更新后的消息列表返回给调用方供下一步使用
编排: 把步骤串联起来
all_results = []
for i, step in enumerate(steps, 1):
if len(steps) > 1:
print(f"\n[Step {i}/{len(steps)}] {step}")
result, actions, messages = run_agent_step(step, messages)
all_results.append(result)
print(f"\n{result}")
final_result = "\n".join(all_results)
save_memory(task, final_result)
可以看到,前一个步骤的输出作为下一个步骤的输入,每个步骤只完成一件事最后输出最终结果。
其中每个步骤内的 messages是短期记忆列表,而 agent_memory.md保存了长期记忆。
| 记忆类型 | 载体 | 生命周期 | 实现 |
|---|---|---|---|
| 短期记忆 | messages列表 | 单次运行内 | 步骤之间共享的对话历史 |
| 长期记忆 | agent_memory.md | 跨任务 | save_memory函数 |
记忆方案的局限性和优化方向
nanoAgent的记忆方案简单粗暴可成为“最小可行性记忆”,但还是不够智能,可以继续优化;
- 记忆无差别截断–>向量数据库+语义检索: 如果只保留最近N行信息可能会丢失重要的早期信息,更好的方案是把记忆存入向量数据库,每次加载记忆时只检索与当前语义相关的记忆。这是RAG的核心思路
- 无记忆压缩–>记忆蒸馏:当记忆超过阈值时,用LLM自动压缩旧记忆——提取关键信息,丢弃细节。
- 全量注入prompt–>记忆作为工具:不把记忆强塞进 system prompt,可提供一个 search_memory工具,agent按需查询,agent决定什么时候需要回忆以及回忆什么。
- 单层记忆–>多层记忆:将记忆细分为 工作记忆(当前任务)、短期记忆(最近几次对话的摘要内容)、长期记忆(压缩后的关键事实);每层的信息密度和保留时间不同。
agent-plus总结
plus版本的agent有了记忆和规划能力,但是还有一些问题需要解决
- 工具是硬编码的:无法接入外部服务,tools market
- 没有行为约束:不同项目,不同团队对 agent要求不同,无法定制agent
- 规划是被动触发的:用户通过 --plan 参数来激活规划功能,agent不知道什么时候该规划
这三个问题,引出了agent架构中最精彩的三个概念:MCP(工具协议)、Skills(技能)、Rules(行为规则)
更多推荐

所有评论(0)