夜雨聆风学习资料网

ARTICLE · 1154883

让本地大模型读懂公司文档:搭个知识库就够

让本地大模型读懂公司文档:搭个知识库就够
合集回顾:
01 你天天用的 Ollama,底层就是 llama.cpp

02 没显卡,5 分钟让电脑跟你聊大模型 ,llama.cpp初识

03 不用显卡,把大模型变成公司内网 API
04 模型塞不进电脑?一招把它压到 1/4 大小
05模型压完变笨了没?一行命令测量化损失

06模型量化变笨了?一招把质量补回来

07你电脑跑大模型有多快?一行命令测出上限
08 一段对话吃掉多少字?大模型计费真相

09 U盘装不下大模型?一行命令把它切了再拼回

10 让本地大模型开口:一行命令把文字念出来

11 别再瞎编格式:让模型只吐程序能读的结果

公司几百页制度、故障手册,大模型一句答不上——因为它「没读过」。

不用上云、不用训练:本地搭个知识库,按意思找相关段落再回答,数据可不出公司。

📌 本文基于 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
只要网页聊天、不想管脚本
Ollama + 现成知识库插件,或商业助手
要权限/审计/多租户/高可用
商业向量库 / 企业知识库
,别用 rag_min.py 扛
内容固定、可接受微调成本
另议微调——本合集不讲训练

▸ 想动手跑通?从这里往下(命令可抄)

前面已经讲清「是什么 / 用在哪 / 边界 / 何时选」。不打算敲命令的读者到此可以合上。 下面才是两个模型、两个服务和可抄脚本。

三、准备:先备齐两个模型

我们用零额外依赖方案:只要 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.gguf
  • qwen2.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.json    kb = []    for f in os.listdir(DOCS_DIR):        if not f.endswith((".txt", ".md")):            continue        text = 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/completions    data = 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
起一个常驻 embedding 服务,对外提供 /v1/embeddings 接口。不加 --embedding 这个接口会返回 501。
curl .../v1/embeddings -d '{"input":"...","model":"local"}'
拿向量的标准 HTTP 调用;input 可以是字符串或字符串数组(批量)。返回 OpenAI 风格 {"data":[{"embedding":[...],"index":0}]}。
--pooling mean
embedding 对整段取平均;大多数中文嵌入模型用 mean。
--embd-normalize 2
向量归一化方式(默认 2 = 欧氏归一化),脚本里再显式归一化一次最保险。
llama cli -m <模型> -p "..." --single-turn -ngl 0
交互终端里单轮生成可用;脚本化/无 TTY 场景改走 HTTP(本篇 rag 脚本即如此)。
llama serve
 + /v1/chat/completions
本篇推荐的生成入口
:稳定、可脚本化,路径见官方 server README。
切片大小 200–500 字
经验值:太小语义碎、太大会引入无关噪声。
TOP_K = 3
一次喂给模型的资料段数,多了占上下文、少了可能漏。

六、翻车实录(这些坑我都替你踩过了)

  1. 普通 server 调 /v1/embeddings 报 501——必须加 --embedding 再起服务(本机实测:不加就报 This server does not support embeddings. Start it with --embeddings)。记住:b10448 里没有 llama embedding 命令,embedding 只能走 server 的 HTTP 接口。
  2. 拿生成模型当 embedding 模型用——Qwen 不是 embedding 模型(除非用专门的 Qwen3-Embedding 系列),喂给 embedding 服务出来的向量没有"语义",检索全乱。两个模型各司其职。
  3. 切块太大或太小——一块 3000 字,检索命中了但噪声一堆,模型答非所问;一块 20 字,语义太碎,相似度算不准。200–500 字最稳。
  4. 问法和文档写法差太远——RAG 是"意思相近"不是"关键词匹配"。比如文档写"设备离线",你问"机器连不上",可能漏检。库里多写几种同义表述能缓解。
  5. 不加 -ngl 0 被当成有显卡——C 机没 GPU,embedding 模型虽小,但养成写 -ngl 0 的习惯最稳(前面系列也强调过)。
  6. 模型瞎编——资料里没有的答案,模型会补。务必在 prompt 里写"资料没有就如实说不知道",并在前端标注"答案来自哪些资料文件",方便核验。
  7. 向量没归一化导致相似度不可比——默认 --embd-normalize 2,脚本里再显式归一化一次最保险(上面脚本已做)。

七、收尾:你已经会搭本地知识库了

RAG 没那么玄:把文档切块 → 用 llama serve --embedding 把每段变向量存起来 → 提问时按余弦相似度找 top-k → 拼进 chat HTTP 服务(/v1/chat/completions)的 prompt。两个模型、几行脚本,纯 CPU 就能跑通一个数据不出本机的私有知识库,特别适合处理公司内部敏感资料。



如果这篇帮到你,点个赞或再看一下就行——方便你下次翻到,也方便平台把同类内容推给需要的人。

合集配套资料(安装速查、常用命令表、翻车排雷、阅读路径)我整理好了:公众号对话框回复 llama.cpp 就能领。

相关学习资料