LLM输出存在幻觉、结果不稳定时,靠什么判断Agent的结果是否合格?

 前言:

当大语言模型(LLM)天生存在幻觉、且每次输出结果具有不可预期的不稳定性时,我们靠什么来客观判定 Agent 的结果是否“合格”?

答案是:绝不能依赖模型自身的承诺或内部逻辑,必须依赖外部的、客观的、可量化的物理约束与工程边界来做判定。

我们通过建立以下两道防线(共6重机制),将“模型的主观输出”转化为“工程的客观判定”:

TIP

交互式架构图:配合下文理论理解

gif888

 

一、靠什么判定单步执行是否合格?(运行时基线判断)

这一层是给模型套上“紧箍咒”,通过确定性的物理规则来约束不确定的模型。

1. 靠“结构化解析器”判定 (Schema Validation)

  • 如何判定:不看模型回复了多少废话,只看其输出能否被后端的强类型转换器(如 Zod 解析器)完全消化。
  • 处理机制:
    • 模型调用工具的参数必须严格符合系统预先定义的 JSON Schema 规范。
    • 一旦格式校验失败(如字段缺失、类型不匹配、枚举值超限),直接判定不合格并拦截请求。
    • 将冰冷的机器报错异常(Error Trace)直接回喂给模型,利用其反思能力迫使其按规则重新生成。

‼️剖析

我们直接用一个**“填写快递单”**的例子来剖析这套机制。

什么是“结构化解析器(Schema Validation)”?

假设系统规定,Agent 必须调用“寄快递”这个工具,而我们预先定义好的“快递单规范(Schema)”极其严格,要求如下:

  • 收件人:必须是字符串(String)
  • 手机号:必须是 11 位数字(Number)
  • 物品类型:必须只能在 ['文件', '数码', '生鲜'] 这三个枚举值里选(Enum)

这个**“极其严格的规定”,在代码里就是用 Zod 这种强类型校验库写死的。Zod 就像一个毫无感情、绝不通融的质检员**。

为什么需要这个质检员?(解决什么问题)

因为大模型(LLM)非常喜欢“自作聪明”和“发散思维”(也就是幻觉):

  • 错误 A(说废话):你让它输出数据,它回答:“好的主人,我为您填好了快递单,下面是数据:{'收件人': '张三'...}”。(前面多了一句废话,导致整个数据格式破坏)
  • 错误 B(类型搞错):它把手机号写成了字符串 "13800000000",而质检员要求必须是纯数字 13800000000
  • 错误 C(胡乱捏造):它在物品类型里填了一个不在选项里的 "衣服"

如果没有质检员,这些错误数据一旦流到下游系统(比如直接存进数据库或发给快递 API),整个系统就会直接崩溃。

“拦截”与“回喂异常(Error Trace)”是怎么运作的?

这正是 Agent 智能(自愈)的体现,分为两步:

第一步:无情拦截 当大模型把上面那些存在幻觉的错误数据交过来时,Zod 质检员一扫,发现格式不对,立刻抛出“红色警报”(报错抛出异常),绝对不允许这步操作继续往下走。这就叫做“直接判定不合格并拦截”。

第二步:回喂与反思重试(重点) 拦截下来之后,系统并不会直接给真实用户弹窗说“系统出错啦”,而是在后台悄悄进行一次“闭环审讯”。 系统会把 Zod 抛出的那段“冰冷的机器报错代码”直接原封不动地发回给大模型,对话大概是这样的:

系统后台自动发送给大模型: "你刚刚调用的参数报错了。错误详情:ZodError: at path ['物品类型'], expected '文件'|'数码'|'生鲜', received '衣服'。请根据这个错误立刻修改你的输出!"

大模型收到这个报错后,它的“反思能力”会被激发。 它会立刻意识到:“哦,原来枚举值里没有衣服啊。” 然后它会在零点几秒内,重新生成一份完美符合规范的数据(比如把衣服改归类为数码或其他合法选项)再交上去。

总结

这一条的核心思想就是:我们不相信大模型能一次性把格式填对,我们只相信代码级别的格式校验器(Zod)。一旦填错,我们用程序报错去“打脸”大模型,逼着它在后台自己改对为止,最后真实用户看到的,就是一次非常完美的执行。

 

