WeKnora v0.8.0落地手记:为RAG知识库装上记忆、工具与技能

📅 发布时间:2026/9/13 7:34:58
WeKnora v0.8.0落地手记:为RAG知识库装上记忆、工具与技能
一个多月前我把公司那个只会“查文档、吐摘要”的静态 RAG 知识库下线了。原因很简单——它不回答多轮问题不查实时数据更不会主动干任何事。直到我把入口和配置迁到 WeKnora v0.8.0让知识库同时接上了记忆、工具和技能并用企业微信号挂到公司内部才真正感觉到这是一个“能干活”的工程系统而不是一个摆设。这篇落地手记不搞产品宣传我会直接讲清楚我为什么从 Dify、RAGFlow、AnythingLLM 这几个方案里挑中它本地用 Ollama 接模型时怎么配置不出错记忆和工具调用是怎么实现的以及最后接入微信回调和生产调优过程里那些必须避开的坑。如果你正在搭开源知识库或者已经有一个“问答还行但不够聪明”的 RAG 系统这篇应该能给你省下不少时间。下面所有步骤都是我自己跑过的能直接抄作业。1. 被“静态知识库”劝退之后我为什么押注 WeKnora v0.8.0先说清楚我原来的架构有多“教科书”。文档上传后切块用嵌入模型转成向量建立向量索引然后每次提问就把问题和库里最相似的片段一起丢给大模型生成回答。看起来是典型的 RAG 知识库搜索热词里人人都在聊的那套。但真实用起来问题接踵而来。我总结了四个逼我换系统的失败场景用户问完“华东区 3 月销售额是多少”紧接着补一句“那上个月呢”系统直接把“上个月”当独立问题去检索返回一堆不知所云的内容因为没有多轮记忆。问“今天下午三点的会议室定了吗”知识库里根本没有日历数据再强的检索也捞不出答案因为没有手脚去调用外部系统。用户说“请把这份审批提醒发给财务经理”模型只会回复“很抱歉我无法执行”因为没有可用的工具链。同一个高频问题隔两天又有人问知识库仍然不会把上次的高质量回答沉淀下来知识永远不会自己长大。这四个问题分别对应标题里的记忆、手脚和技能。WeKnora v0.8.0 之所以能让我留下来不是因为它把界面做得多漂亮而是它在工程上把这三件事变成了可配置、可观测、可维护的模块。1.1 v0.8.0 到底新增了哪些能力记忆、手脚和技能先说记忆。它不是单纯把聊天记录多传几轮给模型而是分成了会话记忆和长期记忆。会话记忆负责把多轮上下文压缩成摘要避免上下文窗口被撑爆长期记忆则会把用户偏好、历史事实、未完成事项写入独立的记忆存储让知识库“记得老用户是谁”。再说手脚。v0.8.0 里我印象最深的是插件和工具协议。官方把工具调用做成了标准 JSON Schema我可以在管理后台注册一个查询数据库的工具、搜索网页的工具、发微信通知的工具然后让模型自己决定什么场景该调用哪个。这本质上就是大模型应用里常说的 function calling但它多了一层权限控制写操作和读操作分开授权。最后是技能。技能不是一句提示词模板而是一整套可编排的工作流。比如“周报数据问答”这个技能内部包括意图识别、查询改写、多路召回、重排、生成回答五个阶段。模型只是最后一步前面每一步都通过配置串联起来。这套东西放在生产环境里最大的好处是每个环节都可以单独优化不用重启整个服务。1.2 和 Dify、RAGFlow、AnythingLLM 的横向对比我真正动手前把市面上常见的几个开源知识库方案都试了一遍包括搜索热词里高频出现的 Dify、RAGFlow、AnythingLLM。每个都有可取之处但都有我没法接受的短板。方案部署复杂度多轮记忆工具/插件扩展微信接入技能编排适合场景Dify中有有但权限粒度偏粗有需自己配渠道低代码编排企业内部 AI 应用搭建RAGFlow中高有较少可二次开发偏重文档解析管线复杂 PDF/表格解析AnythingLLM低较弱有基础功能需要额外插件弱个人和小团队临时用WeKnora v0.8.0中强工具协议标准、权限细原生渠道支持支持多阶段工作流微信办公场景 企业知识库RAGFlow 在文档解析和表格抽取上确实很强但我想做的不只是文档问答还要让知识库去查数据库、发通知它的开放接口明显不够用。AnythingLLM 胜在简单但复杂场景撑不住。Dify 是一个成熟的低代码平台可惜它的定位偏“应用搭建”而我想把知识库当成一个智能体基础设施去深度定制工具调度和记忆策略的自由度不够。WeKnora 刚好补齐了这些点所以最终选型定在了 v0.8.0。2. 本地部署Ollama 接模型向量库选 Qdrant其余交给 Compose选型定了之后就开始落地部署。我的原则是能容器化就容器化能本地跑就本地跑数据不出内网。下面的环境清单和启动方式都是实际验证过的你可以直接照抄。2.1 硬件和软件版本清单本地部署最关键的是先明确硬件底线。我用的是公司一台 8 核 32G 内存的旧服务器没有独立 GPU整体跑起来能接受但回答速度偏慢。如果预算允许建议上一块 8GB 以上显存的消费级 GPU体感会好非常多。配置项建议最低我的实测配置备注CPU8 核8 核CPU 模式也能跑推理慢但稳定内存32 GB32 GB同时运行大模型、向量库、服务端GPU可选无有 GPU 优先用能显著降低延迟磁盘50 GB100 GB主要存模型文件和向量索引软件方面我用了 Docker 24、Docker Compose v2模型服务用 Ollama向量数据库用 Qdrant嵌入模型用 bge-m3重排模型用 bge-reranker-v2-m3对话生成模型用 qwen2.5:7b-instruct。这套组合在中文场景下性价比很高而且都是搜索热词里大家常用的本地化组件。2.2 docker-compose 配置和启动步骤我习惯把整个栈拆成四个服务Ollama、Qdrant、WeKnora Server、WeKnora Console。Console 是管理界面生产环境可以不对公网暴露只走内网。services: ollama: image: ollama/ollama:latest container_name: ollama volumes: - ./ollama:/root/.ollama ports: - 11434:11434 qdrant: image: qdrant/qdrant:latest container_name: qdrant volumes: - ./qdrant_storage:/qdrant/storage ports: - 6333:6333 weknora-server: image: weknora/weknora-server:0.8.0 container_name: weknora-server depends_on: - ollama - qdrant environment: WKNORA_BASE_URL: http://localhost:8080 VECTOR_STORE: qdrant EMBEDDING_PROVIDER: ollama EMBEDDING_MODEL: bge-m3 OLLAMA_BASE_URL: http://ollama:11434 MEMORY_ENABLED: true MEMORY_SESSION_WINDOW: 6 ports: - 8080:8080启动命令很简单mkdir -p /opt/weknora/{ollama,qdrant_storage} cd /opt/weknora docker compose pull docker compose up -d第一次启动后先拉模型docker compose exec ollama ollama pull qwen2.5:7b-instruct docker compose exec ollama ollama pull bge-m3然后打开 http://localhost:8080 初始化管理员账号在模型配置里把 Ollama 地址填成 http://ollama:11434把对话模型设为 qwen2.5:7b-instruct嵌入模型设为 bge-m3。这里有个关键点嵌入模型的维度是 1024向量库第一次创建 Collection 时会记住这个维度。如果后面你换了嵌入模型维度对不上Qdrant 会直接报 dimension mismatch。我吃过这个亏所以建议第一次就把模型定下来不要频繁切换。2.3 文档切块的参数经验创建知识库、上传文档时有一个容易被忽视的配置就是切块大小。我试过 256、512、1024 三档最常用的是 512重叠区设置 64。512 个字符大约能覆盖 300 到 600 字的中文段落既不会让单段语义太碎也不会让向量检索定位到过大的无关区域。64 个字符的重叠区是为了避免句子正好被切在关键短语中间。如果是英文技术手册块大小可以适当调到 1024因为英文 token 密度和中文不一样模板化段落更多。召回数量我也建议从平台的默认 10 改成 5。这个细节后面调优章节会展开说但你可以先记住召回多了不一定是好事尤其是知识库里有很多相似文档时Top 10 会把一些完全不相关但措辞相近的内容也拉进来模型容易把内容“缝”在一起出现幻觉。3. 给知识库装上“记忆”会话记忆与知识沉淀的工程实现接下来聊我最看重的记忆模块。静态 RAG 最大的缺陷是“答完就忘”要让它有记忆不是简单把历史消息拼到 prompt 前面。那样做有两个问题一是上下文窗口很快被撑满二是大量未筛选的历史信息会干扰检索反而让回答变差。3.1 短期记忆压缩摘要而不是硬塞历史WeKnora 的处理方式是每个会话维护一个滑动窗口。我在配置里把session_window设为 6意思是最近 6 轮消息会完整保留。一旦超过这个轮数或者累计 token 超过阈值系统会触发一次摘要压缩用一个小模型把前面的对话内容归纳成几十个字的摘要例如“用户正在核对华东区 3 月销售数据关注逾期订单数量”。后续请求携带的是“摘要 最近几轮原始消息”而不是全部历史。这样设计的好处非常明显。上下文从“不断膨胀的完整日志”变成“定长的压缩摘要 近期原文”既保留了多轮语境又不会让 prompt 越来越臃肿。我在配置里就是这样开的memory: enabled: true session_window: 6 context_compression: true compress_threshold_tokens: 2000 long_term: true long_term_ttl_days: 903.2 长期记忆把真正有价值的信息沉淀下来长期记忆解决的是“跨会话记得用户”的问题。我落地时做了一件比较大胆的事给知识库开放了memory.write工具。当对话过程中模型判断出这是用户偏好、历史事实、未完成任务时会主动触发这个工具把信息写入长期记忆库。下次任何一次对话开始前系统会先根据用户 ID 召回相关长期记忆作为 prompt 的“背景资料”。举个实际例子。用户说过“我做报表时只关心华东区华南区不用看”这个信息会被写入长期记忆。之后无论他问哪个月的数据系统都会先想起这条偏好直接把他关心的范围固定到华东区不用他每次重复。每个长期记忆项带有时间戳和 TTL默认 90 天未有有效交互就自动淘汰。针对敏感内容还可以开启人工审核避免模型把错误的推断当成事实存下来。这里我踩过一个坑模型会把用户随口说的一句“这个月好忙”也当作事实写入记忆导致后来每次回答都莫名其妙地被“用户很忙”影响。后来我把长期记忆的写入条件改成“必须同时满足明确表述 可验证性 用户无否定”才把这种垃圾记忆压下去。3.3 知识沉淀让知识库从静态导入变成动态生长除了用户侧的记忆我还非常看重知识库自身的“成长”。v0.8.0 里有一个问答沉淀链路每次模型回答后用户如果点“有用”这条问答对就会进入待审核队列。管理员审核通过后系统会把问题作为新知识点写入知识库标题就是问题本身正文就是被采纳的回答。之后有人再问类似问题就不再需要临时检索几十个片段拼答案而是直接命中沉淀出来的高质量条目。我把它理解成“用问题喂知识库”。跑了两个星期后平台上最常被问的 30 个问题基本都有了精确匹配的知识条目回答速度从原来的 4 到 5 秒降到了 2 秒以内。这个收益纯粹来自沉淀机制不需要额外调模型。4. 给知识库接上“手脚”工具注册与函数调用链记忆解决的是“记得住”工具解决的才是“做得到”。这一章我专门讲讲我是怎么让知识库去查数据库、查日历、发企业微信通知的。4.1 我注册的六个工具工具协议并不复杂关键在于你给模型多少“能力”以及怎么控制权限。我第一个版本只开了六个工具都是基于真实办公场景切出来的工具名称用途操作类型权限范围web_search外部信息补充知识库未命中时兜底读全员calendar_query查询会议室和日程占用读全员meeting_book预订会议室写仅管理员database_query查询 BI 只读数据库生成报表数据读业务组成员approval_notify向指定审批人发送企业微信提醒写仅管理员knowledge_append把高质量问答沉淀到知识库写仅管理员工具的定义就是标准的 function calling JSON Schema比如database_query是这样的{ type: function, function: { name: database_query, description: 查询只读BI数据库返回Markdown表格格式的数据, parameters: { type: object, properties: { question: { type: string, description: 用户想通过SQL查询的数据需求描述 }, table_hint: { type: string, description: 可选的表名提示辅助生成SQL } }, required: [question] } } }实际运行流程是用户问“华东区 3 月订单完成率是多少”模型识别出这属于数据库查询需求输出一个包含 question 参数的工具调用请求。WeKnora 的引擎收到这个请求后去调一个内部服务这个服务根据 question 用自然语言生成 SQL然后对只读副本执行查询把结果整理成 Markdown 表格返回。模型再基于表格结果生成最终回答。4.2 让本地模型正确识别工具而不是“看起来会调”这里要特别提醒本地模型和云端模型在 function calling 上的稳定性差距不小。我用 qwen2.5:7b-instruct第一周测试时最大的问题是它偶尔会不按标准 JSON 格式输出工具参数或者擅自填一个不存在的工具名。解决办法有三个temperature调低到 0.2减少输出随机性。在系统提示词里明确列出可用的工具名称和调用格式不要指望模型从函数列表里自己猜。对工具调用的返回结果做格式校验如果解析失败给模型一次“修正输出格式”的重试机会。实测下来这三点配合好后工具调用的成功率从 70% 左右提到了 95% 以上。剩下那一小部分失败基本来自用户问题本身模糊模型不知道要不要调用工具这时我会让它先反问澄清而不是强行猜测。4.3 工具权限和失败兜底工具链一旦开放安全边界就变成第一优先级。我给每个工具挂了 RBAC角色和用户组绑定工具对用户组可见。普通用户看不到meeting_book和approval_notify模型就不会给普通用户生成写操作调用。对于写操作我还开了二次确认。比如meeting_book被触发时系统先在微信侧发一张确认卡片用户点“同意”后真正执行。这个设计看着多了一步但在生产环境里非常必要否则模型一次参数理解错误就可能把会议室给定了。失败兜底同样重要。工具调用超时或报错时我统一设置成“向用户说明暂无法获取实时数据”绝不允许模型自行编造一个结果。最开始我没做这个限制模型在工具返回空结果时居然会“补全”一份看似合理的数据这在对内汇报场景里风险很大。加了兜底后至少保证了“不知道就是不知道”。5. “技能”是先厂的工作流多路召回、查询改写、重排的一整套纽带工具解决了“能干什么”技能则解决“怎么把事情干好”。我在落地中期发现单纯靠“向量检索 工具调用 大模型生成”组合出来的回答质量是不稳定的。原因在于RAG 检索的精度只要有一点偏差模型就会基于错误片段生成错误答案。技能编排的价值就是把这个链条变成可控制的流水线。5.1 一个“周报数据问答”技能拆解我以公司里最常用的“经营周报问答”技能为例。这个技能的目标是回答类似“上周华东区订单趋势怎么样”的问题。它内部被拆成五个阶段意图识别判断用户想问数据、问文档还是闲聊。意图识别我用了最简单的分类 prompt成本很低但能避免“数据问题”被误送进“文档检索”通道。查询改写把含有多轮记忆和模糊时间的表达还原成完整查询。比如“那上个月呢”会被改写为“华东区 2025 年 3 月订单汇总”。多路召回向量召回、BM25 全文检索、数据库查询结果并行进行然后把三路结果合并去重。重排把候选内容交给重排模型打分只保留最相关的 5 条。生成基于“重排结果 工具数据”生成回答并要求标注数据出处。配置起来大概是这样的skills: weekly_report: enable: true route_condition: 话题包含:周报,经营,指标,订单 query_rewrite: true recall: vector: true bm25: true top_k: 20 rerank: model: bge-reranker-v2-m3 top_n: 5 generation: temperature: 0.2 strict_source: true多路召回的意义在于互补。向量检索理解语义但拼写和精确术语容易偏BM25 精确匹配强但不管语义。两者合并后再用重排模型做最后把关比任何单一检索都稳。5.2 查询改写和重排器的配置查询改写是本技能里性价比最高的一步。它本质上是用模型把小问题扩展成完整问题但要注意别让改写过程引入错误信息。我的经验是改写尽量基于对话记忆不要发挥只把省略的主语和具体时间补齐其他原样保留。重排器我用了 bge-reranker-v2-m3它是一个交叉编码器模型虽然速度比向量检索慢但因为只对 20 条候选打分整体延迟还是可控的。重排后的 Top 5 质量比直接向量 Top 5 高很多。一个典型的对比是用户问“报销流程”纯向量召回可能返回几条财务制度但重排后真正排前面的就是带“报销流程”字样的操作手册。5.3 技能的评价指标不要只在界面上感觉“好像准了”要落地的话一定要留评价指标。我统计了三个核心指标检索命中率、回答采纳率、平均响应延时。指标优化前优化后首问检索命中率72%91%回答采纳率用户点有用/未投诉68%86%平均响应延时5.2s3.8s提高最明显的是首问检索命中率主要靠的就是多路召回和重排。回答采纳率的提升则是因为强加了“严格引用来源”模型不敢再胡编蜻蜓点水式的错误反而少了。6. 微信侧接入企业微信应用、回调验签和消息收发标题里带“微信”二字说明微信入口是整个落地里绕不开的一环。我选择的是企业微信自建应用方式既可以用私有化聊天记录也可以直接主动推送消息比个人微信机器人正规得多。6.1 回调配置的三个必要操作企业微信接入本质上是一个“消息收发长连接”的替代方案它通过 HTTP 回调把用户发给应用的消息推送到你的服务器。三个必要操作在企业微信管理后台创建自建应用拿到 AgentId 和 Secret。配置“接收消息服务器 URL”格式为https://your-domain/weknora/webhook/wecom同时设置 Token 和 EncodingAESKey。在服务器侧把回调地址反代到 WeKnora 的 8080 端口保证外网能够访问。我本地测试时直接用 Nginx 反代配置大致是server { listen 443 ssl; server_name bot.example.com; ssl_certificate /etc/nginx/cert.pem; ssl_certificate_key /etc/nginx/key.pem; location /weknora/webhook/wecom { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }6.2 消息加解密处理企业微信回调的消息体不是明文而是经过 AES 加密的 XML同时在 URL 参数里有签名。第一次接入时最容易卡在“签名验证失败”和“解密乱码”上。签名验证的逻辑不复杂核心就是把 token、timestamp、nonce 三个参数按字典序排序后拼接做 SHA1 得到签名import hashlib def verify_signature(token, timestamp, nonce, signature): sort_list sorted([token, timestamp, nonce]) calc hashlib.sha1(.join(sort_list).encode(utf-8)).hexdigest() return calc signature但企业微信的echostr还需要 AES 解密这一步强烈建议直接用官方 SDK不要自己从零实现。解密后的 XML 里包含FromUserName用户 ID、Content消息内容、MsgType等字段。WeKnora 拿到这些字段后会把内容交给 Agent 引擎处理。我在生产里遇到过一个典型问题回调验证时能收到消息但实际提问后没有回答。后来发现是企业微信把消息推到了回调 URL但我的服务器处理完没调发送消息 API 来回消息。也就是说你要在回调处理完生成回答后再调用一次企业微信的“发送应用消息”接口给用户推回文本卡片。这一步没配置好整个链路就是断的。6.3 主动推送与群聊 机器人企业微信接入的一个额外好处是可以主动推送。知识库每次有新沉淀、有未解决问题、工具执行失败我都可以通过 WeKnora 的接口主动发送通知到用户或群聊。这个能力在实际管理里非常有用。我做了两个实际应用一是当知识库收到一个没有命中任何内容的问题时自动把问题推送到运维群里二是当数据库工具查询结果异常时主动通知管理员。主动推送需要应用有“发消息”权限接收人必须在应用的可见范围内。群聊场景则是把应用机器人拉到群内成员通过 机器人 提问。企业微信的回调里会带ChatIdWeKnora 渠道插件会识别群聊 ID把回答发回群里。注意群聊里用户 ID 是企业微信内部 ID不是手机号需要做一次映射否则记忆模块会把同一个人的两个不同 ID 当成两个用户。7. 一个月实测下来的调优清单任何系统上了生产都会暴露测试环境看不见的问题。我运行一个月后把最有价值的调优和排错经验按主题整理出来都是可以直接照用的。7.1 检索准确率从崩溃到可用的调试第一周最严重的问题是幻觉。用户问“公司考勤制度是什么”系统回答里居然把“年假”和“加班工资”的条文缝在了一起。这两段内容在知识库里的前后位置差了十万八千里就是因为纯向量召回 Top 10 后把中间所有相似段落全部拉进来了。我做的第一件事是把召回数从 10 降到 5第二件事是强制要求模型在回答中标注“该回答基于知识库哪些片段”。没用重排之前降召回数会让短文档经常被漏掉配上重排之后Top 5 的质量足够幻觉比例肉眼可见地下降了。如果回答里检索到的证据置信度低于阈值我直接让模型回复“我在知识库里没有找到明确信息”。7.2 并发、超时和内存治理并发问题是本地部署最容易爆的雷。好几个同事同时在微信里问问题时Ollama 的处理能力就成了瓶颈。我的服务器是 8 核 CPU默认情况下 Ollama 每个模型只加载一个实例多请求只能排队。我把 Ollama 的OLLAMA_NUM_PARALLEL调到了 2允许两个请求并发同时在 WeKnora 后端把 workers 从默认值调到 4。OLLAMA_NUM_PARALLEL2内存方面7B 模型需要约 5 到 6 GB嵌入模型和重排模型又要占用两三 GB加上 Qdrant 和 WeKnora 服务进程32GB 内存跑起来勉强够用但一旦有人同时上传大量文档并触发重新向量化内存会迅速冲到 90%。治理办法是把文档解析和向量化任务放到低优先级队列避免和实时推理抢资源。7.3 在真实对话中不能踩的隐藏坑最后分享几个不太容易在官方文档里找到的实践细节。第一个是重复消息问题。企业微信回调在网络抖动时会重试同一消息如果不做消息 ID 去重用户会看到同一个问题被回答两遍。我加了一个简单的 Redis 缓存以消息 ID 为 key 设置 60 秒过期时间重复消息直接丢弃。第二个是不要把数据库工具指向生产主库。模型生成的 SQL 可能带上全表扫描也可能在问题不清时漏掉 where 条件。我用的只读副本并且在工具内部强制给查询加 LIMIT 限制。宁可结果不全也不能把主库拖垮。第三个是日志要按request_id串起来。调试工具调用链路时最头疼的是不知道模型为什么选择了一个不相关的工具。我们可以在 WeKnora 日志里把一次请求的完整链路打出来包含原始问题、改写后的问题、召回候选分数、重排分数、工具调用参数、工具返回结果按request_id过滤就能快速定位问题。写在最后的一个小提示如果你也准备在内部搭知识库我最大的建议是把“记忆、工具、技能”当成三个独立模块先分别验证而不是一把梭全配齐。先把静态检索调到可接受的水平再加会话记忆再开放只读工具最后才接写操作。每加一层就在微信里反复压测一轮。我的经验是第一周只跑“静态知识库 微信回调”第二周才放记忆和只读数据库查询第三周才开始上写工具和主动推送。虽然上线节奏被拉长了但真正出问题时你能一眼看出是哪一层出的问题而不是在一个复杂的智能体里大海捞针。