MimirQ 偏向 RAG 基础设施层,解决企业知识库「排错难、改崩效果、无法验收」的工程痛点,适合对知识库质量、可审计性有高要求的项目。

浓缩成一句话:
学的是把 AI 知识库当成可审计的数据流水线来建——组件可插拔、过程可看见、版本可回归;模型只是流水线里可替换的一环。

形成的是「业务内核自研 + 通用能力复用」的三层分层:

  • 上层(编排):LangGraph 负责流程控制(顺序、分支、循环、状态持久化)
  • 中层(组件):LangChain 负责组件标准化(文档、模型、检索、分割)
  • 底层(业务):自研逻辑(检索策略、引用溯源、质量校验、切块插件)

1. 先治链路,再谈模型

企业 RAG 真正难的是:错了能不能定位、策略能不能换、质量能不能证明没滑坡。

把链路拆成:

解析 → 治理 → 切块 → 索引 → 召回 → 重排 → 引用 → 回归

每一步都要有明确输入输出,而不是一个黑盒按钮。

2. 可观察性是产品能力,不是运维附加

解析结果、chunk 边界、召回片段、重排分数、最终引用——全部可视化。

做 AI 系统时,把 Trace / Preview / 证据引用 作为首要能力;没有中间结果,就没有根因分析,只有玄学调参。

LangSmith 是 LangChain 官方推出的 SaaS 形态 LLM/Agent 可观测、调试、评估平台,适合看提示词与模型输入输出;但上游解析、切块、治理问题,仍要靠业务 Trace。

现有 RAG(解析、切块、向量检索、问答)建议分阶段补齐:

  1. Citation 证据引用(优先):带字符位置信息
  2. Preview 切块预览:支持人工审核
  3. Trace-Bundle 日志复盘:整链回放

每个文档块至少记录这些关键字段:

  • document_id:文档唯一 ID
  • chunk_id:块唯一 ID
  • content:片段文本
  • score:相似度分数
  • rerank_score:重排分数(如启用)
  • evidence_start_char、evidence_end_char:该块在原始全文的字符起止下标(最重要)

3. 平台中立 + 业务进插件

平台不写「某市某事项怎么排」;业务规则进插件合约:

  • governance
  • chunk
  • metadata
  • retrieval_policy
  • golden

通用内核吃标准合约,行业差异用插件扩展。换客户、换领域时,不用改核心架构。

插件合约是什么

插件合约不是面向对象的类继承,而是一套「目录包 + 封闭 JSON Schema + 可调用阶段」的标准化协议。

  • 平台内核只识别和消费标准合约字段,完全不感知业务语义
  • 所有业务差异全部收敛在插件包内;更换客户 / 领域只替换插件包,不必改核心架构

两条设计原则:

  1. 内核纯净化:平台(app/)只消费标准合约,不理解具体业务逻辑(例如「某市某事项怎么排」)
  2. 业务插件化:业务差异全部下沉到插件包,核心架构与业务逻辑彻底解耦

RAG 项目流程

PDF 通用解析完成 → governance 插件做业务治理 → metadata_schema 校验 → chunk 插件按业务规则切块 → 再校验并做视图投影

1)调用 governance 插件:全局业务治理

在完整文档上执行业务逻辑,例如:

  • 识别全文办事事项清单,梳理整体结构
  • 补全文档级业务字段:所属区县=常州市、办理部门=市行政审批局
  • 清洗页眉页脚、水印、备注说明
  • 统一业务术语格式

2)第一次校验:metadata_schema 契约校验

平台拿着插件自带的 metadata_schema.json 检查:

  • 有没有写契约外的非法字段
  • 字段类型对不对
  • 有没有试图修改平台保留字段(如 document_id)

校验通过才往下走;有问题直接拦截,避免错误数据进入切块,导致索引全作废。

3)调用 chunk 插件:按业务粒度切块

4)第二次校验 + 投影视图

切块后每个 chunk 再跑一遍 schema 校验,生成:

  • _indexed_metadata:索引用
  • _display_metadata:前端展示用

校验通过后,才送去建向量索引、BM25 索引。

5)通用检索器执行 filter / boost / expand

业务插件只负责写规则,平台只负责通用执行。

