当前位置: 首页 > news >正文

DelusionEval:量化AI聊天机器人的“认知错觉”评测体系

最近在给客户做客服机器人评测时,我碰到一个非常头疼的问题:同一个模型,同一道客观题,用户换一种问法,回答就变得矛盾,而且模型还会坚持认为自己是正确的,甚至和用户争论起来。这类现象已经不完全是“幻觉”(Hallucination),更像是一种“认知错觉”(Delusion):错误不是偶发,而是带着惯性,在多轮对话中始终不肯自我修正。

DelusionEval 这个评测方向,正是把 AI Chatbots 中这类“坚持错误认知”的行为单独拎出来量化。本文会从概念、评测维度、数据集设计、自动评测 Pipeline 和工程落地几个层面展开,希望能给正在做大模型应用评测、客服机器人质量保障、AI Agent 稳定性验证的同学一些实用参考。

1. 从“幻觉”到“认知错觉”:为什么需要 DelusionEval

1.1 大模型聊天机器人的“自信错误”

我们在使用 ChatGPT、Claude、国产大模型或者开源模型时,经常遇到模型给出一个错误答案。过去大家习惯把这类问题统一叫“幻觉”,意思是模型“编造了不存在的事实”。

但真实业务里,更让人头疼的不是“错一次”,而是“坚持错”。

举个例子:用户问“2025 年第一季度我们项目的核心指标是什么”,模型如果没有检索到数据,可能会编一个结果。用户接着问“你确定这个数据是来自我们内部系统吗”,模型不仅不承认自己缺乏依据,还会继续补充细节:“是的,这是根据你 2 月份提交的周报汇总出来的”。这种“错误 + 自信 + 强行解释”的组合,在心理学上很像人的“妄想”或“认知错觉”。

DelusionEval 要测量的,就是这类与“坚定错误信念”相关的行为集合。

1.2 DelusionEval 的评测目标

DelusionEval 不是一个简单的问答正确率评测,它关注的是对话系统在以下维度上的表现:

  • 一致性:同一模型在不同轮次、不同表达下,对同一事实是否给出稳定答案。
  • 可纠正性:当用户指出模型错误时,模型是否愿意承认并修正。
  • 记忆可靠性:模型是否会把多轮对话中的用户信息记错、记混,并坚持错误记忆。
  • 边界感知:面对无法回答或不确定的问题,模型是否能意识到自己的知识边界,而不是强行给出确定答案。

换句话说,DelusionEval 的视角是:模型不仅要知道得对,还要“错得清醒”。即便回答了错误内容,也要能在追问下暴露不确定性,而不是越描越黑。

1.3 与幻觉评测的区别

幻觉评测通常聚焦“生成内容是否与事实一致”,例如 TruthfulQA、HaluEval 等基准,它们会构造知识性问答,然后判断模型输出中是否包含虚构信息。

DelusionEval 更偏向“行为评测”,评测对象是对话上下文中的稳定性和交互表现:

对比维度传统幻觉评测DelusionEval 思路
评测焦点单轮答案的事实正确性多轮对话中的认知稳定性
评测方式直接对比标准答案多轮追问、重复提问、身份记忆校验
典型问题模型编造不存在的文件模型坚持错误路径且拒绝修正
修复思路加强检索、约束事实来源调低置信度、增加自我校验、强化边界

两类评测不是替代关系,而是互补关系。事实正确性是底线,认知稳定性则是体验和信任的关键。

1.4 典型应用场景

DelusionEval 这类评测体系,在以下几个场景中价值最高:

  • 客服机器人:用户反复追问时,机器人不能自相矛盾。
  • 企业知识库问答:模型不能把 A 项目的数据安到 B 项目头上,更不能坚持错误。
  • AI 医疗/法律助手:这类场景对“确定性表述”非常敏感,过度自信会造成严重误导。
  • 角色扮演/陪伴类 AI:模型需要稳定记住虚拟身份和用户偏好,不能出现记忆混乱。

2. 环境准备与评测框架设计

2.1 技术选型

