Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

案例:本地 RAG 知识库构建

从多轮 AI 对话到多角色敏捷评审,再到生产级 Spring AI 集成——记录一个 MITRE ATT&CK 中文知识库 RAG 系统的完整决策过程。关键洞察:Python 快速验证思路,Spring 生态承载生产,而 mdBook 的结构化数据最适合“混合检索 + 渐进披露“。

案例概述

本案例记录了一个 MITRE ATT&CK 中文知识库(约 274 篇 Markdown 文档,覆盖 15 大战术、254 项技术、910+ 子技术)的本地 RAG 系统构建全过程。这不是一个从零开始的技术实现教程,而是一个关于“如何做技术决策“的案例——展示了在多个方案之间如何通过多轮分析、多角色评审、多维度对比来收敛到最优解。

案例的核心冲突是 Python 快速原型 vs Java 生产集成:元宝(Tencent Yuanbao)推荐了一套完整的 Python + LlamaIndex + sqlite-vec 方案,代码量小、落地快;但最终方案选择了将检索能力作为 Scorpius 项目的一个 KnowledgeProvider 插件,使用 Spring AI + pgvector 实现。这个决策背后的权衡过程——从数据特征分析、技术栈适配、团队长期成本到安全合规考量——是本案例最有价值的部分。

读完本文,你将理解如何为一个结构化的 Markdown 知识库设计 RAG 方案,如何在 Python 原型和 Java 生产实现之间做取舍,以及如何将“上下文工程“的思路应用到检索系统的设计中。

⏱ 时间有限?先读这些: 项目背景 → 方案探索 → 架构设计 → 核心组件

1. 项目背景

1.1 数据源特征

attck-knowledge 是一个 MITRE ATT&CK 中文翻译与解读项目,使用 mdBook 构建。数据特征决定了技术选型的方向:

{
  "data_source": {
    "name": "attck-knowledge",
    "format": "mdBook (Markdown + SUMMARY.md 导航)",
    "scale": "274 篇文档,约 3000-5000 chunk(按 ## 小节切分)",
    "structure": "src/{NN}-{TacticName}/README.md(战术概览)+ T{NNNN}-{Name}.md(技术)+ T{NNNN}/ 子目录(子技术)",
    "query_patterns": [
      "精确 TID 查询: 'T1059 是什么'",
      "战术聚合: '侦察阶段有哪些技术'",
      "语义搜索: 'DLL 侧加载怎么检测'",
      "交叉关联: '哪些技术能检测进程注入'"
    ]
  }
}

关键洞察:这份数据结构极强(TA/T/Sub-tech 三级 ID 体系 + 固定小节格式),用户查询混合精确 ID 命中与语义展开两种模式。纯向量检索会浪费其结构化优势,混合检索(Hybrid Search) 是刚需。

1.2 硬件约束

{
  "hardware": {
    "ram": "16 GB",
    "gpu": "无独显",
    "os": "Windows 11",
    "disk": "SSD 充足"
  }
}

16GB 无独显意味着:LLM 推理全走 CPU(8-12 tok/s),embedding 模型必须轻量(bge-small-zh-v1.5 约 2GB 推理峰值),reranker 初期不上。

1.3 需求清单

需求优先级说明
本地运行,数据不出机P0知识库敏感,必须纯本地
混合关键字检索P0TID 精确命中 + 语义搜索双路
团队可共享P1多成员需能方便使用
支持数据更新P1ATT&CK 版本升级时重建索引
可扩展为 APIP2后续可能集成到其他系统

2. 方案探索(多轮迭代)

本案例最特别的地方在于:方案不是一次性设计出来的,而是经历了3 轮与元宝的对话 + 1 轮多角色敏捷评审的迭代。

2.1 第一轮:FAISS 是不是最优?

初始问题是“FAISS 文件索引是不是最优方案“。元宝的分析给出关键纠偏:

