📚 Termux 无 Root 学习开发系列
阶段一:环境搭建与终端基础 ✅ 已完成
01-05 安装 → 包管理 → 文件管理 → Vim/tmux
阶段二:编程开发环境 ✅ 已完成
06-09 Python → C/C++ → Node.js → Git
阶段三:网络与远程操作 ✅ 已完成
10-12 SSH → 网络工具 → 网络代理
阶段四:Termux:API 与自动化 ✅ 已完成
13-14 硬件调用 → 定时任务
阶段五:proot-distro 完整 Linux ✅ 已完成
15-17 proot-distro → Jupyter → 桌面环境/VNC
阶段六:AI 与 AIGC 实战
✓ 18 调用 LLM API 对话与自动化
✓ 19 llama.cpp 本地模型推理
20 RAG 知识库与本地文档问答(当前篇 · 阶段六完结)
阶段七至九共 8 篇,后续持续更新

20 RAG 知识库与本地文档问答

难度:高级 · 阶段六:AI 与 AIGC 实战 · 约 20 分钟阅读
读完本篇你将能:

大模型很聪明,但它的知识有截止日期,而且不知道你手机里存了什么。RAG——检索增强生成(Retrieval-Augmented Generation)——就是解决这个问题的:先从你的文档里搜出相关片段,再把片段连同问题一起喂给模型,让模型基于你的资料来回答。

本篇从零搭建一个完整的本地 RAG 系统:文档切分、向量嵌入、ChromaDB 存储、相似度检索、最后接入 LLM 生成带引用的回答。全程在 Termux 上运行,所有数据不出手机。可以配合上一篇的 llama.cpp 本地模型,做到完全离线。

📖 本篇目录
01 RAG 工作原理与组件
02 环境安装与依赖准备
03 文档加载与文本分块
04 向量嵌入与 ChromaDB 存储
05 检索增强生成:把知识库接进 LLM
06 实战:个人学习笔记问答

01 RAG 工作原理与组件

RAG 的核心思路很朴素:模型回答问题之前,先去文档库里"翻一下书",找到相关内容再作答。整个流程分两大阶段:索引阶段(把文档灌进向量库,只做一次)和 查询阶段(每次提问都走一遍)。

🔍 RAG 完整工作流
索引阶段(一次性)
① 文档加载
→
② 文本分块
→
③ 向量化嵌入
→
④ 存入向量库
查询阶段(每次提问)
⑤ 用户提问
→
⑥ 向量化提问
→
⑦ 相似度检索
→
⑧ 拼接 prompt
→
⑨ LLM 生成回答

核心组件

一个最小 RAG 系统需要四种角色:

组件 作用 本篇选型
嵌入模型 把文本转成向量(数字数组),相似的文本向量距离近 bge-small-zh-v1.5(中文、轻量、本地)
向量数据库 存储向量,支持快速相似度搜索 ChromaDB v1.5.9(纯 Python、本地持久化)
文本分块器 把长文档切成合适大小的片段 RecursiveCharacterTextSplitter
LLM 生成器 根据检索到的文档和问题生成最终回答 DeepSeek API(在线)/ llama.cpp(离线)
💡 小贴士:为什么不用直接问 LLM
三个原因:1)时效性——模型训练数据有截止日期,不知道最新信息;2)私有性——你的个人笔记、公司文档不可能被模型学到;3)准确性——模型会"幻觉"编造事实,RAG 让回答有据可查,还能标注引用来源。

02 环境安装与依赖准备

RAG 需要的 Python 包比较多,在 Termux 原生环境就能装,不需要 proot。主要是三个部分:向量数据库(ChromaDB)、嵌入模型(sentence-transformers)、LLM 客户端(openai SDK)。

Bash · 安装 RAG 依赖
1
2
3
4
5
6
7
8
9
10
11
12
# 1. 升级 pip 并安装基础工具
pip install --upgrade pip setuptools wheel
# 2. 安装核心依赖
pip install \
    chromadb==1.5.9 \
    sentence-transformers \
    langchain langchain-community \
    openai tiktoken \
    pypdf docx2txt
