
1. 为什么我要在本地折腾一个情感智能助手先说结论我做这个项目的出发点特别朴素——我需要一个能随时聊天、能记住我说过的话、还能感知我情绪状态的对话助手但我不想把每天的碎碎念和情绪记录上传到任何云端服务。这个需求听起来简单实际上把 RAG、本地大模型部署、情感识别三件事揉在一起之后坑比我想象的多得多。所谓RAG全称是检索增强生成Retrieval-Augmented Generation通俗讲就是给大模型配一个外挂记忆库模型本身的知识是训练时冻结的但通过 RAG我们可以在每次提问时先从本地知识库里检索相关内容再把这些内容作为上下文喂给模型让它基于你的私有资料回答。而情感智能助手则是在这个基础上再加一层识别用户输入的情绪倾向并让回复带上相应的情感色彩。这套东西适合谁我认为有三类人值得动手一是对隐私敏感、希望所有对话数据留在本机的用户二是想系统学习 RAG 工程落地的开发者因为情感场景比纯问答场景多了不少工程细节三是想做个人知识管理、日记分析、情绪追踪的普通用户。哪怕你只是想在 Mac 上搭一个属于自己的知识库问答这篇文章里的流程也能直接复用。我前后迭代了三版从最初用现成框架拼装到后来自己调检索策略和情感分类模块踩过的坑基本覆盖了本地部署 RAG 的典型问题。下面我把整套思路、选型逻辑、实操步骤和排查经验完整拆开讲。2. 整体架构设计与技术选型思路2.1 为什么是本地部署 RAG 情感层这个组合很多人第一反应是直接调云端 API省事。但情感助手这个场景有个特殊性用户输入的内容往往是私密的情绪表达日记、吐槽、焦虑记录这些东西一旦上云隐私边界就模糊了。本地部署的核心价值不是省钱而是数据不出本机。那为什么一定要 RAG因为通用大模型对你的个人历史一无所知。你上周说过最近项目压力大这周你说还是老样子没有 RAG 的模型根本不知道老样子指什么。RAG 让助手具备长期记忆能力这是情感陪伴类应用的关键。情感层则是这个项目的差异化所在。普通 RAG 问答只关心答得对不对情感助手还要关心答得合不合适。同样一个问题用户愤怒时和低落时需要的回复语气完全不同。所以我在检索之后、生成之前插入了一个情感识别环节用它来调节 prompt 的情感指令。2.2 核心组件选型与理由我把整个系统拆成五个模块每个模块的选型都经过实际对比模块选型备选方案选择理由本地大模型运行时Ollamallama.cpp、vLLM安装简单模型管理方便Mac 上开箱即用生成模型7B~14B 量化模型更大参数模型消费级硬件能跑量化后质量可接受向量化模型bge-small-zhm3e、text2vec中文效果好体积小CPU 也能跑向量数据库ChromaFAISS、Qdrant轻量、纯 Python、支持持久化情感识别本地小模型 规则云端情感 API隐私优先延迟低这里重点说下 Ollama 的选择。我试过直接用 llama.cpp性能确实更可控但模型格式转换、量化参数、上下文长度配置都要手动搞对想快速验证的人不友好。Ollama 把这些封装好了一条命令拉模型改个 Modelfile 就能调系统提示词实测下来在 Mac 上很稳。至于为什么不用 vLLM主要是它更偏向服务端高并发场景个人本地用属于杀鸡用牛刀。向量模型选 bge-small-zh 是个权衡。bge-large 效果更好但显存占用高small 版本在中文语义检索上已经够用而且它只有约 100MB加载快。情感识别我没用大模型而是用了一个轻量中文情感分类模型加关键词规则兜底原因是情感判断本身不需要太强的推理能力用大模型反而慢且浪费。2.3 数据流是怎么走的整个流程我用文字描述一遍方便你建立全局观用户输入一句话 → 情感识别模块判断情绪标签正向/中性/负向 强度→ 把用户输入向量化 → 在 Chroma 里检索最相关的历史片段 → 把检索结果 情感标签 用户输入组装成 prompt → 送给 Ollama 里的模型生成 → 返回带情感色彩的回复 → 把本轮对话写回知识库。这个链路里有两个容易忽略的点一是写回知识库这一步很多人做 RAG 只做检索不做写入导致助手永远记不住新对话二是情感标签要参与 prompt 组装否则情感识别就是白做。这两点后面会详细展开。3. 环境准备与本地模型部署实操3.1 硬件与系统前提先说硬件门槛避免你白忙活。我实测的配置是Mac M 系列芯片 16GB 内存跑 7B 量化模型流畅14B 量化模型勉强能跑但响应慢。如果是 Windows 独立显卡8GB 显存能跑 7B 量化12GB 以上可以上 14B。纯 CPU 也能跑但 7B 模型生成速度大概每秒 2-5 个 token聊天体验会比较煎熬。内存这块有个经验值模型参数量 × 量化位数 ÷ 8 大致是显存/内存占用。比如 7B 模型用 4bit 量化大约需要 3.5GB 左右加上上下文缓存和向量库留 8GB 余量比较稳妥。这个估算不精确但能帮你快速判断机器能不能扛。3.2 Ollama 安装与模型拉取Ollama 的安装在各平台都很直接Mac 和 Windows 下载安装包双击即可Linux 用官方脚本。安装完验证ollama --version拉取模型时我建议先拉一个中文能力较好的 7B 量化版本做验证ollama pull qwen2.5:7b拉完之后测试一下ollama run qwen2.5:7b能正常对话就说明运行时没问题。这里有个细节Ollama 默认的上下文长度可能不够 RAG 用因为我们要塞检索结果进去。可以通过 Modelfile 调整# 创建自定义 Modelfile FROM qwen2.5:7b PARAMETER num_ctx 8192 PARAMETER temperature 0.7然后ollama create my-assistant -f Modelfile生成自定义模型。num_ctx设 8192 是因为检索结果加上对话历史很容易超过默认的 2048上下文被截断会导致模型看不见检索内容这是新手最常见的坑之一。3.3 Python 环境与依赖安装我习惯用 conda 建独立环境避免依赖冲突conda create -n rag-emotion python3.10 conda activate rag-emotion pip install chromadb sentence-transformers ollama langchain版本上有个注意点sentence-transformers和chromadb对numpy版本比较敏感如果装完报 numpy 相关的错锁定numpy2.0通常能解决。我踩过一次排查了半小时才发现是 numpy 2.x 的兼容问题。3.4 向量模型与情感模型的本地加载向量模型用 sentence-transformers 加载from sentence_transformers import SentenceTransformer embed_model SentenceTransformer(BAAI/bge-small-zh-v1.5)第一次运行会自动下载模型之后缓存在本地。情感识别我推荐用一个轻量中文分类模型或者更简单的方式——用关键词词典加规则。别小看规则在情感识别这种任务上精心设计的词典在短文本上准确率并不低而且零延迟、零依赖。我的做法是两者结合模型给一个基础判断规则做修正。4. RAG 知识库构建的核心细节4.1 文档切分策略决定检索质量RAG 效果好不好七成看切分。我见过太多人把整篇文档直接塞进去结果检索出来的是一大坨无关内容。切分的核心原则是每个 chunk 要语义完整且长度适中。我的经验参数是中文文本每块 300-500 字块之间重叠 50-80 字。重叠是为了避免关键信息正好被切在边界上。切分不能只按字数硬切最好按段落、按语义边界切。比如按句号、换行符先粗切再合并到目标长度。def split_text(text, chunk_size400, overlap60): paragraphs text.split(\n) chunks [] current for para in paragraphs: if len(current) len(para) chunk_size: current para \n else: if current: chunks.append(current.strip()) current para \n if current: chunks.append(current.strip()) return chunks这段代码是简化版实际用的时候建议加上按标点二次切分的逻辑。为什么要重叠想象一句话被切成我今天很和难过因为工作检索时两半都语义不完整重叠能让至少一个 chunk 包含完整语义。4.2 向量化与入库切分完就向量化入库。Chroma 的用法很直接import chromadb client chromadb.PersistentClient(path./my_knowledge) collection client.get_or_create_collection(emotion_diary) def add_documents(chunks, metadatasNone): embeddings embed_model.encode(chunks).tolist() ids [fdoc_{i} for i in range(len(chunks))] collection.add( embeddingsembeddings, documentschunks, idsids, metadatasmetadatas or [{} for _ in chunks] )PersistentClient是关键它会把数据存到磁盘重启不丢。用内存版的话每次重启知识库就空了这是新手常犯的错。metadata 我建议至少存两个字段时间戳和来源类型是用户对话还是导入文档后面做时间加权检索时会用到。4.3 检索策略不只是向量相似度纯向量检索有个问题它只看语义相似不看时间。但情感助手场景里最近的对话往往更重要。你三天前说心情不好和三个月前说心情不好权重应该不一样。我的做法是混合打分最终得分 向量相似度 × 0.7 时间衰减因子 × 0.3。时间衰减用指数衰减半衰期设 7 天左右import math from datetime import datetime def time_decay(timestamp, half_life_days7): days (datetime.now() - timestamp).days return math.pow(0.5, days / half_life_days)检索时先取 top 20 候选再用混合得分重排取 top 5。实测下来这个改动让助手对最近相关问题的回答质量提升明显。另外检索数量别贪多塞太多上下文反而会稀释关键信息还挤占上下文窗口。5 条左右是个甜点值。4.4 知识库的写入闭环前面提过只检索不写入的 RAG 是失忆的。每轮对话结束后要把用户输入和助手回复一起写入知识库。但这里有个坑如果每句话都写知识库会迅速膨胀且充满噪音。我的策略是选择性写入只写入包含情绪信息、事实信息或明确偏好的句子。用一个简单的过滤规则比如句子长度超过 15 字、包含情感词或第一人称陈述。这样知识库保持精炼检索质量也更高。写入时打上时间戳和类型标签方便后续加权。5. 情感识别模块的实现与调优5.1 情感标签体系怎么设计情感识别不是简单分正负。我设计的是一个二维体系极性正向/中性/负向加强度1-5。为什么加强度因为有点烦和非常崩溃需要的回复完全不同。前者可以轻松调侃后者必须认真安抚。标签体系设计要克制别搞太细。我一开始设计了十几种情绪焦虑、愤怒、悲伤、喜悦……结果分类器准确率惨不忍睹而且下游 prompt 也没法针对每种情绪写不同指令。最后收敛到三极性加五级强度够用且稳定。5.2 规则与模型结合的实现纯模型方案在短文本上容易翻车比如呵呵这种词模型可能判成中性实际是负向。所以我用规则做兜底和修正negative_words [烦, 累, 崩溃, 难过, 焦虑, 压力, 不想] positive_words [开心, 顺利, 喜欢, 棒, 谢谢, 舒服] def rule_based_emotion(text): neg sum(1 for w in negative_words if w in text) pos sum(1 for w in positive_words if w in text) if neg pos: return negative, min(5, neg 2) elif pos neg: return positive, min(5, pos 2) return neutral, 1模型判断和规则判断冲突时我优先信规则因为规则是我针对场景调过的。这个模型 规则的组合在实测中比纯模型稳定不少尤其是处理网络用语和口语化表达时。5.3 情感标签如何影响生成这是整个项目最容易被做废的一环。识别出情感但不影响生成等于没做。我的做法是把情感标签转成 prompt 里的情感指令emotion_prompts { (negative, 4): 用户情绪低落且强烈请用温和、共情、不评判的语气回应先接纳情绪再给建议。, (negative, 2): 用户略有负面情绪可以轻松回应适当给予鼓励。, (positive, 3): 用户心情不错可以积极互动分享喜悦。, (neutral, 1): 用户情绪平稳正常专业回应即可。 }然后把这个指令拼进系统提示词。实测下来加了情感指令之后回复的人味明显提升。这里有个细节情感指令要放在系统提示词靠前的位置因为模型对开头的指令遵循度更高。6. 常见问题与排查技巧实录6.1 检索不到相关内容怎么办这是最高频的问题。排查顺序我总结成一张表现象可能原因排查方法解决检索结果完全不相关向量模型不匹配手动编码两句话看相似度换中文专用向量模型检索结果为空知识库没写入查 collection.count()检查入库流程检索到但答非所问chunk 太大打印检索内容减小 chunk 尺寸明明有相关内容却检索不到相似度阈值太高打印 top 分数降低阈值或增加召回数我踩过最坑的一次是向量模型用了英文的中文检索效果极差换 bge-small-zh 之后立刻正常。所以向量模型一定要选中文优化的。6.2 模型回答忽略检索内容这个问题的典型表现是检索明明返回了正确内容模型却按自己的知识瞎答。原因通常是上下文太长被截断或者 prompt 里检索内容的位置不对。解决思路一是确认num_ctx够大二是把检索内容放在 prompt 的显眼位置并用明确的分隔符标记比如以下是相关背景信息 --- {retrieved_content} --- 请基于以上信息回答用户问题。明确告诉模型基于以上信息比把内容随便塞进去效果好很多。6.3 响应速度慢的优化本地部署慢是常态但可以优化。我的几个手段一是用更小的量化模型7B 的 q4 量化比 q8 快一倍不止二是减少检索条数从 10 条降到 5 条三是限制生成的最大 token 数情感助手不需要长篇大论num_predict设 256 通常够四是向量模型常驻内存别每次重新加载。6.4 情感判断不准的修正情感识别出错时先看是不是反讽或网络用语。这类文本规则和模型都容易翻车。我的处理方式是维护一个特殊表达词典把呵呵fine随便吧这类词手动标注极性。这个词典是长期积累的用久了准确率会越来越高。7. 我踩过的坑和几条实在经验第一个坑是知识库无限膨胀。我一开始每句话都写回两周后知识库上万条检索质量断崖式下降因为噪音太多。后来加了写入过滤只留有价值的内容检索立刻清爽了。所以记住RAG 的知识库不是越大越好是越精越好。第二个坑是上下文窗口溢出。检索 10 条加对话历史轻松超过 4096模型直接失忆。解决办法是动态控制检索条数根据当前对话历史长度调整历史长了就少检索几条。第三个坑是情感指令被模型忽略。我最初把情感指令放在 prompt 末尾效果很差。后来移到系统提示词开头遵循度明显提升。模型对指令位置是有偏好的这个细节文档里不会写只能自己试。最后一个经验先跑通最小闭环再优化。我第一版就想把情感、检索、记忆全做完美结果卡了两周。后来改成先做一个能检索能回答的简单版本跑通之后再逐个加情感层、时间加权、写入过滤效率高多了。RAG 这种系统模块之间的耦合问题只有跑起来才暴露纸上设计再完美也没用。如果你也想动手我的建议是从一个最小的本地问答开始先让 Ollama 和 Chroma 跑起来能回答一个关于你自己文档的问题然后再往上叠情感和记忆。这个顺序能让你每一步都有正反馈不至于中途放弃。