大模型工程化落地:从 Agent Skill、长文档 RAG 到知识库更新与训练策略
大模型应用真正落地时,问题往往不只是"模型能不能回答",而是整套系统能不能稳定、准确、低成本地运行。
例如:
Agent 有上百个 Skill,是否要一次性全部加载进 Prompt?
几百页 PDF 合同,是否可以直接塞进长上下文窗口?
企业知识库发生变化,RAG 如何做到热更新?
大模型预训练阶段,为什么很多模型不用 Dropout?
这些问题看起来分散,但本质上都指向同一个方向:
大模型能力要进入真实业务系统,不能只依赖模型本身,而需要配套的工程架构、状态治理、检索策略、缓存机制和训练范式选择。
本文围绕四个典型问题展开:
markdown
1. Agent 如何加载海量 Skill
2. 大模型如何处理几百页 PDF 合同
3. RAG 如何做热更新
4. 大模型为什么预训练阶段不用或少用 Dropout
1. 大模型应用不能只靠模型本身
很多大模型系统早期实现比较直接:
把工具说明写进 Prompt
把文档切块后写入向量库
把长文档直接塞进上下文窗口
把传统训练经验直接迁移到大模型训练中
这些做法在简单场景中可以工作,但在复杂业务里会很快遇到瓶颈。
例如 Agent 工具数量增加后,Prompt 会越来越长;长文档进入系统后,解析质量和检索质量会直接影响答案可靠性;知识库更新后,如果旧知识没有失效,就会产生新旧规则冲突;训练大模型时,如果直接照搬传统 Dropout 策略,也可能影响训练效率和稳定性。
所以大模型工程化的核心,不是把所有问题都交给模型硬扛,而是要把模型放进一个可控、可验证、可演进的系统结构里。
可以先用一张表概括:
| 场景 | 常见误区 | 更合理的方式 |
|---|---|---|
| Agent Skill 管理 | 把所有工具说明都塞进 Prompt | Skill 索引、意图路由、按需加载 |
| 几百页 PDF 合同 | 直接依赖长上下文 | 结构化解析、RAG、Map-Reduce、缓存 |
| RAG 知识更新 | 新文档直接 upsert | 版本治理、原子切换、缓存失效、自动评测 |
| 大模型训练 | 默认套用传统正则化 | 根据数据规模和训练阶段选择策略 |
后面分别展开。
2. Agent 海量 Skill 管理:从全量加载到渐进式披露
在简单 Agent 系统中,工具数量可能只有几个:
天气查询
网页总结
文件搜索
邮件草稿
代码执行
这种情况下,把工具说明直接写进 System Prompt 问题不大。
但在企业级 Agent 系统中,Skill 数量可能达到几十个甚至上百个。每个 Skill 通常包含:
工具名称
工具说明
参数 Schema
调用约束
few-shot 示例
错误处理规则
权限说明
如果把所有 Skill 的完整说明一次性塞进上下文,会带来几个明显问题。
首先是 Token 膨胀。用户只是提出一个简单需求,模型上下文里却可能同时存在邮件工具、数据库工具、审批工具、订单工具、PDF 解析工具、代码执行工具等大量无关信息。这些信息大多数与当前任务无关,但仍然会消耗上下文窗口和推理成本。
其次是注意力稀释。大模型不是看到的信息越多,效果一定越好。当上下文里混入大量相似工具说明时,模型更容易出现工具选择不稳定、相似工具混淆、忽略关键约束等问题。
最后是工具说明之间可能互相干扰。多个 Skill 的说明里可能都写着"必要时主动调用工具""遇到失败后自动重试""可以根据上下文自行规划步骤"。当这些说明同时进入上下文时,模型可能会在不同 Skill 的指令之间自行调和,产生不符合预期的执行路径。
因此,海量 Skill 管理不能简单依赖"全量加载"。
更合理的方式是 渐进式披露:
先加载轻量索引
再根据意图加载相关 Skill
复杂执行时再读取完整文档
可以分成三层。
第一层是 L1 元数据索引层。
这一层不加载完整 Skill,只加载每个 Skill 的轻量信息,例如:
json
{
"name": "send_email",
"description": "发送邮件",
"tags": ["email", "gmail", "message"],
"input_hint": "需要收件人、标题、正文"
}
它的作用是让模型知道系统有哪些能力、每个能力大概做什么、什么时候可能用到。它更像一个工具目录,而不是完整工具手册。
第二层是 L2 意图理解层。
当用户输入需求后,系统先做语义路由。例如用户说:
帮我把昨天的会议纪要整理成邮件发给张三。
系统可以先判断出可能需要:
会议纪要读取能力
文本总结能力
邮件发送能力
同时排除图片生成、股票分析、订单退款、数据库管理等无关能力。然后只加载相关 Skill 的完整 Schema、参数定义和调用示例。
这相当于从:
在 200 个工具里直接选择
变成:
先路由到少量候选工具,再在候选工具里选择
思路上类似检索系统中的:
粗召回 → 精排
第三层是 L3 深水执行层。
当任务执行失败、需要读取外部文档、需要理解复杂业务规则时,系统再按需加载更深层内容,例如:
完整工具文档
错误码说明
业务规则
调用案例
历史经验
外部知识库
状态快照
这些内容不需要常驻 Prompt,而是在任务确实需要时动态检索。
整体流程可以表示为:
用户输入
↓
读取 Skill 元数据索引
↓
意图识别 / 语义路由
↓
命中候选 Skill
↓
加载候选 Skill 的完整 Schema
↓
Agent 规划并调用工具
↓
复杂场景下读取外部文档或状态快照
↓
继续执行任务
在复杂 Agent 系统中,还可以进一步引入三个设计。
第一个是 Swarm,也就是多 Agent 分工。不是让一个 Agent 同时负责所有事情,而是拆成客服 Agent、订单 Agent、退款 Agent、知识库 Agent、代码 Agent、审批 Agent 等多个专业 Agent。每个 Agent 只加载自己领域内的 Skill,从而减少上下文干扰,明确权限边界。
第二个是 Prompt Cache。一些高频且稳定的内容,例如核心系统规则、常用工具 Schema、固定业务规则、few-shot 示例,可以通过缓存降低重复编码成本和响应延迟。
第三个是 State Snapshot。复杂任务通常包含多个步骤,例如理解需求、拆解任务、调用工具、处理失败、等待确认、继续执行。如果每一步都依赖完整聊天历史,成本高且容易混乱。可以将关键任务状态保存成快照:
json
{
"user_goal": "整理会议纪要并发送邮件",
"current_step": "生成邮件草稿",
"selected_tools": ["read_notes", "send_email"],
"completed_steps": ["已读取会议纪要", "已生成摘要"],
"pending_steps": ["等待用户确认", "发送邮件"]
}
这样可以避免上下文持续膨胀、重复执行已完成步骤和任务状态断裂。
3. 长文档 RAG:几百页 PDF 不能只靠长上下文
长上下文模型提升了大模型处理长文档的能力,但它并不能单独解决所有问题。
以几百页 PDF 合同为例,系统要处理的不只是"放不放得下",还包括:
PDF 是否解析干净
条款结构是否保留
表格是否还原
跨页引用是否能关联
回答是否有证据
结论是否可复核
长合同有三个典型难点。
第一是 版面解析复杂。
合同 PDF 可能包含双栏排版、嵌套表格、扫描盖章、目录、页眉页脚、脚注、附件、跨页表格和条款编号。如果解析质量不高,可能出现标题丢失、条款层级错乱、表格行列错位、附件与正文关系断裂、主体和金额对应错误等问题。
因此,长文档处理的第一步不是 RAG,也不是长上下文,而是高质量解析。
目标是把 PDF 转成结构清晰的 Markdown、条款树或结构化数据。例如:
shell
# 第 1 条 定义
## 1.1 关联交易
......
# 第 2 条 违约责任
## 2.1 违约情形
......
解析质量决定了后续检索、总结和审计的基础质量。
第二是 长距离逻辑依赖。
合同中很多内容不是局部成立的。例如第 1 页定义了"关联交易",第 400 页出现"关联交易导致违约追责"。如果模型只看到第 400 页附近的片段,就可能无法正确理解该条款的完整含义。
合同问答需要能够关联:
定义条款
引用条款
前置条件
例外条件
补充协议
附件说明
所以不能只依赖局部 chunk,还需要支持跨章节、跨附件的逻辑关联。
第三是 低幻觉容忍度。
合同、财务、法务场景不同于普通问答。普通问答中,错误可能只是体验问题;合同审查中,错误可能带来合规风险、经济损失和责任误判。
因此系统不能只输出看似合理的答案,而要做到:
答案有出处
风险有证据
条款可追溯
结论可复核
不确定时明确说明
在工程上,可以把长文档处理拆成三类方案。
第一类是 增强型 RAG,适合处理局部精准问答。
例如:
某个条款在哪里?
付款条件是什么?
违约责任怎么写?
合同里有没有竞业限制?
某个编号对应什么内容?
增强型 RAG 通常包含三部分。
首先是 Hybrid Search。合同中既有精确关键词,也有语义化表达。
精确关键词包括:
第 12.3 条
附件二
关联交易
违约金
不可抗力
保密义务
合同编号
这类信息适合 BM25 / 关键词检索。
但用户可能会问:
如果对方不付款怎么办?
合同里对应的表述可能是:
逾期支付
迟延履行
违约责任
损害赔偿
这类问题适合向量检索。
因此更合理的方式是:
BM25 负责精确命中关键词和编号
向量检索负责语义召回
最后融合排序
其次是 父子文档分片。
如果 chunk 太小,检索精度高,但上下文不足。例如只召回一句:
乙方应承担违约责任。
这句话本身信息不足,还需要知道什么情况下违约、责任标准是什么、赔偿金额是多少、是否有免责条件。
因此可以采用:
小 chunk 用于检索
大 chunk 用于回答
例如:
子文档:第 12.3.1 条某一小段
父文档:第 12 条完整违约责任章节
流程是:
用户问题
↓
检索小 chunk
↓
命中具体条款
↓
召回对应父级章节
↓
基于完整上下文生成答案
最后是 Rerank 重排序。
第一轮检索通常会召回 Top 20 或 Top 50,但不能全部交给模型,否则会引入噪声。Reranker 可以对候选片段精排,最终选择 Top 3-5 个高质量证据交给 LLM。
第二类方案是 Map-Reduce,适合全篇总结和风险审计。
例如:
总结整份合同
生成合同摘要
提取全部风险点
审查所有合规问题
检查多章节逻辑一致性
生成审计报告
Map 阶段先把整份合同切成多个片段,对每个片段单独分析,提取关键条款、潜在风险、义务主体、金额、日期、期限、异常条件和引用关系。
每个切片可以输出结构化结果:
css
{
"section": "第 8 条 付款",
"obligations": ["乙方应在验收后 30 日内付款"],
"risks": ["未明确逾期付款利息"],
"references": ["第 12 条 违约责任"]
}
Reduce 阶段再把这些子结果合并、去重、归类、识别冲突条款,最终生成全局摘要或审计报告。
Map-Reduce 的核心价值是:
局部不漏,全局归并。
第三类方案是 长上下文 + Context Caching。
长上下文解决的是"放得下"的问题。模型可以直接读取几百页文档的解析结果,用于跨章节推理和多轮围绕同一文档问答。
但长上下文也有成本问题:
首 Token 延迟高
计算成本高
并发压力大
上下文越长,注意力越容易稀释
因此,对于同一份合同的多轮问答,可以配合 Context Caching。第一次处理文档后,把稳定上下文缓存起来,后续多轮提问直接复用缓存,从而降低重复上下文编码成本和响应延迟。
整体来看,长文档处理通常不是三选一,而是组合使用:
javascript
几百页 PDF 合同
↓
深度结构化解析
↓
生成 Markdown / 条款树 / 表格结构 / 页码索引
↓
建立索引
├─ BM25 关键词索引
├─ 向量索引
└─ 元数据索引
↓
用户提问
↓
混合检索
├─ 关键词召回
└─ 语义召回
↓
Rerank 重排序
↓
召回高质量证据
↓
LLM 基于证据回答
↓
全局审计场景使用 Map-Reduce
↓
多轮同文档问答使用长上下文 + Context Caching
可以概括为:
javascript
解析解决"读得准"
检索解决"找得准"
重排解决"选得准"
Map-Reduce 解决"看得全"
长窗口解决"放得下"
缓存解决"问得快、问得便宜"
评测解决"信得过"
4. RAG 热更新:知识库更新不是简单 Upsert
RAG 知识库不是一次性工程。真实业务中,文档会不断变化:
制度更新
合同变更
产品说明修改
接口文档升级
政策规则调整
知识库内容废弃
因此,RAG 必须支持热更新。
RAG 热更新不是简单地把新文档重新 embedding 后 upsert 到向量库,而是要解决:
文档变化如何感知
旧知识如何失效
新旧知识如何避免冲突
索引如何无缝切换
缓存如何失效
GraphRAG 如何避免全局重算
更新后如何验证效果
一个典型问题是新旧知识冲突。
假设旧规则是:
css
A 类项目不允许外包。
新规则变成:
css
A 类项目允许全面外包。
如果系统只是把新文档追加进向量库,而旧文档仍然存在,那么检索时旧规则和新规则都有可能被召回。模型看到冲突内容后,可能会自行调和,生成一个看似合理但实际错误的答案:
css
A 类项目原则上不允许外包,但在特定情况下可以外包。
所以 RAG 热更新的关键不是"新增知识",而是:
新知识及时生效
旧知识正确淘汰
检索结果语义一致
回答结果可验证
一个基础热更新链路可以表示为:
源文档变化
↓
变化感知
↓
解析文档
↓
切分 Chunk
↓
生成 Embedding
↓
写入向量库 / 关键词索引 / 元数据索引
↓
旧版本失效
↓
新版本可检索
这里有几个关键设计。
第一是 增量索引。
企业知识库可能包含几十万甚至几百万文档。如果每次更新都全量重建索引,成本非常高。可以通过 ETag、Last-Modified、文件 Hash、内容 Hash、版本号、更新时间戳等方式判断文档是否发生变化。
例如:
css
文档 A 的 hash 没变 → 不重新解析,不重新 embedding
文档 B 的 hash 变了 → 只更新文档 B 相关 chunk
核心原则是:
只更新变化的数据,不动未变化的数据。
第二是 原子化更新。
一个文档通常会被切成多个 chunk:
doc_001_chunk_001
doc_001_chunk_002
doc_001_chunk_003
如果新版本文档结构变化了,原来有 20 个 chunk,新版本只有 16 个 chunk。如果只 upsert 新 chunk,旧的 17-20 号 chunk 可能仍然留在库里,形成孤儿 chunk 和旧知识残留。
更稳妥的方式是:
先让旧版本失效
再写入新版本
最后切换到新版本
或者:
sql
delete old chunks
+
insert new chunks
同时要保证更新过程具备原子性,避免线上查询看到半新半旧的数据。
第三是 元数据生命周期管理。
每个 chunk 不应该只有文本和向量,还应该携带生命周期元数据:
json
{
"doc_id": "policy_001",
"chunk_id": "policy_001_v3_chunk_008",
"version": 3,
"status": "active",
"valid_from": "2026-07-08",
"valid_to": null,
"source_hash": "xxxx",
"updated_at": "2026-07-08 10:00:00"
}
其中 status 可以包括:
active:当前生效
expired:已经过期
staging:更新中,还不能被正式检索
deprecated:废弃,但保留审计
检索时增加过滤条件:
ini
只检索 status = active 的 chunk
只检索 latest version
只检索当前时间有效的 chunk
这样可以从检索层避免旧知识污染当前回答。
第四是 缓存失效。
即使向量库已经更新,系统中仍然可能存在旧知识:
用户当前 Session 缓存
Prompt Cache
Agent Memory
长期记忆
上一次检索结果缓存
应用层 Redis 缓存
如果这些缓存没有同步失效,就会出现:
向量库是新的
模型上下文里仍然是旧的
因此,热更新还需要处理:
文档版本变更 → 清理相关检索缓存
规则变更 → 标记旧会话需要重新检索
长期记忆中引用旧知识 → 降权或失效
第五是 影子索引与蓝绿切换。
生产环境中,不建议直接修改线上索引。更稳妥的方式是构建影子索引:
线上索引 v1 正在服务用户
↓
后台构建影子索引 v2
↓
对 v2 做一致性校验
↓
校验通过后,将检索路由从 v1 切换到 v2
↓
v1 保留一段时间用于回滚
这类似服务发布中的蓝绿部署,可以避免半新半旧索引,也方便失败回滚。
第六是 自动评测。
知识库更新后,必须验证更新质量。评测内容包括:
以前能答对的问题,现在是否还能答对
新知识相关问题是否能答对
旧知识是否还会被错误召回
冲突问题是否能正确区分版本
引用来源是否正确
可以维护一套测试集:
css
[ { "question": "A 类项目是否允许外包?", "expected_answer": "允许,但需要审批。", "expected_source": "policy_v3_section_2" }, { "question": "旧版 A 类项目规则是否仍然生效?", "expected_answer": "不再生效,已被新版本替代。" }]
更新流程应当是:
更新
↓
评测
↓
通过后上线
↓
不通过则回滚或进入人工审核
如果是 GraphRAG,还要注意图谱重算问题。GraphRAG 中通常包含实体、关系、社区和社区摘要。如果某个实体或关系发生变化,可能影响节点关系、边权重、社区划分和社区摘要。如果每次变更都全局重算,成本和延迟都会很高。
因此 GraphRAG 热更新需要尽量支持局部更新:
该实体
该实体的直接关系
该实体所在局部社区
相关摘要
相关索引视图
也就是图谱变化在哪里,就优先更新哪里,而不是每次重建全局图谱。
完整链路可以总结为:
vbnet
源文档系统
↓
变更感知
├─ ETag
├─ Hash
├─ Webhook
└─ CDC
↓
增量解析
↓
Chunk 切分
↓
Embedding 生成
↓
写入影子索引
↓
旧版本标记为 expired / superseded
↓
新版本标记为 staging
↓
自动评测
├─ 召回测试
├─ 问答测试
├─ 引用测试
└─ 冲突测试
↓
评测通过
↓
蓝绿切换 / Alias 切换
↓
新索引上线
↓
清理相关缓存
↓
保留旧版本用于审计和回滚
可以用一句话概括:
RAG 热更新不是 upsert 一下,而是版本、索引、缓存、评测一起联动。
5. 训练策略变化:为什么大模型预训练阶段少用 Dropout
Dropout 在传统深度学习中是非常常见的正则化手段。
它的基本思想是:
训练过程中随机丢弃一部分神经元激活值,
让模型不能过度依赖某些固定特征。
在 CNN / RNN 时代,Dropout 经常用于缓解过拟合。传统模型经常面对:
数据规模有限
训练轮次较多
样本重复出现频繁
模型容易记住训练集
泛化能力容易下降
在这种情况下,Dropout 通过引入随机扰动,降低模型对局部特征的依赖,从而提升泛化能力。
但在很多大模型预训练中,Dropout 通常会设置为 0 或接近 0。这不是因为 Dropout 技术失效了,而是因为训练范式发生了变化。
核心变化是:
从数据稀缺,走向数据充足。
大模型预训练阶段少用 Dropout,主要有三个原因。
第一,大模型预训练通常接近单轮遍历。
传统训练中,一个数据集可能会被训练很多轮。例如:
10 万张图片
训练 100 个 epoch
同一张图片会被模型反复看到
这种场景容易过拟合。
大模型预训练通常使用数千亿甚至上万亿 token,很多样本只出现一次,或者出现次数非常有限。模型不是在小数据集上反复记忆,而是在不断接触新的数据分布。这使得传统意义上的过拟合风险相对降低。
第二,海量数据本身就是强正则化。
预训练语料通常包含:
网页文本
书籍
代码
论文
论坛
问答
多语言文本
不同领域知识
不同表达风格
这种大规模、多来源、多风格的数据,本身就会迫使模型学习更通用的规律。
可以理解为:
小数据训练:需要 Dropout 人为制造随机性
大数据预训练:数据本身已经提供足够多样性
因此,在大模型预训练阶段,Dropout 的正则化收益会下降。
第三,Dropout 会影响训练效率和稳定性。
Dropout 会随机丢弃激活值,在大规模预训练中可能带来:
有效模型容量波动
梯度噪声增加
收敛速度变慢
训练方差变大
分布式训练稳定性下降
大模型训练成本极高。如果 Dropout 导致更多训练 step,训练成本会明显上升。
关闭 Dropout 后,模型有效容量更稳定,训练过程更容易配合更大的 batch size,从而提高吞吐量、GPU 利用率和分布式训练稳定性。
同时,没有 Dropout 的随机扰动,模型每一步都可以完整利用网络容量,梯度信号更稳定,收敛路径更直接。
需要注意的是,Dropout 不是被废弃,而是变成了按场景使用。
预训练阶段通常具备:
数据规模极大
样本来源多样
训练接近单轮遍历
过拟合风险相对较低
训练稳定性和吞吐效率非常重要
因此很多大模型会采用:
ini
Dropout = 0
或:
Dropout 接近 0
但微调阶段不同。微调数据通常规模更小,例如几千条、几万条、几十万条特定领域数据。这时候过拟合风险可能重新出现。因此微调阶段仍然可能使用:
Dropout
LoRA Dropout
Weight Decay
Early Stopping
冻结部分层
数据增强
可以用表格总结:
| 阶段 | 数据规模 | 过拟合风险 | Dropout 策略 |
|---|---|---|---|
| 预训练 | TB 级甚至更大 | 相对较低 | 通常设为 0 或接近 0 |
| 微调 | MB / GB 级,甚至更小 | 重新上升 | 视情况启用 Dropout |
| 小样本微调 | 样本很少 | 较高 | 可以考虑 Dropout / LoRA Dropout |
| 大规模指令微调 | 数据较多 | 中等 | 根据验证集表现调整 |
| 领域微调 | 数据分布集中 | 可能较高 | 需要观察是否过拟合 |
最终原则是:
数据不足时,Dropout 用来防过拟合;
数据充足时,数据本身就是正则化;
预训练阶段更关注稳定性和效率;
微调阶段是否使用 Dropout,需要根据数据规模和过拟合风险判断。
总结
Agent Skill 管理、长文档 RAG、RAG 热更新、Dropout 策略,看起来属于不同方向:
Agent 架构
长文本处理
知识库更新
模型训练
但它们背后有一个共同点:
大模型应用不能只靠"模型能力"硬扛,而要根据场景选择合适的工程策略。
可以进一步概括为:
Agent 系统:按需加载,而不是全量暴露;
长文档系统:结构化解析,而不是直接硬塞;
RAG 系统:版本治理,而不是简单追加;
训练策略:匹配场景,而不是固定套用。
也可以总结成一句话:
大模型工程化的关键,是把模型能力放进可控、可验证、可演进的系统结构里。