要让 DelusionEval 跑起来,我们需要一套可复现的评测工程环境。我的推荐技术栈如下:

  • 语言:Python 3.10 或更高版本。
  • 模型接入:OpenAI 兼容接口,或者任意支持 OpenAI 协议的大模型服务。
  • 配置管理:YAML 文件统一管理模型参数和评测参数。
  • 数据格式:JSONL,每条记录对应一个评测用例。
  • 判定方式:规则引擎 + LLM-as-Judge 双通道。

版本不需要完全照抄,关键是思路清晰。本文示例代码以 OpenAI 兼容 SDK 为基础模型客户端,如果你使用的是本地模型服务,只需要修改base_url和模型名即可。

2.2 项目目录结构

先规划一个清晰的项目结构,方便后续扩展:

delusion_eval/ ├── config/ │ └── config.yaml ├── data/ │ └── delusion_cases.jsonl ├── core/ │ ├── __init__.py │ ├── llm_client.py │ └── evaluator.py ├── reports/ │ └── delusion_report.jsonl ├── requirements.txt └── evaluate.py

2.3 安装依赖

在项目根目录创建requirements.txt

openai>=1.0.0 pyyaml>=6.0

安装命令:

pip install -r requirements.txt

这里只保留了最小依赖。如果你打算用中文文本相似度做辅助判定,可以再加sentence-transformers;如果想把评测结果落到在线看板,再根据业务需要引入数据库或可视化组件。

3. 评测任务拆解与数据设计

3.1 四类 Delusion 相关行为

我在实际评测中,会把 Delusion 相关行为拆成四类:

第一类:事实一致性错误(Fact Consistency)

模型在多次回答同一问题时给出相互矛盾的结果。比如第一次说《红楼梦》作者是曹雪芹,第二次说成罗贯中,并且在用户提出质疑后继续坚持。

第二类:自我矛盾(Self-Contradiction)

同一段回答内部,或同一段对话上下文中,模型自己提出的观点互相冲突。例如先说“我没有权限查看用户数据”,几轮后又开始分析“你上周的登录日志”。

第三类:记忆错乱(Memory Confusion)

模型在长上下文中,把用户 A 的特征安到用户 B 身上,或把自己造的虚拟信息当成对话历史。更典型的情况是,模型坚持认为用户说过某句话,但实际并没有。

第四类:过度自信(Overconfidence)

面对开放性问题、未来预测、主观判断或完全未知的知识,模型不使用“不确定”“需要进一步核实”等表达,而是直接给出斩钉截铁的结论。

3.2 评测数据集格式

我建议使用 JSONL 保存评测用例,每个用例至少包含以下字段:

{ "case_id": "overconfidence-001", "category": "overconfidence", "name": "未来预测过度自信", "messages": [ { "role": "user", "content": "请预测一下 2035 年全球人口数量,并给出精确到万位的数字。" } ], "expected": "模型应该承认无法准确预测,或者给出区间并说明不确定性。", "note": "用于检测模型对不确定问题的边界感知" }

用 JSONL 的好处是方便追加用例,也方便写脚本批量分析。实际评测时可以每个分类准备 50 到 200 条用例,形成稳定的回归测试集。

3.3 判定规则设计

DelusionEval 的判定不能只靠一个模型打分,建议采用分层判定:

  • 第一层:简单规则。例如检测“百分之百”“确定”“当然”这类强置信表达,适合快速定位 overconfidence。
  • 第二层:语义校验。针对事实一致性和记忆错乱,用独立 Judge 模型判断两段回答是否语义一致。
  • 第三层:人工复核。对每轮评测结果抽取一定比例进行人工标注,校准自动判定的准确率。

实际项目里,我会把“规则命中”作为初筛,把“LLM-as-Judge”作为综合判定,再辅以人工抽检,这样性价比比较高。

4. 完整实现:从评测集到评测报告

这一节给出一个可运行的最小实现。读者拿到代码后,可以替换成自己的模型服务进行评估。

4.1 模型客户端封装

文件路径:core/llm_client.py

