在使用了那么多大模型之后,心里打起了自己构建一个具备搜索能力的大模型,也就是RAG。

RAG = Retrieval-Augmented Generation,检索增强生成。
一句话:
让大模型在回答前,先去你的知识库里“查资料”,再根据查到的内容回答**。类似开卷考试,而不是让模型凭记忆瞎编。

它主要解决:LLM 知识过期、幻觉、无法访问私有数据、答案不可溯源等问题。RAG 不是微调;RAG 负责“外挂知识”,微调更多负责“改变风格/能力”。

这个RAG主要的功能就是它可以先搜索我的代码库以及参数配置文档,然后将结果反馈给大模型进行整合输出。

一、RAG 的核心流程

1
2
3
4
5
离线索引:
文档 → 解析 → 清洗 → 切块 → Embedding → 存入向量库/索引

在线问答:
用户问题 → 查询改写 → 检索 → 重排 → 拼接 Prompt → LLM 生成 → 答案 + 引用

关键组件:

  • 文档加载:PDF、Word、网页、数据库、Markdown、代码等
  • 切块 Chunking:把长文档切成小段
  • Embedding:把文本转成向量
  • 向量数据库:FAISS、Chroma、Milvus、Qdrant、pgvector 等
  • 检索器:向量检索、关键词检索、混合检索
  • 重排 Reranker:对召回结果精排
  • LLM:根据上下文生成答案
  • Prompt:约束模型“只根据上下文回答”

二、如何开发一个 RAG?

1. 明确需求

先确定:

  • 面向什么场景:客服、法律、医疗、代码、企业知识库?
  • 数据来源是什么:PDF、网页、数据库、API?
  • 是否需要权限控制?
  • 是否要求引用来源?
  • 数据更新频率?
  • 用云端模型还是本地模型?

2. 准备数据

把数据解析成纯文本,并保留元数据:

1
2
3
4
5
6
7
8
{
"text": "内容片段",
"source": "员工手册.pdf",
"page": 12,
"title": "报销制度",
"tenant_id": "公司A",
"permission": "hr"
}

元数据很重要,后面可以做过滤、引用、权限控制。

3. 切块

常见策略:

  • 固定长度:如 500~1000 tokens
  • 递归切分:按段落、标题、句子
  • 语义切分:按语义边界
  • 父子块:检索小块,返回大块
  • 表格、代码单独处理

建议:chunk_size=500~1000,overlap=50~150,具体要实验。

4. 向量化并存入向量库

选择 Embedding 模型:

  • 云端:OpenAI text-embedding-3-small/large
  • 开源:BGE、GTE、m3e、Jina Embeddings
  • 多语言场景要选多语言模型

然后存入:

  • 本地小项目:FAISS、Chroma
  • 生产:Milvus、Qdrant、Weaviate、pgvector

5. 实现检索

最基础的是向量相似度检索:

1
问题向量 → 在向量库找最相似的 top k 个 chunk

但生产级通常用混合检索:

  • 向量检索:擅长语义
  • BM25/关键词检索:擅长专有名词、编号、错误码
  • 混合后融合排序

再加重排:

1
先召回 top 20~100 → Reranker 精排 → 取 top 3~8 给 LLM

6. 拼接 Prompt 并生成

Prompt 示例:

1
2
3
4
5
6
7
8
9
10
你是一个严谨的助手。请只根据下面提供的上下文回答问题。
如果上下文没有答案,就说“我不知道”,不要编造。

上下文:
{context}

问题:
{question}

请给出答案,并标注来源。

7. 评估与优化

构建测试集,至少 50~100 条问答,评估:

  • 检索召回率:该找的文档找到了吗?
  • 答案忠实度:是否基于上下文?
  • 答案相关性:是否回答了问题?
  • 引用准确率
  • 延迟和成本

工具:RAGAS、TruLens、人工评估。


三、最小可用 RAG 代码示例

用 Python + LangChain + FAISS + OpenAI:

1
pip install langchain langchain-openai langchain-community langchain-text-splitters faiss-cpu
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import os
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import FAISS
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

os.environ["OPENAI_API_KEY"] = "sk-..."

# 1. 加载文档
docs = TextLoader("knowledge.txt", encoding="utf-8").load()

# 2. 切块
splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=100
)
chunks = splitter.split_documents(docs)

# 3. 向量化 + 存库
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = FAISS.from_documents(chunks, embeddings)

# 4. 检索器
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