2. 靠“物理沙箱的退出码”判定 (Sandbox Verification)

  • 如何判定:不听模型解释代码逻辑有多完美,只看生成的代码在真实环境里能不能成功跑通。
  • 处理机制:
    • 强制在无状态、强隔离的容器或独立工作空间中(如 Git Worktree)对生成产物(脚本、代码)进行真实编译和运行。
    • 严格校验进程退出状态(Exit Code)。若退出码为 0 则判定合格;若执行失败(非 0),精准提取终端错误流(Stderr),并将其作为铁证重新喂给模型触发代码级的自我修复。

‼️剖析

这道防线的精髓在于一句话:“Talk is cheap, show me the code (and run it).”

为什么光有第一步的 Zod 质检员还不够?

因为 Zod 只能检查**“语法格式”,不能检查“业务逻辑”**。 大模型完全可以给你返回一段格式 100% 正确的 JSON,里面包着一段看起来非常优雅的 TypeScript 代码。但是,这段代码可能:

  • 引用了一个根本不存在的第三方库。
  • 调用了一个拼写错误的函数名。
  • 发生了空指针异常(undefined is not a function)。

这个时候,如果我们轻信了大模型的代码,系统直接就崩了。所以我们需要**“物理沙箱”**来进行真实演练。

“物理沙箱”和“退出码(Exit Code)”是什么?

物理沙箱就像是给大模型准备的一个一次性防爆实验舱。大模型写出的代码,必须先扔进这个防爆舱里引爆试试。

在操作系统的世界里,任何程序运行结束都会向系统汇报一个**“退出码(Exit Code)”**:

  • 如果程序从头到尾完美运行,没有报任何错,它会返回 0(代表绝对的成功)。
  • 如果程序中途崩了,它会返回 非 0(比如 1 或 137),并在一个叫 **Stderr(标准错误流)**的管道里,吐出红色的报错堆栈信息。

“自愈重试闭环”在沙箱中是怎么运作的?

结合我们的架构图,过程是这样的:

  1. 真实环境试运行:大模型写好代码后,系统将其丢进沙箱执行(比如跑 npm run buildpython script.py)。
  2. 退出码 0(合格放行):如果沙箱返回 0,证明代码不仅长得对,跑起来也没毛病。判定合格,输出最终结果。
  3. 退出码非 0(拦截与打回):如果代码崩了,沙箱绝对不会让这坨烂代码流出去。系统会精准抓取 Stderr 里那些冰冷的报错堆栈(比如 ReferenceError: X is not defined at line 24)。
  4. 铁证回喂(Feedback):系统把这些红色的报错堆栈作为“铁证”,沿着架构图上的红色虚线扔回给大模型:

    “你刚才写的代码执行失败了,退出码是 1,报错详情是 ReferenceError...,请你立刻根据报错把代码修好再试一次!” 大模型看到具体的报错堆栈,马上就能领悟到:“噢,原来我忘记 import 那个变量了”,然后迅速修正。

总结

如果说 Zod 质检员管的是**“说话的格式对不对”,那沙箱退出码管的就是“做出来的事情炸不炸”**。 通过这两道无情的物理屏障,大模型的幻觉被死死封锁在了“不断尝试、报错、自我修复”的沙箱闭环(架构图左侧)里。

 

3. 靠“人类的主观常识”判定 (Human-in-the-Loop, HITL)

  • 如何判定:对于高危的系统级操作边界,当机器无法兜底风险时,强制依赖人类安全员的最终把关。
  • 处理机制:
    • 针对涉及数据销毁、远程核心代码合并、或实体资产调用等操作,强制配置人工拦截阀门。
    • 工具触发时,系统状态强制设为待审批(Pending),立刻挂起该会话的异步队列。
    • 通过前端界面向人类主管呈现带有详细执行参数(Diffs、Args)的模态审批请求,未获人类明确授权坚决不放行。

‼️剖析

人工在环(HITL,Human-in-the-loop)。

为什么有了 Zod 和沙箱,还需要人类?

前面说的 Zod 质检员和沙箱,它们是绝对理性的机器,但机器最大的弱点是——“它没有业务常识,也不懂闯祸的代价”。

举个极端但真实的例子: 大模型写了一句极其优雅的代码:rm -rf /(删除服务器所有文件)或者一句 SQL:DROP TABLE users

  • Zod 质检员一看:语法格式 100% 完美,放行!
  • 沙箱一跑:这是一条非常标准的合法命令,没有语法错误,放行!

一旦执行,公司的核心资产就灰飞烟灭了。这就叫**“逻辑正确,但业务致命”。对于这类高危边界,我们绝对不能把系统的生杀大权全部交给 AI,必须要有“核弹发射按键上的那两把人类钥匙”**。

