随着大型语言模型的不断发展,一个关键挑战是将它们出色的流畅性转化为可靠的智能助手。本章探讨了通过提示、工具和结构化推理技术增强大型语言模型的方法,以赋予其更大的智能、生产力和可信度。这些方法的统一主题是通过提示、工具和结构化推理技术增强大型语言模型。在本章中,我们将展示一些应用样例,演示这些技术的应用。
我们将从通过自动事实核查来解决虚构内容的关键弱点开始。通过将主张与现有证据进行核实,我们可以减少虚假信息的传播。接下来,我们将讨论大型语言模型的一个重要应用领域——摘要生成,我们将通过在不同层次上集成提示和用于处理非常长文档的Map Reduce方法来深入探讨这一领域。然后,我们将转向通过函数调用从文档中提取信息,这将引出工具集成的主题。我们将实施一个应用程序,展示连接外部数据和服务如何增强大型语言模型的有限世界知识。最后,我们将通过推理策略的应用进一步扩展这个应用程序。
简而言之,本章涵盖了以下内容:
- 通过事实核查减轻虚构内容的问题
- 信息摘要
- 从文档中提取信息
- 利用工具回答问题
- 探索推理策略
让我们开始通过自动事实核查来解决虚构内容的问题!
通过事实核查减轻幻觉
正如前几章讨论的那样,大型语言模型中的幻觉是指生成的文本与输入相比不忠实或毫无意义。这与忠实性形成对比,后者表示输出与源保持一致。幻觉可能传播错误信息,如虚假信息、谣言和欺骗性内容,这对社会构成威胁,包括对科学的不信任、极化和民主过程的影响。
新闻学和档案学对错误信息进行了广泛研究。事实核查倡议为记者和独立核查者提供培训和资源,实现专业验证的大规模应用。解决虚假主张对维护信息的完整性以及对抗对社会造成的有害影响至关重要。
解决幻觉的一种技术是自动事实核查——验证大型语言模型所做主张的真实性,与外部来源的证据相对比。这有助于捕捉不正确或未经验证的陈述。
事实核查包括三个主要阶段:
- 主张检测:识别需要验证的部分
- 证据检索:查找支持或反驳主张的来源
- 判决预测:根据证据评估主张的真实性
最后两个阶段的替代术语分别是理由生成和判决预测。
我们可以通过以下图表(来源 – github.com/Cartus/Auto… by Zhijiang Guo)看到这三个阶段的一般思路:
预训练的语言模型(LLMs)包含丰富的世界知识,可以通过提示获取事实。此外,外部工具可以搜索知识库、维基百科、教科书和语料库以找到证据。通过将声明基于数据,事实核查使LLMs更加可靠。
预训练的LLMs包含来自它们的训练数据的广泛世界知识。从2018年开始,语言模型已经在大型知识库(如维基百科)上进行了预训练;因此,它们能够回答来自维基百科或其他来源(因为它们的训练集越来越包括其他来源)的知识问题,例如互联网、教科书、arXiv和GitHub。
我们可以通过掩蔽和其他技术提示它们检索事实以获取证据。例如,为了回答问题“微软总部位于哪里?”,该问题将被重写为“微软总部位于[MASK]”,然后输入语言模型以获取答案。
另外,我们可以集成外部工具来搜索知识库、维基百科、教科书和其他语料库。关键思想是通过将虚构的声明基于实际数据源来验证它们。
自动事实核查通过检查LLMs的响应是否与现实世界的证据一致,提供了一种使其更可靠的方法。在接下来的章节中,我们将演示这种方法。
在LangChain中,我们有一个用于事实核查的链,采用提示链接,模型会积极质疑陈述中的假设。在这个自检链中,LLMCheckerChain,模型被逐个提示,首先是明确假设,看起来像这样:
Here's a statement: {statement}nMake a bullet point list of the assumptions you made when producing the above statement.n
请注意,这是一个字符串模板,其中花括号中的元素将被变量替换。接下来,这些假设将被反馈给模型,以便通过以下提示逐个检查它们:
Here is a bullet point list of assertions:
{assertions}
For each assertion, determine whether it is true or false. If it is false, explain why.nn
最后,模型被要求做出最终判断:
In light of the above facts, how would you answer the question '{question}'
LLMCheckerChain会自动完成所有这些,正如这个例子所示:
from langchain.chains import LLMCheckerChain
from langchain.llms import OpenAI
llm = OpenAI(temperature=0.7)
text = "What type of mammal lays the biggest eggs?"
checker_chain = LLMCheckerChain.from_llm(llm, verbose=True)
checker_chain.run(text)
该模型对这个问题可能会返回不同的结果,其中一些是错误的,而其中一些它将正确地识别为错误。当我尝试时,我得到的结果包括蓝鲸、北美河狸和已灭绝的巨型恐龙,作为对我的问题“哪种哺乳动物产下最大的卵?”的回答。以下是正确的答案:
Monotremes, a type of mammal found in Australia and parts of New Guinea, lay the largest eggs in the mammalian world. The eggs of the American echidna (spiny anteater) can grow as large as 10 cm in length, and dunnarts (mouse-sized marsupials found in Australia) can have eggs that exceed 5 cm in length.
• Monotremes can be found in Australia and New Guinea
• The largest eggs in the mammalian world are laid by monotremes
• The American echidna lays eggs that can grow to 10 cm in length
• Dunnarts lay eggs that can exceed 5 cm in length
• Monotremes can be found in Australia and New Guinea – True
• The largest eggs in the mammalian world are laid by monotremes – True
• The American echidna lays eggs that can grow to 10 cm in length – False, the American echidna lays eggs that are usually between 1 to 4 cm in length.
• Dunnarts lay eggs that can exceed 5 cm in length – False, dunnarts lay eggs that are typically between 2 to 3 cm in length.
The largest eggs in the mammalian world are laid by monotremes, which can be found in Australia and New Guinea.
Monotreme eggs can grow to 10 cm in length.
> Finished chain.
因此,尽管这种技术不能保证正确的答案,但它可以阻止一些不正确的结果。事实核查方法涉及将声明分解为更小的可检查查询,这些查询可以制定为问答任务。专为搜索领域数据集设计的工具可以帮助事实核查人员有效地找到证据。像谷歌和必应这样的现成搜索引擎还可以检索与主题和证据相关的内容,以准确捕捉陈述的真实性。我们将运用这种方法返回基于网络搜索和本章其他应用的结果。
在下一节中,我们将讨论自动化文本摘要和长文档(如研究论文)的过程。
总结信息
在当今快节奏的商业和研究环境中,跟上不断增长的信息量可能是一项艰巨的任务。对于计算机科学和人工智能等领域的工程师和研究人员,保持对最新发展的了解至关重要。然而,阅读和理解大量论文可能耗时且劳动密集。这就是自动化发挥作用的地方。作为工程师,我们受到构建和创新的激励,并通过创建管道和流程自动化避免重复的任务。这种常常被误解为懒惰的方法使工程师能够专注于更复杂的挑战,并更有效地利用他们的技能。
LLMs通过其强大的语言理解能力在压缩文本方面表现出色。我们将使用LangChain逐渐探索使用摘要技术的方法。
基本提示
要总结几句话,基本提示效果很好。只需指导LLM所需的长度并提供一段文本:
from langchain import OpenAI
prompt = """
Summarize this text in one sentence:
{text}
"""
llm = OpenAI()
summary = llm(prompt.format(text=text))
这与我们在第3章《入门LangChain》中看到的类似。text
是一个字符串变量,可以是我们想要总结的任何文本。
我们还可以使用LangChain装饰器语法,该语法在LangChain装饰器库中实现,如果您按照第3章《入门LangChain》中的说明进行了安装,应该已经安装了所有其他依赖项。
LangChain装饰器提供了与基本LangChain相比更Pythonic的接口,用于定义和执行提示,使更容易利用LLMs的强大功能。函数装饰器将提示文档转换为可执行代码,从而实现了多行定义和自然的代码流。
以下是摘要的装饰器示例:
from langchain_decorators import llm_prompt
@llm_prompt
def summarize(text:str, length="short") -> str:
"""
Summarize this text in {length} length:
{text}
"""
return
summary = summarize(text="让我告诉你一个我年轻时的无聊故事...")
输出,即summary
变量的值,我得到的是“演讲者即将分享他们年轻时的故事。” 您可以尝试更有意义和更长的摘要示例。
@llm_prompt
装饰器将文档字符串转换为提示并处理其执行。参数干净地传递并解析输出。这种抽象使得以自然的Python风格进行提示变得容易,同时处理底层的复杂性,使得专注于创建有效的提示变得容易。通过提供这种直观的接口,LangChain装饰器为开发人员释放了LLMs的潜力。
提示模板
对于动态输入,提示模板使得能够将文本插入预定义的提示中。提示模板允许可变长度限制和模块化的提示设计。 我们可以使用LangChain Expression Language (LCEL) 来实现这一点:
from langchain import PromptTemplate, OpenAI
from langchain.schema import StrOutputParser
llm = OpenAI()
prompt = PromptTemplate.from_template(
"Summarize this text: {text}?"
)
runnable = prompt | llm | StrOutputParser()
summary = runnable.invoke({"text": text})
LCEL提供了一种声明性的方式来组合链,比直接编写代码更直观和高效。LCEL的主要优势包括对异步处理、批处理、流处理、备用、并行处理的内置支持,以及与LangSmith追踪的无缝集成。 在这种情况下,runnable
是一个链,其中提示模板、LLM 和输出解析器被串联在一起。
密度链
Salesforce的研究人员(Adams及其同事,2023年;《从稀疏到密集:使用密度链提示的GPT-4摘要》)开发了一种名为Chain of Density(CoD)的提示引导技术,以逐步增加GPT-4生成的摘要的信息密度并控制长度。
这是与CoD一起使用的提示:
template = """Article: { text }
You will generate increasingly concise, entity-dense summaries of the above article.
Repeat the following 2 steps 5 times.
Step 1. Identify 1-3 informative entities (";" delimited) from the article which are missing from the previously generated summary.
Step 2. Write a new, denser summary of identical length which covers every entity and detail from the previous summary plus the missing entities.
A missing entity is:
- relevant to the main story,
- specific yet concise (5 words or fewer),
- novel (not in the previous summary),
- faithful (present in the article),
- anywhere (can be located anywhere in the article).
Guidelines:
- The first summary should be long (4-5 sentences, ~80 words) yet highly non-specific, containing little information beyond the entities marked as missing. Use overly verbose language and fillers (e.g., "this article discusses") to reach ~80 words.
- Make every word count: rewrite the previous summary to improve flow and make space for additional entities.
- Make space with fusion, compression, and removal of uninformative phrases like "the article discusses".
- The summaries should become highly dense and concise yet self-contained, i.e., easily understood without the article.
- Missing entities can appear anywhere in the new summary.
- Never drop entities from the previous summary. If space cannot be made, add fewer new entities.
Remember, use the exact same number of words for each summary.
Answer in JSON. The JSON should be a list (length 5) of dictionaries whose keys are "Missing_Entities" and "Denser_Summary".
"""
请注意,您可以轻松地调整这个模板以适应任何类型的内容,并提供一组不同的指导方针以适应其他应用。
CoD提示指导像GPT-4这样的高度强大的LLMs生成一篇文章的初始稀疏、冗长摘要,其中仅包含少量实体。然后,它迭代地识别1-3个缺失的实体,并将它们融合到先前摘要的同等词数的重写中。
在长度约束下进行的这种反复重写迫使逐步提高抽象程度、融合细节并进行压缩,以为每个步骤中的额外实体腾出空间。作者测量实体密度和源句对齐等统计数据,以表征增加密度的效果。
通过五个迭代步骤,摘要通过创造性的重写变得高度压缩,每个标记中包含更多的实体。作者进行了人类偏好研究和GPT-4评分,以评估在密度光谱上对整体质量的影响。
结果显示了通过密度获得的信息量与由于过度压缩而导致的连贯性下降之间的权衡。最佳密度平衡简洁和清晰,实体过多会使表达变得混乱。这种方法和分析为在AI文本生成中控制信息密度提供了启示。请尝试自己操作一下!
Map-Reduce 管道
LangChain支持使用LLMs处理文档的Map-Reduce方法,这允许对文档进行高效的处理和分析。可以将链应用于每个文档,然后将输出组合成单个文档。
为了总结长文档,我们可以首先将文档分成适合LLM令牌上下文长度的较小部分(块),然后进行Map-Reduce链在重新组合之前独立地对这些块进行总结。这使总结能够适用于任何长度的文本,并且可以控制块的大小。
关键步骤如下:
- 映射(Map):每个文档通过一个摘要链(LLM链)。
- 折叠(可选):总结的文档被合并为单个文档。
- 归约(Reduce):合并后的文档通过最终的LLM链生成输出。
因此,映射步骤并行地对每个文档应用链。减少步骤汇总映射的输出并生成最终结果。
可选的折叠,可能还涉及LLMs的使用,确保数据适应序列长度限制。如果需要,可以递归执行此压缩步骤。
这在下面的图中进行了说明:
这种方法的影响是它允许对文档进行并行处理,并能够使用LLMs来推理、生成或分析单个文档并组合它们的输出。
以下是加载PDF文档并对其进行总结的简单示例:
from langchain.chains.summarize import load_summarize_chain
from langchain import OpenAI
from langchain.document_loaders import PyPDFLoader
pdf_file_path = "<pdf_file_path>"
pdf_loader = PyPDFLoader(pdf_file_path)
docs = pdf_loader.load_and_split()
llm = OpenAI()
chain = load_summarize_chain(llm, chain_type="map_reduce")
chain.run(docs)
变量 pdf_file_path
是一个字符串,其中包含PDF文件的路径。请将文件路径替换为PDF文档的实际路径。
映射和减少步骤的默认提示为:
Write a concise summary of the following:
{text}
CONCISE SUMMARY:
我们可以为每个步骤指定任何提示。在本章的GitHub示例中,我们可以看到如何传递其他提示以进行文本摘要应用。在LangChainHub上,我们可以看到question-answering-with-sources
提示,该提示采用类似以下的减少/合并提示:
Given the following extracted parts of a long document and a question, create a final answer with references ("SOURCES").
If you don't know the answer, just say that you don't know. Don't try to make up an answer.
ALWAYS return a "SOURCES" part in your answer.
QUESTION: {question}
=========
Content: {text}
在上述提示中,我们可以制定一个具体的问题,但同样,我们也可以给LLM一个更抽象的指令,以提取假设和推论。文本将是映射步骤的摘要。这样的指令有助于防止产生虚构的结果。其他指令的例子可能包括将文档翻译成其他语言或以某种风格重新表达。
通过更改提示,我们可以询问这些文档的任何问题。这可以构建成一个自动化工具,可以快速总结长文本的内容,使其更易理解,正如您可以从该书的GitHub存储库中的summarize
包中了解的那样,该包展示了如何专注于响应的不同视角和结构(改编自David Shapiro)。
GitHub上的工具将以更简洁和简化的方式总结论文的核心断言、推论和机制。它还可以回答有关论文的具体问题,使其成为文献综述和加速科学研究的宝贵资源。总体而言,这种方法旨在通过提供更有效和可访问的方式来使研究人员随时了解最新研究成果。
通过LangChain进行深思熟虑的提示工程可以利用LLMs提供强大的摘要能力。一些建议如下:
- 从简单的方法开始,如果需要,再转向Map-Reduce。
- 调整块的大小以平衡上下文限制和并行性。
- 为获得最佳结果定制映射和减少提示。
- 压缩或递归减少块以适应上下文限制。
一旦我们开始进行大量调用,特别是在映射步骤中,如果我们使用云服务提供商,我们会看到标记(tokens)和因此成本增加。是时候给这个过程一些可见性了!
监控标记使用
在使用LLMs时,特别是在诸如映射操作之类的长循环中,跟踪标记的使用并了解您花费了多少钱非常重要。
对于任何对生成式AI的严肃使用,我们需要了解不同语言模型的能力、定价选项和用例。所有云提供商都提供不同的模型,以满足各种NLP需求。例如,OpenAI提供了强大的语言模型,适用于解决具有NLP的复杂问题,并提供基于使用的标记大小和数量的灵活定价选项。
例如,ChatGPT模型(如GPT-3.5-Turbo)专注于对话应用,如聊天机器人和虚拟助手。它们擅长以准确和流畅的方式生成响应。在InstructGPT家族中的不同模型,如Ada和Davinci,专为单轮指令遵循而设计,提供不同水平的速度和功率。Ada是最快的模型,适用于速度至关重要的应用,而Davinci是最强大的模型,能够处理复杂的指令。模型的定价取决于其功能,价格范围从像Ada这样的低成本选项到像Davinci这样的更昂贵选项。
OpenAI提供了DALL·E、Whisper和API服务,用于各种应用,如图像生成、语音转录、翻译和访问语言模型。DALL·E是一种人工智能驱动的图像生成模型,可以无缝集成到应用程序中,用于生成和编辑新颖的图像和艺术作品。OpenAI提供了三个分辨率级别,允许用户选择所需的详细级别。较高的分辨率提供更复杂和详细的内容,而较低的分辨率则提供更抽象的表示。每张图像的价格根据分辨率而异。
Whisper是一个可以将语音转录成文本并将多种语言翻译成英语的人工智能工具。它有助于捕捉对话,促进交流,并提高跨语言理解。使用Whisper的成本基于每分钟的费率。
我们可以通过连接到OpenAI回调来跟踪OpenAI模型中的标记使用情况:
from langchain import OpenAI, PromptTemplate
from langchain.callbacks import get_openai_callback
llm_chain = PromptTemplate.from_template("Tell me a joke about {topic}!") | OpenAI()
with get_openai_callback() as cb:
response = llm_chain.invoke(dict(topic="light bulbs"))
print(response)
print(f"Total Tokens: {cb.total_tokens}")
print(f"Prompt Tokens: {cb.prompt_tokens}")
print(f"Completion Tokens: {cb.completion_tokens}")
print(f"Total Cost (USD): ${cb.total_cost}")
我们应该看到带有成本和标记的输出。当我运行这个时,我得到了这个输出:
Q: How many light bulbs does it take to change people's minds?
A: Depends on how stubborn they are!
Total Tokens: 36
Prompt Tokens: 8
Completion Tokens: 28
Total Cost (USD): $0.00072
您可以更改模型和提示的参数,应该看到成本和标记随之而变。
获取标记使用的另外两种方法是:
- 作为OpenAI回调的替代方案,llm类的generate()方法返回LLMResult类型的响应,而不是字符串。这包括标记使用情况和完成原因,例如(来自LangChain文档):
input_list = [
{"product": "socks"},
{"product": "computer"},
{"product": "shoes"}
]
llm_chain.generate(input_list)
结果如下:
LLMResult(generations=[[Generation(text='nnSocktastic!', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='nnTechCore Solutions.', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='nnFootwear Factory.', generation_info={'finish_reason': 'stop', 'logprobs': None})]], llm_output={'token_usage': {'prompt_tokens': 36, 'total_tokens': 55, 'completion_tokens': 19}, 'model_name': 'text-davinci-003'})
- 最后,在OpenAI API中的聊天完成响应格式中,包括了一个包含标记信息的usage对象;例如,它可能如下所示(摘录):
{
"model": "gpt-3.5-turbo-0613",
"object": "chat.completion",
"usage": {
"completion_tokens": 17,
"prompt_tokens": 57,
"total_tokens": 74
}
}
这对于了解您在应用程序的不同部分上花费了多少钱非常有帮助。在第9章《生成式AI在生产中》中,我们将研究LangSmith和类似工具,它们提供了LLMs实际操作的额外可观察性,包括它们的标记使用情况。接下来,我们将看看如何使用LangChain和OpenAI函数从文档中提取特定的信息。
从文档中提取信息
在2023年6月,OpenAI宣布了OpenAI API的更新,包括对函数调用的新功能,增强了功能性。OpenAI添加了函数调用的功能,构建在指令调整的基础上。通过在模式中描述函数,开发人员可以调整LLMs以返回符合该模式的结构化输出,例如通过以预定义的JSON格式输出它们从文本中提取的实体。
函数调用使开发人员能够创建能够使用外部工具或OpenAI插件回答问题的聊天机器人。它还允许将自然语言查询转换为API调用或数据库查询,并从文本中提取结构化数据。
开发人员现在可以向gpt-4-0613和gpt-3.5-turbo-0613模型描述函数,并使模型智能生成包含调用这些函数的参数的JSON对象。此功能旨在增强GPT模型与外部工具和API之间的连接,提供一种可靠的方式从模型中检索结构化数据。
此更新的机制涉及在/v1/chat/completions端点中使用新的API参数,即functions。functions参数通过名称、描述、参数和要调用的函数本身来定义。开发人员可以使用JSON模式向模型描述函数,并指定要调用的所需函数。
在LangChain中,我们可以在OpenAI中使用这些函数调用进行信息提取或调用插件。对于信息提取,我们可以使用OpenAI聊天模型中的提取链从文本中获取特定实体及其属性,例如,这可以帮助识别文本中提到的人物。通过使用OpenAI的functions参数并指定模式,可以确保模型输出所需的带有适当类型的实体和属性。
这种方法的含义在于,它允许通过定义具有所需属性及其类型的模式来精确提取实体。它还能够指定哪些属性是必需的,哪些是可选的。
模式的默认格式是一个字典,但我们还可以使用Pydantic(一种流行的解析库)在提取过程中提供控制和灵活性来定义属性及其类型。
以下是用于信息提取CV的期望模式的示例:
from typing import Optional
from pydantic import BaseModel
class Experience(BaseModel):
start_date: Optional[str]
end_date: Optional[str]
description: Optional[str]
class Study(Experience):
degree: Optional[str]
university: Optional[str]
country: Optional[str]
grade: Optional[str]
class WorkExperience(Experience):
company: str
job_title: str
class Resume(BaseModel):
first_name: str
last_name: str
linkedin_url: Optional[str]
email_address: Optional[str]
nationality: Optional[str]
skill: Optional[str]
study: Optional[Study]
work_experience: Optional[WorkExperience]
hobby: Optional[str]
我们可以使用这个模式从CV中提取信息。
请注意,您应该根据第3章“使用LangChain入门”中的说明设置您的环境。我发现在这里导入我的config模块并执行setup_environment()最为方便。这会在代码的开头添加两行额外的代码:
from config import setup_environment
setup_environment()
这是我的建议 – 您可以采纳或忽略。这里有一个来自github.com/xitanggg/op…的CV的示例:
我们将尝试从这份简历中解析信息。
通过在LangChain中使用create_extraction_chain_pydantic()函数,我们可以将我们的模式作为输入提供,输出将是一个符合该模式的实例化对象。在其最简单的形式中,我们可以尝试以下代码片段:
from langchain.chains import create_extraction_chain_pydantic
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import PyPDFLoader
pdf_file_path = "<pdf_file_path>"
pdf_loader = PyPDFLoader(pdf_file_path)
docs = pdf_loader.load_and_split()
# 请注意,并非所有模型都启用了函数调用!
llm = ChatOpenAI(model_name="gpt-3.5-turbo-0613")
chain = create_extraction_chain_pydantic(pydantic_schema=Resume, llm=llm)
chain.run(docs)
请注意,pdf_file_path变量应为PDF文件的相对或绝对路径。我们应该得到类似于这样的输出:
[Resume(first_name='John', last_name='Doe', linkedin_url='linkedin.com/in/john-doe', email_address='hello@openresume.com', nationality=None, skill='React', study=None, work_experience=WorkExperience(start_date='May 2023', end_date='Present', description='Lead a cross-functional team of 5 engineers in developing a search bar, which enables thousands of daily active users to search content across the entire platform. Create stunning home page product demo animations that drives up sign up rate by 20%. Write clean code that is modular and easy to maintain while ensuring 100% test coverage.', company='ABC Company', job_title='Software Engineer'), hobby=None)]
这个结果离完美还有很远 – 只有一个工作经验被解析出来。但考虑到我们迄今为止所付出的努力,这是一个良好的开始。有关完整示例,请参阅GitHub存储库。我们可以添加更多功能,例如猜测个性或领导能力。
OpenAI将这些函数调用注入到系统消息中,采用了一定的语法,这是它们的模型经过优化的。这意味着函数会计入上下文限制,并相应地按输入令牌计费。
LangChain本地具有将函数调用注入为提示的功能。这意味着我们可以在LLM应用程序内使用除OpenAI之外的其他提供者的模型进行函数调用。我们现在将看看这一点,并将其构建到一个使用Streamlit的交互式Web应用程序中。
指令调整和函数调用允许模型生成可调用的代码。这导致了工具集成,LLM代理可以执行这些函数调用,将LLMs与实时数据、服务和运行时环境连接起来。在下一节中,我们将讨论如何通过检索外部知识源来增强理解,从而使工具能够增强上下文。
用工具回答问题
LLMs(大型语言模型)是在一般语料库数据上训练的,可能在需要特定领域知识的任务上效果不佳。单独使用时,LLMs无法与环境互动并访问外部数据源;然而,LangChain提供了一个平台,用于创建可以访问实时信息、执行任务(如天气预报、预订、食谱建议和任务管理等)的工具。框架内的代理和链的工具使得能够开发由LLMs驱动的、具有数据感知和代理性质的应用程序,从而扩展了LLMs解决问题的方法,拓展了它们的用例,并使其更加灵活和强大。
工具的一个重要方面是它们能够在特定领域内工作或处理特定的输入。例如,LLM缺乏固有的数学能力。然而,一个数学工具,如计算器,可以接受数学表达式或方程作为输入并计算结果。结合这样一个数学工具,LLM可以执行计算并提供准确的答案。
工具利用上下文对话表示来搜索与用户查询相关的相关数据源。例如,对于有关历史事件的问题,工具可以检索维基百科文章以增强上下文。
通过基于实时数据的响应,工具减少了虚构或不正确的回复。上下文工具的使用与聊天机器人的核心语言能力相辅相成,使响应更加有用、正确,并与真实世界的知识保持一致。工具为问题提供了创造性的解决方案,并在各个领域为LLMs开辟了新的可能性。例如,可以开发一个工具,使LLM能够执行高级检索搜索、查询数据库获取特定信息、自动撰写电子邮件,甚至处理电话。让我们看看这是如何实现的!
使用工具进行信息检索
我们在LangChain中有很多可用的工具,而且如果这还不够,自己开发工具也不难。让我们设置一个带有几个工具的代理:
from langchain.agents import (
AgentExecutor, AgentType, initialize_agent, load_tools
)
from langchain.chat_models import ChatOpenAI
def load_agent() -> AgentExecutor:
llm = ChatOpenAI(temperature=0, streaming=True)
# DuckDuckGoSearchRun, wolfram alpha, arxiv search, wikipedia
# TODO: try wolfram-alpha!
tools = load_tools(
tool_names=["ddg-search", "wolfram-alpha", "arxiv", "wikipedia"],
llm=llm
)
return initialize_agent(
tools=tools, llm=llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
这个函数返回AgentExecutor
,它是一个链;因此,如果我们愿意,我们可以将它集成到更大的链中。Zero-Shot代理是一个通用的动作代理,我们将在下一部分中讨论。
请注意在ChatOpenAI
构造函数中的streaming
参数被设置为True
。这样做是为了提供更好的用户体验,因为这意味着文本响应将在接收到时更新,而不是在所有文本完成后一次性更新。目前,只有OpenAI、ChatOpenAI和ChatAnthropic实现支持流式处理。
所有提到的工具都有其特定的目的,这是作为描述的一部分传递给语言模型的。这些工具被插入到代理中:
- DuckDuckGo:侧重隐私的搜索引擎;一个额外的优势是它不需要开发者注册
- Wolfram Alpha:将自然语言理解与数学能力结合在一起的集成,用于问题如“2x+5 = -3x + 7?”
- arXiv:搜索学术预印本出版物;这对于与研究相关的问题很有用
- Wikipedia:对于任何关于显著知名实体的问题
请注意,要使用Wolfram Alpha,您必须设置一个帐户并使用您在 products.wolframalpha.com/api 创建的开发者令牌设置WOLFRAM_ALPHA_APPID
环境变量。请注意,该网站有时可能会有点慢,注册可能需要一些耐心。
除了DuckDuckGo之外,LangChain还集成了许多其他搜索工具,让您可以使用Google或Bing搜索引擎或使用元搜索引擎。还有一个用于天气信息的Open-Meteo集成;但是,通过搜索也可以获取这些信息。
构建可视化界面
在使用LangChain开发智能代理后,自然的下一步是将其部署到一个易于使用的应用程序中。Streamlit为此目标提供了一个理想的框架。作为一个针对ML工作流程进行优化的开源平台,Streamlit使得将我们的代理包装成交互式Web应用程序变得简单。那么让我们将我们的代理作为Streamlit应用程序提供!
对于这个应用程序,我们将需要Streamlit、unstructured和docx等库。这些库已经在我们在第3章《开始使用LangChain》中设置的环境中。
让我们使用刚刚定义的load_agent()
函数编写代码:
import streamlit as st
from langchain.callbacks import StreamlitCallbackHandler
chain = load_agent()
st_callback = StreamlitCallbackHandler(st.container())
if prompt := st.chat_input():
st.chat_message("user").write(prompt)
with st.chat_message("assistant"):
st_callback = StreamlitCallbackHandler(st.container())
response = chain.run(prompt, callbacks=[st_callback])
st.write(response)
请注意,我们在对链的调用中使用了回调处理程序,这意味着我们将根据模型的响应即时看到响应。我们可以在终端中像这样本地启动应用程序:
PYTHONPATH=. streamlit run question_answering/app.py
然后我们可以在浏览器中打开我们的应用程序。下面是说明应用程序外观的截图:
Streamlit应用程序的部署可以在本地进行,也可以在服务器上进行。另外,您还可以将其部署到Streamlit Community Cloud或Hugging Face Spaces上。
对于Streamlit Community Cloud,操作如下:
- 创建一个GitHub仓库。
- 转到Streamlit Community Cloud,点击New app,选择新的仓库。
- 点击Deploy!。
至于Hugging Face Spaces,步骤如下:
- 创建一个GitHub仓库。
- 在github.com/billxbf/ReW…,为Binfeng Xu等人于2023年5月发表的论文《为高效的增强语言模型资源解耦推理与观察》实现)。
工具是代理可以使用的可用资源,例如搜索引擎或数据库。LLMChain负责生成文本提示并解析输出以确定下一步操作。代理类使用LLMChain的输出来决定采取哪种行动。
虽然通过工具增强的语言模型将LLMs与搜索引擎和数据库等外部资源结合起来以增强推理能力,但这可以通过代理进一步增强。
在LangChain中,这包括三个部分:
- 工具
- 一个LLMChain
- 代理本身
有两种关键的代理体系结构:
- 行动代理根据每个行动后的观察进行迭代推理。
- 计划和执行代理在采取任何行动之前完全计划。
在依赖观察的推理中,代理通过每个行动后的观察迭代地提供上下文和示例给LLM生成思考和行动。从工具中获得的观察被纳入,以指导下一个推理步骤。这种方法在行动代理中使用。
另一种选择是计划和执行代理,它首先创建一个完整的计划,然后收集证据来执行它。计划生成器LLM生成计划列表(P)。代理使用工具收集证据(E)。P和E组合并馈送到求解器LLM以生成最终输出。
计划和执行将计划与执行分开。可以使用较小的专门模型来执行计划和解决者的角色。这样做的权衡是计划和执行需要更多的前期计划。
我们可以在以下图表中看到依赖观察模式的推理(来源:arxiv.org/abs/2305.18…;Binfeng Xu等人,2023年5月)。
依赖观察的推理涉及根据当前的知识状态或通过观察获取的证据进行判断、预测或选择。在每次迭代中,代理向LLM提供上下文和示例。用户的任务首先与上下文和示例结合,并提供给LLM以启动推理。LLM生成一个思考和一个动作,然后等待来自工具的观察。观察结果被添加到提示中,以启动对LLM的下一次调用。在LangChain中,这是一个行动代理(也称为Zero-Shot代理,ZERO_SHOT_REACT_DESCRIPTION),这是创建代理时的默认设置。
正如前面提到的,计划也可以在采取任何行动之前制定。这种策略(在LangChain中称为计划和执行代理)在这里的图表中有所说明(来源:arxiv.org/abs/2305.18…;Binfeng Xu等人,2023年5月)。
规划者(一个LLM)可以通过进行规划和工具使用进行微调,生成一系列计划(P),并调用一个工作器(在LangChain中是代理)通过使用工具来收集证据(E)。 P和E与任务结合,然后被馈送到求解器(一个LLM)以获取最终答案。我们可以编写一个伪算法,如下所示:
- 规划所有步骤(规划者)。
- 对于每个步骤,确定完成该步骤所需的适当工具并执行。
规划者和求解器可以是不同的语言模型。这打开了为规划者和求解器使用更小、专业化模型,并为每个调用使用更少令牌的可能性。
我们可以在我们的研究应用程序中实现计划和解决方案;让我们开始吧!
首先,让我们向load_agent()函数添加一个strategy变量。它可以取两个值,即plan-and-solve或zero-shot-react。对于zero-shot-react,逻辑保持不变。对于plan-and-solve,我们将定义一个规划者和一个执行者,我们将使用它们创建一个PlanAndExecute代理执行器:
from typing import Literal from langchain.agents import initialize_agent, load_tools, AgentType from langchain.chains.base import Chain from langchain.chat_models import ChatOpenAI from langchain_experimental.plan_and_execute import ( load_chat_planner, load_agent_executor, PlanAndExecute ) ReasoningStrategies = Literal["zero-shot-react", "plan-and-solve"] def load_agent( tool_names: list[str], strategy: ReasoningStrategies = "zero-shot-react" ) -> Chain: llm = ChatOpenAI(temperature=0, streaming=True) tools = load_tools( tool_names=tool_names, llm=llm ) if strategy == "plan-and-solve": planner = load_chat_planner(llm) executor = load_agent_executor(llm, tools, verbose=True) return PlanAndExecute(planner=planner, executor=executor, verbose=True) return initialize_agent( tools=tools, llm=llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True )
请查阅GitHub上的版本(question_answering包中)获取完整版本。例如,我们可能会遇到输出解析错误。我们可以通过在initialize_agent()方法中设置handle_parsing_errors来处理这些错误。
让我们定义一个通过Streamlit中的单选按钮设置的新变量。我们将这个变量传递给load_agent()函数:
strategy = st.radio( "Reasoning strategy", ("plan-and-solve", "zero-shot-react") )
您可能已经注意到,load_agent()方法接受一个字符串列表tool_names。这也可以在用户界面(UI)中选择:
tool_names = st.multiselect( 'Which tools do you want to use?', [ "google-search", "ddg-search", "wolfram-alpha", "arxiv", "wikipedia", "python_repl", "pal-math", "llm-math" ], ["ddg-search", "wolfram-alpha", "wikipedia"])
最后,在应用程序中仍然加载代理:
agent_chain = load_agent(tool_names=tool_names, strategy=strategy)
我们可以使用Streamlit执行此代理。我们应该在终端中运行以下命令:
PYTHONPATH=. streamlit run question_answering/app.py
我们应该看到Streamlit启动我们的应用程序。如果我们在指定的URL上(默认为http://localhost:8501/)打开我们的浏览器,我们应该在这里看到UI:
请在您的浏览器中查看应用程序,查看针对问题“What is a plan-and-solve agent in the context of LLM?”的不同步骤。 步骤如下 – 请注意,结果可能不是100%准确,但这是代理生成的内容:
- 定义LLMs:LLMs是经过大量文本数据训练的人工智能模型,可以根据其接收的输入生成类似人类的文本。
- 在LLMs的背景下理解计划的概念:在大型语言模型的背景下,计划是指模型为解决问题或回答问题生成的结构化大纲或一组步骤。
- 在LLMs的背景下理解解决代理的概念:解决代理是一种作为代理的LLM。它负责生成解决问题或回答问题的计划。
- 了解在LLMs中计划和解决代理的重要性:计划和解决代理有助于组织模型的思维过程,并为解决问题或回答问题的任务提供结构化方法。
给定上述步骤,回应用户的原始问题: 在大型语言模型的背景下,计划是由解决代理生成的结构化大纲或一组步骤,用于解决问题或回答问题。解决代理是大型语言模型的组成部分,负责生成这些计划。
因此,第一步是执行LLMs的查找:
Action: { "action": "Wikipedia", "action_input": "large language models" }
我们没有讨论问题回答中的另一个方面,即在这些步骤中使用的提示策略。我们将在第8章“定制LLMs及其输出”中详细讨论提示技术,但在这里简要概述一下:
- Few-shot chain-of-thought(CoT)提示通过逐步推理指导LLM进行思考过程。
- 零射击CoT提示通过简单地指示LLM“逐步思考”来引导推理步骤,而不提供示例。
- CoT提示旨在通过示例帮助理解推理过程。
此外,虽然在计划和解决中,复杂任务被分解为按顺序执行的子任务计划,但这可以通过更详细的说明进行扩展,以提高推理质量,如强调关键变量和常识。
您可以在BlockAGI项目中找到LangChain增强信息检索的一个非常高级的示例,该项目受到BabyAGI和AutoGPT的启发,网址为github.com/blockpipe/B…
这结束了我们对推理策略的介绍。所有策略都存在问题,可能表现为计算错误、遗漏步骤错误和语义误解。然而,它们有助于提高生成的推理步骤的质量,增加解决问题任务的准确性,并增强LLMs处理各种推理问题的能力。
总结
在本章中,我们首先讨论了幻觉和自动事实检查的问题,以及如何使LLMs更可靠。我们实施了一些简单的方法,有助于使LLM的输出更准确。然后,我们查看并实施了提示策略,以拆分和总结文档。这对于消化大型研究文章或分析可能非常有帮助。一旦我们开始对LLMs进行大量的串联调用,这可能意味着我们会产生很多成本。因此,我专门致力于使用标记的子节。
OpenAI API实现了一些功能,我们可以使用这些功能,其中包括在文档中进行信息提取。我们已经实现了一个非常简单的简历解析器的版本,作为此功能的示例,指示了如何应用此功能。然而,工具和函数调用并不是OpenAI独有的。指令调整、函数调用和工具使用的演进使模型能够超越自由文本生成,实现通过与真实系统交互来强大地自动化任务。这些方法解锁了更有能力、可靠的人工智能助手。通过LangChain,我们可以实现调用工具的不同代理。我们使用Streamlit实现了一个应用程序,该应用程序可以通过依赖外部工具(如搜索引擎或维基百科)来回答研究问题。与检索增强生成(RAG)不同,RAG将在下一章中讨论,它使用向量搜索进行语义相似性,工具通过直接查询数据库、API和其他结构化的外部来源提供上下文增强。由工具检索的事实信息补充了聊天机器人的内部上下文。
最后,我们查看了代理使用的不同策略来做决策。主要的区别在于决策的时机。我们在Streamlit应用中实现了一个计划与执行和一个零射击代理。
虽然本章介绍了许多有希望的方向,以开发功能强大且值得信赖的LLMs,但随后的章节将深入探讨在此处开发的技术。例如,在第6章《使用生成式人工智能开发软件》和第7章《数据科学中的LLMs》,我们将更详细地讨论与代理进行推理,并在第8章《定制LLMs及其输出》中提供提示技术的概述。