# 5. Prompt
prompt = ChatPromptTemplate.from_template("""
你是一个严谨的助手。只根据下面上下文回答;找不到答案就说“我不知道”。
上下文:
{context}
问题:
{question}
""")

def format_docs(docs):
return "\n\n".join(
f"[{i+1}] {d.page_content}" for i, d in enumerate(docs)
)

# 6. 组装 RAG 链
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)

# 7. 提问
print(rag_chain.invoke("公司的报销流程是什么?"))

这就是一个最小 RAG。它已经能跑通“文档 → 检索 → 生成”。


四、生产级 RAG 还要加什么?

最小 RAG 很容易做,难的是效果稳定。生产级通常需要:

  1. 混合检索:向量 + BM25
  2. 重排 Reranker:BGE-reranker、Cohere Rerank
  3. 查询改写:多查询、HyDE、结合历史改写
  4. 多路召回:不同索引、不同粒度
  5. 父子块 / 句子窗口:检索小块,给大上下文
  6. 元数据过滤:时间、产品、租户、权限
  7. 上下文压缩:去掉无关内容,节省 token
  8. 引用来源:输出 [1][2] 并链接原文
  9. 权限控制:检索前过滤,防止越权泄露
  10. 增量更新:文档变更后重新索引、删除旧向量
  11. 缓存:Embedding 缓存、检索缓存、答案缓存
  12. 监控:延迟、成本、召回率、用户反馈
  13. 评估体系:RAGAS、人工标注、A/B 测试

五、技术选型参考

模块 常见选择
框架 LangChain、LlamaIndex、Haystack
向量库 FAISS、Chroma、Milvus、Qdrant、pgvector
Embedding OpenAI、BGE、GTE、m3e、Jina
LLM GPT、Claude、Qwen、DeepSeek、Llama
重排 BGE-reranker、Cohere Rerank
评估 RAGAS、TruLens
部署 FastAPI、Flask、Docker、K8s

六、常见坑

  • 切块太大:噪声多,检索不准
  • 切块太小:语义不完整
  • 只用向量检索:专有名词、编号容易漏
  • 不做重排:top k 里噪声太多
  • Prompt 不约束:模型开始编造
  • 不更新索引:答案过期
  • 不做权限过滤:数据泄露
  • 没有评估:不知道改好还是改坏

总结

RAG 的本质是:搜索系统 + Prompt 工程 + LLM 生成。
开发顺序建议:

  1. 先做最小 MVP:加载文档 → 切块 → 向量化 → 检索 → 生成
  2. 构建测试集,评估检索和答案质量
  3. 再加混合检索、重排、查询改写、权限、引用
  4. 最后做部署、监控、增量更新

如果你告诉我你的具体场景,比如“用本地模型做企业 PDF 知识库”或“用 OpenAI 做客服机器人”,我可以给你一套具体的项目结构和技术方案。

我的最小的RAG的主要功能就是将业务代码和参数配置文档作为资料库,让大模型在回答问题之前现在资料库中进行搜索,然后再进行回答。

我的RAG采用的技术选型分别是:

📊 RAG 技术选型一览表

模块 最终选型 状态 选型理由 / 备注
开发框架 LlamaIndex ✅ 已落地 相较于 LangChain 和 Haystack,LlamaIndex 专为 RAG 设计,API 更精简,检索性能好,内存占用相对低。
大语言模型 qwen3:1.7b ✅ 已落地 经历了 qwen3:4b(太慢、思考啰嗦)的折磨后,换为 1.7B 版本。无思考输出,纯 CPU 推理速度快,响应时间从几分钟降至 50 秒左右。
嵌入模型 BGE-M3 ✅ 已落地 通过 Ollama 调用。中文语义理解强,支持稠密+稀疏检索,对技术文档中的专有名词和错误码召回效果好。
向量数据库 ChromaDB ✅ 已落地 作为嵌入式数据库,零配置,安装简单,与 LlamaIndex 集成度高。当前 2551 个切片的规模完全够用,后续可平滑迁移至 Qdrant。
文档加载器 SimpleDirectoryReader ✅ 已落地 统一加载 Word、TXT、C/C++ 代码文件,自动附加 file_name、file_path 等元数据。
文本切片器 SentenceSplitter ✅ 已落地 用于 Word 和 TXT 文件,按句子边界切分,chunk_size=512,chunk_overlap=64,避免语义截断。
代码切片器 CodeSplitter ✅ 已落地 针对 C/C++ 代码,使用 AST 语法树按函数/类切分。关键踩坑:需传入 tree_sitter_cpp 预构建的 parser 对象,绕开网络下载超时问题。
重排模型 BGE-reranker-v2-m3 ⏸️ 规划中 能进一步提升检索精度,但纯 CPU 下运行会增加延迟,暂未启用。
评估工具 RAGAS ⏸️ 规划中 用于构建测试集,量化评估忠实度、答案相关性等指标,待流程稳定后再引入。
部署方式 Python 本地脚本 ✅ 当前阶段 目前以 RAG_One.py 单文件脚本运行。后续可用 FastAPI 封装 API,搭配 Docker Compose 实现一键部署。

