1. 从零构建情感陪伴智能体为什么长期记忆是绕不开的坎很多人第一次做情感陪伴类 AI-Agent都会掉进同一个坑聊三句还行聊到第十句它就开始失忆前面说过的名字、喜好、情绪状态全忘了。这不是模型不行而是你没给它装记忆。情感陪伴和普通问答最大的区别在于它需要跨会话记住你是谁、你最近在烦什么、你上次说想养猫。没有长期记忆它就是个复读机。我这次用 Cursor 从零搭了一个带长期记忆的情感陪伴智能体核心链路是Cursor 负责写代码和调试TaoToken 统一 Key 负责模型接入向量库负责记忆存储。整个流程实测下来 1 小时能跑通端到端对话。适合谁适合会一点 Python、想快速验证 AI-Agent 想法、但不想在鉴权和多模型切换上耗时间的开发者。为什么强调统一 Key因为情感陪伴场景经常要换模型——便宜的模型跑日常闲聊强一点的模型处理情绪危机如果每个模型都单独配 Key、单独改 Base URL代码里会到处是硬编码。TaoToken 的价值就在于一个 Key、一个 API 通道切换模型只改一个 Model ID 字符串。下面我把完整过程拆开讲包括 Cursor 里怎么配、记忆结构怎么设计、请求怎么验证、报错怎么排。2. TaoToken 前置准备一个 Key 打通多模型接入在动手写代码前先把模型通道准备好。这一步不做后面 Cursor 生成的代码跑起来必然 401。TaoToken 在这里扮演的是统一模型服务入口你不需要为每个模型单独申请账号、单独管理密钥。先注册并拿到 API Key。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 完成账号注册然后进控制台创建密钥。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面点新建复制那串 sk- 开头的字符串。这个 Key 就是你后面所有模型调用的唯一凭证。API 的基础地址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 Base URL 使用。很多新手会把带 UTM 的官网地址误填进代码里结果请求 404这是第一个高频坑。关于模型选择情感陪伴场景我建议至少准备两个 Model ID一个轻量模型跑高频闲聊一个能力更强的模型处理深度情绪对话。TaoToken 支持在同一个 Key 下切换不同模型你只需要在请求体里改 model 字段。具体有哪些模型可用可以在模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 里先手动试聊几句确认模型 ID 拼写正确再写进代码。如果你后续要做长期编码或者更复杂的 Agent 编排可以考虑 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它更适合持续性的开发任务。但本篇的 MVP 阶段一个普通 Key 就够了。这里要提醒一句Key 不要硬编码进 Git 仓库。我习惯用 .env 文件管理Cursor 生成代码时也会默认读环境变量。下面配置片段里我会用 os.getenv 的方式读取你照着做就不会把密钥泄露出去。3. 可复制配置Cursor 项目结构与记忆存储设计这一节是核心直接给你能复制的配置和代码结构。先在 Cursor 里新建一个空项目文件夹比如 emotional-agent然后用 Cursor 打开。我建议的项目结构是这样的emotional-agent/ ├── .env ├── requirements.txt ├── config.py ├── memory.py ├── agent.py └── main.py先写 .env把 Key 和 Base URL 放进去TAOTOKEN_API_KEYsk-你的密钥 TAOTOKEN_BASE_URLhttps://taotoken.net/api CHAT_MODEL你的轻量模型ID DEEP_MODEL你的深度模型ID然后是 config.py统一读取配置避免到处硬编码import os from dotenv import load_dotenv load_dotenv() API_KEY os.getenv(TAOTOKEN_API_KEY) BASE_URL os.getenv(TAOTOKEN_BASE_URL) CHAT_MODEL os.getenv(CHAT_MODEL) DEEP_MODEL os.getenv(DEEP_MODEL) # 记忆检索阈值相似度低于此值不触发长期记忆 MEMORY_THRESHOLD 0.75 # 每次注入对话的历史记忆条数 MEMORY_TOP_K 3接下来是记忆存储结构。情感陪伴的记忆分两层短期记忆当前会话的对话历史和长期记忆跨会话的用户画像与关键事件。短期记忆直接用列表存长期记忆用向量库存。我用 chromadb 做演示轻量且本地可跑。memory.py 的核心结构import chromadb from config import MEMORY_THRESHOLD, MEMORY_TOP_K client chromadb.PersistentClient(path./memory_db) collection client.get_or_create_collection(nameuser_memory) def save_memory(user_id: str, text: str, metadata: dict): 把一条关键信息写入长期记忆 collection.add( documents[text], metadatas[metadata], ids[f{user_id}-{metadata[ts]}] ) def recall_memory(user_id: str, query: str): 根据当前输入检索相关长期记忆 results collection.query( query_texts[query], n_resultsMEMORY_TOP_K, where{user_id: user_id} ) docs results.get(documents, [[]])[0] return docs这里有个关键设计不是每句话都写进长期记忆否则向量库会被嗯好的这种废话塞满检索质量暴跌。我的做法是让模型在回复时顺带判断这句话是否包含值得长期记住的信息如果是才调用 save_memory。这个判断逻辑放在 agent.py 里。agent.py 负责组装请求把短期记忆 检索到的长期记忆一起塞进 messagesfrom openai import OpenAI from config import API_KEY, BASE_URL, CHAT_MODEL, DEEP_MODEL from memory import recall_memory, save_memory client OpenAI(api_keyAPI_KEY, base_urlBASE_URL) SYSTEM_PROMPT 你是一个有温度的情感陪伴助手。 你会收到用户的历史记忆片段请自然地融入对话不要生硬复述。 如果用户表达了值得长期记住的信息如喜好、重要事件、情绪困扰 在回复末尾用 [MEMORY] 标记该信息。 def build_messages(user_id: str, user_input: str, history: list): memories recall_memory(user_id, user_input) memory_text \n.join(memories) if memories else 暂无历史记忆 messages [ {role: system, content: SYSTEM_PROMPT}, {role: system, content: f用户历史记忆\n{memory_text}} ] messages.extend(history[-10:]) # 短期记忆保留最近10轮 messages.append({role: user, content: user_input}) return messages def chat(user_id: str, user_input: str, history: list): messages build_messages(user_id, user_input, history) resp client.chat.completions.create( modelCHAT_MODEL, messagesmessages, temperature0.8 ) reply resp.choices[0].message.content # 解析是否需要写入长期记忆 if [MEMORY] in reply: clean reply.replace([MEMORY], ).strip() save_memory(user_id, clean, {user_id: user_id, ts: str(len(history))}) return clean return reply注意 build_messages 里我把长期记忆作为独立的 system 消息注入而不是拼进用户输入。这样做的好处是模型能区分这是背景记忆和这是用户当前说的话回复更自然。Cursor 在生成这段时我特意让它把记忆注入和用户输入分开实测对话质量明显更好。requirements.txt 内容openai chromadb python-dotenv装依赖pip install -r requirements.txt。到这里配置部分就齐了下面验证请求。4. 端到端验证跑通第一轮带记忆的对话配置写完先别急着写复杂逻辑用最小请求验证通道是否通。main.py 写一个交互循环from agent import chat def main(): user_id test_user_001 history [] print(情感陪伴智能体已启动输入 exit 退出) while True: user_input input(你) if user_input.lower() exit: break reply chat(user_id, user_input, history) print(fAI{reply}) history.append({role: user, content: user_input}) history.append({role: assistant, content: reply}) if __name__ __main__: main()运行python main.py。第一轮你输入我最近工作压力好大晚上总失眠模型会正常回复。这时候长期记忆还没触发因为第一轮没有历史可检索。关键验证在第二轮和第三轮。你继续说我养了只猫叫豆豆它晚上会趴我枕头边如果模型回复里带了 [MEMORY] 标记说明它识别出这是值得记住的信息save_memory 被调用了。然后你退出程序重新运行 main.py输入我家猫最近怎么样这时候 recall_memory 会检索到豆豆这条记忆模型应该能说出猫的名字。我实测下来第一次跑通这个链路大概花了 40 分钟其中 20 分钟在调记忆阈值。默认 0.75 有时候太严聊好几轮才触发一次长期记忆你可以先把 MEMORY_THRESHOLD 调到 0.6 试试观察检索命中率。如果发现检索出来的记忆和当前话题不相关再往上调。验证成功的标志有三个一是控制台没有报错二是第二轮对话出现了 [MEMORY] 标记三是重启程序后模型能回忆起之前的信息。三个都满足说明端到端链路通了。如果你想在 Cursor 里直接调试可以在 chat 函数里打断点看 messages 数组里长期记忆有没有正确注入。Cursor 的调试体验比传统 IDE 顺滑很多变量面板能直接展开看 messages 内容这点对排查记忆注入问题特别有用。5. 常见报错排查401、local proxy failed 与记忆不触发跑不通的时候90% 的问题集中在这几类。我按真实报错逐个说。401 Unauthorized。这是最常见的。原因通常是 Key 没读到或者 Base URL 填错。先检查 .env 文件是否在项目根目录load_dotenv 是否能找到。然后在 config.py 里加一行print(API_KEY[:8])确认 Key 读进来了。如果 Key 正常检查 BASE_URL 是不是写成了带 UTM 的官网地址——必须是 https://taotoken.net/api 不能带任何查询参数。还有一个隐蔽情况Key 复制时带了空格用 strip() 清一下。local proxy failed / connection error。这类报错通常是网络层的问题不是 Key 的问题。先确认你的运行环境能正常访问外网 API。如果你在公司内网可能有防火墙拦截换个网络环境试试。另外检查是不是本地开了什么网络工具导致请求被劫持关掉再试。这个报错和 Key 无关别去反复重置密钥。reading choices 报错提示 NoneType 没有 choices 属性。这说明请求发出去了但返回体结构不对通常是 resp 为 None 或者返回了错误信息。在 chat 函数里加异常捕获把原始返回打出来try: resp client.chat.completions.create(...) except Exception as e: print(请求异常, e) return 抱歉我这边出了点问题打印出来你大概率会看到模型 ID 拼写错误或者该模型当前不可用。去模型对话页面确认一下 Model ID 的准确拼写。OAuth 相关报错。如果你在 Cursor 里配置了某些插件的 OAuth 登录可能会和 API Key 鉴权冲突。情感陪伴项目不需要 OAuth确保你用的是纯 API Key 方式。Cursor 的 settings 里如果有残留的 OAuth 配置清掉。记忆不触发。程序不报错但聊了很多轮长期记忆始终是空的。三个排查方向一是 MEMORY_THRESHOLD 太高检索永远不命中调低到 0.6二是模型没有按格式输出 [MEMORY] 标记检查 SYSTEM_PROMPT 是否完整三是 save_memory 的 metadata 里 user_id 和 recall 时的 where 条件不一致导致写进去查不出来。我踩过的坑是第三条user_id 一个用了字符串一个用了变量查了半天。Codex auth.json 相关。如果你同时用 Codex 类工具注意它的 auth.json 和本项目的 .env 是两套独立配置不要混用。本项目只需要 Base URL、Key、Model ID 三件套分别对应 TAOTOKEN_BASE_URL、TAOTOKEN_API_KEY、CHAT_MODEL。排障的核心思路是先确认 Key 和 Base URL 正确再确认模型 ID 存在最后确认记忆读写用的是同一个 user_id。按这个顺序查基本都能定位。6. 继续迭代把 MVP 变成能长期用的陪伴 Agent跑通之后这个 Agent 还有很多可以打磨的地方。我列几个我实际加过的优化你可以按需跟进。第一是记忆的衰减和合并。向量库会越存越多时间久了很多旧记忆不再相关。可以给每条记忆加时间戳检索时对近期记忆加权。或者定期让模型把相似记忆合并成一条摘要减少冗余。第二是情绪识别分流。日常闲聊用轻量模型一旦检测到用户情绪低落或提到危机信号自动切到 DEEP_MODEL。切换只需要改 chat 函数里的 model 参数因为 TaoToken 是统一 Key不用换客户端。第三是记忆的主动召回。现在是用户说话才检索可以改成每次对话开始前先根据用户画像主动召回几条核心记忆让模型开场就能接上上次的话题陪伴感更强。如果你要把这个 Agent 做成长期运行的服务建议看看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它在持续调用和 Agent 编排上更省心。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到参数细节可以直接查。API Keys 管理页面还是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 需要新建或轮换密钥时去那里操作。最后说个实用技巧Cursor 生成代码后别急着全盘接受。让它先解释每一段在干什么尤其是记忆读写部分确认 user_id 传递链路完整再运行。我一开始就是没检查save 和 recall 用了不同的 user_id 变量白白 debug 了半小时。把记忆链路的 user_id 统一成一个常量能省掉很多麻烦。