为什么我放弃了 RAG:一个工程标书 AI 系统的技术选型复盘

简介: 做技术标最耗时间的不是专业判断,是翻历史标书、换项目名、调章节顺序。我给这个场景做了个对话式的编制系统并开源,但在知识库这一层,我最后把整个向量库删掉了——这篇复盘聊聊为什么 RAG 对不上强结构文档。

先说这个痛点有多普遍

做工程投标的人对下面这个场景应该不陌生。

一份施工组织设计(技术标),几百页、几十万字。真正"因这个项目而异"的内容——重难点分析、针对性措施——可能不到 20%。剩下 80% 是:翻历史标书、翻工法库、把项目名和数字换一遍、调整章节顺序。

换一个项目重来一遍,换一个标段重来一遍。经验留不下来。

最难受的是时间分配反了:精力全耗在"找素材 + 复制粘贴"上,真正需要人来判断的地方反而没时间深想。

我做了个工具来解决这件事,最近把它开源了:

BidCraft 标书匠 —— 对话式工程标书编制智能体。Apache-2.0,免费开源。

BidCraft

它能做的事:上传招标文件 → 自动解析出标段划分、评分办法、技术要求、规定的目录结构 → 生成目录 → 逐章生成编写思路 → 生成正文 → 你在编辑器里改 → 不满意就对话下达指令。

界面长这样(演示项目是虚构的):

工作台


但这不是一篇"看我又做了个 AI 套壳"的文章

先说结论:这个项目的 LLM 走的是阿里云百炼的 qwen 系列(OpenAI 兼容接口,所以换模型只要改 LLM_BASE_URL),文档解析用 MinerU。

但重点不是我又做了个 AI 套壳 —— 是我在知识库这一层踩的坑。

因为这个项目最有意思的技术决策,是我最后没有用 RAG——而且不是一开始就没用,是用到一半把整个向量库删掉了。

如果你正在做"让 LLM 读懂一堆私有文档"这类需求,这段经历可能对你有用。


起点:我一开始是打算用 RAG 的

做知识库的第一反应几乎都是 RAG:

文档 → 切片 → 向量化 → 存向量库
问题 → 向量化 → 相似度检索 → Top-K 片段 → 塞进 prompt

这条路成熟、有大量现成方案、社区验证充分。我一开始就是这么立项的,.env 里甚至预留了 embedding 模型和向量维度的配置,代码里也实装了"三级匹配都零命中时用向量补召回"的兜底路径。

然后我发现它在这个场景下对不上。


问题一:这不是"问答",是"照章办事"

RAG 的隐含假设是:用户有个问题,答案散落在文档某几段里,检索出那几段就够了。

但"写一章施组"不是这样。

当我要写「2.3 隧道超前地质预报」这一节时,我需要的不是"文档里关于地质预报最相似的 5 个片段",我需要的是:

  • 这一节应该怎么写(先写什么、后写什么、详略怎么分配)
  • 有哪些典型表格(预报手段一览表?频率表?)
  • 有哪些典型图(预报流程图?孔位布置图?)
  • 适用范围(什么情况下用 TSP,什么情况下用地质雷达)

这些是结构化的编写知识,不是一堆文本片段。

把它切成 512 token 的块再靠相似度捞回来,捞回来的是"文本",不是"知识"


问题二:切片的边界,恰好切在最不该切的地方

这个我实测过。一份历史施组里,「超前地质预报」这一节的真实结构是:

超前地质预报
├─ 预报目的与依据
├─ 预报手段(TSP / 地质雷达 / 超前钻探 / 瞬变电磁)
│   ├─ 各手段原理与适用条件
│   ├─ 各手段实施方法
│   └─ 各手段成果形式
├─ 预报频率与段落划分
├─ 预报成果的反馈与施工调整
└─ 组织机构与职责