💻 硬件环境配置(附加信息)

环境 配置 说明
主力开发机 Intel i7-1255U / 32GB DDR4 / Intel Iris Xe 核显 纯 CPU 推理,无独立显卡加速。16GB 内存时极易触发瓶颈,32GB 下运行较为稳定。

🖥️ 阶段一:环境搭建与数据准备(2-3天)

1. 安装 Ollama for Windows

Ollama 官方提供了原生 Windows 应用,安装非常简单:

  1. 下载:访问 Ollama 官网 https://ollama.com/download,点击下载 Windows 版安装程序 OllamaSetup.exe。

  2. 安装:双击运行 OllamaSetup.exe,一路“下一步”即可。它默认安装在你的用户目录下,不需要管理员权限。

  3. 验证:安装完成后,Ollama 会自动在后台运行,右下角系统托盘会出现一个羊驼图标。打开 PowerShell 或 CMD,输入以下命令验证:

    1
    ollama -v

    如果显示版本号,说明安装成功。

⚠️ 重要提示:更改模型存储位置
模型文件可能高达数十 GB。如果你的 C 盘空间紧张,建议在安装前或安装后设置环境变量 OLLAMA_MODELS 来更改模型存储路径。

  1. 打开“系统属性” -> “环境变量”。
  2. 在“用户变量”中,新建一个变量,名为 OLLAMA_MODELS,值设置为你的目标路径,例如 D:\OllamaModels。
  3. 保存后,重启 Ollama(在系统托盘右键退出,再从开始菜单重新打开)。

2. 拉取模型

在 PowerShell 中执行以下命令,拉取 Qwen3 4B 和嵌入模型 BGE-M3:

1
2
ollama pull qwen3:4b
ollama pull bge-m3

3. 创建 Python 虚拟环境并安装依赖

在 PowerShell 中,进入你的项目目录,然后执行:

1
2
3
4
5
6
7
8
# 创建虚拟环境
python -m venv rag_env

# 激活虚拟环境(Windows 下激活命令不同)
.\rag_env\Scripts\Activate.ps1

# 安装依赖
pip install llama-index llama-index-llms-ollama llama-index-embeddings-ollama llama-index-vector-stores-chroma chromadb sentence-transformers ragas fastapi uvicorn

4. 准备数据与解析

将你的 Word、Excel、代码文件放入 ./data 目录。使用 SimpleDirectoryReader 统一加载:

1
2
3
from llama_index.core import SimpleDirectoryReader

documents = SimpleDirectoryReader("./data").load_data()

切片策略(与平台无关,但很重要):

  • Word:按标题层级切分,每个标题下的段落作为一个 chunk。
  • Excel:按 Sheet 切分,并确保每个 chunk 都包含表头信息。
  • 代码:按函数/类切分,保留文件路径和语言标记。

🔗 阶段二:核心 RAG 链路开发(3-5天)

5. 配置本地模型与嵌入

1
2
3
4
5
6
from llama_index.core import Settings
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.ollama import OllamaEmbedding

Settings.llm = Ollama(model="qwen3:4b-instruct-2507", request_timeout=120.0)
Settings.embed_model = OllamaEmbedding(model_name="bge-m3")

6. 构建向量索引并持久化(ChromaDB)

在 Windows 上,ChromaDB 以 Python 库的形式在本地运行,数据会持久化到指定目录。

1
2
3
4
5
6
7
8
9
10
11
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.vector_stores.chroma import ChromaVectorStore
import chromadb

chroma_client = chromadb.PersistentClient(path="./chroma_db")
chroma_collection = chroma_client.get_or_create_collection("tech_support")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)

index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
index.storage_context.persist(persist_dir="./storage")