filter(结果过滤)示例:

1
2
3
"filter": [
{ "field": "区县", "value": "常州市", "operator": "==" }
]

平台只做字段匹配,不理解“区县”的行政含义。

boost(加权提权)示例:

1
2
3
"boost": [
{ "field": "section_type", "value": "申报流程", "weight": 2.0 }
]

query_expand(查询扩展)示例:

1
2
3
"query_expand": {
"append_terms": ["办理流程", "申报材料", "办理条件"]
}

本质是:判断「过滤什么、给谁加权、扩什么词」的业务逻辑全在插件配置里;平台检索器是通用执行器。换金融、医疗、教育,核心代码都不用改,只换插件 JSON。

4. 「可替换」靠边界,不靠堆功能

30 个解析器、86 种切块、13 类重排——价值不在数量,而在:

统一接口 + 注册表 + 工厂 + 配置

配置:告诉系统「用哪个」

1
2
3
chunk_strategy = "政务事项型"
RERANKER_PROVIDER = "bge"
VECTOR_BACKEND = "milvus"

注册表:组件名单 + 地址簿

组件名称 对应代码路径 备注
按字数切 chunks.char_size.Chunker 基础切块
政务事项型 chunks.gov_affair.GovChunk 政务业务切块
语义切块 chunks.semantic.SemanticChunker 语义切块

建议懒加载:用到哪个才导入哪个。

工厂:按名字取组件

流水线里不要写一堆 if/else,只写:

1
chunker = ChunkerFactory.get_chunker(配置名)

易变部分接口化

解析器、embedding、向量库、rerank 都定义统一基类接口,例如:

  • BaseChunker.split_documents(docs) → docs
  • BaseReranker.rerank(query, docs) → ranked_docs
  • BaseVectorStore.add_docs / search

换 BGE 重排为 LLM 重排,通常只需:写新实现 → 注册表加一行 → 改配置名。检索编排、Citation、Trace 不用动。

稳定部分强约束

  1. Dataset 范围:禁止无边界全库搜索
  2. Provenance:每个 chunk / citation 可追溯到原始文档
  3. Citation 合约:输出字段固定,前端与 Trace 只认这一套格式

一句话:

可替换 = 统一接口 + 注册表挂实现 + 工厂按配置取件
靠边界 = dataset 范围、来源追溯、引用格式是硬合约

5. 检索质量 ≠ 回答好听

门禁看的是:证据有没有进 TopK、metadata 是否命中、噪声是否过高——而不是 LLM 答得顺不顺。

很多项目会踩坑:检索很烂,但大模型很会“圆话”。表面通顺,实际证据全错,线上悄悄崩掉。

门禁看什么

门禁维度 核心指标 通俗解释
证据进 TopK hit@1/3/5/10、MRR、NDCG 正确答案有没有排进前几名
metadata 命中 expected_metadata_hit_rate 业务字段过滤是否生效
有效依据 / 噪声 effective_context_rate、noise_rate TopK 里多少真正有用
must_recall must_recall_pass_rate 必须召回的文件/锚点是否出现

业务上“什么算对、什么必须有”,写在插件的 golden_rules.json 里。

两道门分开验收

  1. retrieval-only gate(发版必过):只跑检索,不调用大模型
  2. answer / RAGAS gate(可选补充):看忠实度、相关性

检索不合格,直接拦在发版前,连生成资格都没有。

6. Golden 题集 = 质量安全带

固定题集 + 阈值 + CI fail-closed。升级模型、改切分、换解析器后自动回归。

任何影响召回的改动,都要回答四个问题:

  1. 改了哪一层?
  2. 有没有业务逻辑漏进平台?
  3. 有没有回归证据?
  4. 有没有回滚路径?

三要素

  1. 固定题集:平台通用题 + 插件 golden_rules.json
  2. 阈值:如 hit@3 ≥ 90%、噪声率 ≤ 20%、must_recall = 100%
  3. CI fail-closed:不达标直接失败,不准发版

对应落地手段:

  • retrieval_config_hash / pipeline_hash
  • test_pipeline_plugin_boundary.py
  • gate report / run.detail.json
  • 插件开关、旧配置切换、文档多版本激活

7. 先评估数据,再选技术

