主讲能力:MCP 思维、短长期记忆、多工具调度、个人偏好沉淀
业务场景:一个统一的私人助理,帮用户处理邮件、日历、文件、偏好记忆等日常事务。
对应代码:backend/projects/p09_personal_assistant/
11.1 老板的诉求
先讲个真事。我认识一位做投资的老板,每天邮件上百封、会议排到下班、文件散在三四个盘里,事务繁杂到必须配助理。他前后换了三位人类助理,离职原因惊人地一致——记不住他的习惯。第一位帮他订机票,永远忘记他要靠窗;第二位帮他点工作餐,三次里两次带了含花生的菜,而他花生过敏;第三位倒是勤快,可每次都要重新问一遍"您开会喜欢哪个会议室"。他跟朋友吐槽:“我要的不是会做事的人,是知道我是什么人的人。”
这句话,恰恰戳中了 Agent 落地的一个核心命题。
个人助理是 Agent 最自然的应用场景之一。人类助理真正值钱的地方,从来不是会做某一个动作——发邮件、订会议谁都会——而是知道你是谁、记得你的习惯、理解上下文、把多个系统串成一件事。会发邮件的叫工具,记得你对花生过敏的才叫助理。
本项目要造的,就是这么一个"有记忆的私人助理"雏形:
- 发送邮件
- 安排会议
- 搜索文件
- 记住偏好
- 根据长期记忆个性化回复
功能需求
| 功能 | 描述 |
|---|---|
| 邮件 | 模拟发送邮件,生产可替换 SMTP/MCP 邮箱 Server |
| 日历 | 模拟安排会议,生产可接 Google Calendar/Exchange |
| 文件 | 搜索文件,生产可接本地文件 MCP Server |
| 偏好记忆 | 记住用户偏好,跨会话复用 |
| 记忆检索 | 根据当前问题检索相关长期记忆 |
11.2 画个样子:它该长啥样
把上面那张"助理画像"翻译成架构,就是下面这张图:用户一句话进来,Agent 调度邮件、日历、文件、记忆四类工具,记忆沉淀到 SQLite,最后吐出一句"懂你"的个性化回复。
%%{init: {'theme':'base','flowchart':{'useMaxWidth':true,'htmlLabels':true}}}%%
graph TD
U["用户请求"] --> A["个人助理 Agent"]
A --> Mail["邮件工具"]
A --> Cal["日历工具"]
A --> File["文件搜索"]
A --> Mem["记忆系统"]
Mem --> DB[("SQLite 长期记忆")]
A --> Reply["个性化回复"]
classDef a fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92
classDef t fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064
classDef d fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20
class A a
class Mail,Cal,File,Mem t
class DB,Reply d
11.3 拆开看:怎么造出来
11.3.1 记忆系统
回到那位老板。前两位助理的问题不是不努力,是"没记住";可如果反过来,助理把老板说过的每一句话都背下来、逢人就复述,那也不行——那叫八卦,不叫贴心。记忆这件事,少了不贴心,多了不安全。
PersonalMemory 就是来拿捏这个分寸的,它同时提供两类记忆:
- 短期记忆:进程内字典,适合当前会话——相当于助理"手边的便签",会话一结束就撕掉。
- 长期记忆:SQLite 持久化,适合跨会话偏好——相当于助理的"小本本",关机再开机还在。
1 | mem.remember_preference("preferred_room", "A101", description="开会偏爱用的房间") |
说明:本书早期草稿曾用
remember_long/recall_long这样的通用命名来示意长期记忆,但落地代码里长期记忆的入口是围绕"偏好"设计的remember_preference/recall_preference,另有search_preferences做模糊检索;对话和任务则各自有独立表,不挤在同一个 API 里。下文的完整代码会原样呈现这一实现。
为什么不用纯上下文?因为上下文会随会话结束而丢失,且 token 成本随长度线性上涨——把老板三年的对话全塞进上下文,既贵又慢,还会被模型遗忘。生产系统必须把稳定偏好沉淀到外部存储。
💡 顿悟时刻:长期记忆的本质,其实就是 RAG。 把用户的偏好、决策、重要事实写进外部库(这里是 SQLite),回答前先用关键词或向量检索出"跟当前问题相关的那几条",拼进提示词喂给模型——这不就是 Retrieve-Augmented Generation 吗?只不过这里的"知识库"不是公司文档,而是用户自己的小本本。本章用 LIKE 做的是最朴素的检索器,生产环境换成向量召回,就是一套完整的个人 RAG。
⚠️ 避坑:别把所有对话都倒进记忆。 有人会想,既然能存,那干脆把每句对话都存成长期记忆,岂不更"懂我"?恰恰相反。一是成本——全量历史塞进上下文,token 立刻爆炸;二是噪声——大部分对话是寒暄和即兴闲聊,存进去只会稀释信号,检索时把真正重要的偏好淹没掉;三是时效——旧偏好会和新偏好打架(“我喜欢靠窗” vs 后来的"我现在prefer走道");四是隐私——存得越多,泄露时的杀伤面越大。正确做法是:对话归对话(落日志表),偏好归偏好(精选沉淀)。本项目正是这么分的——conversations 表只做可追溯的流水,preferences 表才进检索,各司其职。
11.3.2 MCP 思维
助理要替你办事,就得能"伸手"去够外面的世界——发邮件得够到邮箱,订会议得够到日历,找文件得够到磁盘。问题是,这些系统的协议千差万别,要是每接一个就硬编码一套,助理的代码很快就成一团乱麻。
本章代码先用本地函数把邮件/日历/文件这些能力"演"出来,让你聚焦在助理本身的记忆与调度上。但请心里始终挂着一件事:生产环境里,这些函数的每一个,都应该被替换成一个 MCP Server:
| 当前工具 | 生产替代 |
|---|---|
send_email |
邮箱 MCP Server |
schedule_meeting |
日历 MCP Server |
search_files |
文件系统 MCP Server |
本章真正想讲的,不是某一个具体的 SaaS API 怎么调,而是用一套标准接口把私人助理接入外部世界这个思维方式。工具今天接 SMTP、明天接 SendGrid、后天换成 MCP 邮箱 Server——对 Agent 来说,调用的形状不变,助理的"脑子"不用动。这就是 MCP 思维的价值:让助理的能力可插拔,而不是被某个厂商焊死。
11.4 动手写:三层架构完整代码
设计讲完,开始动手。本节给出全能个人助理 Agent 的完整代码实现。项目采用分层架构:模型层、提示词层、工具层、记忆层、服务层、项目层各司其职,配合入口文件完成注册与对外导出。这么分不是为了"显得专业",而是因为个人助理天生要频繁替换工具(接 MCP)、频繁演进记忆策略——分层之后,换工具不动服务、调记忆不动提示词,整体清晰、好维护、好扩展。
11.4.1 三层架构完整代码
下表列出各层的文件与职责,先有个全局地图,再逐层展开:
| 层次 | 文件 | 职责 |
|---|---|---|
| 模型层 | models.py | 助理能力、用户偏好、日历/邮件/文件模型 |
| 提示词层 | prompts.py | 助理系统提示词、邮件/日历/偏好模板 |
| 工具层 | tools.py | 邮件/日历/文件/记忆/任务工具 |
| 记忆层 | memory.py | 短期/长期记忆系统(SQLite) |
| 服务层 | service.py | 上下文管理、个性化服务 |
| 项目层 | project.py | 项目注册、对外接口 |
| 入口 | init.py | 注册与 re-export |
下面按层次依次给出每个文件的完整代码。代码与仓库 backend/projects/p09_personal_assistant/ 中的实现保持一致。
models.py
1 | """项目九:数据模型层。 |
prompts.py
1 | """项目九:Prompt 层。 |
任务:[任务名称]
优先级:[P0/P1/P2/P3]
截止日期:[YYYY-MM-DD]
子任务:
- [子任务 1]
- [子任务 2]
提醒:[提前 X 天/小时]
1 |
|
tools.py
1 | """项目九:工具层。 |
memory.py
1 | """项目九:记忆层。 |
service.py
1 | """项目九:服务层。 |
project.py
1 | """项目九:项目定义层。 |
init.py
1 | """项目九:全能个人助理 Agent |
11.4.2 核心代码讲解
短期记忆与长期记忆的双层设计
PersonalMemory(memory.py)一手管着两类记忆。短期记忆基于进程内字典 self._short_term,只在当前会话活着,会话一结束就随风而散,适合放临时上下文和中间状态——就像助理桌上那张便签,下班就清掉。长期记忆则通过 SQLite 落盘,能跨会话保留用户偏好与历史对话——就像助理随身带的小本本,关机再开机,"老板花生过敏"这条还在。
这套双层设计有个好处:快的归快、久的归久。短期记忆直接走内存,响应飞快;长期记忆走磁盘,慢一点但靠得住。两者配合,既不丢长期习惯,又不让每次回答都被磁盘拖慢。它也顺手解决了纯上下文方案的两个老毛病——会话结束即失忆、context 越拉越长越贵。
SQLite 持久化的偏好/对话/任务存储
_init_db 在初始化时一次性建好三张表:preferences(用户偏好)、conversations(对话历史)、tasks(任务)。注意这三张表是分而治之的——偏好是"精选记忆",对话和任务是"流水账",三者各占一张表,互不污染。
写操作上,所有 SQL 都用参数化占位符(?),杜绝注入;偏好更新走 INSERT OR REPLACE,同 key 直接覆盖,省去"先查后改"的来回。读操作上,用 sqlite3.Row 把行映射成字段对象,再转成 UserPreference 这类领域模型——数据库里是行,代码里是对象,边界干净。search_preferences 在 key、value、description 三个字段上做 LIKE 模糊匹配,这就是本章记忆检索的"初级检索器"。
💡 为什么选 SQLite?因为它零部署、单文件、跟项目一起走。个人助理这种轻量持久化场景,上 PostgreSQL 属于杀鸡用牛刀;等到多用户、高并发那天再迁移也不迟,反正数据访问都被封装在记忆层里了。
MCP 思维与工具标准化
tools.py 用 LangChain 的 @tool 装饰器,把邮件、日历、文件、记忆、任务这些能力统一封装成标准工具——每个工具都有类型化的入参、清晰的 docstring 和确定的返回值,对外长得一模一样。get_all_tools() 再把所有工具收拢成一个清单交给 Agent 调度。
这里要重申一遍本章的核心主张:我们不是在教你调某个具体 SaaS API,而是在演示**“通过标准接口把私人助理接入外部世界”**这条路。生产环境里,这些本地函数可以逐个被替换成邮箱、日历、文件系统等 MCP Server,而 Agent 调用它们的方式分毫不变。换句话说,工具是"可换的零件",助理的"大脑"和"调度逻辑"是"不动的底盘"——这正是 MCP 思维要带给你的解耦红利。
服务层的上下文增强(基于偏好个性化)
PersonalAssistantService.process_message 在把请求交给 Agent 之前,先悄悄做一件"功课"——_enhance_with_preferences:用一组关键词(邮件、会议、时间、喜欢、偏好……)去扫用户消息,一旦命中,就触发 search_preferences 检索相关长期记忆,再把命中的偏好塞进 AssistantContext 的 active_context。这样后续回答就能自然带出"根据你的偏好,会议室已订 A101……"这种个性化表述。
这一步的精髓在于:记忆不是存起来就完了,得在回答前主动捞出来用。存而不取,等于没记。同时,用户消息和助理回复会双写到上下文与 SQLite,留下可追溯的痕迹。
⚠️ 坦白讲,目前的"关键词匹配 + LIKE 检索"还比较粗——它够用来演示"个性化"这条链路是通的,但离生产级精准还差一层向量召回。这是本章有意留出的演进空间,读者可以把它当作练习题。
错误降级处理
助理替你办事,难免有失手的时候——工具调不通、模型推理挂了、网络抽风。process_message 用 try/except 把 Agent 执行整体兜住:一旦出错,不把原始堆栈甩到用户脸上,而是记日志 + 返回一条友好的降级提示,同时把错误信息作为 system 消息写进上下文,方便事后复盘。recall_preference 这类读取工具在偏好不存在时也会返回明确的提示文本,而不是静默返回空。
这种"失败可见、可恢复"的设计,是个人助理类应用的基本修养。道理很简单:助理是替你伸手的人,它出错你不怕,怕的是它出错了你都不知道。
三层架构的解耦优势
项目按模型层(models.py)、提示词层(prompts.py)、工具层(tools.py)、记忆层(memory.py)、服务层(service.py)、项目层(project.py)分层,入口 init.py 负责统一注册与 re-export。模型与存储分家、工具与提示词分家、业务逻辑与对外接口分家——这"三个分家"带来三个实打实的好处:
一是单层可独立测试与替换。比如把模拟工具换成真实 MCP Server,服务层一行不用动;二是持久化逻辑集中。偏好/对话/任务的存储都收在记忆层,服务层只编排不存储,职责干净不串味;三是对外接口统一。project.py 通过 BaseProject 暴露统一的 build_agent/run,全局注册表和上层框架能一视同仁地调度它,不用关心底下是哪个项目。
💡 一句话:分层不是为了好看,是为了"换零件不动底盘"。个人助理又是最容易频繁换零件的那种系统,所以这层解耦对它尤其值钱。
11.5 跑一跑:它真的行吗
个人助理是"替你伸手"的系统,伸错手的代价很高,所以测试尤其不能糊弄。本项目的测试套件(backend/projects/p09_personal_assistant/test_agent.py)覆盖:
- 短期记忆读写——便签写得进、读得出
- 长期记忆持久化——关掉进程再开,偏好还在
- 记忆搜索——关键词能命中该命中的
- 邮件/日历工具——工具的入参出参契约稳定
- 集成测试(需 API Key)——端到端跑通"用户说话→助理回复"
离线的领域模型与记忆测试不依赖外部服务,随时能跑;涉及真实模型推理的集成测试由 @pytest.mark.skipif 门控(测试套件按 ANTHROPIC_API_KEY 是否存在决定跳过),按需开启。
11.6 送上线:让它上班
助理一旦上线,就不再是"模拟"了——它发的邮件真的会发出去,它订的会议真的会出现在别人日历上,它记的偏好真的会长期留着。权力一大,约束就必须跟上。上线私人助理类应用时,这几个问题得先答清楚:
- 发送邮件前是否需要人工确认?(误发一封含敏感信息的邮件,撤不回来)
- 删除文件是否禁止?(助理不该有"删"的权力,或至少要二次确认)
- 日历邀请是否会打扰他人?(替老板发会议邀请,等于以老板之名动用他人时间)
- 长期记忆是否允许用户查看和删除?(“被记住"也得能"被遗忘”)
⚠️ 避坑:权限白名单、敏感操作人工确认、记忆治理,这三件套一个都不能少。 生产级系统必须提供权限白名单(哪些工具允许自动执行、哪些必须拦下来)、人工确认(涉及费用/权限/外发/删除的操作,先问人再动手)、记忆管理页面(用户能看、能改、能删自己的长期记忆)。
⚠️ 记忆治理尤其容易被忽视。助理记下的偏好里可能混进身份证、口令、医疗信息这类敏感数据,如果不给用户"查看与删除"的入口,就等于在数据库里埋了一颗隐私地雷——GDPR/《个人信息保护法》一类法规第一个找的就是你。
11.7 回头看:学到了什么
回到开头那位换过三个助理的老板。他要的从来不是"会做事"的人,而是"知道他是什么人"的人。本章造的助理,正是在朝这个方向努力:用 MCP 思维让它能伸手够到外部世界,用短长期记忆让它记住你是谁,用上下文增强让回答带上你的偏好,用分层架构让这一切可演进。
个人助理的核心不是工具多,而是工具 + 记忆 + 权限三者平衡。
没有记忆的助理不贴心,没有权限控制的助理不安全;记忆让它懂你,权限让它不害你。
下一章,我们再把规划、反思、记忆和工具全部融合到一起,造一个能自己拆任务、自己回头改错的自主任务规划 Agent。
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !