如果你曾使用过大型语言模型(LLM),你就会知道它们有时会“产生幻觉”。这意味着它们生成的文本要么毫无意义,要么与输入数据相矛盾。这是一个常见问题,可能会损害基于LLM的应用程序的可靠性。
在这篇文章中,我们将探讨几种简单的技术来降低产生幻觉的可能性。遵循这些技巧,你(希望)可以提高你的人工智能应用程序的准确性。
幻觉有多种类型:
技巧1:使用接地(Grounding)
接地是在要求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特殊要求的模式。
这段代码定义了一个Pydantic模式,并将这个模式作为查询的一部分发送在“response_schema”字段中。这迫使LLM在其回应中遵循这个模式,从而使得解析其输出变得更加容易。
技巧3:使用思维链和更好的提示
有时,在给出最终答案之前,给LLM一些空间来构思其回应,可以帮助产生更高质量的回应。这种技术被称为“思维链”,因其有效且易于实现而被广泛使用。
我们还可以明确要求LLM,如果找不到足够的上下文来产生高质量的回应,就用“N/A”来回答。这将为它提供一个简单的出路,而不是试图回答它无法回答的问题。
例如,让我们来看看这个简单的问题和上下文:
上下文
托马斯·杰斐逊(1743年4月13日[旧历4月2日]-1826年7月4日)是美国政治家、种植园主、外交家、律师、建筑师、哲学家和开国元勋,1801年至1809年任美国第三任总统。他是《独立宣言》的主要撰写人。在美国独立战争之后,并在1801年成为总统之前,杰斐逊是乔治·华盛顿领导下的美国第一任国务卿,然后是约翰·亚当斯领导下的美国第二任副总统。杰斐逊是民主、共和主义和自然权利的主要倡导者,他在州、国家和国际层面都制定了具有形成性的文件和决策。
问题
戴维斯·杰斐逊是哪一年去世的?
回应
答案=‘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’回答”。
采用这种新方法后,我们得到了以下推理过程(记住,这是思维链):
提供的文本讨论的是托马斯·杰斐逊,而不是杰斐逊·戴维斯。其中没有包含关于杰斐逊·戴维斯去世的任何信息。
最终答案是:
答案=‘N/A’
技巧4:使用代理方法
我们将构建一个简单的代理,它实现一个三步流程:
为了实现这一点,我们在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}
让我们使用与上面相同的例子来逐步分析这段代码
(注意:我们在第一个提示中没有使用思维链,以便为我们的测试触发验证。)
上下文
托马斯·杰斐逊(1743年4月13日[旧历4月2日]—1826年7月4日)是美国政治家、种植园主、外交家、律师、建筑师、哲学家和开国元勋,1801年至1809年间担任美国第三任总统。[6]他是《独立宣言》的主要撰写人。在美国独立战争之后,以及在1801年成为总统之前,杰斐逊是乔治·华盛顿领导下的美国第一任国务卿,然后是约翰·亚当斯领导下的美国第二任副总统。杰斐逊是民主、共和主义和自然权利的主要倡导者,他在州、国家和国际层面都制定了具有影响力的文件和决策。
问题
戴维斯·杰斐逊是哪一年去世的?
第一个节点结果(首次回答):
相关上下文=’托马斯·杰斐逊(1743年4月13日[旧历4月2日]—1826年7月4日)是美国政治家、种植园主、外交家、律师、建筑师、哲学家和开国元勋,1801年至1809年间担任美国第三任总统。’
答案=’1826'
第二个节点结果(答案重新表述):
陈述性答案=’戴维斯·杰斐逊于1826年去世’
第三个节点结果(验证):
推理=’上下文指出托马斯·杰斐逊于1826年去世。陈述中指出戴维斯·杰斐逊于1826年去世。上下文中并未提及戴维斯·杰斐逊,只提到了托马斯·杰斐逊。’
蕴含关系=’否’
因此,验证步骤拒绝了(两者之间没有蕴含关系)初始答案。现在我们可以避免向用户返回错误的信息。
额外提示:使用更强的模型
这个提示由于预算或延迟限制并不总是容易实现,但你应该知道,更强的LLM更不容易产生幻觉。因此,如果可能的话,对于你最敏感的使用场景,选择更强大的LLM。我们可以看到,在这个基准测试中表现最好的模型(幻觉最少)也在传统的NLP排行榜上名列前茅。
结论
在本文中,我们探讨了通过降低幻觉率来提高大型语言模型(LLM)输出可靠性的策略。主要建议包括仔细格式化和提示以指导LLM调用,以及采用基于工作流的方法,其中代理被设计为验证自己的答案。