“强制挂起”与“模态审批”是怎么运作的?

在我们的系统中,HITL 是一套类似于**“海关查扣”**的机制:

  1. 触发高危工具:当大模型试图调用类似于“合并核心代码、执行支付、删除数据库”等带有 requires_approval: true 标签的高危工具时。
  2. 强制挂起(Pending):大模型的代码执行流会被瞬间“冻结”。就好像按下了暂停键,大模型会在后台一直干等着。
  3. 人类介入审批:系统会将大模型到底想干什么、传递了哪些参数(比如它想删哪个库?它改了哪几行代码 Diff?),通过前端打包成一个清晰的“模态弹窗(Modal)”推送到人类安全员/主管的屏幕上。
  4. 命运的宣判:
    • 如果人类点击 “同意”,系统解冻,大模型继续欢快地执行后续操作。
    • 如果人类点击 “驳回”,系统同样会走“自愈反馈闭环”,丢给大模型一句无情的报错:“执行失败,人类管理员驳回了您的危险操作请求”。大模型收到后,只能乖乖道歉,或者去想别的安全办法。

总结

“机器只能判断对错,只有人类才能判断利弊。” 通过 HITL 这道阀门,我们赋予了 Agent 强大的执行力,但同时把最终的“方向盘和刹车”牢牢捏在了人类手里。


二、靠什么判定整体系统是否失控?(企业级保障防御)

即便单次输出“看似”合格,在长周期的复杂任务中,仍需防范 Agent 整体逻辑偏离和成本失控。

4. 靠“预算账单与重试计数”判定 (Cost & Loop Prevention)

  • 核心痛点:模型在沙箱中反复尝试修复错误,一旦陷入“屡败屡战”的逻辑死循环,会在极短时间内疯狂消耗 Token 导致账单失控。
  • 处理机制:
    • 最大重试阀值(Max Steps):在核心推理循环中硬编码重试上限(如最多容忍 5 次修复)。超过上限依然不合格,立刻判定为“任务彻底失败”,触发全局物理熔断。
    • 算力预算红线(Token Budget):在流控服务中,持续监测单任务累积 Token 消费。一旦触碰设定的预算上限,强制冻结系统并预警。

‼️剖析

这一层我们不再抠具体的代码细节,而是像 CFO(首席财务官)和 CTO 一样,从系统稳定性和财务成本**的角度来做终极判定。

为什么“自愈能力”会变成一颗定时炸弹?

我们在前两条防线里,给 Agent 配置了极度强悍的**“自愈能力”**(报错了就扔回去重写)。 但在真实的生产环境中,大模型有时候是极其“固执”或者“愚蠢”的。

比如:大模型非要去调用一个已经被废弃关停的内部 API。

  • 沙箱报错:“404 API 不存在”。
  • 模型反思:“哦,可能是我参数传错了,我换个参数再试一次”。
  • 沙箱报错:“404 API 不存在”。
  • 模型反思:“那我再换一个方法名试试”。 ... 如果没有外层干预,大模型和沙箱就会像两个不知疲倦的机器,在一秒钟内互相扔报错和重试,疯狂拉扯。每一次重试,都会消耗大量的上下文 Token。在极其短暂的几分钟内,您的 API 账单可能就会爆炸,甚至把公司的额度直接刷爆!

“物理熔断”是怎么运作的?

为了防止这种“死循环发癫”的状况,我们需要在沙箱和模型之外,悬一把绝对的“达摩克利斯之剑”——强制物理熔断机制。

机制一:硬核的“最大重试阀值”(Max Steps) 大模型不是喜欢试错吗?我们给它规定一个绝对上限。 在核心推理循环里硬编码:最多只允许自我纠错 5 次。前 4 次报错我都让你重试,但如果到了第 5 次你依然给不出能够通过沙箱和 Zod 校验的代码,对不起,没机会了。 系统会直接拔断电源(熔断),强制抛出一个全局错误:“Agent 尝试修复失败已达上限,任务强制终止”。这就彻底阻断了逻辑死循环。

机制二:“不见兔子不撒鹰”的 Token 预算(Token Budget) 有时候虽然还没达到 5 次上限,但大模型的单次吐字量极其庞大。 系统会在底层流控中埋点,像水表一样实时监控 Token 的流失速度和总体额度。只要单次会话的算力账单触碰了红线(比如单次任务耗费超过了 $1.0 额度),哪怕大模型正思考到一半,系统也会瞬间冻结执行,向管理后台发出红色预警。

总结

