ARTICLE · 1154883
让本地大模型读懂公司文档:搭个知识库就够
02 没显卡,5 分钟让电脑跟你聊大模型 ,llama.cpp初识
06模型量化变笨了?一招把质量补回来
09 U盘装不下大模型?一行命令把它切了再拼回
10 让本地大模型开口:一行命令把文字念出来
公司几百页制度、故障手册,大模型一句答不上——因为它「没读过」。
不用上云、不用训练:本地搭个知识库,按意思找相关段落再回答,数据可不出公司。
📌 本文基于 llama.cpp b10448 验证(2026-08-16)。重要更正:b10448 里没有llama embedding 命令——embedding 只能走 llama serve --embedding + /v1/embeddings HTTP。下面命令按真实形态给。
本篇速览
本篇讲什么:本地知识库(RAG)——让大模型「现查」你公司的文档再回答,全程不上云、不训练。 你能用在哪:IT→制度/故障手册自助问答;测试/运维→对着手册查问题;嵌入式/合规→敏感资料不出本机。 它干不了啥:不会让模型「学会」新知识(只是临时喂上下文);检索不准会答偏;资料没有时仍可能瞎编。 管理者带走:文档常更新、必须数据不出域、能接受 80 分答案 → 可上本篇做 POC;要企业级检索/权限/审计 → 买商业知识库或向量平台,别硬用脚本扛生产。 跑通标志:问「年假怎么请」能引用你放进 docs/的制度原文,而不是瞎编公司没有的条款。
一、这是什么:RAG 用大白话讲
RAG(Retrieval-Augmented Generation,检索增强生成)听起来吓人,本质就三步:
你问问题 → 程序从你的文档里挑出"最相关"的几段 → 把这几段和问题一起交给大模型 → 大模型照着资料回答。
它解决的痛点是:大模型没见过你公司的内部文档(员工手册、故障案例、接口说明……),你硬问它就瞎编。RAG 不训练模型,只是"临时把资料塞进对话上下文",让模型现查现答。
类比:你不会背全公司的制度,但桌上有一摞文件。同事来问,你先翻出相关的几页,看着回答——你就是那个"大模型",翻文件就是"检索"。
架构(文字三框,管理者也能看懂):
[你的文档 docs/] → [变向量的小模型服务] → [按意思找最相关几段]↓[聊天大模型] → [照着资料回答]
术语人话:
向量 / embedding:把一段话变成一串数字,让电脑能算「两段话意思近不近」; 余弦相似度:意思越近,分数越高(大约 0~1);本篇实跑相关句约 0.84、不相关约 0.31。
二、应用场景 + 边界 + 何时选 RAG
适合用它:
让本地大模型回答公司内部文档问题(制度、SOP、故障库、API 说明)。 做私有知识库:数据不出本机,不连外网。 给测试/运维做"对着手册查问题"的小助手。
它做不到的(边界,别抱错期待):
❌ 不会让模型"学会"新知识——只是临时喂上下文,受上下文窗口限制。 ❌ 检索靠"意思相近",不是关键词精确匹配——问法和文档写法差太远可能找不到。 ❌ 不是搜索引擎,不能替代数据库。 ❌ 资料里没有的答案,模型仍可能"编"——prompt 里必须要求"没有就说不知道"。 ⚠️ 本文是"最小可运行版"(两服务 + 几行脚本);生产要权限、审计、多用户时换常驻方案。
何时选本篇 / 何时别选(拍板用):
| 用本篇做 POC | |
| 商业向量库 / 企业知识库 | |
▸ 想动手跑通?从这里往下(命令可抄)
前面已经讲清「是什么 / 用在哪 / 边界 / 何时选」。不打算敲命令的读者到此可以合上。 下面才是两个模型、两个服务和可抄脚本。
三、准备:先备齐两个模型
我们用零额外依赖方案:只要 llama serve + Python(numpy),尽量不折腾。
1)embedding 模型(负责变向量)——推荐 bge-small-zh-v1.5 的 GGUF 版(维度 512、q8_0 才 26MB、纯 CPU 飞快)。
2)生成模型(负责说话)——Qwen2.5-1.5B-Instruct 的 Q4_K_M(P1 同款)。
方式一(推荐,跟合集最省事):资料包里两个文件放 ./models/ 即可:
bge-small-zh-v1.5-q8_0.ggufqwen2.5-1.5b-instruct-q4_k_m.gguf
方式二 / 方式三(没领包时):
# 方式二:hf-mirror 自动下(embedding 用 download 子命令)HF_ENDPOINT=https://hf-mirror.com/ llama download -hf CompendiumLabs/bge-small-zh-v1.5-gguf:Q8_0 \-o models/# 方式三(手动兜底):直接 curl 或魔塔下 GGUFcurl -L -o models/bge-small-zh-v1.5-q8_0.gguf \https://hf-mirror.com/CompendiumLabs/bge-small-zh-v1.5-gguf/resolve/main/bge-small-zh-v1.5-q8_0.gguf# 生成模型若还没有:HF_ENDPOINT=https://hf-mirror.com/ llama cli -hf Qwen/Qwen2.5-1.5B-Instruct-GGUF -p "你好"
提示:embedding 模型大多用
--pooling mean(说人话:对整段文字取平均,得到一个向量;你不用懂数学,bge 系列写mean即可)。
把你的文档放进 docs/ 目录(.txt 或 .md 都行),例如 docs/员工手册.txt、docs/故障案例.md。
四、动手:搭一个最小可运行的本地知识库
下面这套脚本(命名为 rag_min.py)就是完整可用的 RAG,只调用 llama serve --embedding(HTTP)和 llama cli 两个命令,纯 CPU 可跑,不装任何数据库。
先起一个常驻 embedding 服务(单独开一个终端,别关):
# 终端 A:起 embedding 服务(默认 127.0.0.1:8080)llama serve --embedding -m models/bge-small-zh-v1.5-q8_0.gguf --pooling mean -ngl 0 --port 8080
{"object": "list","model": "local","data": [{ "index": 0,"embedding": [0.0493, 0.0479, 0.0436, 0.0067, "…(共 512 维)"] }]}注意:如果没加
--embedding直接起普通 server,调/v1/embeddings会吃 501:{"error":{"code":501,"message":"This server does not support embeddings. Start it with \--embeddings`"}}——所以这一步的--embedding` 不能省(翻车实录第 1 条)。
脚本 rag_min.py 的四步逻辑:
第 1 步:把文档切块(按空行切,每段 200–500 中文字)。 第 2 步:对每段调 /v1/embeddings向量化,存进kb.json。第 3 步:提问时把问题也向量化,和库里每段算"余弦相似度",取最相似的 top-3 段。 第 4 步:把 top-3 段 + 问题拼成 prompt,交给 llama cli回答。
完整脚本(复制即用,改一下两个模型路径即可):
#!/usr/bin/env python3# rag_min.py —— 最小可运行本地知识库(纯 CPU,仅依赖 numpy)# 前置:另开终端已运行 llama serve --embedding -m models/bge-small-zh-v1.5-q8_0.gguf --pooling mean -ngl 0 --port 8080import os, json, urllib.request, numpy as npEMB_URL = "http://127.0.0.1:8080/v1/embeddings" # ← embedding 服务地址(终端 A)GEN_URL = "http://127.0.0.1:8081/v1/chat/completions" # ← 聊天服务地址(终端 B 起的 8081)# 生成模型(qwen2.5-1.5b-instruct-q4_k_m.gguf)由终端 B 的 8081 聊天服务加载,这里只调它的 HTTP 接口DOCS_DIR = "docs"KB_FILE = "kb.json"TOP_K = 3def embed(text):# 调 embedding 服务的 HTTP 接口,把一段文字变成向量data = json.dumps({"input": text, "model": "local"}).encode()req = urllib.request.Request(EMB_URL, data=data, headers={"Content-Type": "application/json"})with urllib.request.urlopen(req, timeout=30) as r:resp = json.loads(r.read())return np.array(resp["data"][0]["embedding"], dtype=float)def build_kb():# 切片 + 向量化,存 kb.jsonkb = []for f in os.listdir(DOCS_DIR):if not f.endswith((".txt", ".md")):continuetext = open(os.path.join(DOCS_DIR, f), encoding="utf-8").read()for chunk in [c.strip() for c in text.split("\n\n") if len(c.strip()) > 20]:kb.append({"file": f, "text": chunk, "vec": embed(chunk).tolist()})json.dump(kb, open(KB_FILE, "w", encoding="utf-8"), ensure_ascii=False)print(f"建库完成:{len(kb)} 段")def cosine(a, b):return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))def ask(question):qvec = embed(question)kb = json.load(open(KB_FILE, encoding="utf-8"))ranked = sorted(kb, key=lambda x: cosine(qvec, np.array(x["vec"])), reverse=True)[:TOP_K]ctx = "\n\n".join(f"【资料 {i+1}】(来自 {x['file']})\n{x['text']}"for i, x in enumerate(ranked))prompt = (f"以下是参考资料:\n{ctx}\n\n"f"请根据以上资料回答问题。如果资料里没有答案,就如实说“资料里没有”。\n"f"问题:{question}")# 走 HTTP 调本地聊天服务(见下文 8081),不要用 llama cli 子进程# —— 终端单轮可用官方 --single-turn;脚本/无 TTY 场景优先 llama serve + /v1/chat/completionsdata = json.dumps({"model": "local","messages": [{"role": "user", "content": prompt}]}).encode()req = urllib.request.Request("http://127.0.0.1:8081/v1/chat/completions",data=data, headers={"Content-Type": "application/json"})with urllib.request.urlopen(req, timeout=60) as r:ans = json.loads(r.read())["choices"][0]["message"]["content"]print(ans)if __name__ == "__main__":if not os.path.exists(KB_FILE):build_kb()ask(input("问点啥:"))
跑起来:
# 终端 A:embedding 服务(保持运行,给建库和提问做向量化)llama serve --embedding -m models/bge-small-zh-v1.5-q8_0.gguf --pooling mean -ngl 0 --port 8080# 终端 B:聊天(生成)服务(保持运行,rag_min.py 通过 HTTP 调它来回答)llama serve -m models/qwen2.5-1.5b-instruct-q4_k_m.gguf -ngl 0 --port 8081# 终端 C:首次自动建库,然后输入你的问题python3 rag_min.py
提示:生成模型走的是 HTTP 聊天服务(8081),不是
llama cli子进程——脚本/无 TTY 环境下更稳;官方也提供llama cli -p ... --single-turn做终端单轮,但 RAG 自动化场景优先llama serve+/v1/chat/completions。embedding 模型体积小,纯 CPU 向量化几百段也就几十秒。
🔧 实跑佐证(bge-small-zh-v1.5-q8_0,512 维,本机实跑):我拿"如何申请年假?"去算余弦相似度——和"年假申请的具体流程是什么?"这种语义相关的句子得 0.8397;和"今天天气真好,适合去爬山"这种不相关的句子只有 0.3054。这就是 RAG 能"按意思找资料"的根本原因:相关句向量挨得近,不相关句离得远。
下面是搏哥在 b10448 上实跑的真实输出(embedding 用 bge-small-zh-v1.5-q8_0,生成用 0.5B,知识库是两份虚构公司制度文档):
$ python3 rag_min.py建库完成:2 段模型基于资料回答:工作满 3 年的员工,一年有 10 天年假。
建库完成:2 段 是脚本把两份文档按空行切成 2 段、各自向量化存进 kb.json;紧接着的回答,模型是真的从"年假制度"那段资料里找到"满 3 年增至 10 天"再回你的——不是凭空编的。配合上一段"相关句 0.8397 / 不相关 0.3054"的相似度证据,RAG 的"按意思找资料"就闭环了。
五、参数细节(放到最后讲,用到再回看)
llama serve --embedding -m <模型> --port 8080 | /v1/embeddings 接口。不加 --embedding 这个接口会返回 501。 |
curl .../v1/embeddings -d '{"input":"...","model":"local"}' | input 可以是字符串或字符串数组(批量)。返回 OpenAI 风格 {"data":[{"embedding":[...],"index":0}]}。 |
--pooling mean | mean。 |
--embd-normalize 2 | |
llama cli -m <模型> -p "..." --single-turn -ngl 0 | |
llama serve/v1/chat/completions | 本篇推荐的生成入口 |
TOP_K = 3 |
六、翻车实录(这些坑我都替你踩过了)
普通 server 调 /v1/embeddings报 501——必须加--embedding再起服务(本机实测:不加就报This server does not support embeddings. Start it with --embeddings)。记住:b10448 里没有llama embedding命令,embedding 只能走 server 的 HTTP 接口。拿生成模型当 embedding 模型用——Qwen 不是 embedding 模型(除非用专门的 Qwen3-Embedding 系列),喂给 embedding 服务出来的向量没有"语义",检索全乱。两个模型各司其职。 切块太大或太小——一块 3000 字,检索命中了但噪声一堆,模型答非所问;一块 20 字,语义太碎,相似度算不准。200–500 字最稳。 问法和文档写法差太远——RAG 是"意思相近"不是"关键词匹配"。比如文档写"设备离线",你问"机器连不上",可能漏检。库里多写几种同义表述能缓解。 不加 -ngl 0被当成有显卡——C 机没 GPU,embedding 模型虽小,但养成写-ngl 0的习惯最稳(前面系列也强调过)。模型瞎编——资料里没有的答案,模型会补。务必在 prompt 里写"资料没有就如实说不知道",并在前端标注"答案来自哪些资料文件",方便核验。 向量没归一化导致相似度不可比——默认 --embd-normalize 2,脚本里再显式归一化一次最保险(上面脚本已做)。
七、收尾:你已经会搭本地知识库了
RAG 没那么玄:把文档切块 → 用 llama serve --embedding 把每段变向量存起来 → 提问时按余弦相似度找 top-k → 拼进 chat HTTP 服务(/v1/chat/completions)的 prompt。两个模型、几行脚本,纯 CPU 就能跑通一个数据不出本机的私有知识库,特别适合处理公司内部敏感资料。
如果这篇帮到你,点个赞或再看一下就行——方便你下次翻到,也方便平台把同类内容推给需要的人。
合集配套资料(安装速查、常用命令表、翻车排雷、阅读路径)我整理好了:公众号对话框回复 llama.cpp 就能领。