# 3. 验证安装
python -c "import chromadb; print('Chroma', chromadb.__version__)"
python -c "import sentence_transformers; print('ST OK')"
Chroma 1.5.9
ST OK
⚠️ 安装注意事项
1. ChromaDB 依赖较多,首次安装可能需要 5-10 分钟,耐心等待
2. 如果安装报错缺少 numpy / scipy,先单独装:pkg install python-numpy python-scipy
3. sentence-transformers 首次使用会下载嵌入模型(约 100MB),建议在 WiFi 下操作
4. 手机内存紧张时,用 bge-small 系列(~100MB)代替 bge-base(~400MB)

嵌入模型选择

嵌入模型决定了向量质量,直接影响检索准确度。中文场景下 BAAI 的 bge 系列是首选。手机端推荐从轻量的 small 版本开始。

模型 体积 向量维度 推荐场景
bge-small-zh-v1.5 ⭐ ~100 MB 512 维 手机端首选,速度快
bge-base-zh-v1.5 ~400 MB 768 维 质量更高,内存充足时用
bge-large-zh-v1.5 ~1.3 GB 1024 维 旗舰机且追求精度

03 文档加载与文本分块

RAG 的第一步是把文档吃进来。长文档不能直接嵌入——上下文长度有限制,而且大块文本向量不精准。必须切成一段段的"小块"(chunk),每段单独向量化。

加载文档

最简单的是纯文本(.txt)和 Markdown(.md),直接读文件内容即可。如果有 PDF 或 Word 文档,可以用 pypdf 和 docx2txt 解析。

document_loader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pathlib import Path
from langchain_text_splitters import RecursiveCharacterTextSplitter
def load_documents(folder_path):
    """加载指定目录下所有 .md 和 .txt 文件"""
    docs = []
    for file in Path(folder_path).glob("*.md"):
        text = file.read_text(encoding="utf-8")
        docs.append({"text": text, "source": file.name})
    for file in Path(folder_path).glob("*.txt"):
        text = file.read_text(encoding="utf-8")
        docs.append({"text": text, "source": file.name})
    return docs
def split_documents(docs, chunk_size=500, chunk_overlap=50):
    """把文档切成固定大小的块,块之间有重叠避免断句"""
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
续 document_loader.py
19
20
21
22
23
24
25
26
27
28
        separators=["\n\n", "\n", "。", "!", "?", " ", ""]
    )
    
    chunks = []
    for doc in docs:
        split_texts = splitter.split_text(doc["text"])
        for i, chunk in enumerate(split_texts):
            chunks.append({
                "id": f"{doc['source']}_{i}",
                "text": chunk,
                "source": doc["source"]
            })
    return chunks

分块策略说明

分块是 RAG 里最影响效果的环节之一。块太大,单块内信息杂,检索不准;块太小,上下文不完整,模型看不懂。chunk_overlap(块间重叠)是为了避免一句话被从中间切断,导致语义断裂。

💡 小贴士:中文分块的分隔符
默认的 RecursiveCharacterTextSplitter 分隔符是英文的句号和换行,中文要在 separators 里加上 "。"、"!"、"?" 等中文标点,确保尽量在完整句子处切分。

04 向量嵌入与 ChromaDB 存储

文档切好后,下一步是把每一块文本转成向量(embedding),再存进向量数据库。向量数据库的厉害之处在于——你提问时也转成向量,然后它能在百万级数据里快速找出最相似的几个向量,这就是相似度检索。

创建向量数据库

vector_store.py · 建立向量库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import chromadb
from sentence_transformers import SentenceTransformer
# 加载嵌入模型(首次运行自动下载 ~100MB)
embed_model = SentenceTransformer(
    "BAAI/bge-small-zh-v1.5"
)
# 初始化 ChromaDB,持久化到本地目录
client = chromadb.PersistentClient(
    path="./chroma_db"
)
# 创建或获取 collection(相当于表)
collection = client.get_or_create_collection(
    name="knowledge_base",
    metadata={"hnsw:space": "cosine"}  # 余弦相似度
)
# 批量添加文档到向量库
def add_documents(chunks):
续 vector_store.py
20
21
22
23
24
25
26
27
28
29
30
31
32
    ids = [c["id"] for c in chunks]
    texts = [c["text"] for c in chunks]
    metadatas = [{"source": c["source"]} for c in chunks]
    
    # 生成向量
    embeddings = embed_model.encode(texts, show_progress_bar=True)
    
    # 存入 ChromaDB
    collection.upsert(
        ids=ids,
        embeddings=embeddings.tolist(),
        documents=texts,
        metadatas=metadatas
    )
    print(f"已添加 {len(chunks)} 个文档块")

