减少LLM幻觉的五大技巧和方法 原创
本文介绍了使用LangGraph减少LLM幻觉的简单技巧。
如果你使用过LLM,就知道它们有时会产生幻觉。这意味着它们生成的文本要么毫无意义,要么与输入数据相矛盾。这个常见的问题可能会损害基于LLM的应用程序的可靠性。
我们在这篇文章中将探讨一些简单的技巧来降低产生幻觉的可能性。遵循这些技巧,你有望提高AI应用程序的准确性。
幻觉有多种类型:
- 内在幻觉:LLM的响应与用户提供的上下文相矛盾。响应在当前上下文中是错误的,而且这种错误是可验证的。
- 外在幻觉:LLM的响应无法使用用户提供的上下文加以验证。响应可能是错误的,也可能不是错误的,但我们没有办法使用当前的上下文来确认。
- 不连贯的幻觉:LLM的响应并未回答问题或没有意义。LLM无法遵循指示。
我们在这篇文章中将针对上述所有类型作下阐述。
我们将列出一系列以不同方式减少幻觉的技巧和方法。
技巧1:使用锚定
锚定是指要求LLM完成任务时,在LLM的输入中使用领域内相关的附加上下文。这为LLM提供了正确回答问题所需的信息,并降低了产生幻觉的可能性。这是我们使用检索增强生成(RAG)的原因之一。
比如说,问LLM一个数学问题,或者问同样的问题,同时为它提供一本数学书的相关章节,会生成不一样的结果,第二种选择更有可能是正确的。
以下是我在之前的教程中介绍此类实现的示例,在提出问题时提供了从文档提取的上下文信息:
技巧2:使用结构化输出
使用结构化输出意味着强制LLM输出有效的JSON或YAML文本,便于你减少无用的漫谈,从LLM获得“切中要点”的回答。它还有助于下一个技巧,因为它使LLM响应更容易验证。
你可以使用Gemini的API来做到这点:
import json
import google.generativeai as genai
from pydantic import BaseModel, Field
from document_ai_agents.schema_utils import prepare_schema_for_gemini
class Answer(BaseModel):
answer: str = Field(..., description="Your Answer.")
model = genai.GenerativeModel("gemini-1.5-flash-002")
answer_schema = prepare_schema_for_gemini(Answer)
question = "List all the reasons why LLM hallucinate"
context = (
"LLM hallucination refers to the phenomenon where large language models generate plausible-sounding but"
" factually incorrect or nonsensical information. This can occur due to various factors, including biases"
" in the training data, the inherent limitations of the model's understanding of the real world, and the "
"model's tendency to prioritize fluency and coherence over accuracy."
)
messages = (
[context]
+ [
f"Answer this question: {question}",
]
+ [
f"Use this schema for your answer: {answer_schema}",
]
)
response = model.generate_content(
messages,
generation_config={
"response_mime_type": "application/json",
"response_schema": answer_schema,
"temperature": 0.0,
},
)
response = Answer(**json.loads(response.text))
print(f"{response.answer=}")
其中“prepare_schema_for_gemini”是一个效用函数,它准备模式以匹配Gemini的奇特需求。你可以在这里找到它的定义:https://github.com/CVxTz/document_ai_agents/blob/498d8ee6e8597f8ba43b336c64178d186461dba0/document_ai_agents/schema_utils.py#L38。
这段代码定义了Pydantic模式,并将该模式作为查询的一部分发送到“response_schema”字段。这迫使LLM在响应中遵循此模式,并使输出解析起来更容易。
技巧3:使用思维链和更好的提示
有时候,在给出最终回答之前,给LLM足够的空间来思考响应,有助于生成更高质量的响应。这种技术被称为思维链,因有效、易于实现而被广泛使用。
如果LLM找不到足够的上下文来生成高质量的响应,我们还可以明确要求它以“N/A”回答。这将给它一个简单的出路,而不是试图回答它不知道怎么回答的问题。
比如说,不妨看看这个简单的问题和上下文:
上下文
托马斯•杰斐逊(1743年4月13日-1826年7月4日),美国政治家、种植园主、外交官、律师、建筑师、哲学家和开国元勋,1801年至1809年担任美国第三任总统,他是《独立宣言》的主要起草者。在美国独立战争之后,在1801年成为总统之前,杰斐逊是华盛顿领导班子的第一位美国国务卿,然后是亚当斯领导班子的第二副总统。杰斐逊是支持民主、共和主义和自然权利的主要倡导者,他在州、国家和国际等层面制定了形成性的文件和决定。(来源:维基百科)
问题
戴维斯•杰斐逊是哪一年去世的?
一种天真的方法会生成:
响应
answer= '1826年 '
这显然是错误的,因为杰斐逊•戴维斯在上下文中根本没有被提及。托马斯•杰斐逊死于1826年。
如果我们将响应的模式改为使用思维链:
class AnswerChainOfThoughts(BaseModel):
rationale: str = Field(
...,
description="Justification of your answer.",
)
answer: str = Field(
..., description="Your Answer. Answer with 'N/A' if answer is not found"
)
我们还添加了更多关于当问题无法回答时,我们期望输出的细节,使用上下文“如果没有找到回答,以‘ N/A ’回答”。
通过这种新方法,我们得到了以下基本原理(记住,使用思维链):
提供的文本讨论的是托马斯•杰斐逊,而不是杰斐逊•戴维斯。没有关于杰斐逊•戴维斯去世的信息。
最终回答:
answer=’N/A’
这个给出的结果太好了!但是我们可以使用一种更通用的方法来检测幻觉吗?
我们可以,那就是使用代理!
技巧 4:使用代理方法
我们将构建一个简单的代理,实现分三个步骤的流程:
- 第一步是包含上下文并向 LLM 提出问题,以便获得第一个候选回答及其用于回答的相关上下文。
- 第二步是将问题和第一个候选回答重新表述为声明性语句。
- 第三步是要求 LLM 验证相关上下文是否包含候选回答。这被称为“自我验证”:https://arxiv.org/pdf/2212.09561。
为了实现这一点,我们使用LangGraph 定义了三个节点。第一个节点将在包含上下文的同时提出问题,第二个节点将使用 LLM 重新表述问题,第三个节点将检查语句与输入上下文的关系。
第一个节点可以如下定义:
def answer_question(self, state: DocumentQAState):
logger.info(f"Responding to question '{state.question}'")
assert (
state.pages_as_base64_jpeg_images or state.pages_as_text
), "Input text or images"
messages = (
[
{"mime_type": "image/jpeg", "data": base64_jpeg}
for base64_jpeg in state.pages_as_base64_jpeg_images
]
+ state.pages_as_text
+ [
f"Answer this question: {state.question}",
]
+ [
f"Use this schema for your answer: {self.answer_cot_schema}",
]
)
response = self.model.generate_content(
messages,
generation_config={
"response_mime_type": "application/json",
"response_schema": self.answer_cot_schema,
"temperature": 0.0,
},
)
answer_cot = AnswerChainOfThoughts(**json.loads(response.text))
return {"answer_cot": answer_cot}
第二个节点如下定义:
def reformulate_answer(self, state: DocumentQAState):
logger.info("Reformulating answer")
if state.answer_cot.answer == "N/A":
return
messages = [
{
"role": "user",
"parts": [
{
"text": "Reformulate this question and its answer as a single assertion."
},
{"text": f"Question: {state.question}"},
{"text": f"Answer: {state.answer_cot.answer}"},
]
+ [
{
"text": f"Use this schema for your answer: {self.declarative_answer_schema}"
}
],
}
]
response = self.model.generate_content(
messages,
generation_config={
"response_mime_type": "application/json",
"response_schema": self.declarative_answer_schema,
"temperature": 0.0,
},
)
answer_reformulation = AnswerReformulation(**json.loads(response.text))
return {"answer_reformulation": answer_reformulation}
第三个节点如下定义:
def verify_answer(self, state: DocumentQAState):
logger.info(f"Verifying answer '{state.answer_cot.answer}'")
if state.answer_cot.answer == "N/A":
return
messages = [
{
"role": "user",
"parts": [
{
"text": "Analyse the following context and the assertion and decide whether the context "
"entails the assertion or not."
},
{"text": f"Context: {state.answer_cot.relevant_context}"},
{
"text": f"Assertion: {state.answer_reformulation.declarative_answer}"
},
{
"text": f"Use this schema for your answer: {self.verification_cot_schema}. Be Factual."
},
],
}
]
response = self.model.generate_content(
messages,
generation_config={
"response_mime_type": "application/json",
"response_schema": self.verification_cot_schema,
"temperature": 0.0,
},
)
verification_cot = VerificationChainOfThoughts(**json.loads(response.text))
return {"verification_cot": verification_cot}
完整代码位于 https://github.com/CVxTz/document_ai_agents。
请注意每个节点如何使用各自的模式用于结构化输出和自己的提示。这得益于 Gemini 的 API 和 LangGraph具有的灵活性。
不妨使用与上面相同的示例来处理此代码。
(注意:我们没有针对第一个提示使用思路链,因此验证因我们的测试而触发。)
上下文
托马斯•杰斐逊(1743 年 4 月 13日— 1826 年 7 月 4 日)是美国政治家、种植园主、外交官、律师、建筑师、哲学家和开国元勋,曾于 1801 年至 1809 年担任美国第三任总统。他是《独立宣言》的主要起草者。在美国独立战争之后,在 1801 年成为总统之前,杰斐逊是华盛顿领导班子的美国第一任国务卿,然后在亚当斯领导班子担任美国第二副总统。杰斐逊是支持民主、共和主义和自然权利的主要倡导者,他在州、国家和国际层面制定了形成性文件和决定。(来源:维基百科)
问题
戴维斯•杰斐逊死于哪一年?
第一个节点结果(第一个回答):
relevant_context=“托马斯•杰斐逊(1743 年 4 月 13 日— 1826 年 7 月 4 日)是美国政治家、种植园主、外交官、律师、建筑师、哲学家和开国元勋,曾于 1801 年至 1809 年担任美国第三任总统。”
answer=’1826’
第二个节点结果(回答重新表述):
declarative_answer=“戴维斯•杰斐逊于 1826 年去世”
第三个节点结果(验证):
rationale=“上下文指出托马斯•杰斐逊于 1826 年去世。断言指出戴维斯•杰斐逊于 1826 年去世。上下文没有提到戴维斯•杰斐逊,只提到托马斯•杰斐逊。”
entailment=’No’
因此验证步骤拒绝(两者之间没有entailment)了初始回答。现在,我们可以避免向用户返回幻觉。
技巧 5:使用更强大的模型
由于预算或延迟方面的限制,该技巧并不总是容易运用,但你应该知道,更强大的 LLM 更不容易产生幻觉。因此如果可能的话,不妨为最敏感的用例选择功能更强大的 LLM。你可以在此处查看幻觉基准:https://github.com/vectara/hallucination-leaderboard。我们可以看到,该基准中的顶级模型(最少的幻觉)也位居传统 NLP 排行榜的榜首。
来源:https://github.com/vectara/hallucination-leaderboard
源许可证:Apache 2.0
结语
我们在本教程中探索了通过降低幻觉率来提高 LLM 输出可靠性的几种策略。主要建议包括使用思维链和提示以指导 LLM 调用,并使用基于工作流程的方法,其中代理旨在验证自己的回答。
这涉及多个步骤:
- 检索 LLM 用来生成回答的确切的上下文信息。
- 以声明形式重新表述回答以便于验证。
- 指示 LLM 检查上下文和重新表述的回答之间的一致性。
虽然所有这些技巧都可以显著提高准确性,但你应该知道没有那种方法是万无一失的。如果 LLM 在验证过程中过于保守或遗漏了真实的幻觉情况,始终存在拒绝有效回答的风险。因此,严格评估你的特定 LLM 工作流程仍然至关重要。
全部代码详见https://github.com/CVxTz/document_ai_agents。
原文标题:An Agentic Approach to Reducing LLM Hallucinations,作者:Youness Mansar