固定长度切片之后会发生什么:

  • "TSP 的适用范围"和"地质雷达的适用范围"被切到了不同的块里
  • 块与块的边界完全取决于字符数,跟章节结构毫无关系
  • 检索召回了三块,但缺了"频率与段落划分"那块——因为那块单独看相似度不高

结果就是:模型拿到了一堆相关知识,但拿不到完整的写法。

写出来的东西,像是从几篇不同文章里各抄了一段拼起来的。


问题三(最本质):这个场景里,"怎么写"比"写什么"重要得多

RAG 解决的是"写什么"(事实性知识)。

但在标书编制里,"怎么写"的权重远大于"写什么":

  • 一个「施工组织安排」章节,专业内容谁都知道,难的是怎么把这个项目的具体情况组织成一段有说服力的文字
  • 一篇历史施组的价值,很多时候不在于它写了什么数字,而在于它的行文习惯、章节展开方式、详略取舍

这些是风格与方法论层面的知识——切碎了就没了。


所以我换成了什么:LLM-Wiki

核心思路一句话:

不要在生成时去"检索文本",而要在解析时就把知识整理成"条目",生成时让模型读条目做判断。

具体做法分三步。

1. 历史施组 → 知识页(不是切片,是提炼)

一份历史施组进来,先按标题结构切章建树,然后逐章调一次 LLM 提炼,产出一条结构化知识页:

{
   
  "title": "隧道工程 > 超前地质预报",
  "chapter_type": "施工方案",
  "tags": ["隧道"],
  "content": {
   
    "method": "先讲预报目的与依据,再按手段分述(TSP/地质雷达/超前钻探),最后写成果反馈与动态调整",
    "key_points": ["长短结合、多手段相互印证", "断层破碎带加密探测"],
    "tables": [{
    "name": "超前地质预报手段一览表", "columns": ["手段","预报距离","探测对象","频率"] }],
    "images": [{
    "name": "超前地质预报工艺流程图", "caption": "预报工作流程" }],
    "usage": "适用于隧道超前地质预报类章节,尤其需要说明多种手段组合与成果反馈机制时"
  },
  "source_location": "第 3 章 3.4 节"
}

注意几个字段:

  • method 是"怎么写" —— 这一节的展开方式。这是 RAG 拿不到的东西。
  • tables / images 是清单 —— 不是图表本身,而是"这里该有什么表/图"的提示
  • usage 是使用方式 —— 决定它什么时候该被引用(下面会讲这个字段为什么重要)

2. 项目文件 → 章节索引(不切片,索引 + 完整原文)

招标文件、指导性施组动辄几百页。全给塞不下,切片又会切碎。

所以走两段式

  1. 解析时:每一章调一次 LLM,生成 章节全路径标题 + 概要 + 适用范围 作为索引
  2. 使用时:AI 看索引选章节 → 选中后取该章节的完整原文(不截断、不切片)

这个思路本质上是借了 skill 的思路:先给模型一份目录和"这节讲什么"的说明,让它自己判断"我要写这个章节,应该看哪几节",然后给完整的。

3. 工艺工法 → 一工法一条概况

更简单:一篇工法一条"名称 + 概况 + 适用范围",用时 AI 选,选中的整篇注入


效果差异

RAG 切片 LLM-Wiki
知识单位 固定长度文本块 结构化条目(含"怎么写")
检索方式 向量相似度 AI 读索引/使用方式做判断
上下文质量 相关但不完整、边界随机 完整且结构对应章节
可追溯性 知道来自哪个文件,不知道来自哪一节 精确到"某文件 · 某章节"
成本 需要 embedding + 向量库运维 解析时多花 LLM 调用,无额外基础设施

最后两行其实很关键:

做标书的人必须能核对来源。 向量相似度 0.83 对编制人员毫无意义;
"这条写法来自《XX 项目施组》第 3.2 节"才有意义。

而且我少维护了一套基础设施。 没有向量库、没有 embedding 服务、没有索引重建。