前三道防线是在判定大模型**“做的事对不对”,而这第四道防线是在判定大模型“到底有多失控”。 通过限制步骤和限制钱包**双管齐下,我们确保了哪怕 Agent 完全发疯,它的破坏半径和烧钱速度也被死死控制在了极其有限的范围内。

 

5. 靠“交叉评估验证模型”判定 (Semantic Filters & Cross-Validation)

  • 核心痛点:模型输出的结构合法、代码也没报错,但最终给用户的回复存在两大风险:一是“脱离工具结果胡编乱造”(事实幻觉),二是“答非所问”(偏离原始提问)。
  • 处理机制:
    • 意图与事实双向交叉校验(Answer Relevance & Tool Faithfulness):在最终回复触达用户前,引入“旁路独立裁判模型”进行严格的两重打分判定:
      1. 回答必须基于问题:严格评估最终生成的回复是否直接且准确地解答了**“用户最初提出的原始问题”**(Answer Relevance),杜绝避重就轻或答非所问。
      2. 回答必须基于依据:评估生成内容是否完全忠实于**“工具执行后返回的真实客观数据”**(Faithfulness),杜绝模型凭空捏造数据。 (注:上述任一维度评分跌破安全阈值,即刻判定最终结果不合格并要求重写。)
    • 合规黑盒墙拦截:利用专门的内容安全分类模型(如 Llama Guard 等),对于可能涉密、违规或触碰红线的生成内容进行最终的一刀切拦截。

‼️剖析

这是防线中最靠近用户、也是大模型技术中最核心的**“语义防线”**。

前四道防线(Zod、沙箱、人工、限流)防的都是大模型的**“理科错误”**(即硬逻辑和代码层面的崩溃)。但大模型本质上是个“文科生”,它太会“一本正经地胡说八道”了。

为什么代码跑通了,还会出大事故?

想象一个场景: 用户问:“帮我查一下昨天 A 系统的活跃用户量是多少?” 大模型写了一段脚本(Zod 通过),脚本成功运行查询了数据库(沙箱退出码 0),数据库明明返回的真实数据是:[Active Users: 120]。 但在大模型输出给用户的最后一句话时,它为了让句子显得好看,或者突然脑抽(幻觉),它回复说:

“为您查到了,昨天 A 系统的活跃用户量非常高,达到了 12000 人!”

你看,系统没报错,但结果是极其致命的数据捏造!又或者它回复说:

“昨天 A 系统运行非常稳定,没有任何报警。”(完全答非所问)。

这就是第五道防线要解决的终极命题:怎么用机器去判断另一台机器写的“小作文”是不是及格的?

“旁路独立裁判”是怎么打分的?(双向交叉校验)

为了防止大模型“自卖自夸”,我们绝对不能让写作业的人自己给自己改卷子。我们会在系统里引入一个**“旁路裁判”**(通常是另一个经过特殊提示词训练的轻量级大模型,比如专用于打分的模型)。

当主 Agent 写完回复,准备发给用户的前一秒,系统会把这盘菜先端给“裁判”尝一尝,裁判会拿着两把尺子进行双重丈量:

  • 第一把尺子(Tool Faithfulness / 基于依据): 裁判会左手拿着沙箱刚才吐出的原始冰冷数据([Active Users: 120]),右手拿着主 Agent 写的花里胡哨的回复(“活跃用户量 12000”)。 裁判一比对,立刻发现:“你在瞎编!原数据根本没有 12000 !”——忠实度评分直接 0 分,拦截!

  • 第二把尺子(Answer Relevance / 基于问题): 裁判左手拿着用户最初的提问(“活跃用户量是多少?”),右手拿主 Agent 的回复(“系统运行稳定,无报警”)。 裁判比对后判定:“用户在问人数,你却在答系统状态,避重就轻!”——相关度评分 0 分,拦截!

只有当这两把尺子的评分都及格,这句回复才算在“语义上是合格的”。如果不及格,系统就跟沙箱报错一样,把裁判的“判决书”砸回给主 Agent,逼它重新写一份老实本分的回答。

最后一道铁门:合规黑盒墙(Compliance/Guardrails API)

过了上面的裁判,证明回答是“正确且相关”的。但还有最后一种极端的黑天鹅情况:合规红线。

比如,沙箱返回的原始数据里,不小心包含了用户的身份证号、系统底层密码,或者是竞品公司的机密数据。主 Agent 老老实实地(极其 Faithful 地)把这些绝密数据整理好发了出来。 这个时候,“判别事实”的裁判是查不出问题的,因为主 Agent 确实没撒谎。