7. 创建检索器并实现问答

1
2
3
4
5
6
7
8
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core.query_engine import RetrieverQueryEngine

retriever = VectorIndexRetriever(index=index, similarity_top_k=5)
query_engine = RetrieverQueryEngine(retriever=retriever)

response = query_engine.query("设备报错 E-204 怎么处理?")
print(response)

此时已经跑通了最小可用的 RAG 系统。

在这里附一个我的完整的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter ,CodeSplitter
import random
from llama_index.readers.file import DocxReader
from llama_index.core import Settings, VectorStoreIndex, StorageContext, PromptTemplate, load_index_from_storage
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.vector_stores.chroma import ChromaVectorStore
import chromadb
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
import os

all_nodes = []

# ---------- 1. C/C++ 代码 ----------
code_documents = SimpleDirectoryReader(
input_dir="./test_data",
required_exts=[".c", ".cpp", ".h"],
recursive=True,
encoding="gbk"
).load_data()
print(f"共加载 {len(code_documents)} 个代码文件")

cpp_splitter = CodeSplitter(
language="cpp",
chunk_lines=40,
chunk_lines_overlap=15,
max_chars=1500
)
code_nodes = cpp_splitter.get_nodes_from_documents(code_documents)
all_nodes.extend(code_nodes)
print(f"已经加载 {len(code_nodes)} 个代码切片")

# ---------- 2. Word 文档 ----------
docx_reader = DocxReader()

word_documents = SimpleDirectoryReader(
input_dir="./test_data",
required_exts=[".docx"],
recursive=True,
file_extractor={".docx": docx_reader} # 显式指定解析器
).load_data()
print(f"共加载 {len(word_documents)} 个 Word 文件")

word_splitter = SentenceSplitter(chunk_size=512, chunk_overlap=64)
word_nodes = word_splitter.get_nodes_from_documents(word_documents)
all_nodes.extend(word_nodes)
print(f"已经加载 {len(word_nodes)} 个句子切片")

# ---------- 3. 纯文本文件 ----------
# 先尝试 UTF-8,失败再回退到 GBK
try:
txt_documents = SimpleDirectoryReader(
input_dir="./test_data",
required_exts=[".txt"],
recursive=True,
encoding="utf-8"
).load_data()
except UnicodeDecodeError:
print("UTF-8 解码失败,改用 GBK 重试...")
txt_documents = SimpleDirectoryReader(
input_dir="./test_data",
required_exts=[".txt"],
recursive=True,
encoding="gbk"
).load_data()
print(f"共加载 {len(txt_documents)} 个 txt 文件")

# txt 和 Word 一样,用 SentenceSplitter 就行
txt_splitter = SentenceSplitter(chunk_size=512, chunk_overlap=64)
txt_nodes = txt_splitter.get_nodes_from_documents(txt_documents)
all_nodes.extend(txt_nodes)

# ---------- 汇总 ----------
print(f"共生成 {len(all_nodes)} 个知识切片")




# 随机抽查 3 个切片
for i in random.sample(range(len(all_nodes)), 3):
node = all_nodes[i]
print(f"===== 切片 {i} =====")
print(f"来源: {node.metadata.get('file_name', '未知')}")
print(f"内容: {node.text[:500]}...") # 只打印前500字符
print()

# 将切片内容写入文件
with open("slices_preview.txt", "w", encoding="utf-8") as f:
for i, node in enumerate(all_nodes):
f.write(f"===== 切片 {i} =====\n")
f.write(f"来源: {node.metadata.get('file_name', '未知')}\n")
f.write(f"内容:\n{node.text}\n\n")


# ---------- 1. 配套本地模型与嵌入 ----------
# 时间调大
Settings.llm = Ollama(
model="qwen3:1.7b",
request_timeout=900.0,
additional_kwargs={"think": False} # 在这里设置
)
Settings.embed_model = OllamaEmbedding(model_name="bge-m3")

# ---------- 2. 构建向量索引并持久化(ChromaDB) ----------
# 初始化 Chroma
chroma_client = chromadb.PersistentClient(path="./chroma_db")
chroma_collection = chroma_client.get_or_create_collection("tech_support_v1")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)