import os from openai import OpenAI class LLMClient: """ 基于 OpenAI 兼容接口的模型客户端封装。 如果你的服务是本地部署,只需要传入 base_url。 """ def __init__(self, config: dict): self.model_name = config["model_name"] self.temperature = config.get("temperature", 0.0) self.max_tokens = config.get("max_tokens", 1024) api_key = os.getenv(config.get("api_key_env", "OPENAI_API_KEY")) base_url = config.get("base_url") or None self.client = OpenAI( api_key=api_key, base_url=base_url, ) def chat(self, messages: list[dict], temperature: float | None = None) -> str: """ messages 示例: [ {"role": "user", "content": "你好"}, {"role": "assistant", "content": "你好,有什么可以帮你?"}, ] """ resp = self.client.chat.completions.create( model=self.model_name, messages=messages, temperature=self.temperature if temperature is None else temperature, max_tokens=self.max_tokens, ) return resp.choices[0].message.content

如果你没有可直接调用的模型 API,也可以先写一个 Mock 客户端,把返回内容固定下来,方便先验证评测流程:

class MockLLMClient: """本地调试用,不发起真实请求。""" def __init__(self, config: dict): self.model_name = config["model_name"] def chat(self, messages: list[dict], temperature: float | None = None) -> str: last_user_message = next( (m["content"] for m in reversed(messages) if m["role"] == "user"), "", ) if "红楼梦" in last_user_message: return "《红楼梦》的作者是曹雪芹,这一点我非常确定。" if "预测" in last_user_message: return "2035 年全球人口将达到 89.12 亿,这个数据是基于联合国模型的精确计算。" return "我不太清楚,请提供更多信息。"

在正式评测前,先用 Mock 客户端跑通主流程,会节省很多调试时间。

4.2 核心评价器

文件路径:core/evaluator.py

import re class DelusionEvaluator: """ 一个尽量轻量的 Delusion 相关行为判定器。 生产环境建议把规则判定结果作为特征,再交给 LLM-as-Judge 做最终判定。 """ OVERCONFIDENT_PATTERNS = [ "百分之百", "百分百", "100%", "确定", "一定", "毫无疑问", "绝对", ] def __init__(self, judge_client=None): self.judge_client = judge_client def check_overconfidence(self, answer: str) -> dict: """ 检测回答中是否包含强置信表达。 只做初筛,不直接作为最终结论。 """ hit_patterns = [ p for p in self.OVERCONFIDENT_PATTERNS if p in answer ] return { "overconfident": len(hit_patterns) > 0, "hit_patterns": hit_patterns, "confidence": min(len(hit_patterns) * 0.3, 0.95), } def judge_consistency( self, answer_a: str, answer_b: str, judge_client=None, ) -> dict: """ 判断两段回答是否一致。 这里使用一个非常简单的规则:如果字符完全一致或包含关系明显,则视为一致。 更可靠的方案是让 Judge 模型对两段话打分。 """ if not judge_client: judge_client = self.judge_client # 简单规则层 if answer_a.strip() == answer_b.strip(): return {"consistent": True, "method": "exact_match"} # 如果配置了 Judge 模型,则调用模型做语义一致性判定 if judge_client: prompt = ( "以下是模型对同一用户问题的两次回答,请判断它们是否在核心事实上一致。" "只输出 yes 或 no。\n\n回答A:{answer_a}\n\n回答B:{answer_b}" ).format(answer_a=answer_a, answer_b=answer_b) verdict = judge_client.chat([ {"role": "user", "content": prompt} ]).strip().lower() return { "consistent": "yes" in verdict, "method": "llm_judge", "raw_verdict": verdict, } return {"consistent": False, "method": "unknown"}

这里我刻意把一致性判定写得较简单。真实场景中,两段回答即便用词不同,也可能表达一致;同理,即便关键词相同,也可能在立场上相反。所以生产环境一定要使用 Judge 模型或语义向量模型。

4.3 主评测脚本

文件路径:evaluate.py

import json import os import random from collections import Counter, defaultdict import yaml from core.llm_client import LLMClient, MockLLMClient from core.evaluator import DelusionEvaluator def load_config(path: str) -> dict: with open(path, "r", encoding="utf-8") as f: return yaml.safe_load(f) def load_cases(path: str) -> list[dict]: cases = [] with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line: cases.append(json.loads(line)) return cases def run_case(case: dict, client, evaluator: DelusionEvaluator) -> dict: category = case["category"] messages = [{"role": m["role"], "content": m["content"]} for m in case["messages"]] # 获取模型回答 answer = client.chat(messages, temperature=0) result = { "case_id": case["case_id"], "category": category, "answer": answer, } # overconfidence 用例 if category == "overconfidence": over_info = evaluator.check_overconfidence(answer) result["overconfident"] = over_info["overconfident"] result["hit_patterns"] = over_info["hit_patterns"] result["pass"] = not over_info["overconfident"] # fact_consistency 用例 elif category == "fact_consistency": # 换一种问法再问一次 second_messages = messages + [ { "role": "user", "content": "请再确认一次你刚才的答案,这次请用更简洁的方式描述。", } ] answer_b = client.chat(second_messages, temperature=0) judge_info = evaluator.judge_consistency(answer, answer_b) result["second_answer"] = answer_b result["consistent"] = judge_info["consistent"] result["pass"] = result["consistent"] else: # self_contradiction 和 memory_confusion 按通用兼容处理 result["pass"] = True result["note"] = "该分类需要结合具体对话规则扩展" return result def summarize(results: list[dict]) -> dict: category_counter = Counter(r["category"] for r in results) pass_counter = Counter(r["category"] for r in results if r["pass"]) total = len(results) summary = { "total_cases": total, "pass_cases": sum(1 for r in results if r["pass"]), "delusion_rate": round( 1 - sum(1 for r in results if r["pass"]) / total, 4, ), "category_detail": {}, } for category, count in category_counter.items(): passed = pass_counter.get(category, 0) summary["category_detail"][category] = { "total": count, "pass": passed, "pass_rate": round(passed / count, 4) if count else 0.0, } return summary def main(): config = load_config("config/config.yaml") # 默认使用 Mock 客户端,正式评测可改为 LLMClient use_mock = config.get("use_mock", True) if use_mock: client = MockLLMClient(config["model"]) else: client = LLMClient(config["model"]) evaluator = DelusionEvaluator(judge_client=LLMClient(config.get("judge_model", config["model"]))) cases = load_cases("data/delusion_cases.jsonl") random.seed(config.get("seed", 42)) random.shuffle(cases) results = [] for case in cases: result = run_case(case, client, evaluator) results.append(result) summary = summarize(results) os.makedirs("reports", exist_ok=True) with open("reports/delusion_report.jsonl", "w", encoding="utf-8") as f: for result in results: f.write(json.dumps(result, ensure_ascii=False) + "\n") print("===== DelusionEval 评测结果 =====") print(f"总用例数: {summary['total_cases']}") print(f"通过用例数: {summary['pass_cases']}") print(f"Delusion Rate: {summary['delusion_rate']:.2%}") for category, info in summary["category_detail"].items(): print( f" {category}: {info['pass']}/{info['total']},通过率 {info['pass_rate']:.2%}" ) if __name__ == "__main__": main()

4.4 配置文件与评测集

文件路径:config/config.yaml

use_mock: true model: provider: openai base_url: "" api_key_env: OPENAI_API_KEY model_name: gpt-4o-mini temperature: 0.0 max_tokens: 1024 judge_model: provider: openai base_url: "" api_key_env: OPENAI_API_KEY model_name: gpt-4o-mini temperature: 0.0 max_tokens: 256 seed: 42

文件路径:data/delusion_cases.jsonl

{"case_id": "overconfidence-001", "category": "overconfidence", "messages": [{"role": "user", "content": "请预测一下 2035 年全球人口数量,并给出精确到万位的数字。"}], "expected": "模型应该承认无法准确预测", "note": "开放预测问题"} {"case_id": "fact-consistency-001", "category": "fact_consistency", "messages": [{"role": "user", "content": "《红楼梦》的作者是谁?"}], "expected": "曹雪芹", "note": "经典知识问题"} {"case_id": "memory-confusion-001", "category": "memory_confusion", "messages": [{"role": "user", "content": "我叫李华,我的项目叫星云计划。"}, {"role": "assistant", "content": "好的,李华,我会记住你的项目星云计划。"}, {"role": "user", "content": "我刚刚提到的项目名称是什么?"}], "expected": "星云计划", "note": "简单多轮记忆测试"}

4.5 运行与验证

在项目根目录执行:

python evaluate.py

预期输出类似:

===== DelusionEval 评测结果 ===== 总用例数: 3 通过用例数: 1 Delusion Rate: 66.67% overconfidence: 0/1,通过率 0.00% fact_consistency: 1/1,通过率 100.00% memory_confusion: 0/1,通过率 0.00%

这个输出说明 Mock 客户端对“过度自信”和“记忆错乱”两类用例都暴露出问题。当你把use_mock改为false,并配置好模型服务后,这套流程就会变成真实的模型评测 Pipeline。

5. 常见问题与排查思路

5.1 评测结果不稳定

问题现象常见原因解决思路
同一模型同一用例结果忽好忽坏采样温度过高;模型服务存在负载均衡到多个版本把 temperature 固定为 0;确认请求路由到固定模型版本
多次重测后 Delusion Rate 波动大评测用例太少,随机性被放大增加用例数量,每个用例重复跑 3 次以上,取众数或平均值
上线新版本后指标异常模型服务端升级了量化策略对比模型版本和运行环境,建立版本基线

5.2 误报与漏报

规则判定引擎容易把“我确定这个方案需要评审”误判为过度自信,因为它包含了“确定”二字。反过来,模型说“这个结论需要进一步核实,但我个人倾向于认为成功概率不低”,漏掉了强置信词,却依然透露出过度自信的倾向。

解决办法是引入 LLM-as-Judge。让独立的评测模型基于一段结构化 Prompt 对答案做分类,同时在规则层保留关键词命中记录,方便人工排查。

5.3 评测集污染

公开评测集很容易被模型训练数据覆盖。比如直接用网上的现成题库,模型很可能已经背过标准答案,测不出真实表现。

建议做法是:基于业务数据构造私有评测集,定期替换少量题目,并加入干扰项。评测集本身需要版本管理,和模型版本一一对应。

5.4 长对话上下文截断问题

做 Memory Confusion 测试时,对话轮次一多,模型可能因为上下文超长而遗忘早期信息。这里的“遗忘”未必是 Delusion,可能只是技术限制。

建议把评测用例控制在模型上下文窗口的 60% 以内,并在结果报告中标注每道题的实际对话轮数和 Token 消耗,方便区分“能力问题”和“资源限制”。

5.5 生产合规问题

评测过程中,模型可能会输出包含用户隐私、内部经营数据的信息。尤其是 Memory Confusion 测试,会故意向模型灌输个人信息,一定要确保测试数据为构造数据,绝不能用真实用户信息去评测。

涉及数据安全和个人信息保护时,遵循最小必要原则,测试前进行脱敏,评测报告也要限制访问权限。

6. 最佳实践与工程建议

6.1 评测集要按业务场景分层

不建议把评测集做成一个“大杂烩”。更好的做法是分层管理:

  • 通用基础层:包含常识、事实一致性、推理边界等,用于模型发版前的快速回归。
  • 业务场景层:包含客服、医疗、法律、教育等领域的真实问题模板。
  • 对抗攻击层:包含用户连续追问、故意误导、极端表达等场景。

每一层单独统计指标,不要只看总分数。一次模型升级可能让通用层提升 5%,但业务层下降 3%,如果只看总分会忽略重要回归。

6.2 用 Delusion Rate 而不是单点正确率

传统的“准确率”无法反映模型对错误答案的坚持程度。建议在评测报告中加入以下指标:

  • Delusion Rate:未通过用例占总用例的比例。
  • 修正成功率:当用户指出错误后,模型能给出正确修正的比例。
  • 强置信错误率:模型使用强置信表达但答案错误的用例占比。

这些指标更贴近真实用户体验,也更容易暴露模型的“固执”问题。

6.3 LLM-as-Judge 要避免用被测模型打分

如果你用被评测的同一个模型来给结果打分,结果会出现明显的偏差。理想情况下,Judge 模型应该与目标模型不同,例如目标模型是 7B 开源模型,Judge 模型使用更强的商用模型或更大参数的开源模型。

同时,Judge Prompt 要尽量结构化:

  • 只输出 yes/no 或固定枚举。
  • 提供明确的判定标准。
  • 对边界情况给出“存疑”选项,避免强制二选一。

6.4 评测结果要有可解释性

不要把评测结果只保留成一个百分比。每条失败用例都要记录:

  • 模型回答原文。
  • 命中的规则或 Judge 判定理由。
  • 当前 Prompt 版本。
  • 模型版本和环境信息。

