目录[-]
一、背景
公司业务涉及十几个模块,每个模块功能都很复杂。用户经常卡在某一步,不知道怎么操作。产品想做一个RAG系统,类似DeepSeek的一问一答形式,用户提问,系统基于公司内部知识库回答。
我需要先搭建一套本地RAG测试框架,验证可行性,并为后续测试建立评估基准。
核心挑战:
-
纯内网环境,不能调用外网API
-
硬件有限,无GPU,7B模型推理超30秒
-
需要建立可量化的评估体系
二、整体架构
文档(data.txt) → 加载切分 → 向量化(Embedding) → 存入向量库(Chroma)
↓
用户问题 → 向量检索 → 召回相关文档块 → LLM生成答案 → 输出
| 模块 | 技术栈 | 职责 |
|---|---|---|
| 文档处理 | LangChain TextLoader + RecursiveCharacterTextSplitter | 加载文本,智能切分 |
| 向量化 | OllamaEmbeddings (bge-m3) | 将文本转为向量 |
| 向量存储 | Chroma | 存储向量,支持相似度检索 |
| 检索 | Chroma as_retriever | 召回最相关的K个文档块 |
| 生成 | Ollama (qwen2.5:7b) | 基于召回内容生成答案 |
| 测试管理 | test_questions.py | 分层测试用例管理 |
三、核心设计
1. 文档切分策略
CHUNK_SIZE = 300 # 每个块300字符
CHUNK_OVERLAP = 50 # 块间重叠50字符,保持上下文连贯
SEPARATORS = ["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
为什么这样设计:
-
300字符适中:太大会拖慢检索,太小会切碎关键信息
-
50字符重叠:确保被切断的句子在相邻块中完整
-
优先按段落切,其次按句子,最后按字符
2. 向量化与检索
class VectorStoreManager:
def get_vectorstore(self, chunks=None):
# 检查文件MD5,判断是否需要重建向量库
if self._check_changed():
# 文档变化 → 重建
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=self.embeddings,
persist_directory=self.db_path
)
self._save_md5()
else:
# 文档未变 → 直接加载
vectorstore = Chroma(
persist_directory=self.db_path,
embedding_function=self.embeddings
)
return vectorstore
关键设计:
-
用MD5检测文档变化,避免重复建库
-
本地持久化,重启不丢失
3. 检索器配置
RETRIEVAL_K = 3 # 返回最相关的3个文档块
当前硬件(无GPU)下,k=3是速度和准确率的平衡点。理想情况应召回10-15个块。
4. Prompt约束设计
PROMPT_TEMPLATE = """根据以下文档回答问题。
【文档】
{context}
【问题】
{question}
【回答要求】
1. 如果文档有直接答案 → 直接回答
2. 如果文档有线索 → 基于线索合理推断(标注"根据文档推断")
3. 如果文档完全没有 → 回答"文档中没有相关信息"
【输出格式】
- 直接信息:1. xxx
- 推断信息:1. 根据文档推断:xxx
【示例】
问:白雪公主的相貌是什么?
答:根据文档,白雪公主肌肤洁白如雪、嘴唇赤红如血、头发如檀木般黑。
回答:"""
为什么这样设计:
-
强制LLM基于上下文,减少幻觉
-
区分“直接信息”和“推断信息”
-
无相关信息时明确说“不知道”,不编造
5. 测试用例分层设计
TEST_QUESTIONS = [
# Easy:原文直接有答案
{"question": "白雪公主的嘴唇是什么颜色?", "difficulty": "easy"},
{"question": "白雪公主的皮肤是什么颜色?", "difficulty": "easy"},
{"question": "皇后的头发是什么颜色?", "difficulty": "easy"},
# Medium:需要跨句推理
{"question": "皇后和白雪公主是什么关系?", "difficulty": "medium"},
# Hard:需要多段落归纳(后续扩展)
]
| 难度 | 定义 | 示例 |
|---|---|---|
| Easy | 原文单据内、原词匹配 | “白雪公主的嘴唇是什么颜色?” |
| Medium | 需要跨2-3句整合或简单推理 | “皇后为什么要杀白雪公主?” |
| Hard | 需要多段归纳、隐含关系识别 | “皇后总共用了哪几种方法?” |
四、运行效果
以“白雪公主”故事为测试文档(约2万字),测试Easy难度问题:
| 问题 | 预期关键词 | 实际回答 | 结果 |
|---|---|---|---|
| 白雪公主的嘴唇是什么颜色? | 赤红如血、红色 | 根据文档,嘴唇赤红如血 | ✅ 正确 |
| 白雪公主的皮肤是什么颜色? | 洁白如雪、白色 | 根据文档,肌肤洁白如雪 | ✅ 正确 |
| 皇后的头发是什么颜色? | 棕黄色、黄色 | 根据文档,棕黄色头发 | ✅ 正确 |
检索效果:正确答案成功召回,虽然不一定排第一,但LLM能够从召回的文档块中正确提取信息。
五、踩坑记录
坑1:chunk太大导致检索不准
一开始用800字符,只切出35个块。检索“头发颜色”时,正确答案在块中不突出。
解决:chunk调到300字符,切出76个块,检索精准度提升。
坑2:向量库每次重建
每次运行都重建向量库,耗时且浪费。
解决:用MD5检测文档变化,只在文件修改时重建。
坑3:7B模型推理慢
无GPU,单次推理30秒+。
解决:降低RETRIEVAL_K到3,缩短context长度。后续计划升级硬件换14B/32B模型。
坑4:LLM产生幻觉
问“皇后的外貌”,原文是“棕黄色头发”,LLM有时会编造“黑色头发”。
解决:强化Prompt约束,要求“基于文档”回答,无相关信息时说“不知道”。
六、优化方向
| 优先级 | 方向 | 预期效果 |
|---|---|---|
| 1 | 换14B/32B模型 + GPU | 推理速度从30秒降到3-5秒,细节完整率从70%提到80%+ |
| 2 | 混合检索(向量+BM25) | 语义+关键词双重召回,提升检索准确率 |
| 3 | 增加Reranker重排序 | 对召回结果重新打分,把最相关的排前面 |
| 4 | 答案缓存 | 相同问题直接返回缓存,减少重复推理 |
七、总结
这套RAG测试框架的核心价值:
-
完整闭环:文档加载→切分→向量化→检索→生成→评估
-
可量化:通过分层测试用例,量化评估核心事实准确率和细节完整率
-
可复用:框架设计支持换模型、换向量库、调参数
-
工程化:MD5检测变化、本地持久化、模块化设计
当前硬件限制下(无GPU、7B模型),核心事实准确率能做到100%,细节完整率70%。瓶颈在LLM推理能力,后续升级硬件可大幅提升。
这套框架后续被复用到翻译、音生文、图生文等AI测试项目,形成了公司AI测试的方法论基础。
0.5b模型测试报告
3b模型测试报告
7b模型报告
综合说明一下,这三次的测试结果都是无法满意。最重要的就是,LLM模型推理能力弱,无法达到预期目标。以后有更高的模型,效果会更棒。