一个 Python 脚本 + LLM 怎么替代向量检索?ProjQA 技能架构原理深度解析
ProjQA 不用 Embedding、不用向量数据库、不用检索后端,却能让 AI Agent 精准回答项目知识问题。本文从架构层面拆解它的检索原理、数据流、设计取舍,以及为什么这套方案在中小规模场景下比传统 RAG 更优。
整体架构
┌──────────────────────────────────────────────────┐
│ AI Agent (LLM) │
│ │
│ ┌───────────────┐ ┌────────────────────┐ │
│ │ 别名映射表 │ │ 索引文件 │ │
│ │ _aliases.md │ │ _index.md │ │
│ └───────┬───────┘ └─────────┬──────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 检索与匹配引擎(LLM) │ │
│ │ 别名映射 → 关键词匹配 → 语义理解 │ │
│ │ → 候选排序 → 取 Top-K │ │
│ └──────────────────┬──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 文档读取与定位(LLM) │ │
│ │ 多格式解析 → 章节定位 → 内容提取 │ │
│ └──────────────────┬──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 回答生成(LLM) │ │
│ │ 综合内容 + 来源标注 + 兜底机制 │ │
│ └─────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────┘
▲
│ 调用
│
┌────────┴─────────────────────────────────────────┐
│ scripts/scan_projqa.py │
│ 文件扫描 → 增量比对 → 索引更新 → 待补清单 │
│ 纯确定性操作,无 LLM 参与 │
└──────────────────────────────────────────────────┘
▲
│ 扫描
│
┌────────┴─────────────────────────────────────────┐
│ projqa-docs/ │
│ ├── 电商平台/ │
│ │ ├── FAQ/ │
│ │ ├── 运维手册/ │
│ │ └── 服务资料/ │
│ ├── 技术中台/ │
│ │ └── 架构设计/ │
│ ├── _index.md │
│ └── _aliases.md │
└──────────────────────────────────────────────────┘
三层结构,各司其职:
- 底层数据层:知识库目录,存放原始文档和元数据文件
- 中间脚本层:Python 脚本,做确定性的文件系统操作
- 上层智能层:LLM,做所有需要理解的检索与回答
核心数据结构:索引
索引(_index.md)是整个技能的检索枢纽。每条索引对应一个文档:
- [电商平台/FAQ/支付超时排查.md] | 关键词: 支付, 超时, 504, payment-gateway, Redis, 熔断 | 简述: 支付网关504超时的排查链路 | 提问摘要: 支付超时怎么排查? Redis连接池配置多少? 熔断怎么触发? | mtime: 2026-08-27 10:00:00 | size: 612
六个字段的职责分工:
| 字段 | 优先级 | 由谁生成 | 作用 |
|---|---|---|---|
| 相对路径 | --- | 脚本 | 文件定位 |
| 关键词 | P0 必补 | LLM | 实体检索匹配 |
| 简述 | P0 必补 | LLM | 内容概览判断 |
| 提问摘要 | P1 增强 | LLM | 语义精准匹配 |
| mtime | --- | 脚本 | 增量检测 |
| size | --- | 脚本 | 增量检测 |
关键设计:语义字段由 LLM 生成,元数据字段由脚本维护。两套字段互不干涉,各自由最适合的执行者处理。
检索链路详解
第 1 步:别名映射
用户提问:"库存中心的数据挂了怎么办"
LLM 读取 _aliases.md,将口语称呼映射到标准服务名:
库存中心 -> inventory-center
数据挂了 -> 数据库连接失败 / 数据库宕机
这一步是传统向量检索做不到的------向量只能算"库存"和"inventory"的 Embedding 距离,而 LLM 能理解它们指的是同一个实体。
第 2 步:索引扫描匹配
LLM 读取 _index.md 全文,在每个条目的语义字段上做多级匹配:
匹配优先级(从高到低):
| 优先级 | 匹配字段 | 逻辑 | 示例 |
|---|---|---|---|
| 1 | 提问摘要 | 语义高度匹配 | "库存......数据......" ↔ 命中"数据库连接失败怎么排查?" |
| 2 | 关键词 | 核心实体命中 | "inventory-center" ↔ 关键词含 inventory-center |
| 3 | 简述 | 语义相关 | "数据库连接失败" ↔ 简述含"数据库" |
| 4 | 文件路径 | 路径包含关键词 | "库存" ↔ 路径含 /服务资料/ |
| 5 | 文件名 | 名称包含关键词 | "inventory" ↔ 文件名含 inventory |
候选数量控制:
- 默认取 1-3 个最相关文档
- 跨主题问题(如"订单服务依赖库存和支付,怎么排查链路")放宽到 3-5 个
- 单文档命中充分时不读多余文档
第 3 步:文档读取与定位
命中候选后,按格式选择读取方式:
.md / .txt → 直接读取
.docx → 平台 Word 解析能力
.xlsx → 平台 Excel 解析能力
.pdf → 平台 PDF 解析能力
大文档(>200 行)的分章节策略:
1. 先读前 50 行获取标题结构
2. 根据标题定位相关章节的行号范围
3. 只读取该范围内容
4. 正文无序时用关键词位置定位
这里有一个和入库时的联动设计:大文档入库时,提问摘要里会追加章节索引信息:
提问摘要: §部署步骤: 怎么部署? §环境变量: 需要配置什么? §常见故障: 启动失败怎么办?
检索时 LLM 能直接从提问摘要判断应该读哪个章节,跳过无关部分。500 行的文档可能只需要读 30 行。
第 4 步:回答生成
综合文档内容 → 结构化输出 → 来源标注
回答必须包含来源标注:来源:电商平台/FAQ/支付超时排查.md
第 5 步:兜底机制
无匹配时的处理链路:
别名兜底(尝试同义词/缩写)
→ 扩宽检索(放宽关键词、读文件名和开头段落)
→ 明确告知"未找到"
→ 给出最接近的相关文档(若有)
→ 提示用户补充文档
核心原则:禁止编造。找不到就明说,引导用户补文档,形成"提问 → 缺失 → 补文档 → 下次能答"的闭环。
入库链路详解
入库是知识库质量的保障线。完整流程:
放文档到目录
│
▼
运行 scan_projqa.py --check-similarity ← 第一次扫描
│
├──→ 增量比对 mtime/size
├──→ 新文件追加索引条目(语义字段留空)
├──→ 变更文件清空旧语义信息
├──→ 输出分级待补清单(P0/P1)
└──→ 相似检测提醒(≥60% 提醒可能重复)
│
▼
LLM 根据待补清单补充语义信息 ← 智能体参与
│
├──→ P0: 生成关键词(5-10个)+ 简述(一句话)
├──→ P1: 生成提问摘要(2-3个提问式描述)
└──→ 大文档追加章节索引
│
▼
运行 scan_projqa.py ← 第二次扫描(验证)
│
└──→ 确认"待补清单: 无" → 入库完成
分级待补机制
| 级别 | 判定条件 | 标记 | 必要性 |
|---|---|---|---|
| P0 | 关键词或简述为空 | !! |
必须完成 |
| P1 | 提问摘要为空 | ? |
建议完成 |
这个分级机制确保了入库质量可控------P0 不完成就不算入库完成,P1 是检索质量的增量优化。
增量比对原理
脚本通过 mtime(修改时间)和 size(文件大小)两个字段做增量检测:
python
# 伪代码
if old_mtime != new_mtime or old_size != new_size:
# 文件变更了,清空语义信息,需要重新补充
entry.keywords = ""
entry.summary = ""
entry.questions = ""
这个设计的精妙之处:脚本不需要读文件内容就能判断文件是否变更。比对文件系统元数据是 O(1) 操作,上千文件也秒级完成。
而文件变更后清空语义信息,是因为内容变了,旧的关键词/简述/提问摘要可能已经不准确了。宁可重新生成,也不用过时信息。
与传统 RAG 的深度对比
检索原理对比
传统 RAG:
Query → Embedding → 计算与所有文档向量的余弦相似度 → 排序 → Top-K
检索质量取决于 Embedding 模型的质量。两个语义相同但词面不同的句子,如果 Embedding 模型不够好,向量距离可能很远。
ProjQA:
Query → 别名映射 → LLM 读取索引 → 语义理解 + 关键词匹配 → 候选排序 → Top-K
检索质量取决于 LLM 的理解能力。LLM 能理解"库存中心"和"inventory-center"是同一实体,能理解"挂了"等于"宕机"------这是向量检索做不到的。
维护成本对比
| 操作 | 传统 RAG | ProjQA |
|---|---|---|
| 新增文档 | 切块 → Embedding → 写入向量库 | 放文件 → 跑脚本 → LLM 补语义 |
| 更新文档 | 重新切块 → 重新 Embedding → 更新向量库 | 改文件 → 跑脚本(自动清旧语义)→ 重新补充 |
| 删除文档 | 从向量库删除对应 chunk | 删文件 → 跑脚本(自动移除) |
| 检索调优 | 换 Embedding 模型 / 调 chunk 大小 / 加 reranker | 修改索引的关键词/提问摘要(即时生效,无需重新处理) |
可解释性对比
传统 RAG :检索结果是一组向量距离分数,比如 0.873。这个数字代表什么?很难解释。为什么这篇文档排第一?不透明。
ProjQA:检索依据是索引字段,完全可读。比如某文档被选中是因为提问摘要里有"支付超时怎么排查?",和用户提问高度匹配------这个匹配链路是可追溯、可人工修正的。
适用边界
ProjQA 的优势区间
- 文档规模:百级以下(索引 100 行左右,LLM 扫描无压力)
- 文档组织:以项目/团队为单位,结构清晰
- 检索场景:运维问答、排障查询、服务信息查找
- 理解需求:需要别名映射、口语化理解(LLM 的强项)
传统 RAG 的优势区间
- 文档规模:万级以上(索引大小超出上下文窗口)
- 检索场景:开放域问答、海量知识检索
- 响应要求:毫秒级检索延迟
- 技术栈:已有成熟的 Embedding + 向量库基础设施
设计哲学总结
ProjQA 的架构设计可以用三句话概括:
- 脚本做脏活,LLM 做判断------确定性操作用脚本,语义理解交 LLM
- 索引是桥梁------脚本维护结构,LLM 维护语义,索引是两者的交接点
- 简单到不需要解释------没有 Embedding,没有向量库,没有后端,一个脚本 + 一份索引就够了
不是所有问题都需要工程化解决。在中小规模项目知识问答这个场景里,LLM 自身的理解能力就是最好的检索引擎。