# 判断是否已有索引
if os.path.exists("./storage") and chroma_collection.count() > 0:
print("检测到已有向量库,直接加载索引,跳过向量化...")
storage_context = StorageContext.from_defaults(
vector_store=vector_store,
persist_dir="./storage"
)
index = load_index_from_storage(storage_context)
else:
print("未找到现有索引,开始向量化...")
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex(
nodes=all_nodes,
storage_context=storage_context,
show_progress=True
)
index.storage_context.persist(persist_dir="./storage")
print("向量化完成!")

# ---------- 3. 定义防幻觉 Prompt 模板 ----------
qa_prompt = PromptTemplate(
"你是一个技术支持助手。请严格根据以下上下文回答问题。\n"
#"你必须直接给出最终答案,禁止输出任何思考过程、解释或内心独白。\n"
"如果上下文中没有答案,请明确说“根据现有资料无法回答”,不要编造。\n"
"请在回答末尾标注来源文件名。\n"
"---------------------\n"
"上下文:\n{context_str}\n"
"---------------------\n"
"问题:{query_str}\n"
"答案:"
)

# ---------- 4. 创建检索器并实现问答 ----------
retriever = VectorIndexRetriever(index=index, similarity_top_k=3) # 先降到3
query_engine = RetrieverQueryEngine.from_args(
retriever=retriever,
text_qa_template=qa_prompt,
response_mode="compact"
)

# ---------- 5. 测试提问 ----------
print("\n=== RAG 系统已就绪,输入 exit 退出 ===")
while True:
question = input("\n请输入你的问题: ").strip()
if question.lower() in ("exit", "quit", "q"):
print("再见!")
break
if not question:
continue

try:
response = query_engine.query(question)
print("\n=== 回答 ===")
print(response)
print("\n=== 引用来源 ===")
for node in response.source_nodes:
print(f"- {node.metadata.get('file_name', '未知')} (相关度: {node.score:.4f})")
except Exception as e:
print(f"\n[出错] {e}")

踩坑记录:

