防止LLM传话失真:六道工程护栏与信息保真实践
你有没有遇到过这种情况让大模型帮你把一段需求“润色得更专业”结果它把关键数字改了让它在多轮对话里整理会议纪要几十条结论最后只剩下一个大概方向更危险的是当你用 Agent 串联多个模型节点时最开始的原始指令经过几层传递后已经面目全非。这就像小时候玩的“传话游戏”——一句话从第一个人传到第十个人意思早就跑偏了。LLM 虽然是目前最聪明的文本生成工具但它本质上是一个基于概率的续写模型而不是一个可靠的信息存储介质。当它在“传递”你的想法时如果没有任何保护措施信息就会像传话游戏一样逐级失真而且这种失真往往不容易被发现直到产品上线或代码部署后才暴露出问题。本文要讨论的不是“怎么让 LLM 更聪明”而是一个更基础的工程问题如何防止 LLM 在理解、改写、转述、执行你原始想法的时候“添油加醋”或“丢三落四”。我会从 LLM 产生信息失真的根源出发给出六道工程护栏设计并用 Python 代码演示如何建立一个“信息保真测试台”最后讨论 Agent 链路、数据标注和本地模型场景下的防失真方案。1. 为什么 LLM 会“传话失真”而且这个坑比想象中更严重1.1 传话游戏的本质是概率重建传话游戏之所以失真是因为每个传话人并不是逐字背诵而是先理解、再记忆、最后用自己的语言复述。人在这个过程中会丢失细节、脑补缺失、倾向简化。LLM 的工作方式与此高度相似它会把输入文本切分成 token然后根据上下文计算下一个 token 的概率分布再逐个 token 地生成回复。这个过程本质上是“在不完全理解的情况下基于概率重建语义”。更麻烦的是LLM 的生成带有一个关键特性它永远倾向于生成“合理的”内容而不是“完全等于输入”的内容。当你的原始信息不够明确、存在歧义或是在多轮对话中被更晚的指令覆盖模型就会自行“脑补”最合理的解释。这个“脑补”在大多数文本生成任务中是优点但在需要忠实传达意图时就是灾难。1.2 信息保真度一个没有被足够重视的指标我们平时评价 LLM 输出时最关注的是流畅度、相关性、准确性却很少关注“信息保真度”——也就是模型输出相对原始输入有多少关键信息被保留、多少被篡改、多少被新增。准确性和保真度不是一回事。一个句子可能语法正确、通顺流畅但其中最关键的时间、数字、人名、业务规则已经被悄悄改掉。比如下面这句话原意本次上线时间为 2026-03-15目标用户是企业级客户付费方式为年付。模型改写后可能是“为了尽快推向市场初步计划在2026年3月中旬左右主要面向大型企业客户采用按年订阅的收费模式。”乍一看没问题甚至更“专业”了。但“2026-03-15”变成了“2026年3月中旬左右”精确日期变成了模糊日期“企业级客户”变成了“大型企业客户”也许业务上就有严格区分“年付”变成了“按年订阅”也可能改变了产品定价语义。如果下游直接使用这段文本信息就已经失真了。1.3 失真会怎么扩散单个模型输出失真还可以通过人工 review 发现但如果你的流程是“用户需求 - LLM 总结 - LLM 生成 PRD - LLM 生成任务清单 - LLM 写代码注释”那么每一级都可能丢失一部分信息。失真的误差是累积的而不是简单的加减法。最终产品经理发现需求不对开发发现注释误导测试发现用例没覆盖关键业务规则问题已经很难追溯到源头。所以我们真正需要的是一套系统性的防失真工程方法而不是依赖某一次“运气好的生成”。2. 最容易出问题的四类“传话链”在实际开发和内容生成场景中有四条最典型的“传话链”几乎每个用过 LLM 的人都可能踩过坑。2.1 自然语言需求被“润色”成另一种含义这是最常见的场景。你让 LLM 写一封邮件、写一段周报、写一个 PRD模型默认会进行语言层面的“优化”。优化过程中它可能调整句子结构、替换同义词、补充解释甚至是删除它认为不重要的细节。但如果原始信息是业务约束模型根本无法判断哪些重要哪些不重要。预防思路在提示词中明确区分“不可变字段”和“可加工字段”并让模型输出结构化内容而不是一段自由文本。2.2 多轮对话中的上下文覆盖多轮对话中LLM 需要同时记住系统提示词、历史消息、用户最新消息。但模型对上下文的注意力并不是均匀的最新消息通常权重更高早期消息容易被“遗忘”或忽略。当你先说了 A 需求又说了 B 需求模型很可能会用 B 的表述覆盖 A 的细节。比如你第一轮说“必须支持手机号登录”第二轮说“顺便支持邮箱登录”模型可能在最终输出里把“必须”降级为“建议”或者直接丢失“手机号”这个词。这就是典型的上下文覆盖导致的信息失真。预防思路不要依赖模型的“记忆”把每次请求都当作无状态调用来设计。原始约束要重复出现在 system prompt 和关键位置。2.3 Agent 多节点传递当你使用 Agent 架构时一条指令往往会经过 planner规划器、executor执行器、writer总结器等多个模型节点。每个节点都会对输入做一次“理解-重写”。哪怕每个节点只产生 5% 的信息损失经过 5 个节点后关键细节可能已经丢失 20% 以上。更危险的是很多 Agent 框架内部只用字符串拼接消息你无法直观看到中间每一个节点到底修改了什么。等到最终结果出错你根本不知道问题出在哪个环节。预防思路在每个节点都保留“原始输入区块”并让最终输出引用原始输入中的关键字段同时记录完整链路日志。2.4 文本与代码互转代码注释、接口文档、字段命名、数据契约之间互相转换时非常容易失真。例如模型把“订单状态从待支付改为已支付”翻译成英文注释可能会写成“Order status changes from pending to paid”看起来正确但后续再转回中文时可能就变成“订单状态从待付款改为已付款”而你的业务系统里“待支付”和“待付款”是同一个字段吗不见得。另一个典型场景是让 LLM 根据数据库字段生成文档它很可能把枚举值汇总错或者漏掉某个重要约束。因此凡是文本和代码之间存在“语义映射”就必须手工校验关键映射关系不能用 LLM 的输出直接替代人工核对。3. 防止失真的核心设计六道护栏要想不让 LLM 变成传话游戏的参与者我们必须把它当成一个“不可靠组件”来对待。在构建可靠 AI 系统的工程实践中防止信息失真的要义不是提高模型智商而是设计一套从输入到输出的护栏让失真无处可逃。我把它整理为六道护栏可以直接用在你的 LLM 应用和 Agent 架构中。3.1 意图锁定区分“不可变字段”和“可加工字段”这是第一道护栏也是最容易被忽略的。很多人在写提示词时只会说“帮我润色这段话”但模型并不知道哪些信息是事实核心、哪些是表达方式。你应该在提示词里明确列出不可变字段并告诉模型这些字段一旦改变任务失败。推荐的做法是在系统提示词中加入一个“硬约束区块”用 XML 或 JSON 分隔符锁住关键信息例如hard_constraints launch_date2026-03-15 target_userenterprise_customer pay_typeyearly /hard_constraints然后要求模型输出时先原样复述这些字段再做文字加工。这一步虽然简单但能显著降低信息丢失风险。3.2 上下文隔离原始资料只读模型不写不回写在多轮对话和 Agent 链路中不要随意把模型生成的中间结果当作下一轮的“事实基础”。更安全的做法是原始资料库单独存放每一轮生成时都从原始资料库加载最新源头而不是从上一轮输出中取。也就是说系统要有明确的“源数据层”和“生成数据层”。模型只负责读源数据、生成加工数据绝不直接扩大源数据范围。谁要是让模型把上一次生成的总结再作为原始输入就等于主动开启了传话游戏。3.3 结构化约束用 JSON Schema 或枚举锁死关键字段自由文本是最难校验的。它没有边界无法自动比对。如果你希望 LLM 输出可靠、可验证的内容就一定要用结构化输出。你可以通过 function calling、JSON mode 或 Pydantic 定义字段类型和枚举值把模型的行为限制在一个固定的 schema 内。例如“付费方式”只能取月付/年付/一次性三个枚举值之一而不是让模型自由发挥成“按年订阅”“按年度收费”“年缴”等等各种表达。结构化约束让校验从“语义模糊比对”变成“字段级精确比对”失真概率大幅下降。3.4 输出校验跑完就检查不等事后追责每次拿到 LLM 输出后立即用一段规则脚本检查关键信息是否仍然存在。这一步不需要多么智能最简单的字符串包含、正则匹配、数据库查询就可以解决 80% 的问题。例如原文中有日期、金额、用户ID输出中必须包含这些字段的精确值。一旦校验失败马上重新生成或丢弃输出而不是继续往下游走。如果你处理的是复杂长文本可以借助相似度算法如 partial ratio或嵌入向量相似度做参考但要记住相似度只是辅助关键还是要做“实体级”和“字段级”的硬校验。3.5 链路审计每次转换都保存“输入-输出”映射无论任务大小都应该保留一条可复现的审计链原始输入是什么、提示词是什么、模型版本是什么、输出是什么、校验结果是什么。这样一旦最终结果出现问题你可以沿着审计链回溯到出错节点。在 Agent 系统中这一点尤其重要。不要只记录最终结果而要记录每个中间节点收到什么提示词、输出什么内容、修改了原始信息的哪个部分。缺少这个审计链排查传话失真就像大海捞针。3.6 人工复核把人的审批放在风险点对业务影响重大的输出必须在关键节点加入人审。不是所有内容都要人看而是说“不可变字段是否被改动”这个问题必须由人确认一次。比如合同条款生成、数据库变更脚本生成、对外发布文案生成都应该设计一个“草稿-人类审批-生效”的环节而不是 LLM 输出后直接使用。人审的重点是检查原始意图是否被忠实保留其次才是文字是否流畅。如果人审环节成本过高就通过 3.3/3.4 的自动校验先把问题筛掉大部分人工只看高风险样本。4. 环境准备与基础代码构建一个最小“保真测试台”在进入完整架构前我们先来做一个能落地的实验用 Python 调用任意 LLM API写一个“保真测试台”检测模型在改写一句话时是否篡改了关键信息。你不需要复杂框架只需要一个 API 和一个文本相似度库。4.1 环境准备本示例使用 Python 3.10调用 OpenAI 兼容的 API。如果你使用的是 DeepSeek、通义千问、Ollama 本地模型只需修改base_url和model参数即可。演示中使用openaiSDK 和rapidfuzz文本相似度库。pip install openai rapidfuzz pydantic在项目根目录创建.env文件或在命令行中设置环境变量export OPENAI_API_KEY你的API密钥如果你用本地模型比如通过 Ollama 启动 GGUF 模型可以这样配置客户端client OpenAI( base_urlhttp://localhost:11434/v1, api_keyollama # 本地服务一般为任意占位符 )4.2 最小保真测试脚本新建verify_fidelity.py文件内容如下# verify_fidelity.py import os from openai import OpenAI from rapidfuzz import fuzz client OpenAI( api_keyos.environ.get(OPENAI_API_KEY), ) def ask_llm(prompt: str) - str: resp client.chat.completions.create( modelgpt-4o-mini, # 实际使用时替换为你的模型名 temperature0, # 降低随机性提高可复现性 messages[ { role: system, content: 你是一名严谨的文字编辑。你的任务是在不改变任何事实信息的前提下帮助用户润色文字。, }, {role: user, content: prompt}, ], ) return resp.choices[0].message.content.strip() if __name__ __main__: # 原始信息关键字段包括日期、用户类型、付费方式 original 本次上线时间为 2026-03-15目标用户是企业级客户付费方式为年付。 prompt f请将下面这句话改写为更正式的版本。 要求 - 必须保留以下关键字段的原始值不得改写 1. 上线时间 2. 目标用户 3. 付费方式 - 不得增补原文没有的信息比如具体产品名称、部门、日期解释。 原文如下 {cases_original} 输出要求 先用“【无害润色】”输出润色后的文本再用“【关键字段核对】”逐项列出你保留的关键字段。 result ask_llm(prompt) print(模型输出) print(result) print(\n关键字段相似度检查) # 用 partial_ratio 检查关键字段是否出现在输出中 for field in [2026-03-15, 企业级客户, 年付]: score fuzz.partial_ratio(field, result) print(f{field}: {score}) if score 85: print(f - 警告{field} 疑似被改动或丢失)这段代码做的事情非常直白把一段包含明确事实的文本丢给 LLM要求它做“无害润色”然后检查三个关键字段在输出中是否仍然存在。partial_ratio的值越高说明字段保留得越完整低于 85 分就需要警惕。4.3 为什么设置 temperature0把 temperature 设为 0能让模型在同样的输入下尽量输出稳定的结果降低“随机性失真”。但要明白temperature0 并不等于确定性保证。模型仍然可能因为 prompt 措辞不同而产生不同的输出。所以代码中的校验环节永远不能省。5. 进阶方案用结构化输出“锁死”关键字段单纯在提示词里说“请保留字段”还不够可靠尤其当你接入的开源小模型或本地 GGUF 模型遵循指令能力不强时它很容易忽略你的约束。更稳妥的做法是让模型输出结构化 JSON并且用 Pydantic 模型在代码层面对关键字段进行强校验。5.1 定义 Pydantic 输出模型新建schemas.py# schemas.py from pydantic import BaseModel, validator from enum import Enum from datetime import date class PayType(str, Enum): MONTHLY 月付 YEARLY 年付 ONCE 一次性 class ReleasePlan(BaseModel): launch_date: date target_user: str pay_type: PayType validator(target_user) def target_user_not_blank(cls, v): if not v.strip(): raise ValueError(target_user 不能为空) return v这里的关键在于pay_type是枚举类型。模型如果试图输出“年度订阅”Pydantic 会直接抛错而不是悄悄接受一个近似值。5.2 使用 JSON 输出模式强制结构化在你的业务代码中可以通过 response_format 或 function calling 让模型输出符合 schema 的 JSON。以 OpenAI 风格 API 为例# extract_plan.py import os, json from openai import OpenAI from schemas import ReleasePlan client OpenAI(api_keyos.environ.get(OPENAI_API_KEY)) def extract_plan(text: str) - ReleasePlan: resp client.chat.completions.create( modelgpt-4o-mini, # 按需替换 response_format{type: json_object}, messages[ { role: system, content: 你是信息抽取器。只输出 JSON 对象不要输出任何解释性文字。字段必须符合给定 schema。, }, { role: user, content: ( 从以下文本中抽取上线时间、目标用户、付费方式。\n f文本{text}\n 输出 JSON 字段{launch_date: 2026-03-15, target_user: ..., pay_type: 月付/年付/一次性} ), }, ], temperature0, ) content resp.choices[0].message.content data json.loads(content) return ReleasePlan.model_validate(data) if __name__ __main__: sample_text 我们计划2026年3月15日发布主要面向企业级客户采用年费订阅方式。 try: plan extract_plan(sample_text) print(plan) except Exception as e: print(结构化校验失败, e)如果模型把“年费订阅”理解成年付Pydantic 校验会通过如果它理解成“年费订阅”由于不是枚举值程序会抛错。这时你应该意识到要么是 prompt 示例不够清晰要么是当前模型无法区分“年付”和“订阅”。无论如何错误会被提前拦截而不是流向下游。5.3 对“无效枚举”的处理策略不要简单地把校验失败当作“系统故障”。在很多真实项目中模型输出枚举值非法往往意味着你的原始需求存在模糊地带。比如“年付”和“年度订阅”在业务上可能确实不同。此时应该让校验失败回流到“人工作业队列”由人工确认语义后更新词表或映射规则。这就把一次模型错误变成了一次知识沉淀。6. Agent 链路中的“防传话”模式Agent 是目前大模型应用中最容易出现传话失真的架构。一个简单的多节点链路可能是主任务 - 规划节点 - 工具调用 - 汇总节点 - 最终输出每个节点都是独立的 LLM 调用。更严重的是很多 Agent 框架会主动对输入做“精简历史消息”例如只保留最后一轮结果把更早的原始需求丢弃掉。这会让传话失真变成不可逆的。6.1 主任务原文随行防传话的第一原则是主任务原文必须随每个子节点的消息一起传递而不是只传递上游输出。也就是说不论 Agent 中间做了多少步每个 LLM 节点的 prompt 中都要有一块“主任务原文区块”。Python 伪代码如下# agent_fidelity.py def run_agent_step(task_original: str, step_prompt: str, model) - str: task_original: 主任务原文永远不允许被改写 step_prompt: 针对当前节点的具体指令 prompt f 【主任务原文不可修改仅供理解和校验】 {task_original} 【当前节点指令】 {step_prompt} 请基于主任务执行当前指令。在最终输出中必须引用主任务原文中的关键字段不得自行替换。 output model.generate(prompt) # 弱校验主任务中的关键命令词是否仍然出现在输出中 assert_fidelity(task_original, output) return outputassert_fidelity可以是一个自定义函数用来检查主任务原文中的日期、编号、命令动词等是否仍然存在于输出中。虽然稍显粗糙但在多数场景下能拦住“关键字段丢失”问题。6.2 链路审计日志在 Agent 架构中务必为每个节点生成一条日志记录至少包含四样东西节点名称和模型名称输入 prompt含主任务原文字段模型原始输出校验结果哪些字段通过哪些字段丢失这些日志可以用 JSON Lines 格式写入本地或日志平台。下面是一条示例日志结构{ step: planner, model: qwen-plus, prompt_hash: sha256:..., task_original_hash: sha256:..., output: ...节点输出..., fidelity_check: { required_fields: [launch_date, target_user], missing_fields: [], passed: true } }当你发现最终结果不对时按prompt_hash或task_original_hash搜索日志就能定位到第一个“missing_fields”不为空的节点。6.3 长任务的“断点续传”意识如果你的 Agent 链路特别长上下文很容易超限。有些框架会自动截断早期消息要非常小心。建议把“主任务原文”“当前步骤输入”“最近一轮输出”拆成三个独立区域而不是把所有历史消息都塞给模型。主任务原文永远处于最高优先级的区域即使上下文超限也不能先删它。可以优先压缩历史对话摘要但不能压缩原始需求本身。7. 验证与排查如何量化“想法变没变”有了代码和链路设计我们必须有一套验证方法来判断一个 LLM 任务是否遵循了“保真”要求。这套方法不需要高大上但要在工程中持续执行。7.1 用字段级覆盖检查替代“读一遍”对于结构化任务把关键字段列出来逐项检查输出中是否存在。以下表格演示了一个简单的检查规则关键字段原始值输出中是否存在判定标准上线时间2026-03-15是精确匹配2026-03-15或日期规范化后相同目标用户企业级客户是允许同义改写默认不允许付费方式年付否输出里写成了“按年订阅”需要在业务词表中匹配项目名称Alpha是精确匹配Alpha如果某个字段输出中不存在或者只存在一个模糊近似就要重新生成或触发人工复核。7.2 相似度评分只是一个参考partial_ratio可以辅助判断字段是否完整保留但它无法识别“日期变化但语义相近”的情况也无法识别“完全新增了一段无关内容”。例如原词企业级客户输出大型企业 客户partial_ratio得分可能很高但从业务上“企业级”不等于“大型”。因此相似度评分只是过滤工具不是最终裁判。真正可靠的是 3.3 结构化约束 3.4 输出校验的组合。7.3 常见问题与排查思路以下表格整理了五类典型的传话失真问题及排查方法问题现象可能原因排查方式解决方案输出把“2026-03-15”改成“2026年3月中旬”模型认为自然语言表达更流畅主动做语义泛化用正则提取日期字段检查是否精确匹配在 prompt 中把日期列为 hard_constraints或使用结构化输出多轮对话后丢失最初的约束上下文过长早期消息被截断或注意力权重下降检查实际请求中是否仍包含最初的 system prompt打印 prompt 日志压缩内容时保留主任务原文区块使用无状态请求Agent 最终结果里出现了原始输入完全没有的“细节”模型出现幻觉式补全通常是能力不足或 prompt 引导过度对比最终结果与原文的实体集合查找“新增实体”在 system prompt 中禁止补全尚未出现的信息必要时用更小的任务粒度模型把“年付”改成“年订阅”导致下游判定失败枚举值没有被严格约束查看输出 JSON 中 pay_type 字段值使用 Pydantic 枚举类型或 function calling非法值直接报错本地小模型如 7B GGUF发生失真小模型指令跟随能力弱用同一个 prompt 测试开源模型和商业 API 的差异降低 temperature、增加 few-shot 示例、拆分短任务或选用更强模型8. 工程最佳实践与落地建议构建可靠 AI 系统并不是买一个更强的大模型就一劳永逸。从“防止 LLM 传话失真”这个具体问题出发我建议你在自己的项目里落地以下几项工程实践。8.1 建立原始需求台账不要把原始需求散落在对话历史、群聊记录或临时文档里。建立一个单独的、版本化的原始需求库每次 LLM 调用都要引用这个库里的内容。你可以放在 Git 仓库、数据库或内容管理系统中。原始需求一旦变化通过版本号管理而不是让模型“凭记忆”自适应。8.2 受控词表常态化把业务中经常被模型改写的术语维护为一套“受控词表”。比如签约方式、订单状态、客户等级、日期格式。在 prompt 中把受控词表明确列出来并让模型只能使用表中词语。词表本身也要定期更新每次模型输出中出现词表之外的相似表达都记录下来并决定是否加入词表。8.3 输出差异留痕不要让 LLM 输出直接覆盖原始文件。要求模型生成一个“diff”或至少生成一个“修改说明”。在文本类任务中你可以用difflib对比原始文本和输出文本在代码类任务中使用 Git diff。这样任何改动都是可审计的。8.4 回归测试集为每一类高风险任务准备 20~50 个“保真测试用例”每个用例包含原始信息、期望保留字段、允许改写字段。每当你更换模型版本、修改 prompt 模板或接入新的 Agent 节点就跑一遍回归测试观察关键字段保留率是否有下降。这套测试集才是防止传话失真长期有效的关键。8.5 人在回路的设置时机不要让人工审批淹没在全部流程中应该只在高风险输出点设置。例如修改数据库变更脚本时生成对外发布文案时翻译合同、法务文件时Agent 自动修改代码且影响范围较大时对于低风险任务比如转换文章语气、生成周报草稿可以全自动但仍要保留自动校验日志以便事后抽查。9. 总结与后续学习方向回到标题“Dont Let LLMs Play Telephone with Your Ideas”。这句话不是一句口号而是一条工程纪律。LLM 是概率生成器不是可靠的信息信道。你发给它的每一句话都有可能在某一个角落被微妙地改写。如果我们能在架构上把“原始意图”隔离出来、用结构化约束固定关键字段、用校验脚本即时发现问题、用审计日志定位节点、用人工复核守住最后关口那么 LLM 就不会变成传话游戏里那个越传越歪的传声筒而更像是一个表达能力极强但必须被管理的执行器。下一步值得深入的方向包括基于事实核对fact verification的方法来检测模型输出与原文的矛盾利用知识图谱约束生成让实体之间的关系可验证以及针对更多开源小模型设计轻量化的保真率评测方案。如果你正在做 LLM 应用、Agent 或内容自动化项目建议先从文中最小的“保真测试台”开始挑一条容易出错的原始需求跑一遍脚本看看模型有没有真的弄丢你的想法。如果它丢了你就知道为什么需要护栏了。