分层消除不确定性(五·终)|完全脱离 LLM 的确定性判定 + 评测闭环
概述
前文三层方案仍会让LLM参与判断流程,存在概率性出错风险;第四层核心思路:把结构化运算、业务口径校验全部交由工程代码执行,LLM仅负责识别用户意图、编排工具调用参数,不参与任何数值、业务规则判定。
整套方案由三部分构成,存在明确依赖顺序:
data_filter:通用结构化数据处理,解决算错、漏字段、图表错乱;
业务硬约束:兜底LLM无法识别的专属业务口径;
评测闭环:提供量化跑分能力,让所有优化动作可验证、可迭代。
全文最大复盘结论:如果重新落地业务智能体,第一件事搭建评测闭环,再做业务逻辑优化,能大幅缩短迭代成本。
一、什么算「完全脱离 LLM 」
本层与前三层核心差异:本层处理不依赖 LLM。LLM 仍负责意图识别与工具编排,但不再接触数据、不做数值运算、不判业务规则。四层路径核心区别汇总如下:
分层阶段 LLM参与形式 核心定位
前置拦截(第二篇) LLM不做决策,仅消费拦截结果 提前过滤无效/高危请求
收窄决策空间(第三篇) LLM主导决策,可选范围被压缩 降低模型选错概率
兜底修复(第四篇) LLM先输出结果,代码事后校验纠错 事后修复模型错误
确定性判定(本篇) LLM完全不进入判断链路 纯代码执行计算、口径校验
统一分工原则:LLM定「做什么」,工程侧定「怎么算」。
举例:用户提问“统计6月工单完成量并生成柱状图”
LLM仅识别需求,输出data_filter工具调用参数;过滤、分组、计数、绘图全部由Java代码执行,模型全程不接触原始数据、不做任何数学运算。
二、data_filter:结构化数据从 LLM 侧完全搬到工程侧
背景痛点
LLM在文本流中处理结构化数据存在天然缺陷:数据量大易漏字段、分组统计串维度、排序逻辑混乱,仅靠提示词无法根治。
解决方案:新增纯Java工具data_filter,将所有结构化数据运算迁移至工程层;LLM仅负责发起工具调用、传递操作参数,过滤、聚合、排序、绘图等确定性计算全部由代码承载。
2.1 五大原子操作(ToolCallback)
对外仅暴露5类基础算子,支持自由链式组合,LLM无需在上下文内完整推演全链路,仅需分步下发指令:
操作 输入参数 输出 业务用途
filter 字段 + 条件表达式 过滤后数据子集 缩小数据集范围
group_count 分组维度字段 {分组值: 统计数量} 按维度聚合计数
sort 排序字段 + 升降序标识 完整有序数据集 数据排序
count 无(基于当前上下文数据集) 数据总行数 统计总量
to_chart 图表类型 + 字段映射关系 标准化图表JSON 生成可视化图表
组合调用示例
用户需求:筛选2026年6月工单,按工单状态分组统计,输出柱状图
执行链路:filter(创建时间=2026-06) → group_count(status) → to_chart(柱状图)
to_chart彻底解决前文图表JSON字段错位问题:固定映射规则由代码维护,不再依赖LLM拼接图表结构。
2.2 三条核心隔离约束
三条约束共同将数据、计算逻辑与LLM彻底隔离,保障结果稳定:
原始数据不经过LLM,统一从SessionContext读取
SessionContext为会话级隔离存储,单用户单次查询的数据独立存放,多用户并发不会串数据。
data_filter入参仅携带字段名、条件,不含原始业务数据:
规避海量业务数据挤占上下文token;
杜绝LLM转述数据时丢字段、篡改数值、丢失小数精度。
仅处理存量数据,不负责底层数据查询
数据查询由MCP/Skill业务工具完成,查询结果写入SessionContext后,data_filter才可触发。
实现关注点分离:业务工具负责“取数”,data_filter负责“加工数”,职责互不越界。
中间操作回写上下文,终态操作不落地
中间操作filter/sort:处理完成后替换SessionContext当前数据集,支持连续链式调用,无需LLM记忆上一步结果;
终态操作count/group_count/to_chart:输出结论供LLM组装回答,不更新上下文,不可作为后续计算原料。
2.3 LLM与工程侧完整协作流程
用户输入业务查询;
PreprocessEngine 预处理:实体识别 + 消歧(有歧义则短路追问);
LLM(ReactAgent ReAct 循环)识别意图,判断需要获取数据;
LLM 发出 tool_call,调用 mcp_* 或 skill_* 工具获取原始业务数据;
工具执行完成后,结果经 ToolResultNormalizer 归一化为 {records, total, summary} 并写入 SessionContext(以 threadId 为 key);
(可选)LLM 再次发出 tool_call,调用 data_filter 对 SessionContext 中的数据进行 filter → group_count → sort → to_chart 等二次加工;
data_filter 的中间结果回写 SessionContext,支持链式调用;
LLM 综合所有工具返回结果,生成最终自然语言回复。
2.4 收益说明
对照实验条件:200条结构化数值类测试用例,仅替换原生LLM计算逻辑、接入data_filter,模型、提示词、采样参数完全不变。
准确率提升Delta:+10~13pt
收益来源:一次性解决数值计算错误、字段遗漏、图表结构错乱三类高频问题,把结构化数据处理从概率系统转为100%稳定的确定性系统。
局限性:data_filter 仅处理已加载至 SessionContext 内存的数据集,不连接数据库、不做 SQL 优化。超大数据集(万行级)需由上游 MCP / Skill 先做分页预取再写入上下文,data_filter 本身不解决数据量问题。
三、特定场景硬约束
data_filter解决通用数值运算问题,但仍存在大量业务专属口径场景无法通用化处理,必须在代码层硬编码约束兜底。本节围绕三点展开:硬编码必要性、低成本管理方案、暂缓落地规则引擎的理由。
3.1 必须硬编码的三类场景
核心矛盾:业务真值口径属于企业内部定义,无法通过通用语言语义让LLM稳定识别,这类场景存在三大共性:
业务口径与通用语义割裂
例如工单场景“完成”,通用语义仅代表结束,但业务明确限定status=0;LLM无法自主推断业务状态定义,无天然语义映射通路。
错误无异常告警,隐蔽性极强
工具调用流程正常、返回格式合规,仅业务数字错误;普通响应校验只能识别格式崩溃,无法判断业务口径对错,模型无法自主纠错。
容错代价严重不对称
单次正确回答无正向感知,一次口径错误会直接丧失用户对产品数据可信度;软约束“大概率正确”无法抵御此类高风险场景。
3.2 硬编码的价值取舍
硬约束本质:将业务口径判断从概率LLM体系迁移至确定性代码。
优势:if-else规则100%稳定触发、无语义漂移、可单条单元测试、支持回归验证;
短板:新增业务规则需要服务发版,灵活性弱。
企业BI智能体场景下,“保证核心指标绝对准确”优先级高于快速迭代,该取舍具备工程合理性。
3.3 硬约束轻量化管理:记账法
不急于抽象、不直接上规则引擎,采用低成本文档记账管控硬约束持续膨胀,避免无边界补丁堆积。
新增任意一条硬约束,同步记录5项信息,Markdown表格即可落地:
硬约束 ID 业务口径 为什么 LLM 判断不了 理想解法 引入日期
HC-001 工单场景"完成"指 status=0 语料里"完成"的语义与业务状态无桥可通 BI 层暴露口径元数据,替代硬编码 2026-06-15
HC-002 "本月"以创建时间+完成时间双口径回答 LLM 无法可靠选择时间维度,业务上两者都可能是用户意图 需求侧规定统一表述,或在 BI 层拆成两个显式指标 2026-06-20
配套落地规范:
维护人:后端开发;
复盘周期:每2周统一梳理约束清单;
抽象触发阈值:同类约束累计≥8条,启动公共抽象层设计。
字段作用说明:
硬约束ID:唯一编号,用于复盘、需求追溯;
业务口径:清晰描述本条规则校验范围;
为什么LLM判断不了:留存原始痛点,避免后续盲目回退软约束;
理想解法:沉淀长期优化方向,积累多条后提炼公共抽象;
引入日期:观察约束增长密度,密集新增代表需要顶层抽象。
3.4 暂缓落地规则引擎的原因
当硬约束积累到一定数量,极易产生“配置化规则引擎”的想法,但现阶段不推荐优先落地:
规则引擎仅将代码补丁迁移至YAML配置,未解决“新增业务口径就要新增一条规则”的底层问题,系统复杂度无下降;
业务人员缺乏数据建模能力,自行配置极易产生口径冲突;
配置规则无配套单元测试,变更后无法自动回归校验;
多层规则解析增加查询链路开销,拖慢数值查询响应速度。
最优路径:先记账沉淀场景规律,待共性模式浮现后再做抽象层设计,而非提前搭建配置系统。
3.5 收益说明
对照实验条件:基于已上线data_filter能力,仅新增状态拦截、双时间补跑两类硬约束,其余逻辑不变。
准确率提升Delta:+2~4pt
分数提升幅度有限,但核心价值是拦截“一次错误就丢失用户信任”的高危业务场景,量化分数无法体现该隐性价值。
四、评测闭环:全系列最关键的经验教训
整套优化体系唯一后悔的决策:评测闭环搭得太晚。前期靠人工观察和感觉调参,浪费了大量时间。如果重新做一遍,我会先把评测搭起来,再动其他任何东西。
4.1 真值集共建机制(开发+测试协同)
真值集是评测体系核心基准,单一方独立搭建都会出现偏差,固定协作流程:
测试人员:输出全覆盖测试用例,定义每条用例标准期望结果,覆盖边缘、模糊、多维度组合查询;
开发人员:校验期望结果的工程可行性、业务口径正确性,与测试对齐标准;
双方达成共识后,结构化存储用例,支持自动化脚本批量读取跑分。
协作价值互补:测试保障场景覆盖度,开发规避无法稳定复现的无效用例。
真值集构建还需保证数据分布均衡:70% 来自线上真实用户 query,30% 为人工构造的边界与长尾 case,避免纯合成数据导致的离线高分、线上衰减。
4.2 多维度跑分判定口径
先统一准确率口径:准确率 = 判定通过的用例数 ÷ 评测用例总数 × 100%。单条用例「通过」指实际输出与期望值的偏差落在下述容差范围内。
查询分为数值型、分类型、聚合百分比三类,不能使用同一套判定标准,统一设计容差规则适配业务天然误差(时间戳精度、聚合窗口、四舍五入)。
单数字判定公式
∣expect−actual∣≤max(0.5, expect×0.5%)
逻辑说明:大数采用0.5%相对误差,小数采用0.5绝对差值兜底,兼顾两类场景合理性。
示例1:预期值=10000,允许误差50,业务口径微调、四舍五入全部兼容;
示例2:预期值=3,允许误差0.5,不会因差值1误判小数量场景失败。
百分比合计校验:整体合计允许2%误差区间。
容差设计初衷:拒绝严格全等匹配,避免把业务无差异的正确结果判定为失败;容差区间经过业务侧确认,可吸收工程侧无关扰动。
4.3 选用外挂Python评测脚本的原因
内部Java框架仅支持LLM软评分,改造数值精确打分改造成本高、周期长;外挂Python是低成本落地方案,具备三大优势:
判定完全确定性:同一用例多次跑分结果一致,无随机波动;
零推理成本:无需调用裁判LLM,批量跑分无额外token开销;
问题可审计:直接输出「期望数值 vs 实际数值」,快速定位口径错误。
极简脚本目录参考(可直接复制落地):
eval/
├── truth_dataset.json # 真值测试用例库
├── run_eval.py # 批量跑分主程序
├── judge_logic.py # 数值容差判定函数
└── report_output.txt # 跑分结果、失败用例日志
judge_logic.py 核心判定函数:
def is_match(expect: float, actual: float, tolerance_pct: float = 0.005) -> bool:
“”“大数比例、小数绝对,一个阈值覆盖两端”“”
threshold = max(0.5, abs(expect) * tolerance_pct)
return abs(expect - actual) <= threshold
4.4 放弃LLM裁判,选用真值集的核心理由
业界主流方案分为「LLM裁判打分」「真值基准打分」,本文选择后者,LLM裁判存在三大硬缺陷:
结果不稳定:同一用例两次跑分得分不一致,评估误差叠加,无法区分是优化失效还是裁判模型波动;
运行成本高:每条用例额外发起一次LLM调用,大规模评测集成本成倍上涨;
不适合数值场景:数值对错是二元客观事实,LLM主观评判会引入模糊误差。
核心结论:能使用确定性基准判定的场景,绝不引入概率化LLM作为评判标准;数据库查询得到的真值具备唯一性,是最