当 OpenClaw 遇上 RAG:让 AI 基于你的企业知识库回答问题
标签:OpenClawRAG向量数据库个人AI助手Qdrant知识库Skill开发
阅读时间:约 20 分钟
难度:中级 · 有一定 Python 基础即可上手
一、先聊聊 OpenClaw 到底是什么
如果你还没用过 OpenClaw,先花 60 秒理解它——因为它跟大多数人印象中的"AI 应用"完全不是同一种东西。
OpenClaw 是一个跑在你自己电脑上的开源个人 AI 助手。
不是云端 SaaS,不是聊天网页,不是某家大厂的 App。它就安装在你的 Mac / Windows / Linux 上,然后你可以通过 WhatsApp、Telegram、Discord、iMessage 等任何你已经在用的聊天软件来跟它对话。
一行命令,装完就能用:
curl-fsSL https://openclaw.ai/install.sh |bash openclaw onboard 它能做什么?
用社区里流传最广的一句描述来说:
“A smart model with eyes and hands at a desk with keyboard and mouse. You message it like a coworker and it does everything a person could do with that Mac mini.”
不是夸张。OpenClaw 默认就具备:
- 持久记忆:它记得你,每次对话都在积累上下文
- 浏览器控制:它能打开网页、填表单、抓数据
- 完整系统权限:读写文件、执行 Shell 命令、运行脚本
- 多模型后端:Claude、GPT-4o、本地 Ollama,随意切换
- Skills 扩展体系:像装插件一样给它"教技能"
最后这一点,就是我们今天要深挖的核心。
Skills 是什么?
Skills 是 OpenClaw 的灵魂扩展机制。本质上是一个文件夹,里面有一个 SKILL.md,用 YAML frontmatter + Markdown 写清楚"这个技能是干嘛的、怎么用",OpenClaw 读完之后就会在系统提示词里注入这个能力。
~/.openclaw/skills/ my-rag-skill/ SKILL.md ← 技能描述 + 调用说明 search.py ← 实际执行的检索脚本 requirements.txt 社区有个公开的技能市场 ClawHub,装现成技能就像 pip install 一样简单。但今天我们要自己写一个——一个让 OpenClaw 能在你企业知识库里检索答案的 RAG Skill。
二、为什么是 RAG?企业知识库的真实痛点
在进入代码之前,我们必须先把问题说清楚。
大模型不知道你的内部知识
不管是 Claude、GPT-4o 还是 Qwen,它们的训练数据都是公开的互联网内容,截止日期固定。你公司的:
- 内部产品手册、操作规范
- 历史工单、客服记录
- 合规文件、法律条款
- 技术文档、API 说明
…这些,模型一无所知。如果你直接问它,它会结合通用知识"编"一个听起来合理但完全不准确的答案。这就是大名鼎鼎的**幻觉(Hallucination)**问题。
为什么不微调?
微调是一个选项,但对企业知识库场景来说代价太高:
| 维度 | 微调 | RAG |
|---|---|---|
| 知识更新 | 重新训练,周期以天计 | 更新向量库,分钟级生效 |
| 成本 | GPU 算力 + 人力标注 | 只需向量化成本,几乎可忽略 |
| 可溯源性 | 模型黑盒,无法知道答案从哪来 | 每个答案都能追溯到原始文档片段 |
| 数据安全 | 数据进入训练过程 | 数据只存在本地向量库,不出内网 |
RAG(Retrieval-Augmented Generation) 的核心思想是:不要试图让模型"记住"知识,而是在回答问题时实时去检索知识,再基于检索结果来生成答案。
流程如下:
你在 Telegram 问 OpenClaw: "新员工入职流程第三步是什么?" │ ▼ OpenClaw 触发 RAG Skill │ ▼ 把问题向量化 → 去本地 Qdrant 向量库检索 │ ▼ 找到最相关的 3 段文档片段 │ ▼ 把原始问题 + 文档片段一起发给 Claude │ ▼ Claude 基于真实文档生成答案 │ ▼ OpenClaw 把答案 + 来源引用发回你的 Telegram 这就是全链路。接下来我们来一步步实现它。
三、技术选型
| 组件 | 选型 | 理由 |
|---|---|---|
| AI 助手 | OpenClaw | 本文主角,开源、本地部署、Skills 扩展 |
| 向量数据库 | Qdrant | Rust 编写,性能卓越,Docker 一键启动 |
| Embedding 模型 | BAAI/bge-large-zh-v1.5 | 中文语义最强开源模型,C-MTEB 长期领先 |
| Reranker | BAAI/bge-reranker-large | Cross-Encoder 精排,提升检索精度 |
| 运行环境 | Python 3.11 + uv | 快速依赖管理,OpenClaw Skills 推荐 |
四、第一步:搭建向量知识库
4.1 启动 Qdrant
docker run -d\--name qdrant \-p6333:6333 \-v$(pwd)/qdrant_data:/qdrant/storage \ qdrant/qdrant 启动后访问 http://localhost:6333/dashboard,能看到 Web UI 就代表成功。
4.2 安装依赖
pip install qdrant-client sentence-transformers \ langchain-text-splitters pymupdf \ python-docx rich --break-system-packages 4.3 文档入库脚本
这个脚本负责读取你的文档、分割成合适的片段、向量化后存入 Qdrant。
# ingest.py —— 知识库构建脚本import uuid import sys from pathlib import Path from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct from sentence_transformers import SentenceTransformer from langchain_text_splitters import RecursiveCharacterTextSplitter import fitz # PyMuPDFfrom rich.console import Console from rich.progress import track console = Console() COLLECTION ="enterprise_kb" EMBED_MODEL ="BAAI/bge-large-zh-v1.5" VECTOR_SIZE =1024 CHUNK_SIZE =512 CHUNK_OVERLAP =64defload_pdf(path:str)->str: doc = fitz.open(path)return"\n".join(page.get_text()for page in doc)defload_text(path:str)->str:return Path(path).read_text(encoding="utf-8")defmain(docs_dir:str): client = QdrantClient(url="http://localhost:6333") embedder = SentenceTransformer(EMBED_MODEL) splitter = RecursiveCharacterTextSplitter( chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP, separators=["\n\n","\n","。","!","?"," "],)# 创建向量集合(如果不存在) existing ={c.name for c in client.get_collections().collections}if COLLECTION notin existing: client.create_collection( COLLECTION, vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE),) console.print(f"[green]✓ 创建集合:{COLLECTION}[/green]")# 扫描文档目录 supported ={".pdf",".txt",".md"} files =[f for f in Path(docs_dir).rglob("*")if f.suffix.lower()in supported] console.print(f"[blue]发现 {len(files)} 个文档文件[/blue]") total_chunks =0forfilein track(files, description="处理文档中..."):try: raw = load_pdf(str(file))iffile.suffix ==".pdf"else load_text(str(file)) chunks = splitter.split_text(raw) points =[]for i, chunk inenumerate(chunks):# bge 模型建议加指令前缀 vec = embedder.encode(f"为这个句子生成表示以用于检索相关文章:{chunk}", normalize_embeddings=True).tolist() points.append(PointStruct(id=str(uuid.uuid4()), vector=vec, payload={"text": chunk,"source":file.name,"chunk_id": i},)) client.upsert(COLLECTION, points=points) total_chunks +=len(points)except Exception as e: console.print(f"[yellow]⚠ 跳过 {file.name}: {e}[/yellow]") console.print(f"\n[bold green]✓ 入库完成!共处理 {len(files)} 个文件,{total_chunks} 个片段[/bold green]")if __name__ =="__main__": docs_dir = sys.argv[1]iflen(sys.argv)>1else"./docs" main(docs_dir)把你的文档放到 ./docs/ 目录下,然后运行:
python ingest.py ./docs 几分钟后,你的文档就全进向量库了。
五、第二步:编写 RAG 检索脚本
这是核心检索逻辑,负责接收问题、返回最相关的文档片段:
# search.py —— RAG 检索脚本(供 OpenClaw Skill 调用)import sys import json from qdrant_client import QdrantClient from sentence_transformers import SentenceTransformer, CrossEncoder COLLECTION ="enterprise_kb" EMBED_MODEL ="BAAI/bge-large-zh-v1.5" RERANK_MODEL ="BAAI/bge-reranker-large" client = QdrantClient(url="http://localhost:6333") embedder = SentenceTransformer(EMBED_MODEL) reranker = CrossEncoder(RERANK_MODEL)defsearch(query:str, top_k_recall:int=8, top_k_final:int=3)->list[dict]:"""两阶段检索:向量召回 → Rerank 精排"""# 阶段一:向量检索(召回候选集) query_vec = embedder.encode(f"为这个句子生成表示以用于检索相关文章:{query}", normalize_embeddings=True).tolist() results = client.search( collection_name=COLLECTION, query_vector=query_vec, limit=top_k_recall, score_threshold=0.4,# 过滤掉明显不相关的结果)ifnot results:return[]# 阶段二:Rerank 精排(Cross-Encoder,准确率更高) docs =[r.payload["text"]for r in results] sources =[r.payload.get("source","未知")for r in results] pairs =[(query, doc)for doc in docs] scores = reranker.predict(pairs) ranked =sorted(zip(scores, docs, sources), key=lambda x: x[0], reverse=True)return[{"text": doc,"source": src,"score":round(float(score),4)}for score, doc, src in ranked[:top_k_final]]defformat_output(query:str, docs:list[dict])->str:"""格式化为易读的文本,供 OpenClaw 拼入 Prompt"""ifnot docs:returnf"在知识库中未找到与「{query}」相关的内容。" parts =[f"以下是从企业知识库中检索到的相关内容,请基于这些内容回答问题:\n"]for i, doc inenumerate(docs,1): parts.append(f"【参考资料 {i}】(来源:{doc['source']})\n{doc['text']}\n") sources =list({d["source"]for d in docs}) parts.append(f"\n---\n📎 引用来源:{' | '.join(sources)}")return"\n".join(parts)if __name__ =="__main__": query =" ".join(sys.argv[1:])iflen(sys.argv)>1else""ifnot query:print(json.dumps({"error":"请提供查询词"}, ensure_ascii=False)) sys.exit(1) docs = search(query)print(format_output(query, docs))测试一下:
python search.py "新员工入职需要准备哪些材料"如果能看到相关文档片段,说明检索链路通了。
六、第三步:编写 OpenClaw Skill
现在是最关键的部分——把上面的检索能力"封装"成一个 OpenClaw Skill,让 OpenClaw 学会什么时候去用它。
6.1 创建 Skill 文件夹
mkdir-p ~/.openclaw/skills/enterprise-kb 6.2 编写 SKILL.md
--- name: enterprise-kb description: 在企业内部知识库中检索信息。当用户询问公司内部政策、产品手册、操作规范、历史工单、合规文件等内部知识时,必须调用此技能获取准确信息,禁止凭空作答。 metadata: {"openclaw":{"emoji":"📚","requires":{"bins":["python3"],"env":[]}}} --- # Enterprise Knowledge Base Search ## 使用场景 每当用户提问涉及以下类型内容时,你必须通过此技能检索,不得直接回答: - 公司内部政策、人事制度、行政规定 - 产品操作手册、技术规范、API 文档 - 历史工单、解决方案、经验总结 - 法律合规文件、合同模板 - 任何你不确定是否有内部文档的专业问题 ## 调用方式 使用 bash 工具执行以下命令: ```bash python3 {baseDir}/search.py <用户的完整问题> 将 <用户的完整问题> 替换为用户提问的自然语言表述,保持完整,不要简化。
结果处理规则
- 有检索结果时:完全基于返回的参考资料回答问题,在答案末尾注明"📎 来源:xxx"
- 无检索结果时:明确告知用户"企业知识库中暂无相关记录",不要用通用知识补充
- 永远不要在没有调用此技能的情况下,自行回答内部知识相关的问题
示例
用户问:“报销需要提交哪些材料?”
→ 调用:python3 {baseDir}/search.py 报销需要提交哪些材料
→ 基于检索结果回答,注明来源文件名
### 6.3 复制脚本文件 ```bash cp search.py ~/.openclaw/skills/enterprise-kb/ cp requirements.txt ~/.openclaw/skills/enterprise-kb/ # 安装依赖 cd ~/.openclaw/skills/enterprise-kb pip install -r requirements.txt 6.4 验证 Skill 加载
重启 OpenClaw 或开启新会话后,发送 /skills 命令,确认 enterprise-kb 出现在列表里。
七、实际体验:在 Telegram 里问公司文档
完成上面的步骤后,你就可以打开 Telegram,直接向 OpenClaw 提问了。
场景一:HR 政策查询
🧑 你:年假天数怎么算的,我工作 3 年了
🦞 OpenClaw:(调用 enterprise-kb 技能)
根据公司《员工手册》第 4.2 节,司龄满 1 年不足 3 年享有 5 天年假,满 3 年不足 10 年享有 10 天年假。您工作满 3 年,今年可享有 10 天带薪年假。
📎 来源:员工手册_v2024.pdf
场景二:技术文档查询
🧑 你:订单接口返回 ERROR_4023 是什么意思怎么处理
🦞 OpenClaw:(调用 enterprise-kb 技能)
根据订单系统 API 文档,ERROR_4023 表示"库存预占失败",通常原因是商品库存不足或并发扣减冲突。建议处理步骤:1. 检查商品实际库存;2. 重试间隔 500ms 后最多重试 3 次;3. 若持续失败,通知仓储团队检查 SKU 状态。
📎 来源:订单API文档_v3.2.md
场景三:知识库中找不到
🧑 你:竞争对手 XX 公司的产品定价是多少
🦞 OpenClaw:企业知识库中暂无该竞争对手定价的相关记录。如需了解,建议联系市场部门或通过官方渠道获取。
这第三个场景同样重要——OpenClaw 不会用通用知识或猜测来填充答案。这正是企业场景最需要的行为。
八、进阶优化:让检索更聪明
基础版跑起来了,但距离生产级还有几个值得做的优化点。
8.1 查询改写:理解用户真实意图
用户的提问往往不够精确,或者夹带了上下文缩写。可以在 SKILL.md 里增加一条指令:
## 查询优化(在调用搜索前) 如果用户的问题较短或含有缩写,先在心里将其扩展为完整的检索词。 例如:"报销流程" → "员工费用报销申请审批流程及所需材料" 这个"扩展"是由 Claude/GPT 在 Agent 推理阶段完成的,不需要额外代码。
8.2 多轮对话的 Query 改写
当用户在多轮对话中追问时,上下文很重要:
你:年假怎么算?
OpenClaw:司龄 3 年以下 5 天,3 年以上 10 天…
你:那病假呢?
"那病假呢"这个问题如果单独检索,效果会很差。可以在 SKILL.md 里加一条:
## 多轮对话处理 如果当前问题是一个代词性追问(如"那...呢"、"它的...是什么"), 请先根据对话历史补全问题的完整含义,再调用检索。 如上例应改写为"病假天数规定是什么"再进行检索。 8.3 元数据过滤:检索范围精细化
如果你的知识库覆盖多个部门,可以在入库时打上部门标签,检索时只搜特定范围:
# 入库时增加 metadata payload ={"text": chunk,"source":file.name,"department":"HR",# ← 增加部门标签"year":2024,}# 检索时过滤from qdrant_client.models import Filter, FieldCondition, MatchValue results = client.search( collection_name=COLLECTION, query_vector=query_vec, query_filter=Filter( must=[FieldCondition(key="department",match=MatchValue(value="HR"))]), limit=top_k_recall,)8.4 定期增量更新
企业文档是会更新的,你需要一个增量入库机制。最简单的方法是记录文件的最后修改时间:
import hashlib deffile_hash(path:str)->str:return hashlib.md5(Path(path).read_bytes()).hexdigest()# 对比 hash,只处理有变化的文件# 删旧 points(按 source 过滤删除),插新 points或者更简单粗暴:每次重新入库,因为向量化速度够快,1000 页文档也就几分钟。
8.5 设置心跳提醒(Heartbeat)
OpenClaw 支持定时心跳任务,你可以让它每天早上主动提醒你知识库状态:
# 在 openclaw.json 里配置{"heartbeat":{"enabled": true, "cronExpression":"0 9 * * 1-5", "message":"早上好!企业知识库今日状态:运行正常,共索引 XX 篇文档。有什么我能帮你查的吗?"}}九、安全与隐私:这套方案为什么适合企业
这是企业用户最关心的问题,也是这套方案的核心优势。
数据从不离开你的机器。 Qdrant 跑在你本地,文档向量化在本地完成。唯一出去的是:你的问题(Query)和检索到的文档片段,打包成 Prompt 发给 LLM API。
如果连这个也不想出去,可以接入本地模型:
# 用 Ollama 跑本地模型 ollama pull qwen2.5:32b # 在 openclaw.json 里切换{"model":{"provider":"ollama", "name":"qwen2.5:32b"}}这样整个链路 100% 本地,完全离线,企业数据安全级别拉满。
OpenClaw 官方的安全理念也与此一致,项目主页有一句话说得很好:
“Not enterprise. Not hosted. Infrastructure you control. This is what personal AI should feel like.”
十、完整项目结构回顾
enterprise-kb-skill/ │ ├── SKILL.md # OpenClaw Skill 描述(必须) ├── search.py # RAG 检索主脚本 ├── ingest.py # 知识库构建脚本(一次性使用) ├── requirements.txt # Python 依赖 │ └── docs/ # 你的企业文档放这里 ├── 员工手册_v2024.pdf ├── 产品API文档_v3.md ├── 合规指引_2024Q4.docx └── ... 三步完成部署:
# 1. 启动向量数据库docker run -d-p6333:6333 -v ./data:/qdrant/storage qdrant/qdrant # 2. 文档入库 python ingest.py ./docs # 3. 安装 Skill 并重启 OpenClawcp-r enterprise-kb-skill ~/.openclaw/skills/enterprise-kb openclaw restart # 或者开启新会话然后打开 Telegram,开问。
十一、总结与思考
我们做了什么:
- 搞清楚了 OpenClaw 的本质:跑在你电脑上的开源私人 AI 助手,通过 Skills 可以无限扩展
- 用 Qdrant + bge-large-zh + Cross-Encoder Reranker 搭了一套两阶段 RAG 检索链路
- 把检索能力封装成一个 OpenClaw Skill,只需要一个 SKILL.md + 一个 Python 脚本
- 在 Telegram 里实现了"企业文档问答"的完整闭环
这套方案的根本价值是什么?
它把"企业知识库"从一个需要 IT 部门立项的大工程,变成了一个任何开发者下午就能跑起来的个人工具。你可以先给自己用,积累效果后再推给团队,最终形成团队共享的知识助手——而整个过程,你对数据有完全的掌控权。
OpenClaw 还很年轻(2026 年初才出现),Skills 生态正在快速成长。现在入场,还能赶上最有趣的那段时间。
🔗 OpenClaw 官网:https://openclaw.ai
📦 Skills 市场:https://clawhub.ai
📖 官方文档:https://docs.openclaw.ai
💬 社区 Discord:https://discord.com/invite/clawd
如果这篇文章对你有帮助,欢迎点赞收藏。有任何问题欢迎在评论区交流,我会尽量回复。