从「工具」到「伙伴」:打造 AI 个性化专家
产品思考
年初写年终总结的时候就计划,今年要做出一款面向c端的个人产品,那时候其实脑子里面大概是有了一个雏形。年后玩了一段时间 openClaw,最近也是抽出时间出来,开始逐步开发自己的个人项目。
产品设计
产品形态主要是微信小程序,因为有免费的微信小程序云开发能用,服务可以直接用这个,数据库也可以直接免费用。网页端不太适合我这个产品,开发app周期又很长。小程序是最初阶段最好落地的场景。
产品设计是: AI驱动的宠物行为解读工具,让主人听懂宠物的情绪和需求。核心价值是:即时解读3秒、健康守护、新手友好。主要是想通过 AI 的能力去帮助用户解读宠物的意图。但是做到后面发现我的方向好像是错误的。我太想去实现一个通用的 AI 宠物智能体,而没有想到宠物是没有语言的,所谓“喵喵”或者“汪汪”叫都是宠物情绪的一种表达,在动物的世界里是没有语言这个说法的,他们是通过各种行为特征来表达自己语言的。
所以一开始的通过 AI 解读宠物语言是完全错误的方向,这种所谓的通用 agent 也是完全不成立的。宠物在和主人的相处过程中,除了宠物的一些底层代码,宠物和主人之间相处会形成自己特殊的行为特征,简单说这是一个千人千面的产品。
这个问题也是在实现的过程中才发现,但是当时已经开发了差不多百分之七十的功能,本来想着直接放弃的,但是想起导师阿伟的至理名言:想起来全是困难,做起来全是办法,先按照之前的计划先把一些通用的功能完成再说。
按照原计划完成之后的产品形态就是:接入 AI 大模型接口,然后翻译宠物语音内容,然后加入打卡,宠物通用语、指南、百科、训练指导、宠物问诊等一些通用的功能。这些基本上是撑起一个宠物产品的基本功能形态,但是使用了两天发现完全没有继续使用的欲望。首先所谓的翻译,ai根本就不能做到翻译,所以很多时候 AI 其实完全是在胡说,它最多只能通过宠物的一些语气来做到简单的感情变化的识别(生气、不开心),而其他几个功能对于宠物新手来说还会愿意看一看学习一下,对于我来说我的宠物的几个特征我都基本了解了,我似乎完全没有使用的必要,完全无法形成产品依赖性。

转折
上周五,约阿兴一块探店雍和宫附近的一家粤菜,正好他最近一直在研究 AI 产品相关的内容。就着开水拌叉烧(不敢和他喝酒,怕回不去家),从7点一直畅聊到9点半。听了他对 AI 产品的理解和想法,也给我介绍讲解了几个很“非主流”但是很赚钱的 AI 产品,受益匪浅。对我影响最大的一句话是:移动互联网时代做的大部分产品是平台化,做一系列的通用能力,去解决一群人的一部分的通用问题。在 AI 时代 C 端应该做的是私人定制化的能力,每个用户都希望有一个了解我的“私人秘书”来帮助我解决问题,而不是我去平台找这个功能来解决这个问题,其实最近爆火的 openclaw 解决的就是这个问题。
这很好的启发了我对项目的改造,我做的应该是对每个用户(宠物)做定制化的服务,通过用户的每次交流和使用,记录宠物的行为特征,作为记忆和背景,逐渐了解宠物,打造专属个人的宠物 AI 服务,我做的不是“工具”而是一个逐渐理解用户(宠物)的“伙伴”。
核心技术架构
档案模块
首先需要添加一个档案的功能,记录一下宠物的特征(记忆备份)