F:\流离火\AGI\RAG开发>python RAG_One.py
共加载 5 个代码文件
Could not get parser for language cpp. Check https://github.com/Goldziher/tree-sitter-language-pack?tab=readme-ov-file#available-languages for a list of valid languages.
Traceback (most recent call last):
File “F:\流离火\AGI\RAG开发\RAG_One.py”, line 16, in
cpp_splitter = CodeSplitter(
^^^^^^^^^^^^^
File “F:\Python\Lib\site-packages\llama_index\core\node_parser\text\code.py”, line 116, in init
parser = tree_sitter_language_pack.get_parser(language) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “F:\Python\Lib\site-packages\tree_sitter_language_pack\api.py”, line 198, in get_parser
return cast(“Parser”, _rust.get_parser(name=name))
^^^^^^^^^^^^^^^^^^^^^^^^^^^
tree_sitter_language_pack.DownloadError: Download error: Failed to download https://github.com/xberg-io/tree-sitter-language-pack/releases/download/v1.20.0/parsers-windows-x86_64.tar.zst: io: 你的主机中的软件中止了一个已建立的连接。 (os error 10053)

不知道是谁中止的,nnd,大概率是数据卫士,看了一眼路径,然后问了ds,就直接把包解压放在了F:\Python\Lib\site-packages\tree_sitter_language_pack目录下,再跑一次,竟然可以了。

📝 RAG 开发“数据准备与切片”阶段问题总结

以下是我们在这个阶段遇到的坑、原因和解决方案的完整总结,你可以直接存档:

  • 代码文件:5 个,生成 285 个切片(使用 CodeSplitter)
    • Word 文档:2 个,生成 2264 个切片(使用 SentenceSplitter + DocxReader)
    • txt 文件:1 个(使用 SentenceSplitter)
    • 总计:2551 个知识切片

问题 1:Ollama 拉取模型时报错 file does not exist / 网络超时

现象:

  • ollama pull qwen3:4b-instruct-2507 报错 Error: pull model manifest: file does not exist

  • 后续下载 BGE 模型时报错 wsarecv: An established connection was aborted by the software in your host machine.
    原因:

  1. 模型名称错误:qwen3:4b-instruct-2507 是 Hugging Face/ModelScope 的命名,Ollama 官方库对应的 tag 是 qwen3:4b。

  2. 网络问题:国内直连 Ollama 官方源不稳定,导致下载中断。

  3. 残留文件:下载中断后,本地残留了不完整的 manifest 文件。

  4. 解决方案:

  5. 修正模型名称:使用 ollama pull qwen3:4b 和 ollama pull bge-m3。

  6. 使用国内镜像(如果官方源依然超时):

    1
    2
    ollama pull modelscope.cn/Qwen/Qwen3-4B-Instruct-GGUF
    ollama pull modelscope.cn/BAAI/bge-m3
  7. 清理残留:进入 C:\Users\你的用户名\.ollama\models\manifests,删除对应的 qwen3 文件夹。

  8. 配置环境变量:设置 OLLAMA_MODELS 到非 C 盘路径(如 H:\ollama),并彻底退出并重启 Ollama 使其生效。

问题 2:CodeSplitter 报错 ModuleNotFoundError: No module named 'tree_sitter_language_pack'

现象:

1
ImportError: Please install tree_sitter_language_pack to use CodeSplitter.

原因:
CodeSplitter 依赖 tree-sitter-language-pack 来解析代码结构,但环境中未安装该库。

解决方案:

1
pip install tree-sitter-language-pack

若安装后报版本不兼容,可降级安装:

1
pip install "tree-sitter-language-pack<1.0"

问题 3:CodeSplitter 报错 Could not get parser for language c++

现象:

1
DownloadError: Download error: Language 'c++' is not in the download manifest

原因:
tree-sitter-language-pack 中 C++ 的标识符是 cpp,而不是 c++。

解决方案:
修改 CodeSplitter 的参数:

1
2
3
4
5
6
cpp_splitter = CodeSplitter(
language="cpp", # 注意:不是 "c++"
chunk_lines=40,
chunk_lines_overlap=15,
max_chars=1500
)

问题 4:CodeSplitter 报错 DownloadError: Failed to download ... io: 你的主机中的软件中止了一个已建立的连接

现象:
tree-sitter-language-pack 尝试从 GitHub Releases 动态下载 parsers-windows-x86_64.tar.zst,因网络问题超时。

原因:
国内网络访问 GitHub 不稳定,导致动态下载解析器失败。

解决方案(推荐方案):
使用轻量级的 tree_sitter_cpp 包,并手动将解析器对象传给 CodeSplitter,彻底绕开动态下载。

1
pip install tree_sitter tree_sitter_cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tree_sitter
import tree_sitter_cpp
from llama_index.core.node_parser import CodeSplitter

CPP_LANGUAGE = tree_sitter.Language(tree_sitter_cpp.language())
cpp_parser = tree_sitter.Parser(CPP_LANGUAGE)

cpp_splitter = CodeSplitter(
language="cpp",
parser=cpp_parser, # 关键:传入预构建的 parser
chunk_lines=40,
chunk_lines_overlap=15,
max_chars=1500
)

问题 5:Word 文档切片出现严重乱码

现象:
切片内容显示为 /+&W;'rβu&~k8ekmX+1wUm۲M9aRTDYwSW7_.Xz@6E6k7pZBsMs... 等随机字符。

原因:
.docx 本质是 ZIP 压缩包(二进制格式),不是纯文本。SimpleDirectoryReader 在未显式指定 file_extractor 时,自动推断机制可能回退到通用文本读取器,强行用某种编码(UTF-8/GBK)解码 ZIP 的二进制内容,导致乱码。

解决方案(方案A):
显式指定 DocxReader,强制使用 python-docx 解析。

1
pip install llama-index-readers-file python-docx
1
2
3
4
5
6
7
8
9
10
11
from llama_index.readers.file import DocxReader
from llama_index.core import SimpleDirectoryReader

docx_reader = DocxReader()

word_documents = SimpleDirectoryReader(
input_dir="./test_data",
required_exts=[".docx"],
recursive=True,
file_extractor={".docx": docx_reader} # 显式指定解析器
).load_data()

经验总结

  1. Ollama 模型名要用官方 tag:不要混用 Hugging Face/ModelScope 的命名,qwen3:4b 才是官方 tag。

  2. 显式指定读取器优于自动推断:尤其是处理 .docx、.pdf、.xlsx 等二进制格式时,显式传入 file_extractor 能避免回退到通用文本读取器导致的乱码。

  3. tree-sitter-language-pack 动态下载是坑:国内网络环境下,推荐用独立包(如 tree_sitter_cpp)并手动传入 parser 对象。

  4. 代码切片和文本切片策略不同:代码用 CodeSplitter(按 AST 结构切),文本用 SentenceSplitter(按句子边界切)。

  5. 环境变量修改后必须重启服务:Ollama 只在启动时读取一次环境变量,修改后必须彻底退出并重启。