顺便说一句:我后来把向量库整个删掉了——llm.embed() 直接 raise NotImplementedError
那个"零命中时向量兜底"的路径实测几乎从没被有效触发过,触发的场景往往是"本来就没有相关知识",
硬捞回来的东西反而引入噪声。这个兜底从一开始就是心理安慰。


顺着这条路线,衍生了几个有意思的设计

① 三级漏斗:让便宜的手段先拦掉大部分

知识页有了,"写某一章时怎么找到该用的那条"又是问题。

朴素做法两种都不行:全给(上下文浪费、模型在长列表里选择质量下降)、让模型自由发挥(它会说要库里根本没有的东西)。

最后做成三级漏斗(matching.py):

待写章节路径
   ↓
第一级 · 专业标签过滤        「隧道」只保留 tags 含"隧道"的页
   ↓  1000 条 → 几十条
第二级 · 标题相似度(字符 bigram Dice)
        ≥ 0.80 直接命中 / 0.35~0.80 进模糊带 / < 0.35 淘汰
   ↓
第三级 · LLM 终判(只在模糊带触发,每章最多一次调用)

关键点:大部分章节在第二级就结束了,根本不需要调 LLM。

usage 字段:不要让模型猜用途,把用途写下来让它读

第三级要给模型候选列表让它判断。一开始只给标题,结果判断得很随意——光看标题,"超前地质预报"和"超前支护"确实很难说哪个更该用。

后来把知识页的 usage(使用方式/适用场景) 加进判断输入,准确率明显提升:

候选 1:隧道工程 > 超前地质预报
       使用方式:适用于隧道超前地质预报类章节,尤其需要说明多种手段组合时

候选 2:隧道工程 > 超前支护
       使用方式:适用于洞口段、断层破碎带的超前支护措施章节

模型看到"使用方式"就知道该选哪个了。

这个字段现在是整个知识页 schema 里性价比最高的一个——它把"什么时候该用我"这个只有人类知道的信息,显式写进了数据里。

③ 防"抄错 ID":给模型看序号,不给真实 ID

模型会编造 ID。给它看 id=317 / id=892,它可能返回不存在的 id=445,或者把 317 记成 371。

解决很土但有效:给模型看的是连续序号,程序再把序号映射回真实 ID。

候选 1: 超前地质预报
候选 2: 超前支护

模型只回答"候选 1",抄错的概率大幅下降,而且即使抄错也能被程序立刻发现(序号越界),不会静默写入错误引用。

④ 上下文取舍:什么该全给,什么必须精挑细选

这条界线我划得很明确:

约束类和事实类的东西,宁可全给也不漏;素材类和参考类的东西,必须精挑细选,给多了有害。

全给的:标段档案(放在最前面当"硬边界")、全局事实表、用户本次要求、同级章节标题。

精挑的:知识页、项目文件原文、工法。

其中有个反直觉的决定:知识页的 method(怎么写的)反而不注入正文生成

因为实测发现——一旦把历史章节的"编制方式/要点"结构给模型,它会照着这个结构复制,写出来的东西和历史上那一章高度同构,丧失本项目应有的针对性。

最终方案是"给风格,不给内容;给形状,不给数字":

  • 只给该历史章节的完整原文作行文风格示范
  • 只给表格/图片清单(该配什么表、什么图),不给历史表格里的数据

⑤ 降幻觉:把"编内容"转化成"待办事项"

AI 写几十万字专业文档,指望它 100% 正确不现实。所以目标不是"消除幻觉",而是把人工核对的工作量压到最低

最有效的一条硬规则是:

所有数字必须来自「本项目事实」或「本项目文件原文」;两者都找不到的数值,一律标注 [待补充],禁止借用历史资料里的数值。

设计意图很直接:把"幻觉"转化成"待办事项"

  • 模型编一个数字 → 你无法察觉,直到出事
  • 模型标 [待补充] → 你一眼看到,去查一次,填上

宁可留一个显眼的空,也不要填一个看不出问题的错。

