目录[-]

一、背景

公司业务涉及十几个模块,每个模块功能都很复杂。用户经常卡在某一步,不知道怎么操作。产品想做一个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测试框架的核心价值:

  1. 完整闭环:文档加载→切分→向量化→检索→生成→评估

  2. 可量化:通过分层测试用例,量化评估核心事实准确率和细节完整率

  3. 可复用:框架设计支持换模型、换向量库、调参数

  4. 工程化:MD5检测变化、本地持久化、模块化设计

当前硬件限制下(无GPU、7B模型),核心事实准确率能做到100%,细节完整率70%。瓶颈在LLM推理能力,后续升级硬件可大幅提升。

这套框架后续被复用到翻译、音生文、图生文等AI测试项目,形成了公司AI测试的方法论基础。

0.5b模型测试报告

3b模型测试报告

7b模型报告

综合说明一下,这三次的测试结果都是无法满意。最重要的就是,LLM模型推理能力弱,无法达到预期目标。以后有更高的模型,效果会更棒。