扫描件用 MinerU、复杂版式用 Docling、普通 Office 用 MarkItDown——没有全局最优解析器。

完整闭环:

客户语料 → 数据画像 → 多解析器对比预览 / 基准测试 → 自动生成入库策略 → 锁定配置 → 正式入库(保留版本与质量证据)

关键原则:

  • 解析器建议可以自动给,但最终以配置为准
  • 不要在平台核心硬编码“扫描件必须用某解析器”
  • 数据形态决定技术选型,而不是反过来

8. 证据优先于答案

四件套:

  1. Citation:带字符偏移的证据片段
  2. Provenance:完整来源链,防篡改可审计
  3. must_recall:核心证据缺失可触发拒答
  4. Evidence Capsule:一次检索证据的封存快照

系统默认:

没有依据 = 不合格 = 不能过

可配合:

  • RAG_VISIBLE_EVIDENCE_ONLY
  • retrieval_contract_mode = evidence_strict

三道机制:两道在线拦,一道旁路查

「答案必须有据」被拆成三道机制:①②在在线链路里拦,不合规就不放行;③在旁路里看,事后可查证。

机制 管什么 时机 核心动作 达不到时的结果
① 强制召回 must-recall 合同 证据齐不齐(输入侧) 在线,能拦 声明必命中 → 检索后核对 → 不够就补第二轮 仍缺则 must_recall_failed,拒答
② 强制引用 claim verifier 回答有没有据(输出侧) 在线,能拦 拆句 → 逐句比对证据 → 删无据句 删太多 / 整体无据则拒答
③ 可查 lineage + trace 过程查得到(旁路) 事后 落 JSONL → 还原 trace → 补血缘 不影响单次回答

值得留意的是对标范围:可观测性平台(如 Langfuse)只对得上③,管的是事后查证,管不了在线放行。①②这两道「拦」的机制没有现成平台能替,得在业务链路里自己实现。

溯源:分层半结构化,不是单表流水

这套能力支持完整全链路追溯,但采用分层半结构化设计,不是把每一步处理都追加进同一张事件流水表。可以概括成三句:

  • 入库钉证据:用文档 / Chunk 的 JSONB 元数据 + 显式位置列,把证据物理锚定在存储中
  • 回答串引用:用结构化 Citation 串起「文档 → Chunk → 字符 / 页码区间」的精准证据链
  • 审计做聚合:用 rag_trace 日志、lineage 接口、Evidence Capsule 完成全链路回放与校验

三层各司其职,数据粒度、存储周期、使用场景都不一样:

  1. 入库层:阶段摘要写入 documents.metadata 和 document_chunks
    持久化,和数据同生命周期;钉死原始证据与处理版本,是所有溯源的根基。
  2. 回答层:结构化 Citation
    随回答返回、嵌在响应结构里;把答案片段和具体证据位置一一对应,支持点击引用跳原文。
  3. 审计层:rag_trace JSONL + lineage 接口 + Evidence Capsule
    运行时日志 / 按需接口,可开关;负责全链路审计、排障、结果复现。

9. 机器观测 vs 业务 Trace

OTel / LangSmith / Prometheus 业务 Trace
记录内容 函数调用、耗时、模型输入输出 每步业务数据(分数、片段、最终证据)
定位范围 性能、服务故障、提示词问题 检索质量、证据链路、业务逻辑问题
层级 技术运维增强层(可关) 产品核心能力层(自研)

用户投诉“回答错了”时:

  • Prometheus:可能只看到接口正常
  • LangSmith:可能只看到最终喂给模型的上下文
  • 业务 Trace:能回放改写、多路召回、融合、重排、最终 Citation 全链路

10. 全链路版本指纹:方便回滚

版本指纹管的是「这次发布用了哪套配置 / 插件包」,和上一节的证据溯源不是同一层:溯源回答「这句话依据哪段原文」,指纹回答「出问题后如何切回旧配置」。审计复现时两者常一起用——先靠指纹对齐环境,再靠 Citation / rag_trace / Capsule 回放链路。

一次发布绑定多段指纹:

指纹 覆盖什么
pipeline_hash 入库管线:解析器、切块、治理等
retrieval_config_hash 检索策略、融合、重排等
plugin package_hash 业务插件包内容哈希
阈值 / 开关快照 门禁阈值、KG 开关、特性 flag