而且这些数字单独抽出来存进"全局事实表",标明出处、人工可改、改动不会被后续重抽取覆盖。这样"同一个工期在文档里出现三种写法"这种问题从机制上就不会发生。


代价是什么(诚实版)

LLM-Wiki 不是没有代价,我不想只讲好的一面:

  1. 解析成本更高。一份 500 页的历史施组,切片入库几乎免费;逐章提炼要几百次 LLM 调用。所以做了 20 并发和 MD5 缓存(同文件不重复解析)。
  2. 解析质量决定上限。提炼出来的知识页质量差,后面全差。这也是提炼 prompt 改了很多轮的原因。
  3. 放弃了"模糊联想"能力。RAG 有时能捞到"字面不像但语义相关"的内容,章节索引做不到——索引概要里没写的就找不到。靠"概要写全一点"来补偿。
  4. 知识库扩容不是即时的。新加一份历史施组要等提炼跑完才能用(几百页十几分钟)。

这些代价我认为值得,因为换来的是上下文质量。而"生成几十万字专业文档"这个场景下,上下文质量基本等于输出质量


其他值得一提的

对话式而不是按钮式。 编制是"边想边改"的过程,不是一条流水线。所以交互做成了对话:直接说"第三章重写一下,突出隧道超前地质预报"、"把工期那几个数字统一成 26 个月",模型自主决定调哪些工具(目前 15 个)。

特别做了执行核验:模型声称"已生成第三章"时,系统会核验本轮是否真有对应的工具成功记录,没有就否决——因为对话式最大的可用性风险不是"AI 答不好",而是"AI 说它做好了但没做"

docx 解析路线改了三版。 最终结论是:有样式可读的(docx)直接读样式,没有的(PDF/图片)才交给 OCR。中间一度改成"docx 转 PDF 再 OCR",结果发现既依赖本机 Office(部署门槛高),又主动丢掉了 docx 本来的结构信息,绕一圈精度还降了。


开源信息

仓库https://github.com/zeronezer/bidcraft

  • Apache-2.0 —— 可商用、可修改、可闭源分发,唯一要求是注明来源
  • 完全免费,无付费墙、无企业版
  • 数据全部在你自己服务器上,除了你主动调用的 LLM 和文档解析服务,没有数据外流
  • 不锁定模型:走 OpenAI 兼容接口,改个 LLM_BASE_URL 就能换任何厂商

技术栈:FastAPI + Vue 3 + TipTap + LangGraph + MySQL + MinerU + 阿里云百炼(通义千问 qwen 系列)

为什么是百炼:这个场景每个标段要处理几十万字文档,还要反复调用做知识提炼和正文生成,对长上下文调用成本都敏感。qwen 的长上下文实测一次能吃下 40 万字不截断,配合 OpenAI 兼容接口,切换成本几乎为零。

部署最省事的方式:把仓库丢给你的 AI 编程助手(项目自带 AGENTS.md,Claude Code / Codex / Cursor / Trae 等会自动读取),说一句"按 AGENTS.md 把这个项目跑起来"就行。

仓库里还有个 思路/ 目录,把这篇文章讲的东西展开成了 7 篇完整复盘,包括每个决定的代价和踩过的坑。


如果这个项目对你有用,欢迎给个 Star。 也欢迎来提 Issue 聊聊你在自己领域里做知识库的经验——我现在越来越觉得,"该不该用 RAG"这件事,答案取决于你的文档是不是强结构的。这个判断我可能还没想到所有情况。