相似度检索

向量库建好后,查询就很简单了——把问题也转成向量,让 ChromaDB 找出最相似的前 K 个文档块。

检索函数
def search(query, top_k=3):
    query_embedding = embed_model.encode(query).tolist()
    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=top_k
    )
    return [
        {"text": doc, "source": meta["source"], "distance": dist}
        for doc, meta, dist in zip(
            results["documents"][0],
            results["metadatas"][0],
            results["distances"][0]
        )
    ]
💡 小贴士:top_k 选多少
top_k 太小,信息不够;太大,无关内容多,还会超出 LLM 上下文。一般 3-5 块比较合适。配合 500 字符的 chunk_size,大约能给模型 1500-2500 字的参考资料,刚好够回答一个具体问题。

05 检索增强生成:把知识库接进 LLM

检索到相关文档后,最后一步是把文档和问题一起拼进 prompt,让 LLM 基于资料回答。核心就是构造一个好的系统提示词,明确告诉模型:"只能用给你的资料回答,如果资料里没有就说不知道,不要编造。"

rag_qa.py · 完整 RAG 问答
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import os
from openai import OpenAI
from vector_store import search
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com"
)
SYSTEM_PROMPT = """你是一个知识库问答助手。
请根据以下参考资料回答用户的问题。
规则:
1. 只能使用参考资料中的信息回答
2. 如果资料中没有答案,明确说"根据现有资料无法回答"
3. 答案末尾标注信息来源,格式:[来源:文件名]
4. 回答简洁准确,不超过 200 字
参考资料:
{context}
"""
续 rag_qa.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def rag_answer(question):
    # 第一步:检索相关文档
    results = search(question, top_k=3)
    
    # 第二步:拼接上下文
    context_parts = []
    for i, r in enumerate(results, 1):
        context_parts.append(
            f"[资料{i}] 来源:{r['source']}\n{r['text']}"
        )
    context = "\n\n".join(context_parts)
    
    # 第三步:调用 LLM 生成回答
    resp = client.chat.completions.create(
        model="deepseek-v4-flash",
        messages=[
            {"role": "system",
             "content": SYSTEM_PROMPT.format(context=context)},
续 rag_qa.py
33
34
35
36
37
38
39
40
            {"role": "user", "content": question}
        ],
        stream=True
    )
    
    # 流式输出
    for chunk in resp:
        if chunk.choices[0].delta.content:
            yield chunk.choices[0].delta.content

切换到本地模型

上一篇学的 llama.cpp server 在这里派上用场了。只要改两行代码,把 base_url 指到本地,就能完全离线跑 RAG。

切换本地模型只需改 3 行
# 确保 llama-server 正在运行(端口 8080)
client = OpenAI(
    api_key="local",  # 随便填
    base_url="http://localhost:8080/v1"
)
# model 名字也要换成你本地加载的模型名
model="qwen2.5-1.5b",
# 其余代码完全不变!

06 实战:个人学习笔记问答

理论讲完了,现在用一个完整的实战项目把前面的内容串起来:把你的 Termux 学习笔记(Markdown 文件)导入 RAG,做一个能回答"第 15 篇讲了什么"、"怎么用 proot-distro 装 Ubuntu"这类问题的个人知识库。

项目目录结构

Bash · 项目结构
my_knowledge_base/
├── notes/            # 放你的学习笔记 .md 文件
│   ├── 01-Termux安装指南.md
│   ├── 15-proot-distro完整Linux.md
│   └── ... (更多笔记)
├── document_loader.py  # 文档加载与分块
├── vector_store.py    # 向量库操作
├── rag_qa.py         # RAG 问答主程序
├── build_index.py     # 一键建索引脚本
└── chroma_db/        # ChromaDB 数据目录(自动生成)

一键建索引

build_index.py
from document_loader import load_documents, split_documents
from vector_store import add_documents
if __name__ == "__main__":
    docs = load_documents("./notes")
    print(f"加载了 {len(docs)} 个文档")
    chunks = split_documents(docs)
    print(f"切成 {len(chunks)} 个文本块")
    add_documents(chunks)
    print("✅ 索引构建完成!")