所以,在这道防线的末端,我们必须接入一个**“毫无感情的分类器模型”(类似于 Meta 的 Llama Guard,或者云厂商的内容安全网关)。 它不讲任何上下文,就是一个生硬的“黑名单安检机”。只要最后的这串字符串里碰到了敏感词、ZZ红线、隐私 PII(个人身份信息)或者代码密钥,这堵“黑盒墙”会瞬间落下,进行一刀切式的阻断(比如把敏感词打成星号,或者直接强行替换成默认的安全歉意回复)**。

总结

如果说前面是“做事的规矩”,那么第五条防线就是**“说话的规矩”。 通过“双向交叉打分”确保内容不造假、不跑题**;通过**“合规分类器”确保内容不惹祸**。

 

6. 靠“大规模离线基准测试”判定 (Offline Benchmarking)

  • 核心痛点:为了修复某个特定场景的幻觉而微调了 Prompt,结果“按下葫芦浮起瓢”,导致原本正常的 90% 场景出现能力衰退(Regression)。
  • 处理机制:
    • 在系统级维护包含大量高频真实业务场景及罕见攻击注入案例的离线黄金基准测试集(Eval Benchmark)。
    • 每次关键迭代必须经过自动化 CI/CD 跑批,利用多维度相似度评分或“大模型裁判”(LLM-as-a-Judge)得出客观的整体及格率。及格率未达标则判定系统处于非稳定态,禁止上线发布。

‼️剖析

这是我们防护体系的最后一块压舱石,它不发生在线上的每一次对话中,而是发生在每一次**“代码合并与系统发布”**的幕后。

我们常说传统软件工程有“单元测试”,但对于 Agent 开发来说,传统的测试手段几乎是失效的。

为什么“按下葫芦浮起瓢”是 Agent 开发的最大噩梦?

在开发 Agent 时,我们经常遇到这样的情况: 昨天老板测试时发现,Agent 在回答财务数据时忘记带单位“元”了。于是开发人员紧急在 System Prompt(系统提示词)里加了一句话:“你必须在所有数字后面加上单位”。 开发人员自己测了一下财务数据,发现有单位了,非常开心,直接上线。 结果今天,用户让 Agent 写一段代码,Agent 在所有的变量数字后面都加了一个汉字“元”(比如 const limit = 100元;),导致整个系统的代码沙箱全线崩溃!

这就是大模型的可怕之处:它的逻辑是全局漫溢的。你为了修补一个边缘 Case 而改动的 Prompt,极大概率会摧毁 90% 原本运行良好的核心场景。

“离线黄金基准集(Eval Benchmark)”怎么破局?

既然人工点点鼠标根本测不出潜在的灾难,我们就必须建立一套**“自动化的高考试卷”**。

这套试卷(基准测试集)包含了:

  • 几百个极具代表性的高频真实用户提问(正常题)。
  • 几十个曾经导致系统崩溃的历史边缘 Bug(错题本)。
  • 几十个恶意的 Prompt Injection 越权攻击(送命题)。 并且,每道题都配有专家人工校对过的**“标准答案(Ground-Truth)”或“必调用的工具序列”**。

“大模型裁判”如何实现自动化跑批?

传统代码测试是断言 A == B,但大模型的每次输出连标点符号都不一样,没法用等号去判断。

所以,处理机制变成了:

  1. 夜间自动跑批:每次修改 Agent 配置后,在 CI/CD 流水线上,后台会自动拉起 Agent,把这几百道试卷题从头到尾静默回答一遍。
  2. LLM-as-a-Judge(大模型裁判):拿着批改费的顶级大模型(比如 GPT-4o 作为裁判),对比 Agent 刚才生成的答案和试卷的标准答案。裁判不在乎字面是否完全一样,它只评判“语义是否一致”、“工具调用对不对”。
  3. 生死及格线:几百道题跑完,裁判给出一个综合及格率。比如规定及格线必须是 95%。如果这次改了 Prompt 导致及格率跌到了 80%,对不起,系统判定为**“非稳定态”**,发布管道直接锁死,决不允许上线祸害用户。

总结

如果说前五道防线是在**“救火”,那么这第六道防线就是在“防火”**。只有建立了这套无情的打分机器,整个 Agent 团队才敢放开手脚去迭代 Prompt 和重构业务代码,否则每一次上线都是在“闭眼踩地雷”。

 

image

posted @ 2026-07-02 18:23  莲(LIT)  阅读(16)  评论(0)    收藏  举报