目录
相关文章
|
2月前
|
弹性计算 运维 安全
2026 阿里云云服务器ECS免费试用介绍,免费额度300元功能规则介绍
对于云计算初学者、个人开发者、在校学生以及初创小微企业而言,想要系统学习云服务器运维、搭建测试项目、部署个人服务,往往会担心付费服务器成本过高、配置不合适、项目测试失败造成资金浪费。2026年阿里云持续开放**个人用户ECS云服务器300元免费试用额度**,作为普惠性上云福利,该权益无套路、零门槛,实名新用户即可一键申领,全额抵扣ECS实例、带宽、流量产生的费用,帮助用户零成本体验商用级云服务器性能,完整掌握云上部署、运维、项目开发全流程。本文将全方位详解2026年阿里云ECS免费试用权益细则、额度规则、机型配置、申领步骤、适配场景、高频避坑要点,让每一位用户最大化利用免费资源,零基础安全、稳
411 1
|
存储 缓存 测试技术
ZYNQ-AXI Interconnect IP介绍
ZYNQ-AXI Interconnect IP介绍
3838 0
ZYNQ-AXI Interconnect IP介绍
|
Ubuntu
Ubuntu 安装使用yum
Ubuntu 安装使用yum
3742 0
|
5月前
|
弹性计算 安全 双11
阿里云2026优惠券领取与使用:学生300元无门槛,个人企业满减券,企业迁云与出海补贴详解
2026年阿里云推出多样化优惠券活动,包括学生300元无门槛券、企业迁云补贴与出海扶持补贴、新用户满减券等。学生需完成认证领取300元无门槛券,适用于多数公共云产品;企业用户可申请迁云补贴(最高达5亿元)和出海扶持补贴(3500元至10万元不等);新用户可在大促期间领取满减券,用于新购或升级订单。用户可在阿里云控制台管理优惠券,并在购买云产品时自动匹配抵扣。
958 1
|
7天前
|
算法 API 调度
现代GPU渲染队列积压与输入延迟工程调优:从渲染管线帧挂起到端到端响应链路实战
本文深入剖析现代实时图形渲染管线中GPU满载时的渲染队列积压机理,拆解预渲染帧对端到端输入延迟的影响,结合DirectX 12/Vulkan显式Fence同步屏障及Nvidia Reflex/Ultra低延迟技术,提供工程级系统响应与动态帧步调调优实战方案。
|
8月前
|
人工智能 前端开发 开发工具
国内独立开发者社区推荐
国内优质独立开发者社区推荐:涵盖SoloDev、掘金等综合平台,新趣集、创造者日报等产品展示渠道,SegmentFault、CSDN、开源中国等技术交流阵地,以及TapTap、Unity中国等游戏开发专区,还有V2EX、InfoQ、小红书等多元社区,助力开发者成长与创新。
|
存储 SQL 缓存
快手:从 Clickhouse 到 Apache Doris,实现湖仓分离向湖仓一体架构升级
快手 OLAP 系统为内外多个场景提供数据服务,每天承载近 10 亿的查询请求。原有湖仓分离架构,由离线数据湖和实时数仓组成,面临存储冗余、资源抢占、治理复杂、查询调优难等问题。通过引入 Apache Doris 湖仓一体能力,替换了 Clickhouse ,升级为湖仓一体架构,并结合 Doris 的物化视图改写能力和自动物化服务,实现高性能的数据查询以及灵活的数据治理。
1267 3
快手:从 Clickhouse 到 Apache Doris,实现湖仓分离向湖仓一体架构升级
|
SQL Ubuntu 关系型数据库
PostgreSQL介绍和PostgreSQL包安装
PostgreSQL 是一个功能强大、可扩展的开源关系型数据库系统,以其可靠性、数据完整性和高性能著称。它支持复杂查询、事务、多版本并发控制及丰富的数据类型,适用于各种应用场景。本文介绍 PostgreSQL 的核心特性,并详细说明在多种 Linux 发行版上的安装与配置方法,帮助用户快速部署和使用该数据库系统。
1307 0
|
开发工具 内存技术
zynq程序固化补充篇: 不切换启动模式强制烧写
使用SDK2018.2第一次进行烧写 Flash,在qspi模式下会报错,只有切换至jtag模式下才可以进行烧录,后续的再次烧录不会出现类似问题。但是调试的时候必须切回jtag模式(将BOOT MODE 5拉低才可以调试)
4462 1
zynq程序固化补充篇: 不切换启动模式强制烧写

热门文章

最新文章