近半年我一直在折腾本地 AI 助手,陆续试过不少方案,最终把一套叫 Clawdbot 的自托管框架沉淀了下来。如果你也想要一个完全跑在自己机器上、聊天记录不用经过别人服务器、并且能记住你上次说到哪儿的 AI 助手,这篇入门指南应该能帮你少走很多弯路。Clawdbot 的核心就两件事:本地化部署,以及把“记忆”这件事真正落到工程上。我会从设计思路讲起,把选型、搭建、踩坑全部摊开说,适合有一定动手经验、但还没完整做过本地助手的开发者参考。
1. 项目定位:本地化 AI 助手到底解决了什么
1.1 三大痛点:隐私、可用性、上下文割裂
先说隐私。现在用 AI 助手最常见的方式是打开网页或者手机应用,输入一段话,等结果回来。这套模式方便是方便,但你的聊天记录、文件摘要、日程安排全都沉淀在别人的服务器上。你问一句“最近压力有点大”,它可能只当作一次问答,但这些数据一旦被聚合,性质就变了。Clawdbot 的设计目标很直接:所有对话记录、检索索引、用户画像文件都保存在本地磁盘,服务也只监听本机地址,不主动往外发任何内容。
第二个痛点是可用性。公网服务总会有不可控的时候:网络抖动、服务端维护、政策调整、账户到期。你要是正赶着写周报,结果助手连不上,那种感觉真的很糟糕。本地化之后,模型推理在本地完成,向量检索在本地完成,只要机器开了服务,它就在那里,关掉 WiFi 也能用。这对经常出差、网络环境不稳定的人来说,价值是实打实的。
第三个痛点才是标题里最关键的“记忆”。大部分通用 AI 助手如果你不手动开新对话,它还能记得一些上下文;可一旦会话断开、清洗掉历史,它就彻底失忆了。你昨天让它帮你整理的项目清单,今天再问,它只会一脸茫然。Clawdbot 解决的就是这件事:不是简单地把聊天记录堆在一起,而是通过分层记忆机制,让助手在每次新会话里都能主动想起“你是谁、你们聊过什么、现在进行到哪一步”。这一点初听不难,真正做起来会发现坑很多,后面我会专门拆解。
1.2 适合谁参考
如果你符合下面任意一条,这篇指南值得往下看:一是对隐私敏感,不想把所有对话都交给第三方;二是经常需要助手帮你处理跨天、跨周的连续性任务,比如读书笔记、项目进度、健身计划;三是想研究大模型应用工程的原理,搞明白“记忆”到底是怎么落到代码里的;四是手头有一台还算能跑的电脑,想体验一把不联网也能和 AI 对话的感觉。它的门槛不低,但也没有高到需要算法工程师才行。我写这篇,就是希望把那些我自己折腾时没人讲的细节都说清楚。
2. 搭建前必须做的选型与准备
2.1 硬件底线:跑得动才是硬道理
所谓“本地化”,第一道坎就是硬件。Clawdbot 本身只是一个调度壳子,真正的算力消耗在模型推理和向量检索上。先说模型推理。如果你只打算跑一个 7B 级别的对话模型,量化之后大概需要 6GB 到 8GB 显存;要是没有独立显卡,纯靠 CPU 也能跑,但一个回答可能等二三十秒甚至更久。我的建议是,内存至少 16GB,最好 32GB;显卡显存能到 8GB 以上,体验会有一个质变。
向量检索对硬件要求就低多了。普通场景下,几千条记忆片段做向量检索,用 SQLite 级别的存储都能秒回。真正吃资源的是嵌入模型,也就是把文本转成向量那一步。这个模型一般很小,几百 MB 级别,CPU 就能扛。所以务实一点讲:主模型是大头,嵌入模型是小头,向量存储几乎不占资源。按常规实践,先把对话模型跑通,再考虑记忆模块,不要一上来就什么都装。
有一个经验是:别只看显存数字,还要看推理框架是否支持底层优化。同一台机器,用支持量化加速的推理引擎和用裸的模型加载,速度能差好几倍。搭建之前,先把你的推理服务压测一下,让一个常见问题走一遍,看首 token 延迟是多少。如果首 token 超过 5 秒,体验就会很难受,后续所有“记忆召回”都会显得拖沓。
2.2 软件栈:推理、嵌入、存储三件套
本地化助手通常由三个独立组件构成:对话推理服务、文本嵌入服务、记忆存储服务。对话推理服务负责把“用户消息 + 记忆上下文 + 系统提示词”拼起来,生成回复。文本嵌入服务负责把一段话变成固定长度的向量。记忆存储服务负责保存向量和原始文本,并在查询时做相似度检索。
这三个组件最好分开部署,不要挤在一个进程里。原因很朴素:对话推理吃显存,一旦崩了不应该影响记忆库;嵌入服务可以常驻,但它的模型加载很占内存,和推理模型放一起可能导致两者同时换页,延迟飙升。Clawdbot 的做法是在配置里分别指定三个服务的地址,通过 HTTP 接口调用。这样做的好处是,你之后想换更强的推理引擎、换更好的嵌入模型,都只需要改配置,不用动核心代码。
至于具体选哪个实现,我不在这里点名推荐某个具体软件,因为这类工具迭代太快,容易让文章过时。你只需要掌握判断标准:推理服务要支持流式输出、量化加载、OpenAI 兼容接口;嵌入服务要输出维度固定、支持批量编码;记忆存储要支持带 metadata 的过滤检索。满足这三点,就能和 Clawdbot 对接起来。
2.3 数据目录与安全边界
本地化不等于绝对安全,它只是把数据边界收回到你自己的设备上。所以一开始就要规划好数据目录。我的习惯是把所有运行产物放在一个独立文件夹里,比如./data,下面分logs、memories、profiles三个子目录。logs存对话日志,memories存向量索引和原始文本,profiles存用户画像。这样做不仅备份方便,也能防止权限错乱。
安全边界这块有两条红线:第一,服务只绑定127.0.0.1,不要绑定0.0.0.0。如果你需要远程访问,用加密隧道或者局域网安全凭证,但默认一定是本机访问;第二,记忆文件里尽量不要存密码、密钥、身份证号这类高敏感信息。就算文件在本地,一旦系统被入侵,这些就等于裸奔。我没法保证哪套方案绝对安全,但边界划得越细,出事时可控性越高。
3. 记忆系统设计:这是“有记忆”的关键
3.1 短期记忆:会话窗口的维护
Clawdbot 把记忆分为三层,最底层是短期记忆。短期记忆本质上就是当前会话窗口内的上下文。你给模型发送的 prompt 不可能无限长,所以要维护一个滑动窗口。常见做法是用 token 数而不是轮数来控制长度。比如设定窗口上限为 4096 token,每来一条新消息,就把最老的消息弹出,直到总长度回到阈值以内。
这里有个细节很多人忽略:窗口维护不能简单按完整消息切割,否则一条长消息可能直接把整个窗口撑爆。我的做法是,先把消息按 token 长度排序,从最旧开始逐条丢弃,直到剩余总长小于阈值的 90%,留出生成回复的空间。这样模型永远不会在输入阶段因为超长而报错。短期记忆的优点是实时性高,缺点是跨会话就失效。它只能让你“这一轮聊得连贯”,不能让你“下一次还记得”。
3.2 长期记忆:向量检索与摘要归档
长期记忆是 Clawdbot 里最有价值的部分。核心思路是:对话结束后,把对话内容做摘要,然后把摘要和关键实体、重要结论一起转成向量,存进记忆库。下次用户开启新会话、提出新问题时,先对当前问题做嵌入,用相似度检索召回最相关的几条历史记忆,拼进 prompt 让模型感知。
这里为什么要用向量而不是直接全文搜索?因为用户问“我之前让你记的那个优化方案”和原文“页面加载速度优化了 30%”之间,字面上没有重叠,全文检索很容易漏掉;向量检索能根据语义相似度把它捞出来。另外一方面,直接存全文会让记忆库无限膨胀,检索噪音也会变大。摘要归档相当于把原始对话压成一个一个小卡片,只保留“对未来有用的信息”,其他寒暄、重复废话全部丢弃。
具体落地时,我有一个取舍原则:原始聊天记录按天归档成日志文件,不进向量库;向量库里只放经过提炼的摘要、结论、偏好、待办事项。这样做的好处是既能追溯原始记录,又能保证检索结果的质量。向量召回的数量不用设太大,默认top_k=3就够用了。召回太多反而会把不相关的内容塞进 prompt,干扰模型输出。
3.3 用户画像:结构化事实的写入与更新
摘要式记忆解决“发生过什么”,用户画像解决“用户是谁”。这是很多入门教程不会细讲的部分。比如用户说过“我平时用的是 16 寸笔记本”“我喜欢简洁的回答风格”“我周三下午一般有空”——这些信息如果混在对话摘要里,很难被稳定召回;但如果单独抽出来,存成结构化字段,就能长期稳定生效。
Clawdbot 的做法是维护一个profile.json,里面放用户的基本偏好、常用工具、时间安排、称谓习惯等。每次对话结束后,用一个“信息提取”流程扫描本轮对话,把符合条件的新事实提取出来,去重后合并进画像。比如用户说“以后叫我老周就行”,画像里就写入nickname: 老周。下次新会话开始时,画像内容会序列化成一段固定文本,放进系统提示词。这样一来,就算向量召回什么都没命中,助手也不会完全失忆。
要特别注意一个坑:画像更新不能太激进。如果每轮对话都提取并覆盖,很可能把用户随口说的一句话当成长期偏好。我建议只提取带有明确意图和稳定语义的片段,比如“我经常”“我喜欢”“记住”“下次”等标志性短语。其他的信息,让它们留在会话摘要里,等重复出现两三次再写入画像。
4. 完整落地:Clawdbot 搭建实操流程
4.1 第一步:初始化工程骨架
我习惯用一个简洁的目录结构来组织代码,清晰好维护。下面是一个参考结构:
clawdbot/ ├─ config/ │ └─ clawdbot.yaml ├─ memory/ │ ├─ store.py │ └─ schema.py ├─ server/ │ ├─ app.py │ └─ routes.py ├─ data/ │ ├─ logs/ │ ├─ memories/ │ └─ profiles/ └─ start.sh初始化的时候,先把data目录建好,写一个空的profile.json。别小看这一步,后续所有记忆和日志都会沉淀在这些目录里,如果目录权限不对,服务跑起来之后会莫名其妙报错。config/clawdbot.yaml是核心配置,我下面会展开。
4.2 第二步:接入本地推理服务
在写业务代码之前,先确认推理服务已经在本机跑通。不管你用哪种推理引擎,只要它暴露一个 HTTP 接口,就可以接入。Clawdbot 的配置示例大概长这样:
server: host: 127.0.0.1 port: 8787 model: provider: local endpoint: http://127.0.0.1:8080/v1/chat/completions model_name: "{your_local_model}" max_tokens: 2048 temperature: 0.7 memory: type: vector storage_path: ./data/memories embedding_endpoint: http://127.0.0.1:8081/v1/embeddings top_k: 3接入时一个关键点是“兼容接口”。大多数本地推理服务都提供流式和非流式两种方式,但 Clawdbot 内部的 prompt 组装逻辑非常依赖稳定的请求格式。所以我的建议是:先不要开流式,用非流式把整条逻辑跑通,再考虑流式优化。直接上流式调试,出问题的时候很难分清是记忆模块的问题还是流式传输的问题。
4.3 第三步:实现记忆读写核心函数
记忆模块的核心就两个函数:写入和检索。下面是一个极简的内存版实现,方便说明概念:
# memory/store.py class MemoryStore: def __init__(self, config): self.config = config self.items = [] def add(self, text: str, metadata: dict = None): vec = self._embed(text) self.items.append({ "text": text, "vector": vec, "metadata": metadata or {} }) def search(self, query: str, top_k: int = 3): qvec = self._embed(query) scored = [] for item in self.items: score = self._cosine_similarity(qvec, item["vector"]) scored.append((score, item)) scored.sort(key=lambda x: x[0], reverse=True) return [item for _, item in scored[:top_k]]这里的_embed就是调用嵌入服务,把文本变成向量。真实项目里不会把所有 items 放在内存里,而是写入本地向量存储,但接口思想是一致的:写入时给文本配上向量和 metadata,检索时用 query 的向量去算相似度。metadata很重要,比如你可以给每条记忆打上时间戳、会话 ID、类型标签,检索时可以先按时间过滤,再算相似度。这能显著减少无关记忆的干扰。
另一个容易忽略的点是“记忆写入时机”。我一开始是每轮对话结束都写入,结果发现大量中间过程被记进去,比如“好的,我来帮你看看”“这个问题有意思”这种废话完全没必要入库。后来改成:只有一轮对话中包含明确的结论、待办、偏好、实体信息时,才触发记忆写入。判断方法可以是规则,也可以让模型判断,但规则更省算力。
4.4 第四步:封装为本地 API 服务
Clawdbot 对外只暴露一个/chat接口,接收一行用户消息,返回一个回复。在服务内部,它会依次执行:读取用户画像、检索长期记忆、组装 prompt、调用推理服务、更新短期记忆、异步触发记忆提取与写入。下面是核心接口的伪代码:
# server/routes.py from memory.store import MemoryStore from profile.profile import Profile def handle_chat(message: str): profile = Profile.load("./data/profiles/profile.json") mem = MemoryStore.load("./data/memories") context = mem.search(query=message, top_k=3) prompt = build_prompt(user=message, profile=profile, memories=context) reply = call_local_model(prompt) append_to_short_term(reply) async_extract_and_store(message, reply) return reply这里有个值得反复测试的地方:build_prompt的顺序。我最终固定的顺序是系统提示词 + 用户画像 + 检索到的记忆 + 当前消息。记忆部分要明确标出“来自历史记忆,不一定准确”,防止模型把旧事实和新问题混淆。你可以想象一个场景:用户上个月说过“我喜欢喝美式”,这个月说“最近开始喝茶了”。如果记忆原样召回,模型可能会回答“根据你之前的记录,你更喜欢美式”。这时候需要 prompt 里写入“如果历史信息与当前冲突,以当前为准”。这个细节看着小,实际体验差距很大。
4.5 第五步:端到端联调并验证记忆
联调阶段,我会准备一段脚本连续问几个问题,比如“帮我记一下:每周三晚上八点开周会”“我的周会主题是什么”。第一次问完,稍等几秒,再开一个新会话问第二句。如果回答里正确带出了“每周三晚上八点”,说明记忆链路已经通了。
我习惯在联调时打印每一层的输入输出:画像加载了什么、向量检索召回了什么、最终 prompt 长什么样。只看最终回复,永远无法定位问题。有一次我排查了很久,发现检索召回了三条完全无关的记忆,原因是嵌入服务的 API 参数传错了,导致向量全部是随机初始化。这类 bug 不打印中间层,根本发现不了。
5. 实际运行中的典型问题与排查方法
5.1 记忆为什么“时不时失效”
很多人跑通的第一天一切正常,第二天发现助手又“失忆”了。最常见原因是记忆入库失败。异步写入没加错误处理,某个字段为None,整个写入流程静默崩掉;或者向量库连接没有重连机制,服务重启后连接池失效。解决方案是给记忆写入加日志和重试,每次写入成功与否都要留痕。
还有一个隐蔽问题:检索时没做时间衰减。假设你三个月前问过“怎么配置某框架”,三个月后问同一个框架,它把那条旧记忆召回没问题;但如果用户最近已经换技术栈了,旧记忆反而形成误导。我后来给 metadata 里加了created_at,检索时做时间减权,超过一定天数的记忆相似度得分乘以一个衰减系数。这样旧记忆不会完全消失,但不会霸占最前面的位置。
5.2 推理速度慢的三大原因
本地化助手最容易劝退人的就是速度。首 token 慢,通常是模型太大、量化等级不够,或者推理服务没有利用 GPU。首 token 正常,但整体回复慢,往往是 prompt 太长,输入阶段占用了大量计算。第三个原因是记忆检索同步执行,拖慢了整个请求。如果向量库比较大,检索耗时可能几百毫秒到一两秒,体感很明显。
我的优化思路是:把记忆检索改成预加载。每次新会话开始时,先用用户画像里的常用主题做一次预检索,把可能相关的记忆缓存起来;中途用户发消息时,直接检索增量部分,而不是全量重查。这里的效果非常明显,尤其对长对话场景。另一个优化是把嵌入服务升级为批量编码,减少 HTTP 往返次数。
5.3 上下文污染:别把所有信息都塞进 Prompt
上下文污染是比失忆更让人头疼的问题。检索到的 3 条记忆,其中只有 1 条相关,另外 2 条是无关噪音。模型就会把噪音也当成事实,回答变得很奇怪。比如用户问“今晚天气怎么样”,系统却把上周“买牛奶”的记忆塞进去,模型可能回答“出门别忘了买牛奶”。这很荒谬,但确实会发生。
解决办法是给每条召回结果加一个相关性分数阈值,低于阈值就不进入 prompt;同时在 prompt 里明确分块:画像区、记忆区、当前消息区,让模型知道哪些是参考事实、哪些是待办事项。另外,记忆写入时多存“结论”,少存“过程”。如果你想找回原始过程,去翻日志,但不要让向量库里充满低质量过程数据。
下面是一个速查表,方便你在遇到问题时快速对照。
| 现象 | 可能原因 | 处理方案 |
|---|---|---|
| 新会话完全不记得旧内容 | 记忆写入失败或召回为空 | 检查入库日志,确认 embedding 接口返回正常 |
| 回答里出现过时信息 | 召回未做时间衰减 | 给记忆增加时间字段,并按时间加权排序 |
| 每轮第一个字等很久 | 模型量化低显存不足 | 升级量化等级,或减小输入 prompt |
| 回答越来越乱 | prompt 被过多记忆污染 | 降低 top_k,增加相关性阈值 |
| 磁盘占用增长过快 | 向量库存入了太多过程性文本 | 增加入库判定规则,只存结论和待办 |
| 服务重启后记忆丢失 | 向量存储未持久化 | 检查存储路径配置,确认写入后立即落盘 |
6. 几个值得坚持的实操习惯
路径规划比代码重要。我最早没有仔细规划数据目录,导致向量库、日志、配置散落在各个文件夹。后来换机器迁移,折腾了一整天。现在我把data目录整个打包就能迁移,省心很多。
记忆写入要克制。很多人的第一反应是把所有对话都存进记忆库。存储本身不贵,但检索质量会断崖式下降。我现在的标准是:没有明确结论、没有可执行动作、没有稳定偏好,就不入库。宁可少记,不要乱记。
日志一定要带到生产环境。Clawdbot 这类本地助手最尴尬的是出问题无法复现。我在代码里给每个关键节点都加了结构化日志,包含请求 ID、耗时、token 数、召回记忆的 ID 列表。排查的时候看到这些日志,十秒内就能定位是模型问题、记忆问题还是配置问题。
最后再分享一个小技巧:每次升级推理模型后,不要直接复用旧的向量库。嵌入模型的输出空间可能不一样,新旧向量混在一起会导致检索结果乱掉。正确做法是保留旧库,新建一个命名空间,把历史摘要重新嵌入后写入新库。这个过程看似麻烦,却能避免很多后续头疼的问题。如果你正准备动手搭一个本地化、有记忆的 AI 助手,希望这篇 Clawdbot 入门指南能成为你第一份靠谱的参考。