标准回滚路径:

关插件 → 关 KG → 切回 retrieval profile → 恢复旧 thresholds

目标是:切配置,不改代码、不重跑全库。

11. 补充工程基建

Pre-POC:Python 规则扫描(非大模型)

模块 作用
format_distribution 格式分布
length_distribution 篇幅分布
pdf_page_classifier 扫描件 / 混合件 / 原生文本判断
md5_dedup 精确去重
simhash_similarity 近重复检测
sensitive_info 敏感信息抽检

存储与任务队列

  • MinIO / S3:原始文件
  • PostgreSQL:元数据、权限、citations、Trace(JSONB)
  • SQLite:单文档表格旁路存储
  • Arq + Redis:异步任务队列,支持幂等

先分清两类「队列」,别混着选型:

类型 定位 解决的问题 代表产品
任务队列(Task Queue) 专门管「把任务丢后台,Worker 慢慢执行」 异步任务调度、重试、死信、取消、并发控制 ARQ、Celery、RQ
消息队列(MQ) 专门管「服务 A 发消息,服务 B 收消息」 服务解耦、消息路由、发布订阅、流量削峰 RabbitMQ、Kafka、RocketMQ

当前入库、解析、切块这类后台活,用 ARQ + Redis 就够:Python 同栈、调度清晰、幂等也好做。不是 RabbitMQ 不好,而是场景不对。出现下面情况,再考虑从 ARQ 迁到 RabbitMQ 才划算:

  1. 跨服务、跨语言调度:不止 Python,还有 Java、Go 等也要投递任务
  2. 复杂路由:要按任务类型、租户、优先级落到不同 Worker 队列
  3. 极高吞吐量:单秒几万甚至几十万任务,Redis 扛不住
  4. 核心业务链路:任务本身是交易级链路,对可用性、可靠性、消息不丢要求极高
  5. 发布订阅:一个任务要多个消费者同时处理

知识库权限:三层 ACL + Security Trimming

  1. 租户 RBAC
  2. Dataset 权限
  3. Document ACL(只能收紧,不能放宽)

检索前后双重校验:先按允许的 document_id 搜索,返回后再二次过滤。没权限的内容不进 Trace、不进 API、不喂给 LLM。

Query 增强手段

技术 优点 风险
sparse 稀疏检索 擅长专有名词、编号、术语 同义语义弱
query 改写 口语转书面检索问句 改写跑偏
multi-query 扩展 扩大召回 增加噪声与成本
HyDE 短问句也能拿高质量向量 假想文本编造错误
业务词典别名替换 快,处理简称行话 词典维护成本

数据库怎么选

  • 核心主存、多租户、半结构化元数据 → PostgreSQL
  • 单文档附属表格 → SQLite
  • 技术栈强绑定且场景简单 → 可考虑 MySQL

SQLite 存的不是原始文件。 SQLite 本质上也是一个普通文件,和 PDF、Word 没本质区别;但这里它不存 PDF / Excel 原文——源文件都在 MinIO 里。它存的是从单份原始文档里解析抽出来的表格结构化数据,相当于把文档里的表做成一个可直接查的微型数据库。

举个例子:上传《2024 财务报告.pdf》,里面有营收明细、成本汇总、利润分配三张表。解析后,对应的 SQLite 文件里会有:

  • 3 张数据表,列与原表表头一一对应
  • 各单元格的数值、文本
  • 辅助溯源:表格在原文档第几页、单元格合并信息、原格式标记等

后续问「Q3 营收是多少」,不用重新解析 PDF,对这个 SQLite 跑 SQL 就能快速拿到结果,并且还能定位回原文位置。

文档隔离、单文档查询、结构化表 + 向量放一起、离线可拷贝 → sqlite-vec

PostgreSQL 的 JSONB 性能够用。 元数据、权限、citations、Trace 等半结构化内容存成 JSONB 后,可以对 JSON 内部任意字段建索引,查询速度接近普通结构化字段,灵活和性能兼顾。

落地口诀:先把链路拆开、证据留住、门禁立住、组件可换、业务进插件。模型可以换,质量闭环不能丢。