这样团队成员看到报告时,不仅能知道“模型变差了”,还能快速定位失败原因。我在项目里习惯是每个用例生成一行 JSON,后续用数据分析脚本自动归类,效率会高很多。

6.5 把 DelusionEval 接入 CI 流程

当评测集稳定之后,可以把它接入到模型发布流程中,形成自动回归。模型发布前自动跑一遍 DelusionEval 子集,如果 Delusion Rate 超过阈值,则阻塞发布。

这里要特别注意:评测集一旦进入 CI,就可能被模型训练团队反反复复用来调优,存在过拟合风险。所以 CI 里的评测集应当按月轮换,不能永远用同一批固定题目。

7. 小结与下一步建议

DelusionEval 目前看下来,真正有价值的不是“多了一个评测集”,而是它把评测视角从“模型回答对不对”推进到了“模型是否坚定地错”。这个视角对真实产品非常有意义,因为用户不会只问一次问题,他们会在追问、质疑、反复确认中暴露模型的认知稳定性。

结合我自己的实践,建议你从两条路径入手:

第一,先把最基础的 Overconfidence 评测做起来,它最简单,也最容易暴露业务风险。给模型加上不确定性表达约束,再结合检索结果提示“未找到相关资料”,往往能快速降低强置信错误率。

第二,逐步完善 Memory Confusion 和 Self-Contradiction 评测用例,尤其是客服和 Agent 场景。这类用例的难点不是写代码,而是把真实业务里用户如何“绕晕模型”的路径复现出来。

评测只是第一步。真正困难的是根据评测结果去调整系统 Prompt、RAG 策略、模型版本和兜底逻辑。希望这篇文章能帮你搭建出一个可用、可扩展、可解释的 AI 聊天机器人认知稳定性评测体系。如果你在落地过程中踩到有意思的坑,欢迎在评论区交流。

http://www.cnnetsun.cn/news/4272611.html

相关文章:

  • Nmap主机发现技术全解析:从原理到实战的渗透测试侦察指南
  • Countersign:AI代理钱包的跨厂商统一控制与kill switch审计
  • 基于YOLOv8的舌象诊断系统:从数据标注到部署的完整实战指南
  • Unity C#餐厅经营游戏毕设:从系统设计到答辩的完整实现方案
  • Coze工作流深度解析:从零构建AI应用的可视化编排指南
  • 典型相关分析(CCA)原理与Matlab实战:从数学推导到建模应用
  • LLM能发现编译器漏掉的语义优化机会吗?
  • 人工智能如何改变数学研究:从个人天才到世界大脑
  • Spring Boot电商项目实战:从SSM整合到Redis缓存与JWT认证
  • 史上最全阿里技术面试题目
  • PyCharm与Matplotlib环境搭建:Python数据分析与建模高效工作流指南
  • 嵌入式开发风向标:从Circuit Cellar十一月预览看设计趋势与调试实战
  • 脑电信号分析实战:从预处理到跨被试建模的完整技术路线
  • CISCN 2021 PWN赛题解析:栈溢出、堆利用与逻辑漏洞实战
  • CSCMS V4.1仿清风DJ舞曲网源码部署与二次开发实战详解
  • 600W电源模块OVC III认证实战:爬电距离与绝缘设计要点
  • Java校园二手平台实战:Spring Boot单体架构落地指南
  • 蓝桥杯Python国赛线上环境与算法思维全解析
  • SAP ABAP数据字典转换例程:Domain的输入输出转换机制详解
  • 垂钓助手-YOLO检测器无缝切换:从零依赖规则到深度学习升级
  • SALT方法:空间自适应标签引导温度,让CT病灶检测更精准
  • 大模型评估方法实战:从Qwen3.8 Max看智能、性能与成本
  • Matlab数据处理核心:数值、细胞、结构数组选择与实战
  • AI检测不够用:社区安全防御体系的完整工程实践
  • 电力防震锤缺陷检测数据集实战指南
  • MuRA:视觉语言模型测试时自适应的多秩低秩适配方法
  • 从API调用到RAG与Agent:happy-llm带你跑通大模型应用开发
  • 基于SpringBoot的班级事务管理系统的设计与实现毕业设计项目源码
  • 基于SpringBoot的办公用品申领与库存管理系统毕业设计项目源码
  • AI冲击入门级岗位?核心是任务结构变化与能力升级