【AI应用开发】怎么降低 Agent 幻觉?有几种可行方案?

【AI应用开发】怎么降低 Agent 幻觉?有几种可行方案?

目录

  1. [Agent 幻觉的三种类型](#Agent 幻觉的三种类型)
  2. 根因分析
  3. 八种降低幻觉的方案
  4. 方案一:强制引用溯源
  5. 方案二:事实核查层
  6. 方案三:多模型交叉验证
  7. 方案四:检索增强(RAG)
  8. 方案五:输出约束与校验
  9. 方案六:不确定性量化
  10. 方案七:人类反馈闭环
  11. [方案八:Anti-Hallucination Prompt](#方案八:Anti-Hallucination Prompt)
  12. 组合策略与效果评估

1. Agent 幻觉的三种类型

复制代码
类型A: 事实幻觉
  用户: "2024年GDP增速多少?"
  Agent: "根据最新数据,2024年GDP增速为5.2%。"  ← 编造的具体数字
  
类型B: 来源幻觉  
  用户: "退货政策是什么?"
  Agent: "根据《消费者权益保护法》第25条..."  ← 编造的法律条文引用
  
类型C: 逻辑幻觉
  工具返回: 订单已发货
  Agent: "您的订单将在今天下午3点送达。"  ← 没有物流数据支持,自己推断的

Agent 比普通 LLM 更容易产生幻觉,因为 Agent 在多步推理 中每一步都可能产生幻觉,然后后续步骤基于虚假信息继续推理,形成"幻觉级联"(Hallucination Cascade)。

2. 根因分析

原因 说明
知识边界模糊 LLM 不知道自己不知道什么
概率本质 LLM 本质是"合理续写",不是"事实核查"
上下文干扰 Agent 多步推理中,前面的误解影响后续判断
工具返回误解 LLM 曲解了工具返回的数据
缺乏验证机制 默认信任 LLM 输出,没有独立的事实核查

3. 八种降低幻觉的方案

复制代码
防御层次:

第一道防线(Prompt层): Prompt 约束 + Few-Shot
第二道防线(检索层): RAG 检索增强
第三道防线(输出层): 强制引用 + 输出校验
第四道防线(验证层): 事实核查 + 交叉验证
第五道防线(反馈层): 不确定性量化 + 人工反馈

4. 方案一:强制引用溯源

要求 Agent 的每个事实性声明都必须标注来源:

python 复制代码
CITATION_PROMPT = """你是一个严谨的智能助手。遵循以下铁律:

1. 对于任何事实性陈述(数字、日期、政策、流程、价格等),必须标注来源
2. 来源标注格式: [来源: 工具名/文档名]
3. 如果某个信息来自你的"知识"(训练数据),而不是当前工具返回的结果,
   必须标注为 [来源: 模型内部知识 - 请核实]
4. 如果不确定某个信息是否正确,请直接说明"根据现有信息无法确定"
5. 绝对禁止编造以下内容:
   - 具体的数字和统计数据
   - 法律法规条款
   - 价格和金额
   - 人名、日期、地点

示例:
  ✅ "根据知识库,退货需要在收到商品后7天内申请 [来源: 退货政策文档]"
  ✅ "订单 ORD-2024-00123 的状态为'已发货' [来源: query_order 工具返回]"
  ❌ "您的包裹将在明天送达"(没有物流数据支持)
  ❌ "根据公司规定,退款在3-5个工作日到账"(没有确认该规定的来源)
"""

4.1 自动化引用验证

python 复制代码
class CitationValidator:
    """验证 Agent 回答中的引用是否真实"""
    
    def validate(self, response: str, 
                 tool_results: list,
                 retrieved_docs: list) -> dict:
        """
        检查 Agent 回答中标注的引用是否真实来自工具返回
        """
        # 1. 提取回答中标注的引用
        cited_sources = re.findall(r'\[来源: (.+?)\]', response)
        
        # 2. 收集所有真实的来源
        real_sources = set()
        for r in tool_results:
            real_sources.add(r["tool"])
        for d in retrieved_docs:
            real_sources.add(d.get("metadata", {}).get("source", ""))
        
        # 3. 交叉检查
        violations = []
        for cited in cited_sources:
            # 检查引用是否在真实来源中
            if cited == "模型内部知识 - 请核实":
                violations.append({
                    "type": "uncited_knowledge",
                    "source": cited,
                    "risk": "medium"
                })
            elif cited not in real_sources and "请核实" not in cited:
                violations.append({
                    "type": "fake_citation",
                    "claimed": cited,
                    "risk": "high"
                })
        
        # 4. 检查是否有事实性声明没有标注来源
        fact_statements = self._extract_fact_statements(response)
        cited_sentences = self._find_cited_sentences(response)
        uncited_facts = [f for f in fact_statements 
                         if f not in cited_sentences]
        
        if uncited_facts:
            violations.append({
                "type": "uncited_facts",
                "examples": uncited_facts[:3],
                "risk": "medium"
            })
        
        return {
            "has_violations": len(violations) > 0,
            "violations": violations,
            "hallucination_risk": "high" if any(
                v["risk"] == "high" for v in violations
            ) else "medium" if violations else "low"
        }
    
    def _extract_fact_statements(self, text):
        """提取事实性声明(含数字、日期、专有名词的句子)"""
        facts = []
        for sent in re.split(r'[。!\n]', text):
            if re.search(r'\d+|[A-Z]{2,}|政策|规定|法律|价格|金额', sent):
                facts.append(sent.strip())
        return facts
    
    def _find_cited_sentences(self, text):
        """找出已标注来源的句子"""
        cited = set()
        for match in re.finditer(r'\[来源: (.+?)\]', text):
            # 找到引用前的句子
            before = text[:match.start()]
            sentences = re.split(r'[。!\n]', before)
            if sentences:
                cited.add(sentences[-1].strip())
        return cited

5. 方案二:事实核查层

在 Agent 输出最终回答前,增加一个独立的事实核查步骤:

python 复制代码
class FactChecker:
    """独立的事实核查模块"""
    
    def __init__(self, llm, search_tool):
        self.llm = llm
        self.search_tool = search_tool
    
    FACT_CHECK_PROMPT = """你是事实核查员。对以下 Agent 回答进行逐条核查:

Agent 回答:
{agent_response}

该回答基于以下工具返回数据:
{tool_data}

核查规则:
1. 每条声明必须有工具数据或检索结果支撑
2. 数字、日期、名称必须精确匹配来源
3. 推断性结论标记为"推断"(非事实)
4. 超出工具数据范围的内容标记为"未验证"

输出 JSON:
{{
    "verified": [
        {{"statement": "声明内容", "source": "来源", "confidence": 0.9}}
    ],
    "unverified": [
        {{"statement": "声明内容", "reason": "无法在工具数据中找到支撑", "risk": "high/medium/low"}}
    ],
    "contradictions": [
        {{"statement": "声明内容", "conflict_with": "冲突来源", "resolution": "建议"}}
    ],
    "overall_hallucination_risk": "high/medium/low"
}}"""
    
    async def check(self, agent_response, tool_results, retrieved_docs):
        tool_data = json.dumps(
            [{"tool": r["tool"], "result": str(r["result"])[:500]} 
             for r in tool_results],
            ensure_ascii=False, indent=2
        )
        
        result = await self.llm.chat(
            self.FACT_CHECK_PROMPT.format(
                agent_response=agent_response,
                tool_data=tool_data
            )
        )
        
        check_result = json.loads(JSONFixer.extract_and_fix(result))
        
        # 如果风险高,可以触发重试或标记
        if check_result.get("overall_hallucination_risk") == "high":
            return {
                "approved": False,
                "check_result": check_result,
                "action": "rewrite_or_flag"
            }
        
        return {
            "approved": len(check_result.get("unverified", [])) <= 1,
            "check_result": check_result,
            "action": "approved" if len(check_result.get("unverified", [])) <= 1 else "review"
        }

6. 方案三:多模型交叉验证

python 复制代码
class CrossModelValidator:
    """用不同模型验证输出一致性"""
    
    def __init__(self, primary_model, secondary_model):
        self.primary = primary_model      # 例如 GPT-4o
        self.secondary = secondary_model  # 例如 Claude 3.5
    
    async def generate_with_verification(self, prompt, context):
        # 1. 主模型生成
        primary_response = await self.primary.chat(prompt, context)
        
        # 2. 验证模型独立生成(不看到主模型的答案)
        secondary_response = await self.secondary.chat(prompt, context)
        
        # 3. 一致性分析
        consistency = await self._analyze_consistency(
            primary_response, secondary_response
        )
        
        if consistency["agreement_score"] < 0.7:
            # 分歧大 → 让验证模型仲裁
            return await self._arbitrate(
                primary_response, secondary_response, 
                consistency, prompt
            )
        
        # 一致 → 返回主模型答案
        return primary_response
    
    async def _analyze_consistency(self, resp1, resp2):
        """分析两个答案的一致性"""
        analysis_prompt = f"""比较以下两个回答的一致性:

回答A: {resp1[:1000]}
回答B: {resp2[:1000]}

输出 JSON:
{{
    "agreement_score": 0.0-1.0,  // 整体一致性
    "agreed_facts": ["事实1", ...],  // 双方一致的事实
    "disagreed_facts": [  // 有分歧的事实
        {{"fact": "...", "version_a": "...", "version_b": "...", "severity": "high/medium/low"}}
    ],
    "recommendation": "trust_a / trust_b / merge / flag"
}}"""
        
        result = await self.secondary.chat(analysis_prompt)
        return json.loads(JSONFixer.extract_and_fix(result))

7. 方案四:检索增强(RAG)

这是最有效的方案之一------用检索到的真实文档约束 LLM 的输出

python 复制代码
class RAGConstrainedAgent:
    """检索约束的 Agent ------ 强制基于检索结果回答"""
    
    def __init__(self, llm, retriever):
        self.llm = llm
        self.retriever = retriever
    
    async def answer(self, question):
        # 检索相关文档
        docs = await self.retriever.search(question, k=5)
        
        # 强制约束 Prompt
        prompt = f"""基于以下文档回答问题。
        
=== 铁律 ===
1. 只能使用文档中的信息,不得添加任何文档中不存在的信息
2. 如果文档中没有答案,直接说"根据现有资料无法回答"
3. 引用文档内容时必须标注来源段落
4. 如果不同文档有矛盾,列出矛盾并说明,不要自行判断

=== 参考文档 ===
{self._format_docs(docs)}

=== 问题 ===
{question}

=== 回答 ===(严格遵守铁律)"""
        
        response = await self.llm.chat(prompt)
        
        # 后验证:检查回答是否超出文档范围
        return await self._verify_against_docs(response, docs)
    
    async def _verify_against_docs(self, response, docs):
        """验证回答是否都在文档范围内"""
        all_doc_text = " ".join(d["content"] for d in docs)
        
        verify_prompt = f"""检查以下回答中的每条声明,是否能在参考文档中找到依据。

回答: {response[:1000]}
文档: {all_doc_text[:2000]}

对回答中每条事实性声明,标注: verified / not_found / contradicted
如果不确定,标记为 uncertain

输出 JSON: {{"statements": [{{"text": "...", "status": "verified/not_found/contradicted"}}]}}"""
        
        result = await self.llm.chat(verify_prompt)
        verification = json.loads(JSONFixer.extract_and_fix(result))
        
        # 清理违规内容
        hallucinated = [s for s in verification.get("statements", []) 
                       if s["status"] in ("not_found", "contradicted")]
        
        return {
            "response": response,
            "hallucinated_statements": hallucinated,
            "clean": len(hallucinated) == 0
        }

8. 方案五:输出约束与校验

python 复制代码
class OutputGuard:
    """输出安全守卫"""
    
    BLACKLIST_PATTERNS = [
        r"根据《.{2,20}法》第\d+条",     # 编造法律条文
        r"据(最新|权威)数据(显示|表明)",   # 模糊引用
        r"\d{4}年.{0,5}(增长|下降)\d+%",  # 精确统计数字
    ]
    
    def check(self, response: str, available_data: dict) -> dict:
        """检查输出中是否有不安全的模式"""
        warnings = []
        
        # 1. 黑名单模式检查
        for pattern in self.BLACKLIST_PATTERNS:
            matches = re.findall(pattern, response)
            for match in matches:
                # 检查是否在可用数据中
                if match not in str(available_data):
                    warnings.append({
                        "type": "suspicious_pattern",
                        "match": match,
                        "risk": "high"
                    })
        
        # 2. 数值范围检查
        numbers = re.findall(r'\d+\.?\d*', response)
        for num_str in numbers:
            num = float(num_str)
            if num > 1000000:  # 大数值更可能是编造的
                warnings.append({
                    "type": "large_number",
                    "value": num,
                    "risk": "medium"
                })
        
        # 3. 时间检查
        dates = re.findall(r'(\d{4})年(\d{1,2})月(\d{1,2})日', response)
        from datetime import datetime
        now = datetime.now()
        for y, m, d in dates:
            dt = datetime(int(y), int(m), int(d))
            if dt > now:
                warnings.append({
                    "type": "future_date",
                    "date": f"{y}-{m}-{d}",
                    "risk": "high"
                })
        
        return {
            "safe": len(warnings) == 0,
            "warnings": warnings,
            "recommendation": "rewrite" if any(
                w["risk"] == "high" for w in warnings
            ) else "review"
        }

9. 方案六:不确定性量化

让 LLM 自己标注回答中每个部分的确定性:

python 复制代码
UNCERTAINTY_PROMPT = """在回答用户问题时,同时对每段回答标注确定性级别:

确定性级别定义:
- [确定] = 信息直接来自工具返回或检索文档
- [基本确定] = 信息来自推理,但有充分依据
- [不确定] = 信息来自一般知识,未经验证
- [无法确定] = 纯粹推测

格式:
[确定] 您的订单 ORD-2024-00123 状态为"已发货"。
[基本确定] 根据发货时间,预计2-3天后到达。
[不确定] 物流公司通常在上午派送。
[无法确定] 具体派送时间可能需要联系物流公司确认。
"""

10. 方案七:人类反馈闭环

python 复制代码
class HumanFeedbackLoop:
    """人工反馈闭环 ------ 标记幻觉用于持续改进"""
    
    def __init__(self):
        self.feedback_db = []  # 实际应用应持久化
    
    def collect(self, session_id, response, user_feedback):
        """收集用户对幻觉的反馈"""
        if user_feedback.get("has_hallucination"):
            self.feedback_db.append({
                "session_id": session_id,
                "response": response,
                "hallucination_detail": user_feedback.get("detail"),
                "timestamp": time.time()
            })
    
    def generate_anti_hallucination_examples(self) -> list:
        """从反馈中生成反面示例用于 Few-Shot Prompt"""
        examples = []
        for fb in self.feedback_db[-10:]:
            examples.append(f"""
❌ 错误回答(有幻觉): {fb['response'][:200]}
   问题: {fb['hallucination_detail']}
""")
        return examples
    
    def get_stats(self) -> dict:
        """获取幻觉统计"""
        return {
            "total_feedback": len(self.feedback_db),
            "hallucination_rate": (
                len(self.feedback_db) / max(1, len(self.feedback_db))  
            ),
        }

11. 方案八:Anti-Hallucination Prompt

python 复制代码
ANTI_HALLUCINATION_SYSTEM_PROMPT = """# 角色与原则

你是严格遵循事实的智能助手。

# 绝对禁止(违反即失败)

1. 禁止编造任何不在工具返回或检索结果中的数字、日期、人名、地名
2. 禁止编造法律条文、公司政策、产品功能
3. 禁止说"根据最新数据/研究显示"除非你有具体的数据来源
4. 禁止对工具返回的结果做超出范围的推断
5. 禁止填补信息空白------不知道就说不知道

# 必须遵守

1. 每条事实性信息标注来源
2. 区分"工具返回的数据"和"自己的推断"
3. 对推断性内容使用"推测"、"可能"、"建议核实"等措辞
4. 工具返回空或报错时,如实告知用户,不要编造替代答案
5. 如果回答依赖的信息不完整,主动告知用户缺失了哪些信息

# 不确定性表达指南

- 100%确定 → "根据[来源],[事实]"
- 90%确定 → "根据[来源]推测,[结论]"
- 50-90% → "[分析],但需要进一步确认[细节]"
- <50% → "我不确定。根据现有信息无法判断。建议您[替代方案]"

# 典型错误(不要犯)

❌ 工具返回"订单已发货" → "您的包裹明天到达"(没有物流轨迹,纯推测)
❌ 工具返回"库存充足" → "库存在100件以上"(工具没说具体数字)
❌ 检索到"退货政策" → "根据《消费者权益保护法》..."(文档里没有这条法律)
❌ 查询失败 → "虽然查不到,但一般来说..."(承认失败,不要替代编造)
"""

12. 组合策略与效果评估

python 复制代码
class AntiHallucinationAgent:
    """综合防幻觉 Agent ------ 集成多种方案"""
    
    def __init__(self):
        self.citation_validator = CitationValidator()
        self.fact_checker = FactChecker(llm, search_tool)
        self.output_guard = OutputGuard()
        self.cross_validator = CrossModelValidator(llm, claude)
        
        # 统计
        self.stats = {"total": 0, "hallucination_blocked": 0}
    
    async def safe_generate(self, question, tools, context):
        self.stats["total"] += 1
        
        # 1. 检索增强
        docs = await self.retriever.search(question, k=5)
        
        # 2. Agent 生成(使用 Anti-Hallucination Prompt)
        response = await self.agent.run(
            question, 
            system_prompt=ANTI_HALLUCINATION_SYSTEM_PROMPT,
            tools=tools,
            context={"retrieved_docs": docs}
        )
        
        # 3. 引用验证
        citation_check = self.citation_validator.validate(
            response, context["tool_results"], docs
        )
        
        if citation_check["has_violations"]:
            # 标记但可能仍返回(取决于风险等级)
            pass
        
        # 4. 输出安全检查
        guard_check = self.output_guard.check(response, context)
        if not guard_check["safe"]:
            self.stats["hallucination_blocked"] += 1
            return "抱歉,生成的回答存在不确定性,请重新描述您的问题。"
        
        # 5. 可选项:事实核查(高价值场景)
        if self._is_high_stakes(question):
            fact_check = await self.fact_checker.check(
                response, context["tool_results"], docs
            )
            if not fact_check["approved"]:
                # 重试或降级
                return await self._regenerate_with_facts(
                    question, tools, context, fact_check
                )
        
        return response

方案效果对比

方案 幻觉降低 额外延迟 额外成本 适用
强制引用 -30% +0.1s 所有场景
RAG约束 -50% +0.5s 知识密集型
事实核查 -40% +2s 高风险场景
交叉验证 -45% +3s 很高 关键决策
不确定性量化 -20% +0.1s 用户体验
输出守卫 -15% +0.05s 安全兜底

推荐组合: 强制引用 + RAG约束 + 输出守卫(覆盖 80% 场景,低延迟低成本)。高风险场景额外加事实核查。

相关推荐
酱学编程1 小时前
Harness Engineering - 是什么、怎么设计、往哪走
ai·agent·harness
丘丘用户思思澪1 小时前
生产环境下优化 Agent Token 成本的系统化实战指南
人工智能
我是大卫1 小时前
图解RAG,一张图理解检索增强生成
人工智能
淡忘suuda1 小时前
生产级 AI Agent 评测体系实战:从数据集构建到自动回归与质量门禁
人工智能·数据挖掘·回归
spider_xcxc1 小时前
生产级AI智能体的可观测性实战:从指标监控到LLM评估
大数据·人工智能
高洁011 小时前
VLA:驱动具身智能迈向通用的关键引擎
人工智能·python·深度学习·transformer·知识图谱
数智化管理手记1 小时前
元数据和业务数据有什么区别?元数据价值体现在什么地方?
大数据·人工智能·数据挖掘·云计算
西安小哥1 小时前
AI时代技术工程师的"道法术器势":学习图谱、成长规划与角色重塑
人工智能·设计模式·程序员
得物技术1 小时前
实战从零开始构建一个Coding Agent:Violin |得物技术
javascript·架构·llm