用不到 100 行 Python,打造一个有记忆的 AI 编程助手。从「重启就失忆」的聊天机器人,到能跨天接续专案、自动更新记忆、防 path traversal 攻击的真正 AI 同事。
一、问题展示:Claude 的失忆现场
先感受一下问题有多烦。做一个最简单的聊天机器人——你打字、Claude 回你:
先建一个虚拟环境,Mac 现在不让你直接 pip install 了:
python3 -m venv .venv
source .venv/bin/activate
pip install anthropic设定 API key。如果你还没有,去官网注册,在 API Keys 那边建一把就好:
export ANTHROPIC_API_KEY=你的key建立 chatbot.py:
import anthropic
client = anthropic.Anthropic()
messages = []
print("跟Claude聊天(输入quit退出)")
while True:
msg = input("\n> ")
if msg.lower() == "quit":
break
messages.append({"role": "user", "content": msg})
res = client.messages.create(
model="claude-opus-4-6",
max_tokens=512,
messages=messages,
)
reply = res.content[0].text
print(f"Claude: {reply}")
messages.append({"role": "assistant", "content": reply})执行 python chatbot.py,跟它聊聊:
- 输入「我叫 Matt,我在做一个电商平台的后端,用 Python 加 FastAPI」—— Claude 正确回覆。
- 输入「我叫什么名字?我用什么框架?」—— Claude 也答得出来。
到这边都没问题,它记得你是谁。但假设你下班了,程式关掉,明天回来重开:
- 输入
quit退出程式 - 重新执行
python chatbot.py - 输入「我叫什么名字?我用什么框架?」—— Claude 回覆「抱歉,我无法知道你的名字...」
完全不记得了。昨天聊的所有东西全部消失。因为 messages 这个阵列是存在内存里的,程式一关就没了。这就是你用 Claude API 做任何 Agent 都会遇到的问题:重启就失忆。
二、加上 Memory Tool,让 Claude 有记忆
新建一个有记忆的版本 memory_agent.py。先写最上面的设定:
import anthropic
import os
client = anthropic.Anthropic()
os.makedirs("./memories", exist_ok=True)就是 import 跟建一个 memories 资料夹,Claude 的记忆会存在这里面。
记忆 Handler
Claude 会发指令跟你说「帮我看这个档案」或「帮我建一个档案」,你的程式要接住这些指令。先处理「看」跟「建」两个最基本的:
def handle_memory(command, **kwargs):
path = kwargs.get("path", "")
local_path = "." + path # /memories/note.txt -> ./memories/note.txt
if command == "view":
if os.path.isdir(local_path):
entries = []
for root, dirs, files in os.walk(local_path):
for f in files:
fp = os.path.join(root, f)
entries.append(f"{os.path.getsize(fp)}\t{fp}")
return "Files in " + path + ":\n" + "\n".join(entries) if entries else "Empty directory"
elif os.path.isfile(local_path):
with open(local_path, "r") as f:
return f"Content of {path}:\n{f.read()}"
return f"Error: {path} does not exist"
elif command == "create":
os.makedirs(os.path.dirname(local_path), exist_ok=True)
with open(local_path, "w") as f:
f.write(kwargs.get("file_text", ""))
return f"File created at {path}"
return f"Unknown command: {command}"这个函数做的事很单纯。Claude 传一个 command 过来,如果是 view 就去读资料夹或档案,如果是 create 就建一个新档案。**kwargs 就是「把剩下的参数全部收进来」的意思,因为不同指令需要的参数不一样。
Agent Loop:让 Claude 来回操作记忆
最关键的部分。我们要写一个循环让 Claude 可以跟你的程式来回对话。为什么需要循环?因为 Claude 用记忆的时候,一次对话里面可能会连续操作好几次——先看目录有什么档案、再读某个档案、最后建一个新档案。每次操作都是一个 tool call,你的程式要一个一个接住,做完再把结果送回去,直到 Claude 说「我做完了」为止。
def run_agent(user_message):
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=4096,
tools=[{"type": "memory_20250818", "name": "memory"}],
messages=messages,
)
# 印出Claude说的话
for block in response.content:
if block.type == "text":
print(f"Claude: {block.text}")
# Claude不再要求操作记忆 -> 结束
if response.stop_reason != "tool_use":
break
# 处理Claude的记忆操作请求
tool_results = []
for block in response.content:
if block.type == "tool_use":
print(f" [记忆操作] {block.input['command']} {block.input.get('path','')}")
result = handle_memory(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id, # 对应哪个tool call
"content": result,
})
# 把结果送回去,让Claude继续
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})三个重点:
while True让循环一直跑。- 每次检查
stop_reason,如果 Claude 不再要求 tool call 就break跳出。 tool_use_id要从block.id拿,这是让 Claude 知道「这个结果是回覆你哪个请求的」。
小知识:tool result 为什么用 "role": "user" 送回去?这是 Claude API 的规定,tool result 就是要放在 user 的 message 里面,不是因为它是人类说的话,是 API 格式就是这样。
tools 里面的 memory_20250818 是 Anthropic 定义好的内建 tool type,从官方文件查到的。你不需要自己定义 schema,Claude 天生就知道 memory tool 有哪些指令可以用——view、create、str_replace 全部都是内建的。日期是 API 版本号,照写就好。
最后加一个互动循环:
while True:
msg = input("\n> ")
if msg.lower() in ("exit", "quit", "q"):
break
run_agent(msg)实际运行效果
执行 python memory_agent.py,输入「我叫 Matt,我在做一个电商平台的后端,用 Python 加 FastAPI」:
- Claude 第一件事去看记忆资料夹,发现是空的。
- 回覆完之后,它自己决定建一个记忆档案
user_profile.txt把你的信息存下来。没有人叫它存,它自己觉得应该记住。
不退出程式,继续输入「帮我 review 一下我的订单 API」:
- 看到「订单 API」这个关键字,它第一反应是去翻记忆,因为它知道记忆里可能有之前存的进度。
- 读完记忆发现只有 user_profile,没有订单 API 的代码,就直接问你档案路径。它没有乱猜、没有瞎掰,这就是有记忆的 Agent 该有的行为。
给它档案路径后,Claude 完成 review,还主动把 review 结论存进记忆。没有人叫它存。它知道这种 review 结论之后可能还会用到,所以主动写了一份。这就是 Memory Tool 最强的地方:Agent 自己判断什么该记、什么不该记。
关键点:把程式关掉重开,输入「我们上次 review 到哪了?」—— Claude 先去读记忆,读到 user_profile 跟 review 结论,直接接上。全新的 process,跟失忆版天差地远。
三、Claude 自己会更新记忆
记忆存了,但如果情况有变化呢?比如订单 API 你已经修掉一个 bug 了,记忆里的 review 结论就过期了。
如果只有 view 和 create 两个指令,Claude 想修改记忆只能整个档案重写——浪费 token、资料多的时候效率差、写到一半程式挂了整个档案就没了。正确做法是补上精准修改的指令。在 handle_memory 的 create 分支下面加上这些:
elif command == "str_replace":
with open(local_path, "r") as f:
content = f.read()
old_str = kwargs["old_str"]
if content.count(old_str) != 1:
return f"Error: old_str must appear exactly once, found {content.count(old_str)}"
with open(local_path, "w") as f:
f.write(content.replace(old_str, kwargs["new_str"]))
return "Memory file updated"
elif command == "insert":
with open(local_path, "r") as f:
lines = f.readlines()
lines.insert(kwargs["insert_line"], kwargs["insert_text"])
with open(local_path, "w") as f:
f.writelines(lines)
return "Inserted"
elif command == "delete":
import shutil
if os.path.isdir(local_path):
shutil.rmtree(local_path)
else:
os.remove(local_path)
return f"Deleted {path}"
elif command == "rename":
os.rename("." + kwargs["old_path"], "." + kwargs["new_path"])
return f"Renamed to {kwargs['new_path']}"| 指令 | 功能 | 说明 |
|---|---|---|
| str_replace | 精准替换字串 | 旧字串必须唯一出现一次,否则报错 |
| insert | 在指定行插入文字 | 按行号插入 |
| delete | 删除档案或资料夹 | 资料夹用 shutil.rmtree |
| rename | 重命名档案 | os.rename |
补上之后,Claude 就能直接用 str_replace 精准改一行,不用再整个档案重写。而且它还会主动帮你整理目前的进度——哪几条修好了、哪几条还没修,一目了然。
四、安全问题:差点被打穿的教训
功能都好了,但这里有一个严重的安全漏洞。第一版上线没做路径检查,Claude 送了一个带「点点斜线」的路径(../),直接跳出 memories 资料夹,读到了 .env 档——API key、数据库密码、JWT 密钥、Stripe 付款金钥全部外泄。如果是线上服务,这波外泄够你上新闻。
修复方法是用 os.path.realpath 加上 prefix check。在 handle_memory 里面,local_path = "." + path 上面加这段:
# 安全检查:挡掉 path traversal,只允许存取 memories 资料夹底下
real_path = os.path.realpath("." + path)
memories_root = os.path.realpath("./memories")
if not real_path.startswith(memories_root):
return "Error: 路径不合法"os.path.realpath 会把 ../ 这种东西解析成真实路径,然后检查它有没有跑出 memories 资料夹。如果跑出去了就直接挡掉。这样连 .env 这种放在专案根目录的敏感档案也读不到。
多人场景注意:如果是多人使用,记得用 user_id 建不同的子资料夹做隔离,不然使用者之间会互相看到记忆。
五、跨天专案:接上昨天的进度
最后一招,也是真正让 Agent 能长期跟你搭档的关键。清空记忆,重新开始:
输入「我要做一个电商平台的后端,用 Python + FastAPI,功能包括:使用者认证、商品管理、订单系统、支付串接。今天我们先讨论使用者认证的设计,做完把这次讨论的重点和下一步要做什么,存到 /memories/progress.md」。
Claude 没有一上来就硬写代码,而是先跟你讨论设计方向——技术选型、数据库模型、API 端点、token 策略、安全性、权限、目录结构,七个面向全讲了,还主动问三个待确认的问题。这才是真正有用的 pair programming——先对齐设计,再开始写。最后它把讨论重点和下一步存进 progress.md。
正确的记忆用法:不是把程式码塞进去,是存「为什么这样选、接下来要做什么」这种讨论层的东西。
模拟第二天——关掉程式重开,输入「继续昨天的进度,今天接着做下一步」:
- Claude 读完记忆,不只知道昨天讨论到哪、下一步该做什么。
- 连昨天没决定完的事都记得——主动提醒你要先回答三个问题才能继续写代码。
这才是真正有记忆的同事,不是你讲一次它就当过眼云烟。你下班前悬而未决的事,第二天回来它会帮你盯着。第三天、第四天,都能这样接上去。
总结
你现在手上有什么?一个不到 100 行的程式,能记住使用者、能更新记忆、能防攻击、跨天专案也能接上。核心就是四步:
- 加 Memory Tool:用
memory_20250818内建 tool type,Claude 天生知道怎么用。 - 补全指令:view、create、str_replace、insert、delete、rename 六个指令让记忆系统完整。
- 防 path traversal:
os.path.realpath+ prefix check,一行代码挡住目录穿越攻击。 - 跨天接续:存讨论决策和下一步计划,不是存代码,让 Agent 能无缝衔接。