企业级RAG+Agent编排:从多表查询到多轮对话的落地实践
业务同学在群里问了一个看起来很普通的问题上个月华东区的退货率为什么比华北区高帮我查查原因顺便翻一下上月退货处理报告。这个问题的麻烦在于它同时牵扯了三件事先要在订单表和退货表之间做关联计算再要比对两个区域的数据差异最后还得去翻一份PDF格式的退货处理报告做归因。传统对话机器人只会查接口纯大模型只会凭空编一个答案真正的企业级系统需要把RAG检索增强生成和Agent编排结合起来让模型自己决定什么时候查数据库、什么时候翻文档、怎么把多轮上下文的指代关系搞清楚。这篇内容就是把我在这类落地项目里踩过的坑、趟过的路完整拆开讲从多表查询、多文档检索、多轮对话到Agent编排与上线评测不做概念堆砌只讲能直接拿去用的方案和教训。1. 先拆需求企业级问答为什么不能只靠大模型1.1 企业数据的三种形态决定了三种技术路线我在某零售企业的落地项目里接手的第一天就发现团队对RAG的理解太乐观了。大家以为把PDF丢进向量库、接上大模型就能回答所有问题结果一测连上个月退货率是多少这种查询都答不对因为退货率这个指标根本不在文档里而是在数据库里。企业数据大体上分三种形态技术路线完全不同数据形态典型来源技术路线主要难点结构化数据订单表、退货表、库存表、客户表Text-to-SQL让大模型生成SQL去执行schema理解、多表join、权限控制半结构化数据Excel报表、CSV导出、内部周报先入库或做模板化解析格式不稳定、字段不统一非结构化数据PDF合同、制度文档、质检报告切片向量检索即传统RAG切片质量、召回率、表格提取很多团队犯的第一个错误就是用一套RAG管道去处理全部数据。实际落地没多久就会意识到结构化数据必须走查询生成的路线非结构化数据才走向量检索的路线两者在Agent里是两个独立的工具最后由编排层决定调用哪个。1.2 一个业务问题在Agent视角下是四个子任务把上个月华东区的退货率为什么比华北区高这个问题拆开你会发现它不是一个检索任务而是一个任务序列。第一个子任务是参数抽取。需要从问题里抽出时间范围上个月、区域华东区华北区、指标退货率还要理解退货率这两个字在业务里的定义是退货数量/销售数量还是退货金额/销售金额定义不同SQL就完全不同。第二个子任务是数据路由。系统要判断这个问题该查数据库还是该查文档。这里指标计算和差异对比显然是数据库的活但为什么高的归因分析需要去翻文档和质检记录。第三个子任务是查询生成。大模型需要根据表结构和业务语义生成一条能在线上库执行的SQL把订单表和退货表按订单号关联起来再按区域分组算比率。第四个子任务是文档检索。拿到华东区退货率偏高的数据结果后Agent需要去检索退货处理报告、质检记录这些文档把数据差异和文档里的原因描述拼成完整答案。我建议所有人在动手写代码之前先拿二十个真实业务问题做一遍这个拆解练习。拆完之后你会发现大多数问题都不是单一工具能解决的Agent编排不是花架子而是真实需求。1.3 先划清边界哪些场景不该交给Agent不是所有问题都应该让Agent回答。我见过最惨的一个项目是让AI直接对接财务报表口径的取数结果模型把净利润和营业利润搞混了财务部门直接叫停了整个项目。落地前必须和业务方一起定一份能答/不能答清单。涉及财务关账数据、法务合同条款解释、员工薪酬这类高风险场景要么不做要么必须在回答里附带SQL原文和文档引用来源方便人工复核。还有一类场景虽然能答但要限制频率比如每天跑一次的固定报表查询写上缓存不要每次都用大模型重新生成SQL既省成本又稳定。RAG和Agent不是炫技企业级系统的第一原则永远是稳定和可审计。2. 多表查询的路由与改写让Agent学会查数据库2.1 schema感知没有业务注释的大模型SQL质量很差刚开始做Text-to-SQL的时候我以为把数据库的表结构DDL喂给大模型就够了。实测下来在只有字段名和类型的情况下大模型生成的SQL连字段含义都经常搞错更别提业务口径。最典型的一次退货表里有个字段叫is_return直接看名字是是否退货但业务实际定义是1代表退货0代表正常模型经常把它当成布尔类型传true。还有一张表叫refund_status取值有PENDING、APPROVED、REJECTED没有注释的情况下模型会瞎猜枚举含义。解决方案是维护一份语义增强字典把表、字段、枚举值都解释清楚后拼进prompt{ table: return_records, description: 退货记录表每行代表一笔退货申请, fields: [ {name: id, description: 退货单号主键}, {name: order_id, description: 关联订单表orders.id的外键}, {name: return_qty, description: 退货数量单位件}, {name: return_amount, description: 退货金额单位元}, {name: region, description: 区域枚举值east_china华东 / north_china华北 / south_china华南}, {name: return_reason, description: 退货原因枚举值quality_issue质量问题 / size_mismatch尺码不合适 / no_reason七天无理由} ] }这层字典做得越细SQL生成效果越好。我建议这个字典由业务数据分析师和开发一起维护因为很多口径只存在于老员工的脑子里不写进去大模型永远不知道。2.2 多表join与更安全的查询生成多表查询最容易翻车的不是语法而是表关系。模型不知道return_records.order_id指向orders.id就可能生成orders.order_id return_records.order_id这种关联条件或者干脆做笛卡尔积数据量大一点的表直接跑死。所以在schema增强里我还会放一张关系图描述orders(id, order_no, customer_id, region, order_date, total_amount, status) return_records(id, order_id, return_qty, return_amount, return_date, region) 关系 - return_records.order_id orders.id一对一或一对多 - orders.customer_id customers.id join时优先使用orders.id作为关联主键实测下来明确写出用什么字段关联、是几对几关系之后SQL的join准确率明显提升乱关联的情况少了很多。另外还会在生成SQL的prompt里加一条硬约束所有联表查询必须先写出join条件和预估值不允许出现无join条件的多表select。这个约束不能靠模型自觉要在代码层面做静态检查发现from后面有多个表但没有join条件直接判定为非法SQL触发重新生成。2.3 把自然语言里的反话翻译成SQL条件业务提问很少像SQL一样直白他们最爱说不含华东除了促销品大概上个月十号左右。这些说法如果不做改写模型生成的SQL经常漏条件。我做了一张常用改写映射表喂给大模型当示例用户说法SQL语义示例不含/不包括XNOT IN / 不含华东区 →region ! east_china除了促销品NOT IN子查询product_id NOT IN (SELECT product_id FROM promotions)全部/所有产品不加where过滤所有产品的退货率 → 不要加product筛选大概/左右使用范围或聚合近似退货率大概多少 → 保留两位小数即可上个月按当前日期回推自然月order_date date_trunc(month, CURRENT_DATE - INTERVAL 1 month)等一个很容易踩的坑是时间表达。用户的上个月是自然月不是最近30天很多模型会把order_date CURRENT_DATE - 30当成上个月这样算出来的退货率口径完全不对。我在prompt里会明确写涉及月份一律用date_trunc(month, ...)处理禁止用天数差替代。还有单位归一化。用户说退货率是百分之几数据库里存的可能是0.05这种比例值生成的SQL就得乘100。这种口径问题我会直接通过schema说明写进字段的description里。2.4 权限隔离行级权限与数据脱敏企业级系统绕不开权限。不是所有业务同学都能看所有区域的数据华东的运营不应该查到华南的退货明细。我的做法是在查询执行层强制加行级过滤。数据库连接使用只读账号每次会话开始前把当前用户的可见范围写入会话变量然后所有生成的SQL都强制附带这一层过滤条件-- 假设当前用户只允许查看east_china和north_china的数据 SET app.visible_regions east_china,north_china; -- 执行前检查SQL提示模型统一加上 AND region IN (...)这个检查不依赖大模型自觉而是在SQL执行前做一次解析如果发现select的表涉及区域字段但没有带上会话变量里的可见范围就拒绝执行并触发重新生成。返回结果也做了脱敏。手机号、身份证、银行卡这些字段在schema描述里标记为sensitive: trueAgent拿到结果后会用掩码处理业务侧只能看到138****1234这种形式。2.5 用黄金SQL集做底线评测Text-to-SQL这个环节不能靠感觉差不多来验收。我建了一套黄金SQL集从真实工单选了50个高频业务问题每个问题标注标准SQL、期望返回字段和正确结果。评测分三层看。第一层是SQL可执行率生成出来能不能在数据库里跑通跑不通的直接算失败。第二层是结果一致性把生成SQL执行出来的结果和标准SQL的结果做对比看行数和关键数值是否一致。第三层是结构合理性检查是否有多余的全表扫描、是否缺少应有的过滤条件。我们当时给自己定的底线是可执行率不低于95%结果一致性不低于80%。低于这个数不要谈上线先在schema增强和prompt示例上继续调。实测加了一轮few-shot示例之后结果一致性从65%涨到82%效果非常明显。3. 多文档检索的切片与融合让Agent学会翻文档3.1 切片策略固定长度是最稳的起点但不是最优多文档RAG第一个坑就是切片。团队初期图省事把所有PDF按固定500字符切结果文档里的表格被拦腰截断语义全丢了。后来我用的是固定长度结构感知结合的策略。基础做法是正文用500 token左右切片切片之间保留50 token重叠遇到有标题层级的地方优先按章节切不让一个章节的内容被切开。表格是最大的问题直接把表格文本化送到向量库里召回效果很差我一般先把表格转成markdown格式保留行列结构再作为独立切片单独索引。页眉页脚、版本声明这类噪声要在预处理阶段去掉否则检索时会频繁命中一些无意义片段。有一个供应商合同文档页脚里公司名出现了几百次第一次检索时前五条命中的全是页脚加了一步去重和规则过滤才解决。3.2 混合检索向量不是唯一答案纯向量检索在专业文档上表现不稳定。合同里违约金和违约责任是两个不同表述但语义相近这是向量的强项但合同编号HC-2023-001这种精确字符串向量检索几乎找不到BM25却一找一个准。所以生产环境我用的都是混合检索BM25和向量召回各取top 20做分数归一化后按权重合并再取top 5送进重排模型。权重我一般设置向量0.6、BM25 0.4具体调参可以在验证集上用hit5指标来回测。重排模型这一层不能省。便宜的方案是用 bge-reranker-base效果更好的用 cross-encoder实测重排之后命中率能再涨5到10个百分点。代价是耗时增加但如果并发压力不大这笔成本值得花。3.3 多文档场景的文档路由当知识库里有几百份文档时直接把所有文档切片混在一个集合里检索效果会迅速退化因为语义相近的内容会互相干扰。比如退货政策、客户投诉处理流程、质检标准三份文档都聊退货但实际内容完全不一样。我在多文档场景里加了一道文档路由先为每份文档建立摘要和目录信息第一次检索目标锁定在应该查哪个文档而不是直接在几十万个切片里大海捞针。用户问退货处理时限是几天先匹配到《退货管理制度》这份文档再在这个文档的切片子集里检索。这个思路跟人找资料很像先知道去哪个文件夹找再打开具体文件精读。实现上也不复杂文档的标题、摘要、一级目录单独建一个小索引路由判断可以用大模型做一次分类也可以用embedding相似度先捞候选文档。3.4 检索不到时需要的是拒绝不是编造RAG最危险的时刻不是检索不到而是检索到了无关内容但模型硬要答。我在上线前专门测了一批知识库之外的问题比如问某个根本没发布过的新政策模型在最开始会试图编一个看起来合理的答案。后来的处理方式是检索结果的相似度分数要过一个阈值低于阈值时不进入生成环节Agent直接回答知识库里没有找到相关内容建议联系XXX部门确认并附上当前检索到的近似文档标题方便用户判断是不是换个问法。这个拒绝机制极其重要。企业场景里给出一个错误的制度解释比不给答案严重得多。敢说不知道是系统成熟的表现。4. 多轮对话的上下文管理让Agent记住刚才聊了什么4.1 会话状态的三层存储多轮问答比单轮难在状态管理。我通常把会话状态拆成三层短期层用Redis存最近十轮对话原文中期层把每轮问答做摘要后向量化存进记忆库长期层存用户偏好比如这个用户常看华东区数据他用万元作为金额单位。短期层直接服务当轮问答中期层用来做指代消解和个性化回答长期层是跨会话的。比如用户第二周回来说还是看华东的退货率系统能认出他上次关注的区域。注意一点记忆是双刃剑。长期记忆一旦错了用户说华东其实是想看华北Agent会被旧记忆带偏。所以我们只把明确的、经过用户确认过的信息写入长期记忆其他信息最多放中期。4.2 Query改写与指代消解多轮问答最核心的一步是Query改写。用户说那上个月呢单独拿出来没有检索价值必须结合前文补全成上个月华东区退货率是多少。我用的是一段改写prompt你是对话系统的查询改写器根据对话历史和当前用户问题生成一条完整的独立查询。 要求 1. 补全所有指代词它、这个、那边等指代对象必须还原 2. 补全缺失的时间、区域、指标 3. 保持原问题的业务含义不变 4. 如果当前问题不需要改写直接输出原问题 对话历史 用户上个月华东区的退货率是多少 助手上个月华东区退货率是3.2%比前月上升了0.5个百分点。 当前问题那华北区呢 输出这里有一个容易翻车的细节改写要发生在检索之前。如果先拿那华北区呢去检索再拿改写后的query去检索会浪费一次计算且可能污染上下文。正确的顺序永远是先改写当前问题再用改写后的完整问题去做路由、生成SQL或检索。4.3 Token控制不是把全部历史塞给模型最初我们图省事把最近十轮对话全部拼进prompt结果token消耗大不说模型注意力被分散还容易被早期错误信息干扰。现在我的策略是最近三轮原文完整保留更早的对话只保留摘要。摘要由模型在每轮结束后异步生成包含问题、答非所问的情况、用户的偏好信息。这样传入上下文基座的是最近对话历史摘要的组合既保住多轮关联性又控制长度。另外当用户明确说不是问那个重新来之类的转移信号时Agent要能清空短期上下文不让之前的指代关系继续污染后续问题。这个能力Scott不如做不到但企业场景里必须做。4.4 多轮问答的典型失败与修法我调试时发现多轮问答最大的失败点是修正场景。用户先问华东区退货率Agent答了用户又说我要的是金额不是数量如果Agent不理解这是修正就会把金额当成一个新问题去检索答非所问。修法是显式识别修正意图。在改写prompt里加一条规则如果当前问题包含我说的是不是改成这类词优先处理修正关系把用户要修正的对象和原始问题的同类结构匹配。另外当Agent发现自己上一轮的SQL口径有误时要主动道歉并重新执行查询而不是辩解。5. Agent编排与全流程串联从单次问答到复杂任务5.1 工具定义与路由判断Agent层我把能力收敛成四个工具query_database查结构化数据、search_documents查文档、calculate做简单计算和单位换算、escalate_to_human转人工。每个工具都有清晰描述给大模型做函数调用时用。路由判断是第一步。一个问题是查数据库还是查文档可以用一条指令让模型选根据用户问题判断应该调用哪个工具 - 涉及具体数值、统计、对比、趋势问题query_database - 涉及制度、流程、合同条款、报告解读问题search_documents - 两者都要先查数据再检索文档归因 - 超出能力范围escalate_to_human实测这条路由方法在大多数场景够用。但如果并发量大我建议接一个轻量意图分类模型先做好工具选择只有分类置信度低的时候才交给大模型判断成本更低、响应更稳定。5.2 复杂任务的拆解与中间结果传递复杂问题需要多个工具配合。比如分析华东区退货率为什么比华北区高并列出主要原因Agent的工作流是先用query_database拿到两个区域的退货率数据再用search_documents检索退货处理报告和质量报告最后把数据表格和文档要点拼成分析结论。中间结果怎么传很关键。我使用的是结构化传递SQL工具返回结果为JSON包含数字和表格摘要检索工具返回文档引用片段写答案时模型只基于这些结构化结果生成不允许自己脑补外部知识。我们还在这一步做了trace日志每一轮问答都记录用户问题、改写后query、路由结果、工具调用链、检索到的文档ID、最终答案。这个日志既是评测的数据来源也是出问题之后排查的救命稻草。5.3 反思机制让Agent从SQL错误中自愈SQL生成不可能一次成功。线上最常见的错误是字段不存在、函数用错、语法不兼容。我加了反思重试机制SQL执行报错时把错误信息原样返回给模型让它根据错误修正SQL最多重试两次。这个机制的实现很简单但效果很好。最开始错误信息只是执行失败模型无从下手把数据库返回的具体报错如column return_time does not exist反馈回去后模型就能自己改成正确的字段名。还要注意一种特殊情况重试两次仍然失败时不要第三次重试直接走转人工流程告诉用户这个问题我暂时无法查询已记录反馈。无限重试不仅浪费token还会让用户等待时间过长。5.4 人工复核与安全阀Agent权限上数据库连接账户始终是只读的物理上就不允许执行任何insert、update、delete。查询结果高风险时比如涉及财务数字、法律条款、客户隐私系统会强制附加该回答由AI生成建议人工复核的提示并在前端提供一键转人工入口。另外所有Agent的调用记录都会写入审计日志包含用户身份、完整对话、工具调用参数、返回结果。这部分不是可选项是安全底线。6. 上线前的评测与性能优化交给业务前的最后一公里6.1 评测数据集怎么建我会从两部分收集评测问题一是线上IM工单里真实用户问过的问题二是业务方整理的高频知识问答。目标是50到100条必须覆盖单表查询、多表join、多文档检索、多轮指代、纠错修正这几个类别。每条记录标注标准答案、答案来源哪张表哪个字段或哪份文档哪一页、期望的工具链路。比如一条评测里可以写期望调用query_database且SQL必须包含region和return_qty字段。这个评测集不仅要用来做上线验收还要在每次换模型、调prompt之后回归一遍。没有评测集的项目改一次prompt全靠肉眼感受根本不可持续。6.2 三个核心指标准确率、召回率、多轮成功率端到端准确率是业务最关心的我建议用关键事实匹配来评不要求模型逐字一致但答案里的关键数字、区域、时间必须和标准答案一致。比如退货率3.2%这个数字错了一点点就算错。检索召回率hit5单独评标准答案所指向的文档/表格是否出现在检索结果的前5条里。这个指标反映的是检索质量不掺生成环节的影响能帮你定位到底是没检索到还是检索到了但生成不好。多轮成功率评的是在第N轮用户给出指代或修正时系统是否理解对了并给出正确回答。这个指标一开始肯定难看我们当时只有55%逐步优化到70%多再多就要靠产品话术引导用户把问题问清楚。6.3 性能与成本优化性能优化先看瓶颈在哪。如果慢在向量检索上索引量化如HNSW的M参数、efSearch能显著提速如果慢在大模型生成优先控制prompt长度、减少重试次数如果慢在重排模型可以只对top 10重排取top 3不必对所有候选重排。成本优化上最重要的一招是缓存。相同或高度相似的query在30分钟窗口内命中缓存直接返回结果。当时我们用embedding相似度判重相同问题重复率达到20%以上缓存节省的成本相当可观。纯计算型问题缓存价值更高因为SQL结果本来就是确定性的。向量库选型上数据量在百万级以下用开源版本完全够配置简单、部署轻亿级以上再考虑分布式方案。不要一开始就上最重的架构企业项目最怕过度设计。6.4 实际效果与剩余风险在这个项目后期跑稳定后单表查询的SQL生成准确率能到91%多表join降到78%文档检索hit5约85%多轮指代成功率70%端到端人工综合评分4.5分满5分。这个成绩可以交给业务试用但离全自动没人管还差得远。剩余的坑也很清楚复杂多跳推理仍然不稳用户问题里超过两层逻辑就容易答偏涉及多个数据源的交叉校验容易出错权限和数据质量问题会让SQL生成再难一点。这些风险控制的方式就是高风险场景保留人工复核入口系统明确告知能力边界不承诺100%准确。最后说几点我自己在多个项目里反复验证过的体会。第一先跑通单轮再做多轮先做好单表再碰多表顺序别反。很多问题其实是单表都没做好就急着上Agent编排结果崩在基础层。第二schema增强和黄金评测集是投入产出比最高的两件事比换任何模型都见效快。第三上线不是终点每天抽20条线上日志回头检查把错误样本回流到评测集一路迭代准确率才会稳中有升。