然后就是最核心的记忆系统的构建:将用户与助手的每次对话转化为宠物专属知识,随时间积累形成「只懂这只宠物」的个性化专家系统。
记忆系统
循序渐进一共分成四步
第一步:基础版(存档 + 提取)
在 pets 集合中新增 memory 字段:
{_id: "xxx",name: "薯条",type: "cat",breed: "英短",// 新增字段memory: {health: [{ content: "2026年3月食欲不振", date: "2026-03", source: "healthchat" }],behavior: [{ content: "喜欢抓沙发左侧角", date: "2026-03", source: "trainingchat" }],diet: [{ content: "不吃鸡肉罐头,喜欢金枪鱼", date: "2026-03", source: "healthchat" }],events: [{ content: "2026-02 做了绝育手术", date: "2026-02", source: "user_input" }],personality: ["胆小", "粘人"],lastUpdated: "2026-03-23" }}
用户每次离开聊天页面时,将本轮完整对话发给大模型,大模型按照定义好的字段格式提取字段,加到memory字段内。
核心Prompt:
你是一个宠物信息提取助手。以下是用户与宠物顾问的完整对话:[对话内容]宠物信息:名字={{petName}},种类={{petType}},品种={{breed}}请从对话中提取关于这只宠物的新增关键信息。规则:1. 只提取用户明确陈述的事实,不推断2. 模糊表述("好像"、"可能")不提取3. 已知的通用知识不提取,只提取该宠物的个性化信息输出 JSON 格式(没有信息的维度输出空数组):{"health": ["健康相关具体信息"],"behavior": ["行为特征"],"diet": ["饮食偏好"],"events": ["发生的具体事件,尽量带时间"],"personality": ["性格标签"],"hasNewInfo": true/false}
然后在每次对话的时候,都将 memory 拼入 system prompt中:
// 构建 system promptfunctionbuildSystemPrompt(petName, petType, memory) {let prompt = `你是${petName}专属的宠物健康顾问。`;if (memory) {const parts = [];if (memory.health?.length) parts.push(`健康历史:${memory.health.map((i) => i.content).join(";")}`);if (memory.behavior?.length) parts.push(`行为特征:${memory.behavior.map((i) => i.content).join(";")}`, );if (memory.diet?.length) parts.push(`饮食偏好:${memory.diet.map((i) => i.content).join(";")}`);if (memory.personality?.length) parts.push(`性格:${memory.personality.join("、")}`);if (memory.events?.length) parts.push(`重要事件:${memory.events.map((i) => i.content).join(";")}`);if (parts.length > 0) { prompt += `\n\n关于${petName}你已知的信息:\n${parts.join("\n")}`; prompt += `\n\n请基于以上背景提供个性化建议,如有关联请主动结合分析。`; } }return prompt;}
效果:
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
第二步:强化版(置信度 + 双轨存储)
置信度评分系统
提取时增加置信度字段,低于阈值的信息不存档
上一阶段:
{"health": ["两天未进食", "好像有点不舒服"],"hasNewInfo": true}
升级后:
// 提取结果新增 confidence 字段{"health": [ { "content": "两天未进食", "confidence": 95 }, // 明确事实 → 存档 { "content": "好像有点不舒服", "confidence": 35 } // 模糊描述 → 不存档 ]}
置信度判断规则(在 Prompt 中明确要求):
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
存档阈值:confidence ≥ 60 才写入数据库。
事件流 + 状态快照双轨制
将 memory 拆分为两个集合:
集合 1:pet_memory_snapshot(状态快照,可覆盖)
// 当前宠物的最新状态,每次更新直接覆盖{petId: "xxx",health: "轻微肾结石(已控制),无其他慢性病",diet: "喜欢金枪鱼冻干,不吃鸡肉,只喝流动水",behavior: "喜欢抓沙发,见陌生人躲猫窝",personality: ["胆小", "粘人", "夜间活跃"],updatedAt: "2026-03-23"}
集合 2:pet_memory_events(事件流,只追加)
// 每条独立事件,永久保留,带时间戳{petId: "xxx",content: "呕吐两次,后自愈",category: "health",date: "2026-03-10",confidence: 92,source: "healthchat",createdAt: serverDate}
查询策略:
-
日常对话 → 只读 snapshot(快,省 Token) -
用户问「它以前有什么病史」→ 查询 events(按需加载)
语义去重
提取新信息时,先把已有 snapshot 传给大模型,让它判断是否重复:
已有档案信息:- 饮食:不吃鸡肉罐头本次新提取到:- 「它对鸡肉过敏」请判断:1. 与已有信息是否语义重复或矛盾?2. 如重复:选择更准确的表述覆盖3. 如矛盾:标记冲突,两条并存,新条目加「最新」标记4. 如全新:直接新增输出操作指令:{ "action": "update/append/conflict", "content": "xxx" }
第三步:高级版(记忆衰减 + 分层注入 + 主动追问)
记忆衰减机制
每条记忆引入有效分数,控制是否注入 prompt:
// 有效分数计算functioncalcMemoryScore(memory) {const daysSince = (Date.now() - newDate(memory.date)) / 86400000;// 时间衰减(180天后衰减到37%)const decay = Math.exp(-daysSince / 180);// 引用次数强化(每被引用一次+10%,上限200%)const reinforcement = Math.min(1 + memory.mentionCount * 0.1, 2.0);// 来源权重const sourceWeight = {vet_confirmed: 1.5, // 医生确诊user_stated: 1.0, // 用户陈述inferred: 0.6, // 系统推断 }[memory.source] || 1.0;return memory.confidence * decay * reinforcement * sourceWeight;}// 注入时过滤:分数低于阈值的记忆不注入 promptconst activeMemories = memories.filter((m) => calcMemoryScore(m) > 40);
分层注入策略
将记忆分三层,控制 Token 消耗:
第一层:核心档案(每次必注入,~200 Token)├── 品种、年龄、绝育状态├── 已知慢性病└── 最重要的 3 条饮食/行为特征第二层:近期动态(最近 30 天,每次注入,~300 Token)├── 近期健康状况变化└── 近期行为问题第三层:历史归档(按需检索,0~500 Token)├── 超过 30 天的历史事件└── 仅在用户明确询问历史时查询注入
// 分层注入实现asyncfunctionbuildLayeredPrompt(petId, userMessage) {// 第一层:永远注入const snapshot = await getSnapshot(petId);// 第二层:近30天事件const recentEvents = await getRecentEvents(petId, 30);// 第三层:检测用户是否在问历史const isAskingHistory = /以前|历史|之前|过去|曾经/.test(userMessage);const historyEvents = isAskingHistory ? await getAllEvents(petId) : [];return buildPrompt(snapshot, recentEvents, historyEvents);}
主动追问式信息补全
检测档案中的缺失字段,在合适时机主动追问:(这也是你使用很多AI产品可能还有有一些奇怪的追问,其实就是在收集补全信息)
// 定义档案完整度权重const FIELD_WEIGHTS = {vaccine_date: { label: "疫苗接种时间", weight: 9 },spayed: { label: "是否绝育", weight: 8 },chronic_disease: { label: "慢性病史", weight: 8 },allergy: { label: "过敏史", weight: 7 },diet_brand: { label: "常用粮食品牌", weight: 5 },};// 每次对话随机概率追问一个缺失字段(避免每次都问,烦人)functionshouldAskForInfo(snapshot) {const missing = Object.keys(FIELD_WEIGHTS).filter((k) => !snapshot[k]);if (missing.length === 0) returnnull;if (Math.random() > 0.3) returnnull; // 70% 概率不追问// 按权重选最重要的缺失字段 missing.sort((a, b) => FIELD_WEIGHTS[b].weight - FIELD_WEIGHTS[a].weight);return FIELD_WEIGHTS[missing[0]].label;}// 在回复末尾追加// "顺便问一下,薯条打过疫苗吗?记录一下方便以后参考~"
冲突检测与处理
新旧信息矛盾时,不简单覆盖,而是并存+标记:
// 冲突记录结构{field: "behavior",old: { content: "不喜欢被抱", date: "2026-01", status: "outdated" },new: { content: "变得很粘人,喜欢蜷在主人身上", date: "2026-03", status: "active" },conflictNote: "行为可能随年龄/季节发生变化,以最新记录为主"}// 注入 prompt 时的处理// "注意:用户1月反映不喜欢被抱,3月反映变得粘人,// 可能性格随年龄发生变化,请以最新信息为主,但可提示用户关注此变化。"
整体流程
阶段一:对话结束时
① extractMemory 云函数 — 节点1:写入时埋下衰减基础数据
触发时机:用户关闭页面 / 返回上一页
提取信息 → 写入数据库,同时为每条记录埋入衰减算法所需字段:
|
|
|
|
|---|---|---|
pet_memory_events
|
mentionCount: 0 |
|
pet_memory_events
|
createdAt: serverDate |
|
pet_memory_snapshot
|
createdAt: "2026-03-24" |
|
阶段二:下次对话时
② 用户打开聊天页面,发出消息
③ askVet / askTrainer 云函数 — 节点2:分层读取 + 衰减过滤
调用 loadPetMemoryLayered(),按需加载三层记忆:
-
第一层:读取 snapshot,经applyDecayFilter()过滤掉评分过低的过期记忆 -
第二层:读取近 30 天 events,每次必加载 -
第三层:读取更早 events,仅当用户消息含「以前 / 历史 / 之前 / 曾经」时才加载
④ buildMemoryContext() — 节点3:分层拼装注入 System Prompt
-
【核心档案】 第一层(必注入,~200 Token) -
【近期动态】 第二层(必注入,~300 Token) -
【历史记录】 第三层(按需注入,0~500 Token)
⑤ shouldAskForInfo() — 节点4:主动追问缺失字段
检测 snapshot 里哪些关键字段为空,以 30% 的概率在回复末尾追问一句。
⑥ 调用 MiniMax 大模型
基于完整上下文(档案背景 + 分层记忆 + 追问指令)返回个性化回复。
阶段三:回复返回后
⑦ updateMentionCounts() — 节点5:异步更新引用次数(不阻塞)
本次被注入 Prompt 的每条 event → mentionCount + 1,形成「重要记忆自动强化」效果。

步骤四:闭环版(用户反馈矫正 + 数据飞轮)
Phase 1~3 的记忆系统都是单向的。
用户说话 → 提取信息 → 存档 → 下次注入
但这个过程有一个致命漏洞:信息可能是错的,却没有人纠正它。 比如: 用户6月说”薯条不吃鸡肉”,存档了 9月薯条口味变了,现在吃鸡肉了 但系统永远记着”不吃鸡肉”,还一直用这条错误信息给建议 Phase 4 要做的,就是让这个系统形成一个能自我纠错的闭环。
隐式反馈识别
自动识别对话中的矫正信号,无需用户手动操作:
// 矫正信号识别 Prompt`检查用户的最新回复是否包含对已有档案信息的矫正:已有信息:「不吃鸡肉」用户回复:「它现在吃鸡肉了,只是不吃罐头」如果是矫正,输出:{ "isCorrection": true, "field": "diet", "oldContent": "不吃鸡肉", "newContent": "不吃鸡肉罐头,干粮可以" }如果不是,输出:{ "isCorrection": false }`;
档案管理页(用户显式反馈)
在宠物档案页新增「记忆管理」Tab:
薯条的专属记忆 [+ 手动添加]─────────────────────────────🏥 健康记录 • 2026-03 食欲不振(已恢复) [✓] [✕] • 2026-02 绝育手术 [✓] [✕]🍽️ 饮食偏好 • 不吃鸡肉罐头 [✓] [✕] [✏️] • 喜欢金枪鱼冻干 [✓] [✕]🐾 行为特征 • 喜欢抓沙发 [✓] [✕]─────────────────────────────档案完整度:72% [去完善]
用户点击「✕」删除或「✏️」修改,直接更新数据库,同时记录「用户主动矫正」日志,用于后续优化提取 Prompt。
多模态信息源汇入
除对话外,其他数据源也自动归档:
录音分析结果 ──────────→ 情绪趋势记录 「2026-03-23 情绪=焦虑(87%)」打卡记录 ──────────────→ 长期健康趋势 「连续7天情绪评分下滑 → 触发提醒」用户手动填写 ──────────→ 高可信度档案 疫苗记录、绝育状态、过敏原
趋势分析触发提醒:
// 打卡数据分析(每周一次)functionanalyzeCheckinTrend(checkins) {const recent7 = checkins.slice(-7);const avgScore = recent7.reduce((s, c) => s + c.score, 0) / 7;if (avgScore < 60) {// 推送提醒 wx.cloud.callFunction({name: "sendSubscribeMessage",data: {templateId: "xxx",content: `${petName}最近一周情绪偏低,要不要和健康助手聊聊?`, }, }); }}
短期对话记忆优化处理
这里在补充一个关于对话记忆的处理:
问题
-
成本激增:对话进行到第 20 轮时,每次请求携带的历史 Token 可能超过 3000+,是第 1 轮的 10 倍 -
响应变慢:大模型处理更长上下文时延迟明显上升 -
边际效益低:10 轮以前的对话对当前回答的参考价值很低
其实上面的记忆系统已经涵盖解决了一部分问题,就是跨轮次的记忆其实已经是存在memory中去了,后续重新开启对话,其实都是有记忆的。
但是本轮次长对话是没有存储进去的,有可能就会出现本轮对话中的记忆缺失。虽然作为宠物辅助的这种产品形态,一般不会有一轮超长对话,但是这是很好的问题,值得花时间去探索如何解决。
常用的方法(十条截断或者基于 Token 长度的滑动窗口截断)
十条截断
任务 10 轮以前的对话对当前回答的参考价值很低,本轮对话中直接截断丢弃,只要最近的 10 轮
问题:每条消息本身可能很长,10 条仍可能携带大量文本,需要消耗很多token,且10轮以前真的有很重要的信息,我们确实保存需要。
Token 长度的滑动窗口截断
不按条数截断,改为按字符数(近似 Token 数)截断,超出阈值时从最旧的消息开始丢弃。
/** * 按字符数截断历史记录,保证传入云函数的上下文不超过阈值 * @param {Array}messages - 完整消息数组 * @param {number}maxChars - 最大字符数(约等于 Token 数 × 1.5,默认 2000) * @returns {Array}截断后的消息数组 */functionbuildSlidingWindowHistory(messages, maxChars = 2000) {// 过滤掉 loading 占位和空消息const valid = messages .filter((m) => !m.loading && m.content) .map((m) => ({ role: m.role, content: m.content }));let total = 0;const result = [];// 从最新的消息往前取,直到超出字符限制for (let i = valid.length - 1; i >= 0; i--) {const len = valid[i].content.length;if (total + len > maxChars && result.length > 0) break; result.unshift(valid[i]); total += len; }return result;}// 在 _sendText 中替换原有历史构建逻辑const historyForCloud = buildSlidingWindowHistory(messages, 2000);
效果:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
问题: 解决了token过长消耗过多的问题,但是依旧会丢失重要对话。
摘要压缩
当历史超过阈值时,不是直接丢弃旧消息,而是调用大模型将旧消息压缩成一段摘要,用摘要替代原始历史,同时保留最近几轮的完整对话。流程:
完整历史(20 轮) │ ▼ 超出阈值? ──否──→ 直接发送 │是 ▼ 旧消息(前15轮)──→ 调用大模型压缩 │ │ │ 生成摘要(~200字) │ │ └────────────────────┘ │ [摘要消息] + [最近5轮完整对话] │ ▼
// 在前端或云函数中执行摘要压缩asyncfunctionbuildCompressedHistory(messages) {const valid = messages.filter((m) => !m.loading && m.content);const KEEP_RECENT = 6; // 保留最近 6 条完整对话const COMPRESS_THRESHOLD = 2000; // 超过 2000 字符才压缩const totalChars = valid.reduce((s, m) => s + m.content.length, 0);if (totalChars <= COMPRESS_THRESHOLD) {// 未超阈值,直接返回return valid.map((m) => ({ role: m.role, content: m.content })); }// 超阈值:压缩旧消息const oldMessages = valid.slice(0, -KEEP_RECENT);const recentMessages = valid.slice(-KEEP_RECENT);// 调用云函数做摘要(或直接在前端调用 API)const summaryRes = await wx.cloud.callFunction({name: "summarizeHistory",data: { messages: oldMessages }, });const summary = summaryRes.result?.summary || "";// 将摘要作为 system 角色消息注入return [ { role: "system", content: `以下是之前对话的摘要:${summary}` }, ...recentMessages.map((m) => ({ role: m.role, content: m.content })), ];}
这套系统本质上是在做三件事:
-
记忆:把用户每次输入的碎片信息,结构化存起来 -
理解:通过置信度、衰减、去重,让记忆越来越准确 -
进化:用户反馈形成闭环,越用越懂这只宠物
这是从「工具」到「伙伴」的本质跨越,也是产品护城河的来源。
最后虽然整个产品已经改造完成了,也打包上传了,但是很遗憾卡在了审核,现在小程序的关于AI的产品个人是无法审核上架,必须要有企业资质才行。草!如果想体验产品的朋友可以私我,加体验版的权限使用。

一些思考
感觉最近 AI 给大家带来太多的焦虑,上到企业家,下到打工人,很多人无所适从,不知道该怎么办,似乎随时要被这个时代抛弃了。我之前也很焦虑,但是最近看到三句话,帮我摆脱了这种焦虑的困境。
就在这里分享给大家与诸君共勉:
你是领先的
世界总人口现在是81亿,如果画成一个方块图,每个方块代表320万人。

68.8亿人还从没用过 AI(灰色方块),占84%。
13.8亿人跟 AI 有过对话(绿色方块),占16%。
1500万~2500万人付费订阅了 AI 服务(黄色方块),占0.3%。
200万~500万人使用 AI 生成了自己的编程项目(红色方块),占0.04%。
正在阅读这份文章的你,如果是关注 AI 动态,用过 AI 生成代码,就已经领先全世界99%的人了。(出自阮一峰第387期周报)
保持思考和学习
王师傅:大家不要因为每次AI的狼来了的故事而感到什么焦虑,什么我觉得要被AI淘汰了,不要纠结这些事情,你只要去学习它就好了,我觉得人最重要的就是学习能力,如果你什么时候会被淘汰,只有在你停止学习的那一刻,才会被淘汰。


保持健康和热情
在 AI 时代,生产力在很大程度上取决于你能够调用的算力;而当你所能掌控的算力达到你的带宽(你的打字速度和精力)极限时,真正稀缺的,反而是你的身体健康,以及持续保持高能状态的能力。(好像也是出自阮一峰老师的周报,具体哪一期找不到了)
AI 是对整体社会形态的重构,不是单一职业或者行业的影响,作为互联网相关行业,你现在已经领先了很多人,不用过分焦虑,保持思考和学习,多去尝试。最后无论如何保持身体健康,因为不管是 AI 时代还是其他任何时代身体最重要。
夜雨聆风