FAISS 不是 RAG 完整方案,它是检索引擎,只覆盖 RAG pipeline 里“向量搜索“那一段。RAG 需要的切片管理、元数据过滤、持久化、标量混合检索——FAISS 全得自己补。

同档替代方案对比:

方案定位内置 embeddingmetadata 过滤持久化
FAISS检索引擎库手动 save/load
Chroma轻量向量库自动 SQLite
LanceDB文件式向量库✅ SQL 式自动 Lance 列式
sqlite-vecSQLite 扩展✅ SQL 原生自动 SQLite

结论:FAISS 单用不够,sqlite-vec + FTS5(同一 SQLite 文件,原生支持混合检索)最适合这份结构化数据。

2.2 第二轮:结合本书的上下文工程理念

将本书的上下文工程理念注入 RAG 设计,提出四级优化:

级别优化来源
P0按 mdBook 目录结构做语义分块(非简单 ## 切割)本书 AST 感知分块
P1模型降级链(精确查询→3B,分析查询→7B)本书上下文工程性能调优
P2渐进式披露(按查询类型控制上下文深度)+ 质量度量本书上下文质量度量
P3封装为 Skill(SKILL.md + reference 体系)本书 Skill 系统

P3 的 Skill 化设计是最具 OpenCode 特色的部分:不把 RAG 系统做成一个黑盒服务,而是拆分为两个 Skill——一个管索引构建、一个管查询问答。每个 Skill 都附带独立的 SKILL.md 说明文件和 references/ 参考目录,智能体通过自然语言就能触发对应技能。

Skill职责触发词
attck-index-builder构建/更新 ATT&CK 知识库索引“帮我重建索引”、“更新知识库”
attck-rag-query执行 RAG 查询并生成回答“T1059 是什么”、“侦察阶段有哪些技术”

这种设计的好处是解耦——索引构建是一次性操作,查询是高频操作,两者不需要共享同一个进程。Python 原型天然适合 Skill 化:index_builder.pyquery.py 本身就是自包含的 CLI 入口,Skill 的 references/ 目录只需指向 attck-rag/config/ 下的配置文件即可。

元宝确认了 P0(nav 分块)和 P1(模型降级)的设计方向,建议 P2 分阶段实施,并认可 P3 的 Skill 化思路是“长期维护成本最低的方案“。

2.3 第三轮:团队可共享的完整方案

元宝给出完整的 attck-rag/ 独立项目,包含:

attck-rag/
├── app/
│   ├── main.py              # FastAPI 服务
│   ├── index_builder.py     # mdBook 解析 + 索引构建
│   ├── retriever.py         # 混合检索 + RRF 融合 + 模型降级
│   ├── config.py            # 配置
│   └── requirements.txt     # 依赖
├── scripts/
│   ├── build_and_start.ps1  # Windows 一键启动
│   └── build_and_start.sh   # Linux/macOS 一键启动
├── Dockerfile & docker-compose.yml
└── README.md

核心代码用 200+ 行 Python 实现了完整链路:

def parse_attck_chunks(src_dir):
    """按 mdBook 目录层级解析,nav 结构感知的分块"""
    chunks = []
    for tactic_dir in sorted(os.listdir(src_dir)):
        # 00-reconnaissance/ → TA0001
        readme_path = os.path.join(tactic_dir, "README.md")
        if os.path.exists(readme_path):
            # 提取 TA_ID 作为 metadata
            ta_id = extract_ta_id(readme_path)
            chunks.append({"text": content, "metadata": {"level": "tactic", "ta_id": ta_id}})
        
        for fname in sorted(os.listdir(tactic_path)):
            # T1059-command-and-scripting-interpreter.md
            t_id = fname.split("-")[0]
            chunks.append({"text": content, "metadata": {"level": "technique", "t_id": t_id}})
            
            # T1059/ 子目录下的子技术
            for sub_fname in sorted(os.listdir(sub_dir)):
                sub_id = sub_fname.split("-")[0]
                chunks.append({"text": content, "metadata": {"level": "sub_technique", "sub_id": sub_id}})
    
    return chunks

2.4 多角色敏捷评审

作为敏捷教练,组织了 5 个角色的并行评审:

角色发现裁决
📋 需求分析师需求覆盖率 100%✅ 通过
🏗 架构顾问sqlite-vec 选型合理,评分 8/10✅ 通过
🛠 后端架构师发现 4 个 bug(Docker 过度设计、t_id 作用域错误、索引路径不匹配、版本过新)⚠️ 需修复
🧪 QA 工程师测试方案完备,需构造 20-30 问题覆盖四类场景✅ 通过
🤖 智能体工程师长期维护路径清晰✅ 通过

4 个 bug 被元宝全部采纳修复,方案从“Python 独立项目“演变为“纯本地 venv 默认,Docker 可选“。

2.5 与 Scorpius 项目的鸿沟分析

当尝试将 Python 方案集成到 Scorpius(一个 Spring Boot + Spring AI 的 AI 筹划系统)时,发现存在根本性差距:

{
  "gap_analysis": {
    "query_model": {"python": "Q&A 问答", "scorpius": "资产特征→技战法推荐"},
    "tech_stack": {"python": "Python + LlamaIndex", "scorpius": "Java + Spring AI"},
    "security": {"python": "无安全层", "scorpius": "PromptSanitizer + OutputValidator"},
    "architecture": {"python": "单体 FastAPI", "scorpius": "Generator-Evaluator 分离"},
    "data_isolation": {"python": "全局数据", "scorpius": "目标级数据隔离"},
    "overall_score": {"python": "4/10", "scorpius": "需要原生方案"}
  }
}

Python 方案的 4/10 分揭示了一个核心矛盾:原型验证的价值与生产集成的成本。最终决策不是“谁的代码好“,而是“哪个方向长期维护成本低“。

3. 架构设计

3.1 方案对比决策

维度权重Python 独立Spring 集成混合架构
落地合理性40%586
落地难度30%653
后续扩展30%495
加权总分100%5.07.34.8

最终决策:Spring AI 集成到 Scorpius,吸取 Python 原型的 4 个设计精华。

3.2 系统架构

graph TB
    subgraph Scorpius["Scorpius 系统"]
        KP[KnowledgeProvider 接口层]
        RP[检索管线]
        GE[Generator-Evaluator]
        SP[安全管线]
    end
    
    subgraph Attck["ATT&CK KnowledgeProvider"]
        MR[MdBookReader]
        MD[Metadata Extractor]
        CI[Chunk Indexer]
    end
    
    subgraph Store["存储层"]
        PG[(pgvector<br/>向量 + FTS5 + 元数据)]
    end
    
    subgraph Model["模型层"]
        QC[QueryClassifier<br/>exact / factual / analysis]
        LLM7[Ollama<br/>qwen2.5:7b]
        LLM3[Ollama<br/>qwen2.5:3b]
    end
    
    User[用户] --> QC
    QC -->|exact| LLM3
    QC -->|analysis| LLM7
    QC --> RP
    RP --> PG
    RP --> GE
    GE --> SP
    MR --> MD --> CI --> PG
    KP --> Attck

3.3 关键技术决策

决策点选择理由
向量存储pgvectorScorpius 已有 PostgreSQL,零新依赖
检索融合RRF(Reciprocal Rank Fusion)无需手动调权,初版省调参
文档解析自研 MdBookReadermdBook 结构规整,200 行 Java 实现
Embeddingbge-small-zh-v1.5已验证,96MB 轻量,中文够用
LLM 推理Ollama + qwen2.5Scorpius 已有集成
安全控制沿用 Scorpius 管线PromptSanitizer / OutputValidator 不改动
反馈闭环Generator-EvaluatorScorpius 原生模式

4. 核心组件设计

4.1 MdBookReader(文档解析器)

从 Python 原型提取的核心设计——按 mdBook 的目录导航结构做语义分块,而非简单按 ## 切割:

graph LR
    subgraph mdbook["mdBook 目录结构"]
        T1[00-reconnaissance/]
        T2[01-resource-development/]
    end
    subgraph chunk["分块策略"]
        C1[README.md → 战术级<br/>level=tactic, ta_id=TA0001]
        C2[T1059-command.md → 技术级<br/>level=technique, t_id=T1059]
        C3[T1059/001-sub.md → 子技术级<br/>level=sub_technique, sub_id=T1059.001]
    end
    T1 --> C1
    T1 --> C2
    T1 --> C3

关键元数据字段设计:

{
  "metadata_schema": {
    "level": "tactic | technique | sub_technique",
    "ta_id": "TA0001~TA0043",
    "ta_name": "侦察 | 资源开发 | ...",
    "t_id": "T1059",
    "t_name": "命令和脚本解释器",
    "sub_id": "T1059.001",
    "difficulty": "⭐⭐⭐",
    "section": "攻击流程 | 检测建议 | Sigma 规则"
  }
}

4.2 VectorRetrievalService(混合检索)

使用 pgvector 同时承载向量搜索和 FTS5 全文搜索。实际实现采用 DDD 四层:Controller 暴露 REST API,AttckQueryAppService 编排业务,HybridRetrieverService 接口定义领域协议,VectorRetrievalService 在基础设施层完成具体检索逻辑:

-- 向量 + 全文 + 元数据 三合一表
CREATE TABLE attck_chunks (
    id UUID PRIMARY KEY,
    chunk_text TEXT NOT NULL,
    embedding vector(768),               -- bge-small-zh
    metadata JSONB NOT NULL DEFAULT '{}',-- ta_id, t_id, level, ...
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 向量索引(IVFFlat)
CREATE INDEX idx_attck_embedding ON attck_chunks 
    USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

-- 全文检索索引
CREATE INDEX idx_attck_fts ON attck_chunks 
    USING GIN (to_tsvector('simple', chunk_text));

-- 元数据过滤索引
CREATE INDEX idx_attck_metadata ON attck_chunks USING GIN (metadata);

-- 级别过滤索引(用于渐进披露的 level 字段快速过滤)
CREATE INDEX idx_attck_level ON attck_chunks ((metadata ->> 'level'));

实际实现采用 DDD 四层架构——VectorRetrievalService 在基础设施层实现 HybridRetrieverService 接口,使用 Spring AI 1.1.x 的 SearchRequest.Builder 进行向量检索,AttckChunkRepository 提供全文检索能力。RRF 融合算法——5 行核心逻辑:

@Override
public List<AttckChunk> retrieve(String query, QueryType queryType, int topK) {
    // 超出检索(over-retrieve)提供更多候选给 RRF 融合
    int overRetrieve = topK * DEFAULT_OVER_RETRIEVE;  // margin = 3x
    // 向量检索(Spring AI SearchRequest.Builder)
    SearchRequest searchRequest = SearchRequest.builder()
            .query(query)
            .topK(overRetrieve)
            .similarityThreshold(0.3)
            .build();
    List<AttckChunk> vecResults = vectorSearch(searchRequest);
    // 全文检索(Repository 层)
    List<AttckChunk> ftsResults = chunkRepository.fullTextSearch(query, overRetrieve);
    // RRF 融合:sum(1 / (60 + rank)),rank 从 1 开始
    return fuse(vecResults, ftsResults, topK, queryType.getDisclosureDepth());
}

private List<AttckChunk> fuse(List<AttckChunk> vecResults,
        List<AttckChunk> ftsResults, int topK, int depth) {
    Map<String, Double> rrfScores = new HashMap<>();
    int K = 60;
    for (int i = 0; i < vecResults.size(); i++)
        rrfScores.merge(vecResults.get(i).getId(), 1.0 / (K + i + 1), Double::sum);
    for (int i = 0; i < ftsResults.size(); i++)
        rrfScores.merge(ftsResults.get(i).getId(), 1.0 / (K + i + 1), Double::sum);
    
    return rrfScores.entrySet().stream()
        .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
        .limit(topK)
        .map(entry -> /* 按 ID 回填 AttckChunk */)
        .filter(chunk -> passesDisclosureGate(chunk, depth))
        .toList();
}

4.3 Progressive Disclosure(渐进披露)

从本书上下文工程理念中提取的核心模式——不是把所有信息一次性塞给 LLM,而是按需披露:

查询类型披露深度上下文范围示例
exact(TID 精确)level 1单篇文档正文“T1059 是什么?”
factual(事实查询)level 2同战术下所有技术摘要“侦察阶段有哪些技术?”
analysis(分析查询)level 3全库 Top-5 + 元数据聚合“哪些技术能检测 DLL 注入?”
{
  "progressive_disclosure": {
    "exact": {
      "depth": "level_1",
      "disclosure_filter": "WHERE metadata->>'t_id' = '{tid}' OR metadata->>'sub_id' = '{tid}'",
      "prompt_depth": "只包含目标文档的完整内容",
      "model": "qwen2.5:3b"
    },
    "analysis": {
      "depth": "level_3",
      "disclosure_filter": "无过滤,全库检索",
      "prompt_depth": "包含 Top-5 结果 + 战术聚合摘要 + 关联检测建议",
      "model": "qwen2.5:7b"
    }
  }
}

4.4 Model Cascade(模型降级链)

按查询复杂度路由到不同的模型,在保证质量的同时控制资源:

graph LR
    Q[用户查询] --> QC{QueryClassifier}
    QC -->|"正则匹配 T\\d{4}\\.?\\d{0,3}\|TA\\d{4}"| EX[exact]
    QC -->|含'是什么/定义/包括哪些'| FA[factual]
    QC -->|默认| AN[analysis]
    
    EX -->|qwen2.5:3b ~2GB| R1[快速精确响应]
    FA -->|qwen2.5:3b ~2GB| R2[稳定事实响应]
    AN -->|qwen2.5:7b ~5GB| R3[深度推理响应]

4.5 Quality Evaluator(质量审计)

从本书上下文质量度量(5 个黄金指标)延伸出 ATT&CK 专用评估维度:

指标测量方法目标
TID_HitRate答案中 TID 的准确率≥ 90%
Technical_Fact技术描述/平台/权限准确率≥ 95%
Citation_Gap出现未在上下文中出现的 TID(幻觉检测)≤ 5%
Level_Match答案深度与查询类型的匹配度≥ 85%

5. 执行计划(3 周 MVP)

gantt
    title ATT&CK RAG 实施路线图
    dateFormat  YYYY-MM-DD
    section Phase 1 - 基础管线
    Spring AI 集成 Ollama           :p1a, 2026-07-06, 2d
    实现 MdBookDocumentReader        :p1b, after p1a, 2d
    搭建 pgvector 表 + embedding 管线 :p1c, after p1b, 2d
    实现基本向量检索                  :p1d, after p1c, 1d
    
    section Phase 2 - 混合检索
    添加 pgvector FTS5 全文索引       :p2a, after p1d, 2d
    VectorRetrievalService + RRF 融合  :p2b, after p2a, 2d
    QueryClassifier + Model Cascade   :p2c, after p2b, 1d
    Progressive Disclosure            :p2d, after p2c, 2d
    
    section Phase 3 - 质量闭环
    AttckEvaluator 实现               :p3a, after p2d, 2d
    安全管线集成                      :p3b, after p3a, 1d
    KnowledgeProvider 插件封装        :p3c, after p3b, 1d
    团队联调 + 文档                   :p3d, after p3c, 1d
Phase内容工期产出
Phase 1Spring AI 接 Ollama + MdBookReader + pgvector 建索引1 周可查 T1059
Phase 2FTS5 全文 + RRF 融合 + 模型降级 + 渐进披露1 周混合检索 MVP
Phase 3Evaluator + 安全 + KnowledgeProvider 封装1 周生产就绪

6. 经验总结:什么是该“拿“的,什么是该“放“的

从 Python 原型中提取的 4 个设计

设计Python 原型实现Spring 中落地
mdBook 导航级分块index_builder.py{NN}-{Name}/ 目录解析,metadata 注入 levelMdBookKnowledgeSource 实现 DocumentReader 接口
渐进披露chunk metadata 的 level 字段用于查询时按深度过滤Spring AI DocumentTransformer 保留字段,ProgressiveDisclosureFilter 实现
模型降级链classify_query()model_map[...]接入 Scorpius AIModelManager / CostRouter
质量度量四指标手动验证嵌入 Scorpius Evaluator 体系

不拿的

Python 设计替换方案理由
sqlite-vecpgvectorScorpius 已有 PostgreSQL
LlamaIndexSpring AI统一技术栈
Docker ComposeScorpius 部署体系无需额外运维

迭代经验

Python 快速原型 → 多角色评审发现 bug → 元宝修复 → 鸿沟分析识别深层问题 → Spring AI 重构。

每条路径都有价值:Python 验证了设计方向,评审暴露了实现缺陷,鸿沟分析揭示了架构层面的不匹配。没有浪费的步骤——只有认知的递进。

常见反模式

反模式一:把 FAISS 当 RAG 全家桶用。 很多团队看到 FAISS 在向量检索上的高性能,就直接拿它搭建整套 RAG 管线。但 FAISS 本质上只是一个向量索引库,不提供元数据过滤、全文检索、文档切片管理或持久化能力。你需要自己实现 FTS5/关键词检索、metadata 过滤逻辑、索引文件的序列化和反序列化,以及 embedding 模型的调用封装。这些“周边工程“的工作量往往远超 FAISS 本身。更关键的是,FAISS 不支持混合检索(向量 + 关键词),而像 ATT&CK 知识库这类结构化数据,精确 TID 查询(如“T1059“)必须走关键词匹配才能保证准确率,纯向量检索会把“T1059“和“T1059.001“混在一起。正确做法是选择 sqlite-vec + FTS5 或 pgvector + GIN 索引这类原生支持混合检索的方案,从一开始就避免“补丁叠补丁“的架构腐化。

反模式二:Python 原型直接上生产。 Python + LlamaIndex 的组合确实能在 200 行内跑通完整 RAG 管线,这让很多团队产生“已经可以用了“的错觉。但原型和生产之间存在巨大的工程鸿沟:没有安全层(PromptSanitizer / OutputValidator)、没有目标级数据隔离、没有 Generator-Evaluator 反馈闭环、没有团队共享的部署方案。本案例的鸿沟分析给出 Python 方案 4/10 分,核心扣分项就是安全和架构层面的缺失。正确路径是用 Python 验证设计方向(分块策略、检索融合算法、模型降级逻辑),然后将验证过的设计移植到生产技术栈中。原型的价值在于“低成本试错“,而非“低成本上线“。

反模式三:简单按 ## 标题切分文档。 很多 RAG 实现用最直觉的方式切分 Markdown:遇到 ## 就切一刀。这对通用文档或许够用,但对结构化知识库是灾难。ATT&CK 的 mdBook 目录本身包含三层语义信息(战术级 → 技术级 → 子技术级),简单按标题切分会把一个技术文档拆成多个无关联的碎片,丢失“T1059 属于TA0002 执行战术“这样的层级关系。检索时用户问“侦察阶段有哪些技术“,碎片化的 chunk 无法聚合回答。本案例的 MdBookReader 按目录导航结构({NN}-{TacticName}/T{NNNN}-{Name}.mdT{NNNN}/ 子目录)做语义分块,每个 chunk 自带 levelta_idt_id 等元数据,检索时可以按层级聚合,这才是结构化知识库的正确分块方式。

反模式四:所有查询用同一个大模型。 “统一用 7B 模型处理所有查询“看似简单,实则浪费资源且拖慢响应。用户问“T1059 是什么“只需要精确匹配单篇文档,3B 模型 2 秒就能给出准确答案;而“哪些技术能检测 DLL 注入“需要跨战术关联分析,才值得调用 7B 模型。本案例的 QueryClassifier 将查询分为 exact/factual/analysis 三类,分别路由到 3B 和 7B,既控制了 16GB 内存下的资源占用,又保证了复杂查询的推理质量。不做查询分类的直接后果是:简单查询等太久(7B 推理慢),复杂查询答不准(3B 推理弱),两边都不满意。

7. 适用场景与限制

适合

  • 结构化的 Markdown 知识库(API 文档、合规手册、技术规范)
  • 需要在现有 Java/Spring 项目中嵌入检索能力
  • 数据量在十万级 chunk 以内
  • 查询模式混合精确匹配与语义搜索

不适合

  • 非结构化 PDF/扫描件知识库(需 OCR + PDF 解析,mdBook 解析器不可用)
  • 纯语义搜索场景(不需要混合检索的复杂度)
  • 已有成熟 Elasticsearch 基础设施(应考虑 ES 的 knn + query 融合)

限制

  • mdBook 结构变动时需要同步更新解析器的 nav 遍历逻辑
  • pgvector 在百万级向量以上需考虑索引维护成本
  • CPU 推理 7B 模型在长上下文时降至 3-5 tok/s

常见失败与陷阱

陷阱一:QueryClassifier 分类错误导致模型降级链失效。 QueryClassifier 用正则和关键词把查询分为 exact/factual/analysis 三类,分别路由到 3B 和 7B 模型。但正则规则可能把“DLL 侧加载怎么检测“误判为 exact(因为包含“T“开头的子串),路由到 3B 模型,结果 3B 推理能力不够,给出模糊甚至错误的回答。反过来,“T1059“这种纯 TID 查询可能被误判为 analysis,路由到 7B,白白浪费推理时间和内存。本案例的解决方法是用多层正则:先匹配严格的 TID 格式(^T\d{4}(\.\d{3})?$),再匹配关键词(“是什么”、“定义”、“有哪些”),最后用默认分类兜底。即使如此,分类准确率也不是 100%。生产环境中建议加入置信度阈值:低置信度的分类直接路由到 7B 作为安全网。

陷阱二:RRF 融合在精确查询场景下反而降低准确率。 RRF(Reciprocal Rank Fusion)对向量检索和全文检索的排名做加权融合,“不需要手动调权“是它的卖点。但对于“T1059“这种精确 TID 查询,全文检索(FTS5)的排名几乎完美——直接命中文档标题,而向量检索可能返回语义相似但 TID 不同的文档(比如 T1059.001、T1059.002)。RRF 融合后,向量检索的“噪声“结果被提升到前列,干扰了精确匹配。本案例的解决方案是先用 QueryClassifier 判断查询类型:exact 查询直接走 FTS5,跳过向量检索和 RRF 融合;只有 factual 和 analysis 查询才启用混合检索。如果你的系统没有这层分类,纯 RRF 在精确查询场景下可能反而不如纯 FTS5。

陷阱三:embedding 模型更新后索引不一致。 bge-small-zh-v1.5 生成的 embedding 是 768 维向量,索引表 attck_chunksembedding 列也定义为 vector(768)。如果你升级了 embedding 模型(比如换成 bge-base-zh-v1.5,维度可能不同),旧的向量和新的向量无法在同一列中混合查询。必须重建整个索引——删除旧表、用新模型重新生成所有 embedding、重新插入数据。这个过程在 274 篇文档(3000-5000 chunks)的规模下大约需要 10-15 分钟,但在更大规模下可能需要数小时。建议在 schema 中记录 embedding 模型的版本和维度,每次重建时校验一致性。

关联章节