如果你曾与LLM打过交道,就会知道它们有时会“胡言乱语”。这意味着它们生成的文本要么没有意义,要么与输入数据相矛盾。这是一个常见问题,会降低由LLM驱动的应用程序的可靠性。
在这篇文章中,我们将探讨几种简单的技术来减少“胡言乱语”的可能性。遵循这些提示,你(希望)可以提高人工智能应用程序的准确性。
“胡言乱语”有多种类型:
本文将针对上述所有类型进行探讨。
技巧1:使用锚定
锚定是指在向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年任美国第三任总统。[6]他是《独立宣言》的主要撰写人。美国独立战争后,在1801年成为总统之前,杰斐逊是乔治·华盛顿领导下的美国第一任国务卿,然后是约翰·亚当斯领导下的美国第二任副总统。杰斐逊是民主、共和主义和自然权利的主要倡导者,他在州、国家和国际层面制定了具有形成性的文件和决策。(来源:维基百科)
问题
戴维斯·杰斐逊是哪一年去世的?
一个天真的方法得出的回答是:
回答
answer=’1826'
这显然是错误的,因为上下文中根本没有提到杰斐逊·戴维斯。是托马斯·杰斐逊在1826年去世。
如果我们将响应的模式改为使用思维链:
class AnswerChainOfThoughts(BaseModel):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:使用代理方法
我们将构建一个简单的代理,实现一个三步流程:
为了实现这一点,我们在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}
注意,每个节点都使用自己的模式来结构化输出,并使用自己的提示。这得益于Gemini API和LangGraph的灵活性。
让我们用上面相同的例子来逐步讲解这段代码
(注:我们在第一个提示中没有使用思维链,以便触发我们的测试验证。)
上下文
托马斯·杰斐逊(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。你可以在这里查看幻觉基准测试:https://github.com/vectara/hallucination-leaderboard。我们可以看到,在这个基准测试中排名靠前的模型(幻觉最少)也在传统NLP排行榜上名列前茅。
结论
在本文中,我们探索了通过降低幻觉率来提高LLM输出可靠性的策略。主要建议包括仔细格式化提示和引导LLM调用,以及采用基于工作流的方法,其中设计代理来验证其自身的答案。
这涉及多个步骤:
虽然所有这些技巧都能显著提高准确性,但你应该记住,没有哪种方法是万无一失的。如果LLM在验证过程中过于保守,则总是存在拒绝有效答案的风险,或者可能遗漏真正的幻觉情况。因此,对你的特定LLM工作流进行严格评估仍然是必不可少的。