交互式问答界面

chat.py · 终端知识库问答
import sys
from rag_qa import rag_answer
print("📚 本地知识库问答(输入 /quit 退出)")
while True:
    q = input("\n你: ")
    if q in ("/quit", "exit"): break
    print("AI: ", end="", flush=True)
    for token in rag_answer(q):
        sys.stdout.write(token)
        sys.stdout.flush()
    print()
💡 进阶优化方向
1. 重排序(Rerank):检索 top_k=10 后,用 bge-reranker 模型精排取前 3,显著提升准确度
2. 混合检索:向量相似度 + 关键词检索(BM25)结合,互补长短
3. 增量更新:记录每个文件的修改时间,只重新处理有变化的文档
4. 多模态 RAG:图片也能检索,下一篇再展开

到这里,阶段六的三篇就全部完成了。从调用云端 API,到本地运行大模型,再到搭建个人知识库,你已经掌握了在手机上做 AI 应用的完整技能树。后续阶段还会深入更多实战场景,但这三篇打下的基础,足够你探索绝大多数 AI 应用的可能性。

✏️ 分级练习
🟢 基础验证 跑通最小 RAG 流程
完成最小 RAG 验证:1)创建 3 个简单的 .txt 文档(内容自定,比如 Termux 常用命令、Python 基础语法、Linux 文件操作);2)用 ChromaDB + bge-small-zh-v1.5 建立向量索引;3)对每个文档提一个问题,验证检索结果是否命中正确文档;4)调用 DeepSeek API 生成最终回答。要求能看到 RAG 的完整链路工作正常。
💡 参考解法:先装 chromadb 和 sentence-transformers → 手动构造 3 段文本 → embed_model.encode() 生成向量 → collection.add() 存入 → collection.query() 检索 → 拼接 prompt 调用 LLM。
🟡 组合应用 个人学习笔记知识库
把你自己的学习笔记(至少 5 篇 Markdown)导入 RAG 系统,构建一个可以回答笔记内容的知识库问答工具。要求:1)实现递归目录扫描,自动加载 notes/ 下所有 .md 文件;2)使用 RecursiveCharacterTextSplitter 进行中文友好的分块(chunk_size=500, overlap=50);3)回答问题时标注信息来源文件名;4)提供命令行交互界面,输入问题输出回答。验证:提 3 个只有你的笔记里才有的特定问题,检查回答是否准确且引用了正确的来源。
💡 参考解法:用 pathlib.Path().rglob("*.md") 递归扫描 → 读取文件内容 → 分块时 separators 加入中文标点 → 向量化存入 ChromaDB → 检索 top_k=3 → 拼接 system prompt → 流式输出。
🔴 开放挑战 全离线 RAG 系统
构建一个完全离线的 RAG 系统:嵌入模型用本地 sentence-transformers,LLM 用上一篇的 llama.cpp 本地服务(Qwen2.5-1.5B 或 3B)。挑战目标:1)断网状态下完整运行(关闭 WiFi 验证);2)回答速度可接受(生成速度 ≥ 3 token/s);3)回答质量对比在线 API 版本,分析差异在哪里。写一份 300 字以内的性能/质量对比报告。思考:本地模型在 RAG 场景下最容易出什么问题?有哪些改进思路?
提示:确保 llama-server 启动并加载了模型,rag_qa.py 中把 base_url 改成 http://localhost:8080/v1,api_key 随便填。用相同的 5 个问题分别测试在线和离线版本,从准确度、速度、幻觉率三个维度对比。
🏷️ 知识回顾
RAG 检索增强生成 ChromaDB v1.5.9 向量数据库 bge-small-zh sentence-transformers 文本分块 chunk_size 余弦相似度 top_k 检索 引用来源标注 全离线 RAG
📬 阶段六完结
阶段六「AI 与 AIGC 实战」三篇已全部更新完毕:LLM API 调用 → llama.cpp 本地推理 → RAG 知识库问答,构成了从云端到本地、从对话到检索的完整技能闭环。下一阶段将进入「多媒体与创意应用」,探索 AI 绘画、语音合成、视频生成等更多有趣的玩法,敬请期待。
关注系列,持续更新中 · 点赞在看支持作者