AI 智能体实战(三)
在 AI 应用中,记忆区分了非结构化和结构化记忆,突出了它们在为更相关的交互情境化提示中的应用。检索增强生成(RAG)是一种通过使用向量嵌入和相似度搜索从外部文档中检索相关内容来增强提示的上下文机制。使用文档索引进行语义搜索,通过 TF-IDF 和余弦相似度将文档转换为语义向量,增强在索引文档中执行语义搜索的能力。向量数据库和相似度搜索存储将文档向量存储在向量数据库中,便于高效的相似度搜索并提高检
原文:
zh.annas-archive.org/md5/85b56eb87c9f253ac48b31cb2ceb388a译者:飞龙
第八章:理解代理记忆和知识
本章涵盖
-
人工智能功能中的知识/记忆检索
-
使用 LangChain 构建检索增强生成工作流程
-
Nexus 中用于代理知识系统的检索增强生成
-
代理中记忆的检索模式
-
使用记忆和知识压缩改进增强检索系统
现在我们已经探讨了使用外部工具(如原生或语义函数形式的插件)进行代理动作,我们可以看看在代理和聊天界面中使用检索来处理记忆和知识的作用。我们将描述记忆和知识以及它们与提示工程策略的关系,然后,为了理解记忆知识,我们将研究文档索引,使用 LangChain 构建检索系统,利用 LangChain 使用记忆,并使用 Nexus 构建语义记忆。
8.1 理解人工智能应用中的检索
代理和聊天应用中的检索是一种获取知识并将其存储在通常外部且长期存在的存储中的机制。非结构化知识包括对话或任务历史、事实、偏好或其他用于上下文化的提示所需的项目。结构化知识通常存储在数据库或文件中,通过原生函数或插件访问。
如图 8.1 所示,记忆和知识是用于向提示添加更多上下文和相关信息的基本元素。提示可以通过从文档信息到先前任务或对话以及其他参考信息的一切进行增强。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-1.png
图 8.1 展示了使用以下提示工程策略(使用外部工具和提供参考文本)对提示进行记忆、检索和增强。
图 8.1 中展示的提示工程策略可以应用于记忆和知识。知识并不被视为记忆,而是对现有文档提示的增强。知识和记忆都使用检索作为查询非结构化信息的基础。
检索机制,称为检索增强生成(RAG),已成为提供相关上下文的标准。驱动 RAG 的确切机制也驱动着记忆/知识,理解其工作原理至关重要。在下一节中,我们将检查 RAG 是什么。
8.2 检索增强生成(RAG)的基本原理
RAG 已成为支持文档聊天或问答聊天的流行机制。系统通常通过用户提供相关文档(如 PDF 文件),然后使用 RAG 和大型语言模型(LLM)查询该文档来实现。
图 8.2 展示了如何使用 LLM(大型语言模型)查询文档。在查询任何文档之前,它必须首先被加载,转换为上下文块,嵌入到向量中,并存储在向量数据库中。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-2.png
图 8.2 RAG 的两个阶段:首先,文档必须被加载、转换、嵌入和存储,其次,可以使用增强生成进行查询。
用户可以通过提交查询来查询先前索引的文档。然后,该查询被嵌入到向量表示中,以在向量数据库中搜索相似的片段。与查询内容相似的内容随后用作上下文,并填充到提示中以进行增强。提示被推送到一个大型语言模型 (LLM),该模型可以使用上下文信息来帮助回答查询。
非结构化的记忆/知识概念依赖于图 8.2 所示的检索模式的一种文本相似度搜索格式。图 8.3 展示了记忆如何使用相同的嵌入和向量数据库组件。而不是预加载文档,对话或对话的一部分被嵌入并保存到向量数据库中。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-3.png
图 8.3 增强生成中的记忆检索使用相同的嵌入模式将项目索引到向量数据库中。
检索模式和文档索引复杂且需要仔细考虑才能成功应用。这需要理解数据是如何存储和检索的,我们将在下一节开始展开讨论。
8.3 深入探讨语义搜索和文档索引
文档索引将文档信息转换为更易于恢复的形式。索引的查询或搜索方式也会起到作用,无论是搜索特定的单词集还是想要逐词匹配。
语义搜索是通过词语和意义匹配搜索短语的内容。通过语义进行搜索的能力强大且值得深入探讨。在下一节中,我们将探讨向量相似性搜索如何为语义搜索奠定框架。
8.3.1 应用向量相似性搜索
现在让我们看看如何将文档转换为语义向量,或者说是可以用于执行距离或相似度匹配的文本表示。将文本转换为语义向量的方法有很多,所以我们将探讨一个简单的方法。
在一个新的 Visual Studio Code (VS Code) 工作区中打开 chapter_08 文件夹。创建一个新的环境,并使用 pip install 命令安装 requirements.txt 文件以解决所有章节的依赖项。如果您需要帮助设置新的 Python 环境,请参阅附录 B。
现在打开 VS Code 中的 document_vector_similarity.py 文件,并查看列表 8.1 的顶部部分。此示例使用词频-逆文档频率 (TF-IDF)。这个数值统计量反映了单词在文档集合或文档集中的重要性,其比例与单词在文档中出现的次数成正比,并受到文档集中单词频率的影响。TF-IDF 是理解文档集合中单个文档重要性的经典度量。
列表 8.1 document_vector_similarity(转换为向量)
import plotly.graph_objects as go
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
documents = [ #1
"The sky is blue and beautiful.",
"Love this blue and beautiful sky!",
"The quick brown fox jumps over the lazy dog.",
"A king's breakfast has sausages, ham, bacon, eggs, toast, and beans",
"I love green eggs, ham, sausages and bacon!",
"The brown fox is quick and the blue dog is lazy!",
"The sky is very blue and the sky is very beautiful today",
"The dog is lazy but the brown fox is quick!"
]
vectorizer = TfidfVectorizer() #2
X = vectorizer.fit_transform(documents) #3
#1 文档样本
#2 使用 TF–IDF 进行向量化
#3 向量化文档。
让我们使用样本句子,“The sky is blue and beautiful”,将 TF–IDF 分解为其两个组成部分,并专注于单词 blue。
术语频率(TF)
术语频率 衡量一个术语在文档中出现的频率。因为我们只考虑一个文档(我们的样本句子),blue 的 TF 的最简单形式可以通过将 blue 在文档中出现的次数除以文档中的总单词数来计算。让我们来计算它:
blue 在文档中出现的次数:1
文档中的总单词数:6
TF = 1 ÷ 6TF = .16
逆文档频率(IDF)
逆文档频率 衡量一个术语在整个语料库中的重要性。它是通过将文档总数除以包含该术语的文档数,然后取该商的对数来计算的:
IDF = log(文档总数 ÷ 包含该词的文档数)
在这个例子中,语料库是包含八个文档的小集合,其中 blue 出现在其中的四个文档中。
IDF = log(8 ÷ 4)
TF–IDF 计算
最后,我们通过将 TF 和 IDF 分数相乘来计算样本句子中 blue 的 TF–IDF 分数:
TF–IDF = TF × IDF
让我们使用提供的示例来计算单词 blue 的实际 TF–IDF 值;首先,计算词频(单词在文档中出现的频率)如下:
TF = 1 ÷ 6
假设对数的底数为 10(常用),则逆文档频率的计算如下:
IDF = log10 (8 ÷ 4)
现在,让我们计算句子“ The sky is blue and beautiful”中单词 blue 的确切 TF–IDF 值:
术语频率(TF)大约为 0.1670。
逆文档频率(IDF)大约为 0.301。
因此,blue 的 TF–IDF(TF × IDF)分数大约为 0.050。
这个 TF–IDF 分数表示在给定的文档(样本句子)中,在指定的语料库(八个文档,其中四个包含 blue)的上下文中,单词 blue 的相对重要性。更高的 TF–IDF 分数意味着更大的重要性。
我们在这里使用 TF–IDF,因为它简单易用。现在,我们已经将元素表示为向量,我们可以使用余弦相似度来衡量文档相似度。余弦相似度是一种用于计算多维空间中两个非零向量之间角度余弦的度量,它表示它们在不考虑它们大小的情况下有多相似。
图 8.4 展示了余弦距离如何比较两段文本或文档的向量表示。余弦相似度返回一个从 –1(不相似)到 1(相同)的值。余弦距离 是一个介于 0 到 2 之间的归一化值,通过从余弦相似度中减去 1 得到。余弦距离为 0 表示相同的项目,而 2 表示完全相反。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-4.png
图 8.4 如何测量余弦相似度
列表 8.2 展示了如何使用 scikit-learn 的 cosine_similarity 函数计算余弦相似度。对集合中的每个文档与其他所有文档之间的相似度进行计算。文档的相似度矩阵存储在 cosine_similarities 变量中。然后,在输入循环中,用户可以选择文档以查看其与其他文档的相似度。
列表 8.2 document_vector_similarity(余弦相似度)
cosine_similarities = cosine_similarity(X) #1
while True: #2
selected_document_index = input(f"Enter a document number
↪ (0-{len(documents)-1}) or 'exit' to quit: ").strip()
if selected_document_index.lower() == 'exit':
break
if not selected_document_index.isdigit() or
↪ not 0 <= int(selected_document_index) < len(documents):
print("Invalid input. Please enter a valid document number.")
continue
selected_document_index = int(selected_document_index) #3
selected_document_similarities = cosine_similarities[selected_document_index] #4
# code to plot document similarities omitted
#1 计算所有向量对的文档相似度
#2 主要输入循环
#3 获取要比较的选定文档索引
#4 从所有文档中提取计算出的相似度
图 8.5 展示了在 VS Code 中运行示例的输出(按 F5 进入调试模式)。选择文档后,您将看到集合中各种文档之间的相似度。一个文档与其自身之间的余弦相似度为 1。请注意,由于 TF-IDF 向量化,您不会看到负相似度。我们将在稍后探讨其他更复杂的测量语义相似度的方法。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-5.png
图 8.5 选定文档与文档集之间的余弦相似度
向量化方法将决定文档之间的语义相似度度量。在我们继续探讨更好的文档向量化方法之前,我们将检查存储向量以执行向量相似度搜索。
8.3.2 向量数据库和相似度搜索
向量化文档后,它们可以存储在向量数据库中以供后续的相似度搜索。为了演示其工作原理,我们可以使用 Python 代码高效地复制一个简单的向量数据库。
在 VS Code 中打开 document_vector_database.py,如列表 8.3 所示。此代码演示了在内存中创建向量数据库,然后允许用户输入文本以搜索数据库并返回结果。返回的结果显示了文档文本和相似度分数。
列表 8.3 document_vector_database.py
# code above omitted
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(documents)
vector_database = X.toarray() #1
def cosine_similarity_search(query,
database,
vectorizer,
top_n=5): #2
query_vec = vectorizer.transform([query]).toarray()
similarities = cosine_similarity(query_vec, database)[0]
top_indices = np.argsort(-similarities)[:top_n] # Top n indices
return [(idx, similarities[idx]) for idx in top_indices]
while True: #3
query = input("Enter a search query (or 'exit' to stop): ")
if query.lower() == 'exit':
break
top_n = int(input("How many top matches do you want to see? "))
search_results = cosine_similarity_search(query,
vector_database,
vectorizer,
top_n)
print("Top Matched Documents:")
for idx, score in search_results:
print(f"- {documents[idx]} (Score: {score:.4f})") #4
print("\n")
###Output
Enter a search query (or 'exit' to stop): blue
How many top matches do you want to see? 3
Top Matched Documents:
- The sky is blue and beautiful. (Score: 0.4080)
- Love this blue and beautiful sky! (Score: 0.3439)
- The brown fox is quick and the blue dog is lazy! (Score: 0.2560)
#1 将文档向量存储到数组中
#2 执行查询返回、匹配和相似度分数的相似度匹配函数
#3 主要输入循环
#4 遍历结果并输出文本和相似度分数
运行这个练习以查看输出(在 VS Code 中按 F5)。输入任何你喜欢的文本,并查看返回的文档结果。这种搜索表单对于匹配相似单词和短语非常有效。这种搜索方式会错过文档中的单词上下文和意义。在下一节中,我们将探讨一种将文档转换为向量以更好地保留其语义意义的方法。
8.3.3 解密文档嵌入
TF–IDF 是一种试图在文档中捕获语义意义的简单形式。然而,它不可靠,因为它只计算单词频率,而不理解单词之间的关系。一种更好且更现代的方法是使用文档嵌入,这是一种文档向量化形式,能更好地保留文档的语义意义。
嵌入网络是通过在大数据集上训练神经网络来构建的,将单词、句子或文档映射到高维向量,基于数据和上下文中的关系捕获语义和句法关系。你通常使用在大量数据集上预训练的模型来嵌入文档并执行嵌入。模型可以从许多来源获得,包括 Hugging Face 和当然还有 OpenAI。
在我们的下一个场景中,我们将使用 OpenAI 嵌入模型。这些模型通常非常适合捕捉嵌入文档的语义上下文。列表 8.4 显示了使用 OpenAI 将文档嵌入到向量中的相关代码,然后这些向量被减少到三维并渲染成图表。
列表 8.4 document_visualizing_embeddings.py(相关部分)
load_dotenv() #1
api_key = os.getenv('OPENAI_API_KEY')
if not api_key:
raise ValueError("No API key found. Please check your .env file.")
client = OpenAI(api_key=api_key) #1
def get_embedding(text, model="text-embedding-ada-002"): #2
text = text.replace("\n", " ")
return client.embeddings.create(input=[text],
model=model).data[0].embedding #2
# Sample documents (omitted)
embeddings = [get_embedding(doc) for doc in documents] #3
print(embeddings_array.shape)
embeddings_array = np.array(embeddings) #4
pca = PCA(n_components=3) #5
reduced_embeddings = pca.fit_transform(embeddings_array)
#1 将所有项目用逗号和空格连接起来。
#2 使用 OpenAI 客户端创建嵌入
#3 为每个 1536 维度的文档生成嵌入
#4 将嵌入转换为 NumPy 数组以进行 PCA
#5 将维度减少到 3 以进行绘图
当使用 OpenAI 模型对文档进行嵌入时,它将文本转换为一个 1536 维度的向量。我们无法可视化这么多维度,因此我们使用主成分分析(PCA)这种降维技术,将 1536 维度的向量转换为 3 维。
图 8.6 显示了在 VS Code 中运行文件生成的输出。通过将嵌入减少到 3D,我们可以绘制输出以显示语义相似的文档是如何分组的。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-6.png
图 8.6 3D 嵌入,显示相似语义文档是如何分组的
你可以选择使用哪种嵌入模型或服务。OpenAI 的嵌入模型被认为是通用语义相似度方面最好的。这使得这些模型成为大多数记忆和检索应用的标准。通过我们对于如何使用嵌入将文本向量化并存储在向量数据库中的理解,我们可以在下一节中继续一个更实际的例子。
8.3.4 从 Chroma 查询文档嵌入
我们可以将所有部分结合起来,通过使用一个名为 Chroma DB 的本地向量数据库来查看一个完整的示例。存在许多向量数据库选项,但 Chroma DB 是一个优秀的本地向量存储,适用于开发或小规模项目。还有许多更健壮的选项,你可以在以后考虑。
列表 8.5 展示了document_query_chromadb.py文件中的新和相关的代码部分。注意,结果是根据距离评分,而不是根据相似度评分。余弦距离由以下公式确定:
余弦距离(A,B) = 1 – 余弦相似度(A,B)
这意味着余弦距离的范围从 0(最相似)到 2(语义上相反)。
列表 8.5 document_query_chromadb.py(相关代码部分)
embeddings = [get_embedding(doc) for doc in documents] #1
ids = [f"id{i}" for i in range(len(documents))] #1
chroma_client = chromadb.Client() #2
collection = chroma_client.create_collection(
name="documents") #2
collection.add( #3
embeddings=embeddings,
documents=documents,
ids=ids
)
def query_chromadb(query, top_n=2): #4
query_embedding = get_embedding(query)
results = collection.query(
query_embeddings=[query_embedding],
n_results=top_n
)
return [(id, score, text) for id, score, text in
zip(results['ids'][0],
results['distances'][0],
results['documents'][0])]
while True: #5
query = input("Enter a search query (or 'exit' to stop): ")
if query.lower() == 'exit':
break
top_n = int(input("How many top matches do you want to see? "))
search_results = query_chromadb(query, top_n)
print("Top Matched Documents:")
for id, score, text in search_results:
print(f"""
ID:{id} TEXT: {text} SCORE: {round(score, 2)}
""") #5
print("\n")
###Output
Enter a search query (or 'exit' to stop): dogs are lazy
How many top matches do you want to see? 3
Top Matched Documents:
ID:id7 TEXT: The dog is lazy but the brown fox is quick! SCORE: 0.24
ID:id5 TEXT: The brown fox is quick and the blue dog is lazy! SCORE: 0.28
ID:id2 TEXT: The quick brown fox jumps over the lazy dog. SCORE: 0.29
#1 为每个文档生成嵌入并分配一个 ID
#2 创建 Chroma DB 客户端和集合
#3 将文档嵌入添加到集合中
#4 查询数据存储并返回最相关的 n 个文档
#5 用户输入循环和输出相关文档/分数的输入循环
如前文场景所示,你现在可以使用语义意义而不是仅仅关键词或短语来查询文档。这些场景现在应该为理解检索模式在底层是如何工作的提供背景。在下一节中,我们将看到如何使用 LangChain 来应用检索模式。
8.4 使用 LangChain 构建 RAG
LangChain 最初是一个专注于抽象多个数据源和向量存储检索模式的开源项目。它已经演变得更多,但基础层面上,它仍然为检索实现提供了优秀的选项。
图 8.7 展示了 LangChain 中的一个流程图,该图标识了存储文档以供检索的过程。这些相同的步骤可以全部或部分复制以实现记忆检索。文档检索和记忆检索之间的关键区别在于来源以及内容是如何被转换的。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-7.png
图 8.7 显示了存储文档以供后续检索的加载、转换、嵌入和存储步骤
我们将探讨如何使用 LangChain 实现这些步骤,并理解伴随此实现的细微差别和细节。在下一节中,我们将首先使用 LangChain 分割和加载文档。
8.4.1 使用 LangChain 分割和加载文档
检索机制通过添加与请求相关的特定信息来增强给定提示的上下文。例如,你可能需要关于本地文档的详细信息。在早期的语言模型中,由于标记限制,将整个文档作为提示的一部分提交不是一个选项。
今天,我们可以将整个文档提交给许多商业 LLM,如 GPT-4 Turbo,作为提示请求的一部分。然而,结果可能不会更好,并且可能会因为标记数量的增加而成本更高。因此,更好的选择是将文档分割,并使用相关部分请求上下文——这正是 RAG 和记忆所做的事情。
分割文档对于将内容分解成语义上和具体相关的部分至关重要。图 8.8 显示了如何分解包含老母鸡童谣的 HTML 文档。通常,将文档分割成上下文语义块需要仔细考虑。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-8.png
图 8.8 理想情况下文档如何分割成块以获得更好的语义和上下文意义
理想情况下,当我们将文档分割成块时,它们应按相关性和语义意义分解。虽然 LLM 或代理可以帮助我们做这件事,但我们将查看 LangChain 中当前的工具包选项,用于分割文档。在本章的后面部分,我们将查看一个可以帮助我们在嵌入内容时进行语义划分的语义函数。
对于下一个练习,在 VS Code 中打开langchain_load_splitting.py,如列表 8.6 所示。此代码显示了我们在上一节列表 8.5 中留下的地方。这次我们不是使用样本文档,而是加载这次的老母鸡童谣。
列表 8.6 langchain_load_splitting.py(部分和输出)
From langchain_community.document_loaders
↪ import UnstructuredHTMLLoader #1
from langchain.text_splitter import RecursiveCharacterTextSplitter
#previous code
loader = UnstructuredHTMLLoader(
"sample_documents/mother_goose.html") #2
data = loader.load #3
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=100,
chunk_overlap=25, #4
length_function=len,
add_start_index=True,
)
documents = text_splitter.split_documents(data)
documents = [doc.page_content
↪ for doc in documents] [100:350] #5
embeddings = [get_embedding(doc) for doc in documents] #6
ids = [f"id{i}" for i in range(len(documents))]
###Output
Enter a search query (or 'exit' to stop): **who kissed the girls and made**
**them cry?**
How many top matches do you want to see? 3
Top Matched Documents:
ID:id233 TEXT: And chid her daughter,
And kissed my sister instead of me. SCORE: 0.4…
#1 新的 LangChain 导入
#2 将文档作为 HTML 加载
#3 加载文档
#4 将文档分割成 100 个字符长、25 个字符重叠的文本块
#5 仅嵌入 250 个块,这更便宜且更快
#6 返回每个文档的嵌入
注意在列表 8.6 中,HTML 文档被分割成 100 个字符的块,并且有 25 个字符的重叠。这种重叠允许文档的部分不会切断特定的想法。我们选择这个分割器进行这个练习,因为它易于使用、设置和理解。
好吧,在 VS Code 中运行langchain_load_splitting.py文件(F5)。输入一个查询,看看你得到什么结果。列表 8.6 中的输出显示了给定特定示例的良好结果。请记住,我们只嵌入 250 个文档块以降低成本并使练习简短。当然,你总是可以尝试嵌入整个文档或使用较小的输入文档示例。
构建适当的检索最关键的因素可能是文档分割的过程。你可以使用多种方法来分割文档,包括多种并发方法。超过一种方法会通过分割文档为同一文档的多个嵌入视图。在下一节中,我们将检查一种更通用的文档分割技术,使用标记和标记化。
8.4.2 使用 LangChain 按标记分割文档
分词 是将文本分割成单词标记的过程。一个单词标记代表文本中的一个简洁元素,一个标记可以是一个像 hold 这样的单词,甚至是一个像左花括号 ({) 这样的符号,具体取决于什么是有意义的。
使用分词技术分割文档为语言模型如何解释文本提供了一个更好的基础,以及语义相似性。分词技术还允许移除无关字符,如空白字符,使文档的相似性匹配更加相关,并通常提供更好的结果。
对于下一个代码练习,请在 VS Code 中打开 langchain_token_splitting.py 文件,如图 8.7 所示。现在我们使用分词技术将文档分割成大小不等的部分。这种不均匀的大小是由于原始文档中大量空白区域造成的。
列表 8.7 langchain_token_splitting.py(相关新代码)
loader = UnstructuredHTMLLoader("sample_documents/mother_goose.html")
data = loader.load()
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
chunk_size=50, chunk_overlap=10 #1
)
documents = text_splitter.split_documents(data)
documents = [doc for doc in documents][8:94] #2
db = Chroma.from_documents(documents, OpenAIEmbeddings())
def query_documents(query, top_n=2):
docs = db.similarity_search(query, top_n) #3
return docs
###Output
Created a chunk of size 68,
which is longer than the specified 50
Created a chunk of size 67,
which is longer than the specified 50 #4
Enter a search query (or 'exit' to stop):
who kissed the girls and made them cry?
How many top matches do you want to see? 3
Top Matched Documents:
Document 1: GEORGY PORGY
Georgy Porgy, pudding and pie,
Kissed the girls and made them cry.
#1 更新为 50 个标记和 10 个标记的重叠
#2 仅选择包含押韵的文档
#3 使用数据库的相似性搜索
#4 由于空白字符而分割成不规则大小的块
在 VS Code 中运行 langchain_token_splitting.py 代码(按 F5)。您可以使用上次使用的查询或您自己的查询。注意,结果比上一个练习明显更好。然而,结果仍然可疑,因为查询使用了几个相同顺序的相似词。
一个更好的测试是尝试一个语义上相似的短语,但使用不同的词,并检查结果。代码仍在运行时,输入一个新的查询短语:为什么 女孩们 在哭泣? 列表 8.8 显示了执行该查询的结果。如果您自己运行此示例并向下滚动输出,您将看到乔治·波吉出现在返回的第二或第三份文档中。
列表 8.8 查询:谁让女孩们哭泣?
Enter a search query (or 'exit' to stop): Who made the girls cry?
How many top matches do you want to see? 3
Top Matched Documents:
Document 1: WILLY, WILLY
Willy, Willy Wilkin…
这个练习展示了如何使用各种检索方法来返回语义上的文档。在这个基础上,我们可以看到 RAG 如何应用于知识和记忆系统。下一节将讨论 RAG 在应用于代理和代理系统知识时的应用。
8.5 将 RAG 应用于构建代理知识
代理中的知识包括使用 RAG 在非结构化文档中进行语义搜索。这些文档可以是 PDF 文件、Microsoft Word 文档以及所有文本,包括代码。代理知识还包括使用非结构化文档进行问答、参考查找、信息增强和其他未来模式。
Nexus,是与本书一起开发并在上一章中介绍的代理平台,为代理提供了完整的知识和记忆系统。在本节中,我们将揭示知识系统是如何工作的。
要仅为此章节安装 Nexus,请参阅列表 8.9。在 chapter_08 文件夹内打开一个终端,并执行列表中的命令以下载、安装和以正常或开发模式运行 Nexus。如果您想参考代码,应将项目安装在开发模式下,并配置调试器从 VS Code 运行 Streamlit 应用。如果您需要回顾这些步骤中的任何一项,请参阅第七章。
列表 8.9 安装 Nexus
# to install and run
pip install git+https://github.com/cxbxmxcx/Nexus.git
nexus run
# install in development mode
git clone https://github.com/cxbxmxcx/Nexus.git
# Install the cloned repository in editable mode
pip install -e Nexus
无论您登录后决定使用哪种方法运行应用程序,请导航到如图 8.9 所示的知识库管理器页面。创建一个新的知识库,然后上传 sample_documents/back_to_the_future.txt 电影剧本。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-9.png
图 8.9 添加新的知识库并填充文档
该脚本是一个大型文档,加载、分块和将部分嵌入到 Chroma DB 向量数据库中可能需要一些时间。等待索引完成,然后您可以检查嵌入并运行查询,如图 8.10 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-10.png
图 8.10 嵌入和文档查询视图
现在,我们可以将知识库连接到支持的代理并提问。使用左上角的选择器在 Nexus 界面中选择聊天页面。然后,选择一个代理和 time_travel 知识库,如图 8.11 所示。您还需要选择一个支持知识的代理引擎。每个代理引擎都需要适当的配置才能访问。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-11.png
图 8.11 启用知识库以供代理使用
目前,截至本章,Nexus 仅支持一次访问单个知识库。在未来版本中,代理可能能够一次选择多个知识库。这可能包括从语义知识到使用其他形式的 RAG 的更高级选项。
您也可以在知识库管理器页面中的配置选项卡内配置 RAG 设置,如图 8.12 所示。到目前为止,您可以从拆分文档的类型(拆分选项字段)中选择,以及选择分块大小字段和重叠字段。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-12.png
图 8.12 管理知识库拆分和分块选项
LangChain 目前提供的加载、拆分、分块和嵌入选项是唯一支持的基本选项。在 Nexus 的未来版本中,将提供更多选项和模式。支持其他选项的代码可以直接添加到 Nexus 中。
我们不会介绍执行 RAG 的代码,因为它与我们之前介绍的内容非常相似。您可以自由地回顾 Nexus 代码,特别是 knowledge_manager.py 文件中的 KnowledgeManager 类。
对于增强知识库和记忆库的检索模式相当相似,但在填充存储时,这两种模式有所不同。在下一节中,我们将探讨使代理中的记忆独特的原因。
8.6 在代理系统中实现记忆
在代理和人工智能应用中,记忆通常用与认知记忆功能相同的术语描述。认知记忆描述了我们用来记住 30 秒前我们做了什么或 30 年前我们有多高的记忆类型。计算机记忆也是代理记忆的一个基本要素,但本节不会考虑这一点。
图 8.13 展示了记忆是如何分解成感觉、短期和长期记忆的。这种记忆可以应用于人工智能代理,以下列表描述了每种记忆形式如何映射到代理功能:
-
人工智能中的感觉记忆 — 函数类似于 RAG,但使用图像/音频/触觉数据形式。短暂地保存输入数据(例如,文本和图像)以供即时处理,但不进行长期存储。
-
人工智能中的短期/工作记忆 — 作为对话历史的活跃记忆缓冲区。我们正在保存有限数量的最近输入和上下文以供即时分析和响应生成。在 Nexus 中,短期和长期对话记忆也保存在线程的上下文中。
-
人工智能中的长期记忆 — 与代理或用户生活相关的长期记忆存储。语义记忆提供了强大的存储和检索相关全局或局部事实和概念的能力。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-13.png
图 8.13 如何将记忆分解成各种形式
虽然记忆使用与知识完全相同的检索和增强机制,但在更新或追加记忆时通常会有显著差异。图 8.14 突出了捕获和使用记忆来增强提示的过程。因为记忆通常与完整文档的大小不同,我们可以避免使用任何分割或分块机制。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-14.png
图 8.14 基本记忆检索和增强工作流程
Nexus 提供了一个类似于知识库的机制,允许用户创建可以配置用于各种用途和应用的记忆存储。它还支持图 8.13 中突出显示的一些更高级的记忆形式。下一节将探讨 Nexus 中基本记忆存储的工作方式。
8.6.1 在 Nexus 中消费记忆存储
在 Nexus 中,记忆存储的操作和构建方式类似于知识存储。它们都高度依赖于检索模式。不同的是,记忆系统在构建新记忆时采取的额外步骤。
开始运行 Nexus,如果需要安装,请参考列表 8.9。登录后,选择记忆页面,并创建一个新的记忆存储,如图 8.15 所示。选择一个代理引擎,然后添加一些关于你自己的个人事实和偏好。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-15.png
图 8.15 向新创建的记忆存储添加记忆
我们需要代理(LLM)的原因在之前的图 8.14 中已经展示。当信息被输入到记忆存储中时,它通常通过使用记忆函数的 LLM 进行处理,该函数的目的是将陈述/对话处理成与记忆类型相关的语义相关信息。
列表 8.10 展示了用于从对话中提取信息到记忆中的对话记忆函数。是的,这只是发送给 LLM 的提示的标题部分,指示它如何从对话中提取信息。
列表 8.10 对话记忆函数
Summarize the conversation and create a set of statements that summarize
the conversation. Return a JSON object with the following keys: 'summary'.
Each key should have a list of statements that are relevant to that
category. Return only the JSON object and nothing else.
在生成一些关于自己的相关记忆后,返回 Nexus 的聊天区域,启用my_memory记忆存储,看看代理对你了解得有多好。图 8.16 展示了使用不同代理引擎的示例对话。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-16.png
图 8.16 在同一记忆存储上与不同的代理进行对话
这是一个从对话中提取事实/偏好并将其作为记忆存储在向量数据库中的基本记忆模式示例。许多其他记忆的实现方式遵循之前图 8.13 中展示的。我们将在下一节中实现这些。
8.6.2 语义记忆及其在语义、情景和程序性记忆中的应用
心理学家根据记忆中记住的信息将记忆分为多种形式。语义、情景和程序性记忆都代表不同类型的信息。情景记忆关于事件,程序性记忆关于过程或步骤,而语义代表意义,可能包括感觉或情感。其他形式的记忆(如地理空间记忆),这里没有描述,但可能存在。
由于这些记忆依赖于额外的分类级别,它们也依赖于另一个级别的语义分类。一些平台,如语义内核(SK),将这称为语义记忆。这可能令人困惑,因为语义分类也应用于提取情景和程序性记忆。
图 8.17 展示了语义记忆分类过程,有时也称为语义记忆。语义记忆与常规记忆的区别在于多了一个处理输入语义并提取可用于查询记忆相关向量数据库的相关问题的步骤。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-17.png
图 8.17 语义记忆增强的工作原理
使用语义增强的好处是能够提取更多相关记忆的能力增强。我们可以通过回到 Nexus 并创建一个新的语义记忆存储来在操作中看到这一点。
图 8.18 展示了如何使用语义记忆配置新的记忆存储。到目前为止,您还不能配置记忆、增强和总结的具体功能提示。然而,阅读每个功能提示以了解它们的工作方式可能是有用的。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-18.png
图 8.18 将记忆存储类型更改为语义的配置
现在,如果你回顾并添加事实和偏好,它们将转换为相关记忆类型的语义。图 8.19 显示了将同一组陈述填充到两种不同形式的记忆中的示例。一般来说,输入到记忆中的陈述会更具体地对应记忆的形式。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-19.png
图 8.19 比较两种不同记忆类型给出的相同信息的记忆
记忆和知识可以显著帮助各种类型的代理。确实,单个记忆/知识存储可以喂养一个或多个代理,允许对这两种存储类型进行进一步的专业解释。我们将通过讨论记忆/知识压缩来结束本章。
8.7 理解记忆和知识压缩
就像我们自己的记忆一样,记忆存储可能会随着时间的推移变得杂乱,充满冗余信息和众多无关的细节。在内部,我们的心智通过压缩或总结记忆来处理记忆杂乱。我们的心智记住更重要的细节,而不是不那么重要的细节,以及更频繁访问的记忆。
我们可以将记忆压缩的类似原则应用于代理记忆和其他检索系统,以提取重要细节。压缩的原则与语义增强类似,但为相关记忆的预聚类组添加了另一层,这些组可以共同总结。
图 8.20 展示了记忆/知识压缩的过程。记忆或知识首先使用如 k-means 之类的算法进行聚类。然后,将记忆组通过压缩函数传递,该函数总结并收集项目以形成更简洁的表示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-20.png
图 8.20 记忆和知识压缩的过程
Nexus 通过使用 k-means 最优聚类为知识和记忆存储提供压缩。图 8.21 显示了记忆的压缩界面。在压缩界面中,您将看到以 3D 形式显示并聚类的项目。簇的大小(项目数量)显示在左侧的表中。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/8-21.png
图 8.21 压缩记忆的界面
如果聚类中的项目数量很大或不平衡,通常建议压缩记忆和知识。每个压缩用例可能根据记忆的使用和应用而有所不同。不过,一般来说,如果对存储中的项目进行检查发现存在重复或重复信息,那么进行压缩就是时候了。以下是对从压缩中受益的应用程序的用例总结。
知识压缩的案例
知识检索和增强也已被证明可以从压缩中受益显著。结果将因用例而异,但通常,知识来源越冗长,它从压缩中受益就越多。具有文学散文的文档,如故事和小说,将比代码库等受益更多。然而,如果代码同样非常重复,压缩也可能显示出其益处。
压缩应用频率的案例
定期应用压缩通常会从内存中受益,而知识库通常只在第一次加载时提供帮助。你应用压缩的频率将很大程度上取决于内存使用、频率和数量。
多次应用压缩的案例
同时进行多次压缩已被证明可以提高检索性能。其他模式也建议在压缩的不同级别使用记忆或知识。例如,知识库被压缩两次,从而产生三个不同的知识级别。
知识和记忆压缩融合的案例
如果一个系统专门针对特定的知识来源,并且该系统还使用记忆,那么可能还有进一步的优化来整合存储。另一种方法是直接用文档的起始知识填充记忆。
多个记忆或知识存储的案例
在更高级的系统里,我们将探讨使用与其工作流程相关的多个记忆和知识库的代理。例如,一个代理可以作为其与单个用户的对话的一部分使用单独的记忆库,也许包括能够与不同群体分享不同组记忆的能力。记忆和知识检索是代理系统的基石,我们现在可以总结我们所学的内容,并在下一节回顾一些学习练习。
8.8 练习
使用以下练习来提高你对材料的了解:
- 练习 1 — 加载并拆分不同的文档(中级)
目标 — 使用 LangChain 了解文档拆分对检索效率的影响。
任务:
-
-
选择不同的文档(例如,新闻文章、科学论文或短篇小说)。
-
使用 LangChain 加载并将文档拆分成块。
-
分析文档如何拆分成块以及这对检索过程的影响。
-
-
练习 2 — 尝试语义搜索(中级)
目标 — 通过执行语义搜索比较各种向量化技术的有效性。
任务:
-
-
选择一组文档进行语义搜索。
-
使用 Word2Vec 或 BERT 嵌入等向量化方法而不是 TF–IDF。
-
执行语义搜索,并将结果与使用 TF–IDF 获得的结果进行比较,以了解差异和有效性。
-
-
练习 3 — 实现自定义 RAG 工作流程(高级)
目标 — 在实际环境中使用 LangChain 应用 RAG 的理论知识。
任务:
-
-
选择一个特定的应用(例如,客户服务查询或学术研究查询)。
-
使用 LangChain 设计和实现一个定制的 RAG 工作流程。
-
调整工作流程以适应所选应用,并测试其有效性。
-
-
练习 4 — 构建知识库并实验分割模式(中级)
目标 — 理解不同的分割模式和压缩如何影响知识检索。
任务:
-
-
构建一个知识库,并用几份文档填充它。
-
尝试不同的分割/分块模式,并分析它们对检索的影响。
-
压缩知识库,并观察对查询性能的影响。
-
-
练习 5 — 构建和测试各种记忆存储(高级)
目标 — 理解不同记忆存储类型的独特性和用例。
任务:
-
-
构建各种形式的记忆存储(对话式、语义、情景和程序性)。
-
使用每种类型的记忆存储与代理进行交互,并观察差异。
-
压缩记忆存储,并分析对记忆检索的影响。
-
摘要
-
在 AI 应用中,记忆区分了非结构化和结构化记忆,突出了它们在为更相关的交互情境化提示中的应用。
-
检索增强生成(RAG)是一种通过使用向量嵌入和相似度搜索从外部文档中检索相关内容来增强提示的上下文机制。
-
使用文档索引进行语义搜索,通过 TF-IDF 和余弦相似度将文档转换为语义向量,增强在索引文档中执行语义搜索的能力。
-
向量数据库和相似度搜索存储将文档向量存储在向量数据库中,便于高效的相似度搜索并提高检索准确性。
-
文档嵌入通过使用如 OpenAI 的模型等模型捕获语义含义,生成嵌入以保留文档的上下文并促进语义相似度搜索。
-
LangChain 提供了执行 RAG 的几个工具,并抽象了检索过程,使得在各个数据源和向量存储中轻松实现 RAG 和记忆系统成为可能。
-
LangChain 中的短期和长期记忆实现了 LangChain 内的对话式记忆,区分了短期缓冲模式和长期存储解决方案。
-
在数据库中存储文档向量对于在 AI 应用中实现可扩展的检索系统至关重要。
-
代理知识直接关联到在文档或其他文本信息上执行问答的通用 RAG 模式。
-
代理记忆是与 RAG 相关的模式,它捕捉了代理与用户、自身和其他系统之间的交互。
-
Nexus 是一个实现代理知识记忆系统的平台,包括为文档检索设置知识库和为各种形式的记忆设置记忆库。
-
语义记忆增强(语义记忆)区分不同类型的记忆(语义记忆、情景记忆、程序性记忆)。它通过语义增强实现这些记忆类型,增强代理者回忆和使用与记忆性质相关的特定信息的能力。
-
记忆与知识压缩是用于压缩存储在记忆和知识系统中的信息的技术,通过聚类和总结来提高检索效率和相关性。
第九章:使用提示流掌握代理提示
本章涵盖了
-
理解系统化的提示工程并设置你的第一个提示流
-
构建有效的配置文件/角色提示
-
评估配置文件:评分标准和扎根
-
对大型语言模型配置文件进行扎根评估
-
比较提示:获得完美的配置文件
在本章中,我们深入探讨了系统性地测试变化的提示工程策略。如果你还记得,我们在第二章中介绍了 OpenAI 提示工程框架的大策略。这些策略在帮助我们构建更好的提示、进而构建更好的代理配置文件和角色方面至关重要。理解这一角色对于我们提示工程的旅程至关重要。
系统性地测试变化是提示工程的核心要素之一,因此微软开发了一个围绕这一策略的工具,称为提示流,将在本章后面进行描述。在了解提示流之前,我们需要理解为什么我们需要系统性的提示工程。
9.1 为什么我们需要系统化的提示工程
提示工程本质上是一个迭代过程。在构建提示时,你通常会进行迭代和评估。为了看到这一概念的实际应用,可以考虑将提示工程简单应用于 ChatGPT 的问题。
你可以通过打开浏览器到 ChatGPT (chat.openai.com/),将以下(文本)提示输入到 ChatGPT 中,然后点击发送消息按钮(如图 9.1 左侧所示的一个对话示例):
你能推荐一些东西吗
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-1.png
图 9.1 应用提示工程和迭代的不同
我们可以看到,ChatGPT 的回应是要求更多信息。请继续与 ChatGPT 开启一个新的对话,并输入以下提示,如图 9.1 右侧所示:
你能推荐一部设定在中世纪时期的时空旅行电影吗?
图 9.1 的结果显示了在请求中省略细节和更加具体之间的明显差异。我们刚刚应用了礼貌地撰写清晰指令的策略,ChatGPT 为我们提供了一个好的推荐。但也要注意 ChatGPT 本身如何引导用户进行更好的提示。如图 9.2 所示的刷新屏幕显示了 OpenAI 的提示工程策略。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-2.png
图 9.2 OpenAI 提示工程策略,按代理组件分解
我们刚刚应用了简单的迭代来改进我们的提示。我们可以通过使用系统提示/消息来扩展这个例子。图 9.3 展示了系统提示在迭代通信中的应用和作用。在第二章中,我们在各种示例中使用了系统消息/提示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-3.png
图 9.3 LLM 对话中的消息以及消息的迭代
你也可以在 ChatGPT 中尝试这个方法。这次,输入以下提示,并在单词system后面加上小写字母,然后换行(在消息窗口中按 Shift-Enter 键换行,但不发送消息):
system
你是时间旅行电影的专家。
ChatGPT 将回应一些愉快的评论,如图 9.4 所示。因此,它很高兴接受其新角色,并询问任何后续问题。现在输入以下通用的提示,就像我们之前做的那样:
你能推荐一些东西吗
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-4.png
图 9.4 向我们之前的对话添加系统提示的效果
我们刚刚看到了提示的迭代优化,即提示工程,以提取更好的响应。这是通过使用 ChatGPT UI 进行的三次不同对话来实现的。虽然这不是最有效的方法,但它有效。
然而,我们还没有定义评估提示和确定何时提示有效的迭代流程。图 9.5 展示了使用迭代和评估的系统方法进行提示工程。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-5.png
图 9.5 提示工程的系统方法
迭代和评估提示的系统涵盖了广泛的系统化测试策略。评估提示的性能和有效性仍然是新的,但我们将使用教育技术,如评分标准和扎根,这些将在本章后面的部分进行探讨。然而,正如下一节所阐述的,在我们这样做之前,我们需要了解角色和代理配置文件之间的区别。
9.2 理解代理配置文件和角色
一个代理配置文件是对描述代理的组件提示或消息的封装。它包括代理的角色、特殊指令和其他可以指导用户或其他代理消费者的策略。
图 9.6 展示了代理配置文件的主要元素。这些元素映射到本书中描述的提示工程策略。并非所有代理都会使用完整代理配置文件的所有元素。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-6.png
图 9.6 代理配置文件的部分组件
在基本层面上,一个代理配置文件是一组描述代理的提示。它可能包括与动作/工具、知识、记忆、推理、评估、计划和反馈相关的其他外部元素。这些元素的组合构成了一个完整的代理提示配置文件。
提示是代理功能的核心。一个提示或一组提示驱动着配置文件中每个代理组件。对于动作/工具,这些提示定义得很好,但正如我们所看到的,记忆和知识的提示可以根据用例显著变化。
人工智能代理配置文件的定义不仅仅是系统提示。提示流程可以让我们构建构成代理配置文件的提示和代码,同时还包括评估其有效性的能力。在下一节中,我们将打开提示流程并开始使用它。
9.3 设置您的第一个提示流
提示流是由微软在其 Azure 机器学习工作室平台内开发的工具。该工具后来作为开源项目发布在 GitHub 上,在那里它吸引了更多的关注和使用。虽然最初旨在作为应用平台,但它后来在开发和评估提示/配置文件方面显示出其优势。
由于提示流最初是为了在 Azure 上作为服务运行而开发的,它具有强大的核心架构。该工具支持多线程批量处理,这使得它在评估大量提示时非常理想。下一节将探讨使用提示流的入门基础知识。
9.3.1 入门
在尝试本书中的练习之前,有一些先决条件需要完成。本节和章节的相关先决条件如下所示;确保在尝试练习之前完成它们:
-
Visual Studio Code (VS Code) — 请参考附录 A 中的安装说明,包括额外的扩展。
-
提示流,VS Code 扩展 — 详细安装扩展的步骤请参考附录 A。
-
Python 虚拟环境 — 详细设置虚拟环境的步骤请参考附录 A。
-
安装提示流包 — 在您的虚拟环境中,执行快速
pip install,如图所示:
pip install promptflow promptflow-tools
-
LLM(GPT-4 或更高版本) — 您需要通过 OpenAI 或 Azure OpenAI Studio 访问 GPT-4 或更高版本。如需帮助访问这些资源,请参考附录 B。
-
书籍的源代码 — 将书籍的源代码克隆到本地文件夹;如需帮助克隆存储库,请参考附录 A。
打开 VS Code 到书籍的源代码文件夹,第三章。确保您已连接虚拟环境并安装了提示流包和扩展。
首先,您需要在提示流扩展内创建与您的 LLM 资源的连接。在 VS Code 中打开提示流扩展,然后点击打开连接。然后,点击 LLM 资源旁边的加号创建一个新的连接,如图 9.7 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-7.png
图 9.7 创建新的提示流 LLM 连接
这将打开一个 YAML 文件,您需要填写连接名称和其他与您的连接相关的信息。按照指示操作,不要在文档中输入 API 密钥,如图 9.8 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-8.png
图 9.8 设置 LLM 资源连接信息
当输入连接信息后,点击文档底部的创建连接链接。这将打开文档下方的终端提示,要求您输入密钥。根据您的终端配置,您可能无法粘贴(Ctrl-V,Cmd-V)。或者,您可以通过将鼠标光标悬停在终端上并右键单击 Windows 来粘贴密钥。
我们现在将通过首先打开chapter_09/promptflow/simpleflow文件夹中的简单流程来测试连接。然后,在 VS Code 中打开flow.dag.yaml文件。这是一个 YAML 文件,但提示流程扩展提供了一个可以通过点击文件顶部的“可视化编辑器”链接访问的可视化编辑器,如图 9.9 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-9.png
图 9.9 打开提示流程可视化编辑器
打开可视化编辑器窗口后,您将看到一个表示流程和流程块的图形。双击推荐器块,并设置连接名称、API 类型以及模型或部署名称,如图 9.10 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-10.png
图 9.10 设置 LLM 连接详细信息
提示流程由一组块组成,从 Inputs 块开始,以 Outputs 块结束。在这个简单流程中,recommender 块代表 LLM 连接和与模型对话所使用的提示。对于这个简单示例,echo 块会回显输入。
当在提示流程或通过 API 创建与 LLM 的连接时,以下是我们始终需要考虑的关键参数(提示流程文档:microsoft.github.io/promptflow):
-
连接 — 这是连接名称,但也代表了您所连接的服务。提示流程支持多个服务,包括本地部署的 LLM。
-
API — 这是 API 类型。选项包括用于聊天完成 API 的
chat,例如 GPT-4,或用于较老完成模型的completion,例如 OpenAI Davinci。 -
模型 — 这可能是模型或部署名称,具体取决于您的服务连接。对于 OpenAI,这将是指模型的名称,而对于 Azure OpenAI,它将代表部署名称。
-
温度 — 这代表了模型响应的随机性或可变性。
1的值表示响应的高度可变性,而0表示不希望有任何可变性。这是一个关键参数,我们将看到,它将根据用例而变化。 -
停止 — 这个可选设置告诉 LLM 调用停止创建标记。它更适合较老和开源模型。
-
最大标记数 — 这限制了对话中使用的标记数量。了解您使用了多少标记对于评估您的 LLM 交互在扩展时的表现至关重要。如果您正在探索和进行研究,标记计数可能不是问题。然而,在生产系统中,标记代表了 LLM 的负载,使用大量标记的连接可能无法很好地扩展。
-
高级参数 — 您可以设置一些更多选项来调整您与 LLM 的交互,但我们将在此书后面的章节中介绍这个主题。
在配置了 LLM 块之后,向上滚动到输入块部分,并查看用户输入字段中显示的主要输入,如图 9.11 所示。保持默认设置,然后点击窗口顶部的播放按钮。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-11.png
图 9.11 设置输入并启动流
流中的所有块都将运行,结果将在终端窗口中显示。您可能会发现有趣的是,输出显示了时间旅行电影的推荐。这是因为推荐块已经设置了一个简单的配置文件,我们将在下一节中看到它是如何工作的。
9.3.2 使用 Jinja2 模板创建配置文件
流的响应是基于它使用的提示或配置文件的时间旅行电影推荐。默认情况下,提示流使用 Jinja2 模板来定义提示的内容或我们称之为 配置文件 的内容。为了本书和我们对 AI 代理的探索,我们将把这些模板称为流的配置文件或代理的配置文件。
虽然提示流没有明确将自己称为助手或代理引擎,但它确实符合产生代理和通用类型代理的标准。正如您将看到的,提示流甚至支持将流部署到容器中作为服务。
打开 VS Code 到 chapter_09/promptflow/simpleflow/flow.dag.yaml,并在可视化编辑器中打开该文件。然后,定位到提示字段,并点击如图 9.12 所示的 recommended .jinja2 链接。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-12.png
图 9.12 打开提示 Jinja2 模板并检查配置文件/提示的部分
Jinja 是一个模板引擎,Jinja2 是该引擎的一个特定版本。模板是定义任何形式文本文档布局和部分的一种极好方式。它们已被广泛用于生成 HTML、JSON、CSS 和其他文档形式。此外,它们支持将代码直接应用到模板中。虽然没有标准的方式来构建提示或代理配置文件,但本书中我们更倾向于使用模板引擎,如 Jinja。
在这一点上,更改系统提示中 recommended.jinja2 模板的角色。然后,通过在可视化编辑器中打开流并点击播放按钮来运行流的所有块。下一节将探讨其他运行提示流的方法,用于测试或实际部署。
9.3.3 部署提示流 API
由于提示流也被设计为可以部署为服务,它支持几种快速部署为应用程序或 API 的方法。提示流可以作为本地 Web 应用程序和 API 部署,从终端运行或作为 Docker 容器。
返回 VS Code 中的视觉编辑器中的flow.dag.yaml文件。在窗口顶部的播放按钮旁边有几个我们想要进一步调查的选项。如图 9.13 所示,点击构建按钮,然后选择部署为本地应用程序。将创建一个新的 YAML 文件来配置应用程序。保留默认设置,并点击启动本地应用程序链接。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-13.png
图 9.13 构建并启动流程作为本地应用程序
这将启动流程作为本地 Web 应用程序,你将看到一个浏览器标签页打开,如图 9.14 所示。在用户输入字段中输入一些文本,该字段用红色星号标记为必填项。点击 Enter 并等待几秒钟以获取回复。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-14.png
图 9.14 将流程作为本地 Web 应用程序运行
你应该会看到一个像图 9.12 中早些时候显示的回复,其中流程或代理回复了一个时间旅行电影的列表。这太棒了——我们刚刚开发出了我们的第一个代理配置文件和代理代理的等效物。然而,我们需要确定这些推荐的成功率或价值。在下一节中,我们将探讨如何评估提示和配置文件。
9.4 评估配置文件:评分标准和扎根
任何提示或代理配置文件的关键要素是其执行给定任务的表现。正如我们在我们的推荐示例中所看到的,提示代理配置文件给出一个推荐列表相对容易,但要知道这些推荐是否有帮助,我们需要评估响应。
幸运的是,提示流已经被设计成可以大规模评估提示/配置文件。强大的基础设施允许将 LLM 交互的评估并行化并作为工作者进行管理,这使得数百个配置文件评估和变体可以快速发生。
在下一节中,我们将探讨如何配置提示流以运行提示/配置文件之间的变体。在评估配置文件性能之前,我们需要理解这一点。
提示流提供了一个机制,允许在 LLM 提示/配置文件内进行多种变体。这个工具在比较配置文件变体之间的细微或显著差异时非常出色。当用于执行批量评估时,它可以快速评估配置文件性能,非常有价值。
在 VS Code 和流程视觉编辑器中打开recommender_with_variations/flow.dag.yaml文件,如图 9.15 所示。这次,我们使配置文件更加通用,并允许在输入级别进行定制。这使我们能够将我们的推荐扩展到任何事物,而不仅仅是时间旅行电影。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-15.png
图 9.15 推荐系统,具有不同的流程和扩展的输入
新的输入主题、流派、格式和自定义使我们能够定义一个可以轻松调整到任何推荐的配置文件。这也意味着我们必须根据推荐用例来初始化输入。有几种初始化这些输入的方法;图 9.16 中展示了两种初始化输入的示例。该图显示了两种初始化输入的选项,选项 A 和 B。选项 A 代表经典的用户界面;例如,可能有供用户选择主题或流派的物体。选项 B 放置一个代理/聊天代理以更好地与用户互动,以了解所需的主题、流派等。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-16.png
图 9.16 与代理配置文件交互的用户交互选项,以向代理配置文件提供初始输入
即使考虑到 LLM 的力量,您可能仍然希望或需要使用选项 A。选项 A 的好处是您可以像使用任何现代用户界面一样约束和验证输入。另一方面,选项 A 的缺点是受限的行为可能会限制和限制未来的用例。
选项 B 代表一种没有传统用户界面的更流畅和自然的方式。它比选项 A 更强大、更可扩展,但也为评估引入了更多未知因素。然而,如果选项 B 使用的代理代理编写得很好,它可以在收集更好的用户信息方面提供很大帮助。
您选择的选项将决定您如何评估您的配置文件。如果您对受限的用户界面没有异议,那么输入可能也会被限制为一系列离散值。目前,我们将假设选项 B 用于输入初始化,这意味着输入值将由其名称定义。
要回到 VS Code 和具有变体流的推荐器的可视化视图,请点击图 9.15 中显示的图标以打开变体并允许编辑。然后,点击recommend.jinja2和recommender_variant_1.jinja2链接以并排打开文件,如图 9.17 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-17.png
图 9.17 推荐器变体配置文件模板的并排比较
图 9.17 展示了不同配置文件之间的差异。一个配置文件将输入注入到用户提示中,而另一个则注入到系统提示中。然而,重要的是要理解,变化可以涵盖不仅仅是配置文件设计,正如表 9.1 中指出的那样。
表 9.1 提示流中 LLM 变化选项
| 选项 | 评估选项示例 | 备注 |
|---|---|---|
| Jinja2 提示模板 | 比较系统提示变化、用户提示变化或混合提示变化。 | 这里可以应用一些无穷无尽的组合和技术。提示工程一直在不断发展。 |
| LLM | 将 GPT-9.5 与 GPT-4 进行比较。将 GPT-4 与 GPT-4 Turbo 进行比较。
比较开源模型和商业模型。
| 这是一种评估和定位模型性能与提示相对的有效方式。它还可以帮助您调整配置文件以与开源和/或更便宜的模式一起工作。 |
|---|
| 温度 |
| 最大令牌数 |
| 高级参数 |
| 函数调用 |
在这个简单的例子中,我们将通过改变输入来使用提示变体,以反映系统或用户提示。参考图 9.17 了解其外观。然后,我们可以通过点击顶部的播放(运行全部)按钮并选择两者来快速运行这两种变体,如图 9.18 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-18.png’
图 9.18 同时运行两个提示变体
在终端窗口中,您将看到两次运行的结果。结果可能看起来很相似,因此现在我们必须继续到下一节,了解如何评估变体之间的差异。
9.5 理解评分标准和定位
提示/配置文件性能的评估通常不能通过准确度或正确百分比来衡量。衡量配置文件性能取决于用例和期望的结果。如果这仅仅是确定响应是否正确或错误,那就更好了。然而,在大多数情况下,评估不会那么简单。
在教育中,评分标准概念定义了学生必须建立的一套结构化标准,以获得特定的成绩。评分标准也可以用来定义配置文件或提示的性能指南。我们可以遵循以下步骤来定义我们可以用来评估配置文件或提示性能的评分标准:
-
确定目的和目标。 确定您希望配置文件或代理完成的任务。例如,您是想评估特定受众的推荐质量,还是评估特定主题、格式或其他输入的整体质量?
-
定义标准。 制定一套您将用来评估配置文件的准则或维度。这些标准应与您的目标一致,并为评估提供明确的指导。每个标准都应该是具体和可衡量的。例如,您可能希望根据推荐与体裁的契合度来衡量推荐,然后根据主题和格式来衡量。
-
创建量表。 建立一个描述每个标准表现水平的评级量表。标准量表包括数值量表(例如,1-5)或描述性量表(例如,优秀、良好、一般、差)。
-
提供描述。 在量表上的每个级别,提供清晰简洁的描述,说明每个标准中良好表现和较差表现的定义。
-
应用评分标准。 在评估提示或个人资料时,使用评分标准根据既定的标准评估提示的表现。为每个标准分配分数或评级,考虑每个级别的描述。
-
计算总分。 根据你的评分标准,你可能需要通过将每个标准的分数相加来计算总分,或者如果某些标准比其他标准更重要,可以使用加权平均。
-
确保评估一致性。 如果多个评估者正在评估个人资料,确保评分的一致性至关重要。
-
审查、修改和迭代。 定期审查和修改评分标准,以确保其与评估目标和目标一致。根据需要调整以提高其有效性。
依据 是一个可以应用于个人资料和提示评估的概念——它定义了回答与给定评分标准的具体标准和准则的契合程度。你也可以将依据视为提示或个人资料输出的基本期望。
此列表总结了使用依据进行个人资料评估时的一些其他重要考虑因素:
-
依据指的是将回答与评分标准、目标和情境对齐。
-
依据涉及评估回答是否直接针对评分标准,是否保持主题一致,以及是否遵守任何提供的指示。
-
评估者和评估在评估依据时衡量准确性、相关性和对标准的遵守程度。
-
依据确保输出回答牢固地根植于指定的情境,使评估过程更加客观和有意义。
一个有充分依据的回答与给定情境和目标下的所有评分标准相符。依据不足的回答可能会失败或完全不符合标准、情境和目标。
由于评分标准和依据的概念可能仍然比较抽象,让我们看看如何将它们应用于我们当前的推荐示例。以下是一个列表,列出了应用于我们的推荐示例的评分标准定义过程:
-
确定目的和目标。 我们的个人资料/提示的目的是在给定主题、格式、体裁和自定义输入的情况下推荐三个顶级项目。
-
定义标准。 为了简单起见,我们将评估特定推荐与给定输入标准、主题、格式和体裁的一致性。例如,如果个人资料在要求电影格式时推荐一本书,我们预计在格式标准上得分较低。
-
创建一个量表。 再次,保持简单,我们将使用 1-5 的量表(1 是差的,5 是优秀的)。
-
提供描述。 请参阅表 9.2 中所示评分尺度的通用描述。
-
应用评分标准。 在这个阶段分配评分标准后,手动评估评分标准与建议是一个很好的练习。
-
计算总分。 对于我们的评分标准,我们将对所有标准的评分进行平均,以提供总分。
-
确保评估一致性。 我们将使用的评估技术将提供非常一致的结果。
-
审查、修改和迭代。 我们将审查、比较和迭代我们的个人资料、评分标准和评估本身。
表 9.2 评分标准
| 评分 | 描述 |
|---|---|
| 1 | 对齐差:这与标准预期的相反。 |
| 2 | 对齐不良:这与给定的标准不匹配。 |
| 3 | 一般性对齐:可能与给定的标准相匹配,也可能不匹配。 |
| 4 | 良好对齐:可能不完全符合标准,但在其他方面是合适的。 |
| 5 | 优秀对齐:这是符合给定标准的良好建议。 |
现在可以将这个基本的评分标准应用于评估个人资料的回答。你可以手动进行,或者如你将在下一节中看到的,使用第二个 LLM 个人资料。
9.6 使用 LLM 个人资料进行定位评估
本节将使用另一个 LLM 提示/个人资料进行评估和定位。在生成建议之后,这个第二个 LLM 提示将添加另一个块。它将处理生成的建议,并根据之前的评分标准评估每个建议。
在 GPT-4 和其他复杂的 LLM 出现之前,我们从未考虑过使用另一个 LLM 提示来评估或定位个人资料。当使用 LLM 定位个人资料时,你通常希望使用不同的模型。然而,如果你正在比较个人资料,使用相同的 LLM 进行评估和定位是合适的。
在提示流程可视化编辑器中打开recommender_with_LLM_evaluation/flow.dag.yaml文件,向下滚动到evaluate_recommendation块,并点击evaluate_recommendation.jinja2链接以打开文件,如图 9.19 所示。图中的每个评分标准部分都已标识。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-19.png
图 9.19 评估提示,其中评分标准的各个部分都已概述
我们有一个评分标准,不仅定义良好,而且以提示的形式存在,可以用来评估建议。这使我们能够自动评估给定个人资料的推荐效果。当然,你也可以使用评分标准手动评分和评估建议,以获得更好的基线。
注意:使用 LLM 评估提示和配置文件为比较配置文件的性能提供了一个强大的基线。它还可以以受控和可重复的方式做到这一点,而不带有人为偏见。这为任何配置文件或提示的基线定位提供了一个极好的机制。
返回到 recommender_with_LLM_evaluation 流程可视化编辑器,我们可以通过点击播放按钮来运行流程并观察输出。您可以选择运行单个推荐或当被提示时运行两种变体。以下列表显示了使用默认输入的单次评估输出。
列表 9.1 LLM 评分评估输出
{
"recommendations": "Title: The Butterfly Effect
Subject: 5
Format: 5
Genre: 4
Title: Primer
Subject: 5
Format: 5
Genre: 4
Title: Time Bandits
Subject: 5
Format: 5
Genre: 5"
}
我们现在有一个评分标准来定位我们的推荐器,并且使用第二个 LLM 提示自动运行评估。在下一节中,我们将探讨如何同时执行多个评估,然后对全部内容进行总评分。
9.7 比较配置文件:获取完美的配置文件
通过我们对评分标准和定位的理解,我们现在可以继续评估和迭代完美的配置文件。不过,在我们这样做之前,我们需要清理 LLM 评估块的输出。这需要我们将推荐解析成更符合 Python 的形式,我们将在下一节中解决这个问题。
9.7.1 解析 LLM 评估输出
由于评估块的原始输出是文本,我们现在希望将其解析成更易用的形式。当然,编写解析函数很简单,但还有更好的方法可以自动转换响应。我们在第五章中介绍了关于代理动作的返回响应的更好方法。
在 VS Code 中打开 chapter_09\prompt_flow\recommender_with_parsing\flow.dag.yaml,并在可视化编辑器中查看流程。定位到 parsing_results 块,并点击链接在编辑器中打开 Python 文件,如图 9.20 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-20.png
图 9.20 在 VS Code 中打开 parsing_results.py 文件
parsing_results.py 文件的代码在列表 9.2 中显示。
列表 9.2 parsing_results.py
from promptflow import tool
@tool #1
def parse(input: str) -> str:
# Splitting the recommendations into individual movie blocks
rblocks = input.strip().split("\n\n") #2
# Function to parse individual recommendation block into dictionary
def parse_block(block):
lines = block.split('\n')
rdict = {}
for line in lines:
kvs = line.split(': ')
key, value = kvs[0], kvs[1]
rdict[key.lower()] = value #3
return rdict
parsed = [parse_block(block) for block in rblocks] #4
return parsed
#1 特殊装饰器用于表示工具块
#2 分割输入和双换行符
#3 创建字典条目并设置值
#4 遍历每个块并将其解析为键/值字典
我们正在将列表 9.1 中的推荐输出(仅是一个字符串)转换为字典。因此,此代码将此字符串转换为以下显示的 JSON 块:
解析前:
"Title: The Butterfly Effect
Subject: 5
Format: 5
Genre: 4
Title: Primer
Subject: 5
Format: 5
Genre: 4
Title: Time Bandits
Subject: 5
Format: 5
Genre: 5"
解析后:
{
"title": " The Butterfly Effect
"subject": "5",
"format": "5",
"genre": "4"
},
{
"title": " Primer",
"subject": "5",
"format": "5",
"genre": "4"
},
{
"title": " Time Bandits",
"subject": "5",
"format": "5",
"genre": "5"
}
这个 parsing_results 块的输出现在被传递到输出,并封装在一个推荐列表中。我们可以通过运行流程来查看所有这些内容的样子。
在可视化编辑器中打开 flow.dag.yaml 文件以查看流程,并点击播放(运行全部)按钮。请确保选择使用两种推荐变体。您将看到两种变体都在运行并将输出到终端。
在这一点上,我们已经有一个完整的、可工作的推荐和 LLM 评估流程,该流程为每个输出标准输出一个分数。然而,为了对特定配置文件进行全面评估,我们希望使用各种标准生成多个推荐。我们将在下一节中看到如何批量处理流程。
9.7.2 在提示流程中运行批量处理
在我们的通用推荐配置文件中,我们希望评估各种输入标准如何影响生成的推荐。幸运的是,提示流程可以批量处理我们想要测试的任何变化。限制仅限于我们愿意花费的时间和金钱。
为了执行批量处理,我们首先需要创建一个包含我们输入标准的 JSON Lines (JSONL) 或 JSON 列表文档。如果您还记得,我们的输入标准在 JSON 格式下看起来如下:
{
"subject": "time travel",
"format": "books",
"genre": "fantasy",
"custom": "don't include any R rated content"
}
我们希望创建一个类似于刚才展示的 JSON 对象列表,最好是随机生成的。当然,简单地通过提示 ChatGPT 使用以下提示创建 JSONL 文档是做这件事的简单方法:
我正在开发一个推荐代理。该代理将根据以下标准推荐任何内容:
-
主题 - 示例:时间旅行、烹饪、度假
-
格式 - 示例:书籍、电影、游戏
-
类型:纪录片、动作、浪漫
-
自定义:不包含任何 R 级内容
请您生成一个包含这些标准的随机列表,并以 JSON Lines 文件格式输出。请列出 10 项。
您可以通过访问 ChatGPT 并输入前面的提示来尝试一下。之前生成的文件可以在流程文件夹中找到,文件名为 \bulk_recommend.jsonl。此文件的内容在此处展示以供参考:
{
"subject": "time travel",
"format": "books",
"genre": "fantasy",
"custom": "don't include any R rated content"
}
{
"subject": "space exploration",
"format": "podcasts",
"genre": "sci-fi",
"custom": "include family-friendly content only"
}
{
"subject": "mystery",
"format": "podcasts",
"genre": "fantasy",
"custom": "don't include any R rated content"
}
{
"subject": "space exploration",
"format": "podcasts",
"genre": "action",
"custom": "include family-friendly content only"
}
{
"subject": "vacation",
"format": "books",
"genre": "thriller",
"custom": "don't include any R rated content"
}
{
"subject": "mystery",
"format": "books",
"genre": "sci-fi",
"custom": "don't include any R rated content"
}
{
"subject": "mystery",
"format": "books",
"genre": "romance",
"custom": "don't include any R rated content"
}
{
"subject": "vacation",
"format": "movies",
"genre": "fantasy",
"custom": "don't include any R rated content"
}
{
"subject": "cooking",
"format": "TV shows",
"genre": "thriller",
"custom": "include family-friendly content only"
}
{
"subject": "mystery",
"format": "movies",
"genre": "romance",
"custom": "include family-friendly content only"
}
使用这个批量文件,我们可以使用批量 JSONL 文件中的各种输入标准运行两个变体。在可视化编辑器中打开 flow.dag.yaml 文件,点击批量(试管图标)以启动批量数据加载过程,并选择如图 9.21 所示的文件。对于某些操作系统,这可能显示为 本地 数据 文件。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-21.png
图 9.21 将批量 JSONL 文件加载到运行多个输入变体的流程中
在选择批量文件后,将打开一个新的 YAML 文档,文件底部添加了一个运行链接,如图 9.22 所示。点击链接以执行输入的批量运行。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-22.png
图 9.22 运行输入的批量运行
在这一点上,将发生几件事情。将出现流程可视化编辑器,旁边将打开一个日志文件,显示运行进度。在终端窗口中,您将看到各种工作进程的生成和运行。
请耐心等待。即使是 10 项,批量运行也可能需要几分钟或几秒钟,这取决于各种因素,如硬件、之前的调用等。等待运行完成,您将在终端中看到结果摘要。
您还可以通过打开提示流扩展并选择最后一个运行来查看运行结果,如图 9.23 所示。然后,通过单击表格单元格深入了解每个运行。在此对话框中公开了大量信息,可以帮助您调试流程和配置文件。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-23.png
图 9.23 开启运行可视化以及检查批量运行
批量运行期间捕获了大量信息,您可以通过可视化器探索其中大部分。更多信息可以通过从终端窗口单击输出文件夹链接来找到。这将打开另一个 VS Code 会话,输出文件夹允许您查看运行日志和其他细节。
现在我们已经完成了每个变体的批量运行,我们可以应用地面并评估两个提示的结果。下一节将使用一个新的流程来执行配置文件/提示评估。
9.7.3 为地面创建评估流程
在可视化编辑器中打开chapter_3\prompt_flow\evaluate_groundings\flow.dag.yaml,如图 9.24 所示。评估流程中没有 LLM 块——只有将运行评分然后汇总评分的 Python 代码块。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-24.png
图 9.24 查看用于地面推荐运行的evaluate_groundings流程
现在我们可以查看scoring和aggregate块的代码,从列表 9.3 中的评分代码开始。此评分代码将每个标准的得分平均到一个平均得分。函数的输出是处理过的推荐列表。
列表 9.3 line_process.py
@tool
def line_process(recommendations: str): #1
inputs = recommendations
output = []
for data_dict in inputs: #2
total_score = 0
score_count = 0
for key, value in data_dict.items(): #2
if key != "title": #3
try:
total_score += float(value)
score_count += 1
data_dict[key] = float(value) #4
except:
pass
avg_score = total_score / score_count if score_count > 0 else 0
data_dict["avg_score"] = round(avg_score, 2) #5
output.append(data_dict)
return output
#1 将一组三个推荐输入到函数中。
#2 遍历每个推荐和标准
#3 标题不是标准,所以忽略它。
#4 对所有标准的得分进行总计,并将浮点值设置为键
#5 将平均得分作为推荐的地面得分
从地面推荐中,我们可以继续使用aggregate块来汇总得分——aggregate块的代码如下所示。
列表 9.4 aggregate.py
@tool
def aggregate(processed_results: List[str]):
items = [item for sublist in processed_results
↪ for item in sublist] #1
aggregated = {}
for item in items:
for key, value in item.items():
if key == 'title':
continue
if isinstance(value, (float, int)): #2
if key in aggregated:
aggregated[key] += value
else:
aggregated[key] = value
for key, value in aggregated.items(): #3
value = value / len(items)
log_metric(key=key, value=value) #4
aggregated[key] = value
return aggregated
#1 输入是一个列表的列表;将其展平为项目列表。
#2 检查值是否为数值,并为每个标准键累积得分
#3 遍历汇总的标准得分
#4 将标准作为度量记录
汇总的结果将是每个标准的汇总得分和平均得分。由于评估/地面流程是分开的,它可以在我们进行的任何推荐运行上运行。这将允许我们使用任何变化的批量运行结果来比较结果。
我们可以通过在可视化编辑器中打开flow.dag.yaml并单击批量(水壶图标)来运行地面流。然后,当提示时,我们选择一个现有运行,然后选择我们想要评估的运行,如图 9.25 所示。这将打开一个包含底部运行链接的 YAML 文件,就像我们之前看到的那样。单击运行链接以运行评估。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-25.png
图 9.25 加载先前的运行以进行归一化和评估
运行完成后,你将在终端窗口中看到结果摘要。你可以点击输出链接在 VS Code 中打开文件夹并分析结果,但有一个更好的方法来比较它们。
打开提示流扩展,关注批量运行历史记录窗口,并向下滚动到如图 9.26 所示的运行对比部分。选择你想要比较的运行——可能是靠近顶部的那些——以便出现勾选标记。然后,右键单击运行,并选择可视化运行选项。批量运行可视化窗口打开,你将在顶部看到每个运行的指标。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/9-26.png
图 9.26 可视化多个运行的指标并进行比较
我们现在可以清楚地看到配置文件/提示变体 0、用户提示和变体 1、系统提示之间的显著差异。如果你需要刷新提示/配置文件的外观,请参考图 9.15。此时,应该很明显,将输入参数注入系统提示可以提供更好的推荐。
你现在可以回去尝试其他配置文件或其他变体选项,看看这对你推荐的影响。可能性几乎是无限的,但希望你能看到提示流将是一个多么出色的工具,用于构建代理配置文件和提示。
9.7.4 练习
使用以下练习来提高你对材料的了解:
- 练习 1 — 为推荐流程创建新的提示变体(中级)
目标 — 通过在提示流中创建和测试新的提示变体来改进推荐结果。
任务:
-
-
在提示流中为推荐流程创建一个新的提示变体。
-
以批量模式运行流程。
-
评估结果,以确定它们与原始提示相比是更好还是更差。
-
-
练习 2 — 将自定义字段添加到评分标准并进行评估(中级)
目标 — 通过将自定义字段纳入评分标准和更新评估流程来增强评估标准。
任务:
-
-
将自定义字段作为新的标准添加到评分标准中。
-
更新评估流程以评分新标准。
-
评估结果,并分析新标准对评估的影响。
-
-
练习 3 — 开发新的用例和评估评分标准(高级)
目标 — 通过开发新的用例和创建评估评分标准来扩展提示工程的应用。
任务:
-
-
除了推荐之外,开发一个新的用例。
-
构建新用例的提示。
-
为新的提示创建评分标准。
-
更新或修改评估流程以汇总和比较新用例与现有用例的结果。
-
-
练习 4 — 使用 LM Studio 评估其他 LLM(中级)
目标 — 通过使用 LM Studio 托管本地服务器来评估不同开源 LLM 的性能。
任务:
-
-
使用 LM Studio 来托管本地服务器以评估 LLM。
-
评估其他开源 LLM。
-
如果需要设置服务器和执行评估的帮助,请参考第二章。
-
-
练习 5 — 使用提示流程构建和评估提示(中级)
目标 — 使用提示流程应用提示工程策略来构建和评估新的提示或配置文件。
任务:
-
-
使用提示流程构建新的提示或配置文件以进行评估。
-
应用第二章中的“编写清晰指令”提示工程策略。
-
使用提示流程评估提示和配置文件。
-
如果需要复习策略和实施细节,请参考第二章。
-
摘要
-
代理配置文件由几个其他组件提示组成,可以驱动诸如动作/工具、知识、记忆、评估、推理、反馈和计划等功能。
-
提示流程可用于评估代理的组件提示。
-
系统性提示工程是一个评估提示和代理配置文件的迭代过程。
-
系统性测试更改策略描述了迭代和评估提示,系统提示工程实现了这一策略。
-
代理配置文件和提示工程有许多相似之处。我们定义代理配置文件为引导和帮助代理完成任务的提示工程元素的组合。
-
提示流程是来自微软的开源工具,它为开发和评估配置文件和提示提供了几个功能。
-
提示流程中的 LLM 连接支持额外的参数,包括温度、停止令牌、最大令牌和其他高级参数。
-
LLM 块支持提示和配置文件变体,这允许评估提示/配置文件或其他连接参数的变化。
-
应用到 LLM 提示上的评分标准是提示/配置文件必须满足的准则和标准,以实现扎根。扎根是对评分标准的评分和评估。
-
提示流程支持以单次运行或批量运行的方式运行多个变体。
-
在提示流程中,在生成流程之后运行评估流程以评分和汇总结果。可视化运行选项可以比较跨多个运行评分标准汇总的聚合标准。
第十章:代理推理和评估
本章涵盖
-
使用各种提示工程技术来扩展大型语言模型功能
-
使用涉及推理的提示工程技术来参与大型语言模型
-
使用评估提示来缩小和识别未知问题的解决方案
现在我们已经检查了定义代理中语义记忆组件的记忆和检索模式,我们可以看看代理中最后一个也是最关键的组件:规划。规划包括许多方面,从推理、理解、评估到反馈。
为了探索如何通过提示工程来引导 LLMs 进行推理、理解和规划,我们将演示如何通过提示工程来参与推理,然后扩展到规划。语义内核(SK)提供的规划解决方案包含多种规划形式。我们将通过将自适应反馈纳入一个新的规划器来结束本章。
图 10.1 展示了本章将涵盖的高级提示工程策略以及它们与我们将要介绍的各种技术之间的关系。图中的每种方法都将在本章中探讨,从左上角显示的解决方案/直接提示的基础,到右下角的自我一致性和思维树(ToT)提示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-1.png
图 10.1 两种规划提示工程策略如何与各种技术相匹配
10.1 理解直接解决方案提示
直接解决方案提示通常是用户在向 LLMs 提问或解决特定问题时采用的第一种提示工程形式。对于任何 LLM 的使用,这些技术可能看起来很明显,但它们值得回顾,以建立思考和规划的基础。在下一节中,我们将从提问和期待答案开始。
10.1.1 问答提示
对于本章的练习,我们将使用提示流来构建和评估各种技术。(我们已经在第九章中广泛介绍了这个工具,所以如果需要复习,请参考该章节。)提示流是一个理解这些技术如何工作以及探索规划和推理过程流程的绝佳工具。
打开 Visual Studio Code (VS Code)到chapter 10源文件夹。为文件夹创建一个新的虚拟环境,并安装requirements.txt文件。如果您需要帮助设置章节的 Python 环境,请参阅附录 B。
我们将查看prompt_flow/question-answering-prompting文件夹中的第一个流程。在可视化编辑器中打开flow.dag.yaml文件,如图 10.2 所示。在右侧,您将看到组件的流程。顶部是question_answer LLM 提示,后面跟着两个Embedding组件,最后是一个用于评估的最终 LLM 提示,称为evaluate。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-2.png
图 10.2 flow.dag.yaml文件,在可视化编辑器中打开,突出显示流程的各个组件
列表 10.1 中的分解详细展示了流程的结构和组件,使用了一种类似 YAML 简化的伪代码。您还可以看到各个组件的输入和输出,以及运行流程的示例输出。
列表 10.1 question-answer-prompting流程
Inputs:
context : the content to ask the question about
question : question asked specific to the content
expected : the expected answer
LLM: Question-Answer (the prompt used to ask the question)
inputs:
context and question
outputs:
the prediction/answer to the question
Embeddings: uses an LLM embedding model to create the embedding
representation of the text
Embedding_predicted: embeds the output of the Question-Answer LLM
Embedding_expected: embeds the output of the expected answer
Python: Evaluation (Python code to measure embedding similarity)
Inputs:
Embedding_predicted output
Embedding_expected output
Outputs:
the similarity score between predicted and expected
Outputs:
context: -> input.context
question: -> input.question
expected: -> input.expected
predicted: -> output.question_answer
evaluation_score: output.evaluation
### Example Output
{
"context": "Back to the Future (1985)…",
"evaluation_score": 0.9567478002354606,
"expected": "Marty traveled back in time 30 years.",
"predicted": "Marty traveled back in time 30 years from 1985 to 1955
in the movie \"Back to the Future.\"",
"question": "How far did Marty travel back in time in the movie
Back to the Future (1985)"
}
在运行此流程之前,请确保您的 LLM 块配置正确。这可能需要您设置与所选 LLM 的连接。如果您需要复习如何完成此操作,请再次参考第九章。如果您不使用 OpenAI,您需要配置 LLM 和Embedding块与您的连接。
在配置您的 LLM 连接后,通过从可视化编辑器中点击播放按钮或在 YAML 编辑器窗口中使用测试(Shift-F5)链接来运行流程。如果一切连接和配置正确,您应该会看到列表 10.1 中的输出。
在 VS Code 中打开question_answer.jinja2文件,如图 10.2 所示。这个列表显示了基本的问答式提示。在这种提示风格中,系统消息描述了基本规则,并提供了解答问题的上下文。在第四章中,我们探讨了检索增强生成(RAG)模式,而这个提示遵循了类似的模式。
列表 10.2 question_answer.jinja2
system:
Answer the users question based on the context below. Keep the answer
short and concise. Respond "Unsure about answer" if not sure about the
answer.
Context: {{context}} #1
user:
Question: {{question}} #2
#1 用 LLM 应该回答问题的内容替换。
#2 用问题替换。
这个练习展示了使用大型语言模型(LLM)对某段内容提问的简单方法。然后,使用相似度匹配分数来评估问题回答。我们可以从列表 10.1 的输出中看到,LLM 在回答关于上下文的问题方面做得很好。在下一节中,我们将探讨一种类似的直接提示技术。
10.1.2 实现少样本提示
少样本提示类似于问答提示,但提示的构成更多地是提供几个示例,而不是事实或上下文。这允许 LLM 适应之前未见过的模式或内容。虽然这种方法听起来像是问答,但实现方式相当不同,结果可能非常强大。
零样本、单样本和少样本学习
机器学习和人工智能的一个圣杯是能够在尽可能少的样本上训练模型。例如,在传统的视觉模型中,数百万张图片被输入到模型中,以帮助识别猫和狗之间的差异。
一个单样本模型是一个只需要单个图像来训练的模型。例如,可以展示一张猫的图片,然后模型可以识别任何猫的图片。一个少样本模型只需要少量东西来训练模型。当然,零样本表示在没有先前示例的情况下识别某物的能力。LLM 是高效的学习者,可以完成这三种类型的学习。
在 VS Code 和可视化编辑器中打开 prompt_flow/few-shot-prompting/flow.dag.yaml。大部分流程看起来像图 10.2 中早些时候展示的那样,差异在列表 10.3 中突出显示,它展示了 YAML 伪代码表示。这个流程与之前的流程之间的主要区别是输入和 LLM 提示。
列表 10.3 few-shot-prompting 流程
Inputs:
statement : introduces the context and then asks for output
expected : the expected answer to the statement
LLM: few_shot (the prompt used to ask the question)
inputs:statement
outputs: the prediction/answer to the statement
Embeddings: uses an LLM embedding model to create the embedding
representation of the text
Embedding_predicted: embeds the output of the few_shot LLM
Embedding_expected: embeds the output of the expected answer
Python: Evaluation (Python code to measure embedding similarity)
Inputs:
Embedding_predicted output
Embedding_expected output
Outputs: the similarity score between predicted and expected
Outputs:
statement: -> input.statement
expected: -> input.expected
predicted: -> output.few_shot
evaluation_score: output.evaluation
### Example Output
{
"evaluation_score": 0.906647282920417, #1
"expected": "We ate sunner and watched the setting sun.",
"predicted": "After a long hike, we sat by the lake
and enjoyed a peaceful sunner as the sky turned
brilliant shades of orange and pink.", #2
"statement": "A sunner is a meal we eat in Cananda
at sunset, please use the word in a sentence" #3
}
#1 评估分数表示预期和预测之间的相似度。
#2 在句子中使用 sunner
#3 这是一个错误的陈述,但目的是让 LLM 使用这个单词,就像它是真实的一样。
通过按 Shift-F5 或从可视化编辑器中点击播放/测试按钮来运行流程。你应该会看到类似于列表 10.3 的输出,其中 LLM 正确地在一个句子中使用了单词 sunner(一个虚构的术语),这是基于初始语句的。
这个练习展示了使用提示来改变 LLM 行为的能力,使其与它所学习的内容相反。我们正在改变 LLM 理解为准确的内容。此外,我们随后使用这种修改后的观点来引发对虚构词汇的使用。
在 VS Code 中打开 few_shot.jinja2 提示,如列表 10.4 所示。这个列表展示了设置一个简单的角色,即古怪的词典编纂者,然后提供它之前定义和使用的词汇的例子。提示的基础允许 LLM 扩展例子并使用其他词汇产生类似的结果。
列表 10.4 few_shot.jinja2
system:
You are an eccentric word dictionary maker. You will be asked to
construct a sentence using the word.
The following are examples that demonstrate how to craft a sentence using
the word.
A "whatpu" is a small, furry animal native to Tanzania.
An example of a sentence that uses the word whatpu is: #1
We were traveling in Africa and we saw these very cute whatpus.
To do a "farduddle" means to jump up and down really fast. An example of a
sentence that uses the word farduddle is:
I was so excited that I started to farduddle. #2
Please only return the sentence requested by the user. #3
user:
{{statement}} #4
#1 展示了一个定义虚构词汇并在句子中使用它的例子
#2 展示了另一个例子
#3 一条规则,用于防止 LLM 输出额外信息
#4 输入语句定义了一个新词并要求使用。
你可能会说我们在这里迫使 LLM 幻觉,但这项技术是修改行为的基础。它允许构建提示来引导 LLM 做出与它所学习的一切相反的事情。提示的基础还确立了其他形式改变行为的技术。从改变 LLM 的感知和背景的能力,我们将继续在下一节展示一个直接解决方案的最终例子。
10.1.3 使用零样本提示提取概括
零样本提示或学习 是以这种方式生成提示的能力,允许 LLM 进行泛化。这种泛化嵌入在 LLM 中,并通过零样本提示来展示,其中不提供示例,而是给出一系列指南或规则来引导 LLM。
使用这种技术很简单,并且很好地引导 LLM 根据其内部知识和没有其他上下文来生成回复。这是一种微妙而强大的技术,它将 LLM 的知识应用于其他应用。这种技术与其他提示策略相结合,正在证明在替代其他语言分类模型——例如识别文本中的情感或情绪的模型——方面是有效的。
在 VS Code 提示流程可视化编辑器中打开 prompt_flow/zero-shot-prompting/flow.dag.yaml。这个流程与之前图 10.1 中显示的几乎相同,但在实现上略有不同,如列表 10.5 所示。
列表 10.5 zero-shot-prompting 流程
Inputs:
statement : the statement to be classified
expected : the expected classification of the statement
LLM: zero_shot (the prompt used to classify)
inputs: statement
outputs: the predicted class given the statement
Embeddings: uses an LLM embedding model to create the embedding
representation of the text
Embedding_predicted: embeds the output of the zero_shot LLM
Embedding_expected: embeds the output of the expected answer
Python: Evaluation (Python code to measure embedding similarity)
Inputs:
Embedding_predicted output
Embedding_expected output
Outputs: the similarity score between predicted and expected
Outputs:
statement: -> input.statement
expected: -> input.expected
predicted: -> output.few_shot
evaluation_score: output.evaluation
### Example Output
{
"evaluation_score": 1, #1
"expected": "neutral",
"predicted": "neutral",
"statement": "I think the vacation is okay. " #2
}
#1 显示了完美的评估分数 1.0
#2 我们要求 LLM 进行分类的声明
在 VS Code 提示流程可视化编辑器中按 Shift-F5 运行流程。你应该会看到类似于列表 10.5 所示的输出。
现在打开如列表 10.6 所示的 zero_shot.jinja2 提示。该提示简单,不使用示例来从文本中提取情感。特别值得注意的是,提示中甚至没有提到“情感”这个词,而 LLM 似乎理解了意图。
列表 10.6 zero_shot.jinja2
system:
Classify the text into neutral, negative or positive.
Return on the result and nothing else. #1
user:
{{statement}} #2
#1 提供了执行分类的基本指导
#2 文本分类的声明
零样本提示工程是关于利用 LLM 基于其训练材料广泛概括的能力。这个练习展示了 LLM 中的知识如何用于其他任务。LLM 自我情境化和应用知识的能力可以超越其训练范围。在下一节中,我们将进一步探讨 LLM 如何进行推理。
10.2 提示工程中的推理
类似于 ChatGPT 这样的 LLM 被开发成作为聊天完成模型,其中文本内容被输入到模型中,其响应与完成该请求相一致。LLM 从未被训练过推理、规划、思考或拥有思想。
然而,就像我们在上一节中的示例所展示的那样,LLM 可以被提示提取其概括性,并扩展到其初始设计之外。虽然 LLM 不是为推理而设计的,但输入到模型中的训练材料提供了对推理、规划和思维的理解。因此,通过扩展,LLM 理解推理是什么,并且可以运用推理的概念。
理解和规划
推理 是智力(无论是人工的还是非人工的)理解通过问题进行思考或思维过程的能力。智力可以理解行动有结果,并且可以利用这种能力通过从一系列行动中选择哪个行动可以应用于解决给定的任务来进行推理。
规划 是智力推理出行动或任务的顺序并应用正确的参数以实现目标或结果的能力——智力计划依赖于问题范围的程度。智力可以结合多个层次的规划,从战略和战术到操作和应急。
我们将探讨另一组提示工程技术,这些技术允许或模拟推理行为,以展示这种推理能力。通常,在评估推理的应用时,我们会寻找 LLM 解决它未设计去解决的问题。这类问题的一个很好的来源是逻辑、数学和文字问题。
使用时间旅行主题,理解时间旅行比解决哪一类独特问题更好?图 10.3 展示了一个独特且具有挑战性的时间旅行问题的例子。我们的目标是获得一种能力,能够以正确解决问题的方法提示 LLM。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-3.png
图 10.3 我们打算使用具有推理和计划的 LLM 解决的时间旅行问题的复杂性
时间旅行问题是一些看似难以解决的思维练习。图 10.3 中的例子对 LLM 来说很复杂,但它出错的部分可能会让你感到惊讶。下一节将使用提示中的推理来解决这些独特的问题。
10.2.1 思维链提示
思维链(CoT)提示是一种提示工程技术,它使用一次或几次示例来描述推理和实现预期目标的步骤。通过推理的展示,LLM 可以推广这个原则,并通过类似的问题和目标进行推理。虽然 LLM 没有以推理为目标进行训练,但我们可以通过提示工程技术来激发模型进行推理。
在 VS Code 提示流程可视化编辑器中打开 prompt_flow/chain-of-thought-prompting/flow.dag.yaml。如图 10.4 所示,这个流程的元素很简单,只有两个 LLM 块。流程首先使用 CoT 提示来解决一个复杂问题;然后,第二个 LLM 提示评估答案。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-4.png
图 10.4 CoT 流程
列表 10.7 展示了描述流程中的块及其输入/输出的 YAML 伪代码。在这个例子中,默认的问题陈述与图 10.3 中的不同。
列表 10.7 chain-of-thought-prompting 流程
Inputs:
statement : the statement problem to be solved
expected : the expected solution to the problem
LLM: cot (the prompt used to solve the problem)
inputs: statement
outputs: the predicted answer given the problem statement
LLM: evaluate_answer (the prompt used to evaluate the solution)
inputs:
statement: -> input.statement
expected: -> input.expected
predicted: -> output.cot
outputs: a score of how well the problem was answered
Outputs:
statement: -> input.statement
expected: -> input.expected
predicted: -> output.cot
evaluation_score: output.evaluate_answer
### Example Output
{
"evaluation_score": "0.5", #1
"expected": "After the final jump, Max finds himself
in the year 1980 and he is 75 years old.", #2
"predicted": " Max starts in the year 2300 and
travels backward in 40-year increments, spending 5 years
in each period. The journeys will be as follows:
\n\n- From 2300 to 2260: Max is 25 + 5 = 30 years old.
\n- From 2260 to 2220: Max is 30 + 5 = 35 years old.
\n- From 2220 to 2180: Max is 35 + 5 = 40 years old.
\n- From 2180 to 2140: Max is 40 + 5 = 45 years old.
\n- From 2140 to 2100: Max is 45 + 5 = 50 years old.
\n- From 2100 to 2060: Max is 50 + 5 = 55 years old.
\n- From 2060 to 2020: Max is 55 + 5 = 60 years old.
\n- From 2020 to 1980: Max is 60 + 5 = 65 years old.
\n- From 1980 to 1940: Max is 65 + 5 = 70 years old.
\n- From 1940 to 1900: Max is 70 + 5" #3
}
#1 给定解决方案的评估分数
#2 问题的预期答案
#3 预测答案显示了推理步骤和输出。
深入研究输入并检查问题陈述;尝试自己评估这个问题。然后,通过按 Shift-F5 运行流程。你应该会看到类似于列表 10.7 中所示的结果。
按照列表 10.8 中的说明打开 cot.jinja2 提示文件。这个提示给出了一些时间旅行问题的例子,然后是经过思考和推理的解决方案。向 LLM 展示完成问题的步骤提供了推理机制。
列表 10.8 cot.jinja2
system:
"In a time travel movie, Sarah travels back in time to
prevent a historic event from happening. She arrives
2 days before the event. After spending a day preparing,
she attempts to change the event but realizes she has
actually arrived 2 years early, not 2 days. She then
decides to wait and live in the past until the event's
original date. How many days does Sarah spend in the past
before the day of the event?" #1
Chain of Thought: #2
Initial Assumption: Sarah thinks she has arrived 2 days before the event.
Time Spent on Preparation: 1 day spent preparing.
Realization of Error: Sarah realizes she's actually 2 years early.
Conversion of Years to Days:
2 years = 2 × 365 = 730 days (assuming non-leap years).
Adjust for the Day Spent Preparing: 730 - 1 = 729 days.
Conclusion: Sarah spends 729 days in the past before the day of the event.
"In a sci-fi film, Alex is a time traveler who decides
to go back in time to witness a famous historical battle
that took place 100 years ago, which lasted for 10 days.
He arrives three days before the battle starts. However,
after spending six days in the past, he jumps forward in
time by 50 years and stays there for 20 days. Then, he
travels back to witness the end of the battle. How many
days does Alex spend in the past before he sees the end of
the battle?" #3
Chain of Thought: #4
Initial Travel: Alex arrives three days before the battle starts.
Time Spent Before Time Jump: Alex spends six days in the past.
The battle has started and has been going on for 3 days (since he
arrived 3 days early and has now spent 6 days, 3 + 3 = 6).
First Time Jump: Alex jumps 50 years forward and stays for 20 days.
This adds 20 days to the 6 days he's already spent in the past
(6 + 20 = 26).
Return to the Battle: When Alex returns, he arrives back on the same
day he left (as per time travel logic). The battle has been going on for
3 days now.
Waiting for the Battle to End: The battle lasts 10 days. Since he's
already witnessed 3 days of it, he needs to wait for 7 more days.
Conclusion: Alex spends a total of 3 (initial wait) + 3 (before the
first jump) + 20 (50 years ago) + 7 (after returning) = 33 days in the
past before he sees the end of the battle.
Think step by step but only show the final answer to the statement.
user:
{{statement}} #5
#1 几个示例问题陈述
#2 将问题陈述的解决方案输出为一系列推理步骤
#3 几个示例问题陈述
#4 将问题陈述的解决方案输出为一系列推理步骤
#5 LLM 被指示解决的问题陈述
你可能会注意到图 10.3 的解决方案也作为例子在列表 10.8 中提供。回顾列表 10.7 中 LLM 对问题的回复也是很有帮助的。从这一点,你可以看到 LLM 应用了哪些推理步骤来得到其最终答案。
现在,我们可以查看评估解决方案如何解决问题的提示。打开列表 10.9 中所示的evaluate_answer.jinja2以回顾所使用的提示。这个提示很简单,使用了零样本提示,并允许 LLM 泛化它应该如何评分预期和预测。我们可以提供示例和分数,从而将此转变为一个几样本分类的例子。
列表 10.9 evaluate_answer.jinja2
system:
Please confirm that expected and predicted results are
the same for the given problem. #1
Return a score from 0 to 1 where 1 is a perfect match and 0 is no match.
Please just return the score and not the explanation. #2
user:
Problem: {{problem}} #3
Expected result: {{expected}} #4
Predicted result: {{predicted}} #5
#1 评估解决方案的规则
#2 指示只返回分数,不返回其他内容
#3 初始问题陈述
#4 预期或基于事实的答案
#5 之前 CoT 提示的输出
观察到在列表 10.7 中之前展示的 LLM 输出,你可以理解为什么评估步骤可能会变得令人困惑。或许解决这个问题的一个方法就是建议 LLM 以单个陈述的形式提供最终答案。在下一节中,我们将继续探讨另一个提示推理的例子。
10.2.2 零样本 CoT 提示
正如我们的时间旅行所展示的,CoT 提示在特定类问题上的提示生成可能很昂贵。虽然效果不如前者,但有一些类似于 CoT 的技术不使用示例,并且可以更加通用。本节将检查用于在 LLM 中引发推理的简单短语。
在 VS Code 的提示流程视觉编辑器中打开prompt_flow/zero-shot-cot-prompting/flow.dag.yaml。这个流程与之前的 CoT 非常相似,如图 10.4 所示。下一个列表显示了描述该流程的 YAML 伪代码。
列表 10.10 zero-shot-CoT-prompting流程
Inputs:
statement : the statement problem to be solved
expected : the expected solution to the problem
LLM: cot (the prompt used to solve the problem)
inputs: statement
outputs: the predicted answer given the problem statement
LLM: evaluate_answer (the prompt used to evaluate the solution)
inputs:
statement: -> input.statement
expected: -> input.expected
predicted: -> output.cot
outputs: a score of how well the problem was answered
Outputs:
statement: -> input.statement
expected: -> input.expected
predicted: -> output.cot
evaluation_score: output.evaluate_answer
### Example Output
{
"evaluation_score": "1", #1
"expected": "After the final jump, ↪
↪ Max finds himself in the year 1980 and
he is 75 years old.", #2
"predicted": "Max starts in… ↪
↪ Therefore, after the final jump, ↪
↪ Max is 75 years old and in the year 1980.", #3
"statement": "In a complex time travel …" #4
}
#1 最终评估分数
#2 预期答案
#3 预测答案(已省略步骤以显示最终答案)
#4 初始问题陈述
在 VS Code 的视觉编辑器中按 Shift-F5 运行/测试流程。流程将运行,你应该会看到类似于列表 10.10 所示的输出。这个练习示例在相同问题上比之前的例子表现更好。
在 VS Code 中打开cot.jinja2提示,如列表 10.11 所示。这个提示比之前的例子简单得多,因为它只使用了零样本。然而,一个关键短语将这个简单的提示转换成了一个强大的推理引擎。提示中的这一行让我们 一步一步 来思考触发了 LLM 考虑内部上下文以展示推理。这反过来又指导 LLM 分步骤推理出问题。
列表 10.11 cot.jinja2
system:
You are an expert in solving time travel problems.
You are given a time travel problem and you have to solve it.
Let's think step by step. #1
Please finalize your answer in a single statement. #2
user:
{{statement}} #3
#1 一行魔法代码,用于从 LLM 中构建推理
#2 要求 LLM 提供一个答案的最终陈述
#3 LLM 被要求解决的问题陈述
类似的短语要求 LLM 思考步骤或要求它以步骤的形式回答,也会提取推理。我们将在下一节中演示一个类似但更复杂的技术。
10.2.3 使用提示链分步骤
我们可以将向一个 LLM 逐步提问的行为扩展成一个链式提示,迫使 LLM 分步骤解决问题。在本节中,我们将探讨一种称为提示链的技术,该技术迫使 LLM 分步骤处理问题。
打开可视化编辑器中的prompt_flow/prompt-chaining/flow.dag.yaml文件,如图 10.5 所示。提示链将解决问题的推理方法分解成一系列提示。这项技术迫使 LLM 以步骤的形式回答问题。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-5.png
图 10.5 提示链流程
列表 10.12 展示了描述该流程的 YAML 伪代码,它提供了更多细节。这个流程将第一个 LLM 块的输出链式连接到第二个,然后从第二个连接到第三个。迫使 LLM 以这种方式处理问题揭示了推理模式,但它也可能过于冗长。
列表 10.12 prompt-chaining流程
Inputs:
statement : the statement problem to be solved
LLM: decompose_steps (the prompt used to decompose the problem)
inputs:
statement: -> input.statement #1
outputs: the breakdown of steps to solve the problem
LLM: calculate_steps (the prompt used to calculate the steps)
inputs:
statement: -> input.statement
decompose_steps: -> output.decompose_steps #2
outputs: the calculation for each step
LLM: calculate_solution (attempts to solve the problem)
inputs:
statement: -> input.statement
decompose_steps: -> output.decompose_steps
calculate_steps: -> output.calculate_steps #3
outputs: the final solution statement
Outputs:
statement: -> input.statement
decompose_steps: -> output.decompose_steps
calculate_steps: -> output.calculate_steps
calculate_solution: -> output.calculate_solution
### Example Output
{
"calculate_steps": "1\. The days spent by Alex",
"decompose_steps": "To figure out the …",
"solution": "Alex spends 13 days in the ↪
↪ past before the end of the battle.", #4
"statement": "In a sci-fi film, Alex …"
}
#1 提示链的开始
#2 将前一步的输出注入到这一步
#3 将前两个步骤的输出注入到这一步
#4 最终的解决方案陈述,尽管是错误的,但更接近了。
通过从可视化编辑器按 Shift-F5 运行流程,你会看到如列表 10.12 所示的输出。对于 Alex 问题,答案仍然不正确,但我们可以看到 LLM 为了推理出问题所做的工作。
打开所有三个提示:decompose_steps.jinja2、calculate_steps.jinja2和calculate_solution.jinja2(分别见列表 10.13、10.14 和 10.15)。列表中展示的所有三个提示可以进行比较,以展示输出是如何链式连接的。
列表 10.13 decompose_steps.jinja2
system:
You are a problem solving AI assistant.
Your job is to break the users problem down into smaller steps and list
the steps in the order you would solve them.
Think step by step, not in generalities.
Do not attempt to solve the problem, just list the steps. #1
user:
{{statement}} #2
#1 迫使 LLM 只列出步骤,不列出其他任何内容
#2 初始问题陈述
列表 10.14 calculate_steps.jinja2
system:
You are a problem solving AI assistant.
You will be given a list of steps that solve a problem.
Your job is to calculate the output for each of the steps in order.
Do not attempt to solve the whole problem,
just list output for each of the steps. #1
Think step by step. #2
user:
{{statement}}
{{steps}} #3
#1 要求 LLM 只解决步骤,而不是整个问题
#2 使用魔法语句提取推理
#3 将分解步骤步骤产生的步骤注入其中
列表 10.15 calculate_solution.jinja2
system:
You are a problem solving AI assistant.
You will be given a list of steps and the calculated output for each step.
Use the calculated output from each step to determine the final
solution to the problem.
Provide only the final solution to the problem in a
single concise sentence. Do not include any steps
in your answer. #1
user:
{{statement}}
{{steps}} #2
{{calculated}} #3
#1 要求 LLM 输出最终答案,而不输出任何步骤
#2 分解的步骤
#3 计算出的步骤
在这个练习示例中,我们并没有进行任何评估和评分。没有评估,我们可以看到这个提示序列仍然存在一些问题,无法解决之前在图 10.3 中展示的更具挑战性的时间旅行问题。然而,这并不意味着这项技术没有价值,这种提示格式在解决一些复杂问题方面表现良好。
然而,我们想要找到的是一种推理和规划方法,可以一致地解决如此复杂的问题。接下来的部分将从推理转向评估最佳解决方案。
10.3 使用评估来实现一致性的解决方案
在上一节中,我们了解到即使是经过最佳推理的计划也可能不会总是得出正确的解决方案。此外,我们可能并不总是有答案来确认该解决方案是否正确。现实情况是,我们经常想要使用某种形式的评估来确定解决方案的有效性。
图 10.6 显示了作为使 LLMs 进行推理和规划手段而设计的提示工程策略的比较。我们已经涵盖了左侧的两个:零样本直接提示和 CoT 提示。本节下面的示例练习将探讨与 CoT 和 ToT 技术结合的自洽性。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-6.png
图 10.6 比较各种提示工程策略以实现从 LLMs 中进行推理和规划
我们将继续关注复杂的时间旅行问题,以比较这些更高级的方法,这些方法通过评估扩展了推理和规划。在下一节中,我们将评估自洽性。
10.3.1 评估自洽提示
提示的一致性不仅仅是降低我们发送给 LLM 的温度参数。通常,我们希望生成一个一致的计划或解决方案,同时使用较高的温度来更好地评估计划的全部变体。通过评估多个不同的计划,我们可以更好地了解解决方案的整体价值。
自洽提示是针对给定问题生成多个计划/解决方案的技术。然后,评估这些计划,并接受出现频率更高或更一致的计划。想象一下生成了三个计划,其中两个相似,但第三个不同。使用自洽性,我们将前两个计划评估为更一致的答案。
在 VS Code 提示流可视化编辑器中打开prompt_flow/self-consistency-prompting/flow.dag.yaml。流程图显示了图 10.7 中提示生成流程的简单性。在图旁边的是自洽评估流程。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-7.png
图 10.7 自洽提示生成与评估流程并置
提示流使用直接无环图(DAG)格式来执行流程逻辑。DAGs 是展示和执行流程逻辑的绝佳方式,但由于它们是无环的,意味着它们不能重复,因此不能执行循环。然而,由于提示流提供了一个批量处理机制,我们可以使用它来模拟流程中的循环或重复。
参考图 10.6,我们可以看到自洽过程在收集结果并确定最佳计划/回复之前对输入进行了三次处理。我们可以应用相同的模式,但使用批量处理来生成输出。然后,评估流程将汇总结果并确定最佳答案。
在 VS Code 中打开self-consistency-prompting/cot.jinja2提示模板(参见列表 10.16)。列表已被缩短,因为我们之前已经看到了部分内容。此提示使用两个(少样本提示)CoT 示例来向 LLM 展示推理过程。
列表 10.16 self-consistency-prompting/cot.jinja2
system:
"In a time travel movie, Sarah travels back… " #1
Chain of Thought:
Initial Assumption: … #2
Conclusion: Sarah spends 729 days in the past before the day of the event.
"In a complex time travel movie plot, Max, a 25 year old…" #3
Chain of Thought:
Starting Point: Max starts … #4
Conclusion: After the final jump,
Max finds himself in the year 1980 and he is 75 years old.
Think step by step,
but only show the final answer to the statement. #5
user:
{{statement}}
#1 Sarah 时间旅行问题
#2 样本 CoT,为了简洁已截断
#3 最大时间旅行问题
#4 样本 CoT,为了简洁已截断
#5 最终指南和声明以约束输出
在 VS Code 中打开self-consistency-prompting/flow.dag.yaml文件。通过从可视化编辑器中点击批量运行(水壶图标)来以批量模式运行示例。图 10.8 展示了逐步过程:
-
点击批量运行。
-
选择 JSON Lines (JSONL)输入。
-
选择
statements.jsonl。 -
点击运行链接。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-8.png
图 10.8 启动批量过程的逐步过程
提示:如果您需要回顾过程,请参阅第九章,其中更详细地介绍了此过程。
列表 10.17 显示了批量模式下执行流程的 JSON 输出。statements.jsonl文件有五个相同的 Alex 时间旅行问题条目。使用相同的条目允许我们模拟在重复条目上执行提示五次。
列表 10.17 self-consistency-prompting 批量执行输出
{
"name": "self-consistency-prompting_default_20240203_100322_912000",
"created_on": "2024-02-03T10:22:30.028558",
"status": "Completed",
"display_name": "self-consistency-prompting_variant_0_202402031022",
"description": null,
"tags": null,
"properties": {
"flow_path": "…prompt_flow/self-consistency-prompting", #1
**"output_path"**: "…/.promptflow/.runs/self-
↪ consistency-prompting_default_20240203_100322_912000", #2
"system_metrics": {
"total_tokens": 4649,
"prompt_tokens": 3635,
"completion_tokens": 1014,
"duration": 30.033773
}
},
"flow_name": "self-consistency-prompting",
"data": "…/prompt_flow/self-consistency-prompting/
↪ statements.jsonl", #3
"output": "…/.promptflow/.runs/self-consistency-↪
↪ prompting_default_20240203_100322_912000/flow_outputs"
}
#1 流程执行的路径
#2 包含流程输出的文件夹(注意此路径)
#3 批量运行流程使用的数据
您可以通过按 Ctrl 键并点击列表 10.17 中突出显示的输出链接来查看生成的流程。这将打开 VS Code 的另一个实例,显示包含运行所有输出的文件夹。我们现在想检查最一致的答案。幸运的是,提示流程中的评估功能可以帮助我们使用相似度匹配来识别一致的答案。
在 VS Code 中打开self-consistency-evaluation/flow.dag.yaml(参见图 10.7)。此流程将预测答案嵌入,然后使用聚合来确定最一致的答案。
从流程中,在 VS Code 中打开consistency.py(如列表 10.18 所示)。此工具函数的代码计算所有答案对之间的余弦相似度。然后,它找到最相似的答案,记录它,并将其作为答案输出。
列表 10.18 consistency.py
from promptflow import tool
from typing import List
import numpy as np
from scipy.spatial.distance import cosine
@tool
def consistency(texts: List[str],
embeddings: List[List[float]]) -> str:
if len(embeddings) != len(texts):
raise ValueError("The number of embeddings ↪
↪ must match the number of texts.")
mean_embedding = np.mean(embeddings, axis=0) #1
similarities = [1 - cosine(embedding, mean_embedding) ↪
↪ for embedding in embeddings] #2
most_similar_index = np.argmax(similarities) #3
from promptflow import log_metric
log_metric(key="highest_ranked_output", value=texts[most_similar_index]) #4
return texts[most_similar_index] #5
#1 计算所有嵌入的平均值
#2 计算每对嵌入的余弦相似度
#3 找到最相似答案的索引
#4 将输出记录为指标
#5 返回最相似答案的文本
我们还需要以批量模式运行评估流程。在 VS Code 中打开self-consistency-evaluation/flow.dag.yaml,以批量模式(水壶图标)运行流程。然后,选择现有运行作为流程输入,当提示时,选择你刚刚执行的顶部或最后一个运行作为输入。
再次,在流程完成处理之后,你会看到类似于列表 10.17 中所示的结果。在输出文件夹链接上 Ctrl 点击以打开一个新的 VS Code 实例,显示结果。在 VS Code 中定位并打开如图 10.9 所示的metric.json文件。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-9.png
图 10.9 VS Code 打开到批量运行输出文件夹。突出显示的是metrics.json文件和显示最相似答案的输出。
图 10.9 中显示的答案对于这次运行仍然是不正确的。你可以继续进行几个更多的提示批量运行,或者增加批量中的运行次数,然后评估流程以查看你是否能得到更好的答案。这种技术通常对更直接的问题更有帮助,但仍然展示了无法推理出复杂问题的能力。
自洽性使用反思方法来评估最可能的思想。然而,最可能的事情并不总是最好的。因此,在下一节中,我们必须考虑一个更全面的方法。
10.3.2 评估思维树提示
如前所述,ToT 提示,如图 10.6 所示,结合了自我评估和提示链技术。因此,它将规划序列分解为一系列提示,但在链的每个步骤中,它都提供了多次评估。这创建了一个可以在每个级别执行和评估的树,可以是广度优先,也可以是自顶向下的深度优先。
图 10.10 显示了使用广度优先或深度优先执行树之间的差异。遗憾的是,由于提示流的 DAG 执行模式,我们无法快速实现深度优先方法,但广度优先工作得很好。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-10.png
图 10.10 ToT 模式中的广度优先与深度优先执行
在 VS Code 中打开tree-of-thought-evaluation/flow.dag.yaml。流程的可视化如图 10.11 所示。这个流程像一个广度优先的 ToT 模式——流程将一系列提示链在一起,要求 LLM 在每一步返回多个计划。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/10-11.png
图 10.11 表达的 ToT 模式和提示流程
由于流程以广度优先的方式执行,每个节点的每个级别输出也会被评估。流程中的每个节点都使用一对语义函数——一个用于生成答案,另一个用于评估答案。语义函数是一个自定义的 Python 流程块,它处理多个输入并生成多个输出。
列表 10.19 展示了semantic_function.py工具。这个通用工具在本流程的多个块中被重复使用。它还展示了从 SK 直接用于提示流程的嵌入功能。
列表 10.19 semantic_function.py
@tool
def my_python_tool(
input: str,
input_node: int,
history: str,
semantic_function: str,
evaluation_function: str,
function_name: str,
skill_name: str,
max_tokens: int,
temperature: float,
deployment_name: str,
connection: Union[OpenAIConnection,
AzureOpenAIConnection], #1
) -> str:
if input is None or input == "": #2
return ""
kernel = sk.Kernel(log=sk.NullLogger())
# code for setting up the kernel and LLM connection omitted
function = kernel.create_semantic_function(
semantic_function,
function_name=function_name,
skill_name=skill_name,
max_tokens=max_tokens,
temperature=temperature,
top_p=0.5) #3
evaluation = kernel.create_semantic_function(
evaluation_function,
function_name="Evaluation",
skill_name=skill_name,
max_tokens=max_tokens,
temperature=temperature,
top_p=0.5) #4
async def main():
query = f"{history}\n{input}"
try:
eval = int((await evaluation.invoke_async(query)).result)
if eval > 25: #5
return await function.invoke_async(query) #6
except Exception as e:
raise Exception("Evaluation failed", e)
try:
result = asyncio.run(main()).result
return result
except Exception as e:
print(e)
return ""
#1 使用并集允许不同类型的 LLM 连接
#2 检查输入是否为空或 None;如果是,则不应执行该函数。
#3 设置生成函数,创建计划
#4 设置评估函数
#5 运行评估函数并确定输入是否足够好以继续
#6 如果评估分数足够高,则生成下一步
语义函数工具用于树中的专家、节点和答案块。在每一步,该函数确定是否有文本输入。如果没有文本,则块返回而不执行。向块传递无文本意味着上一个块评估失败。通过在每一步之前评估,ToT 会短路其认为无效的计划执行。
这可能是一个一开始难以掌握的复杂模式,所以请继续在 VS Code 中运行该流程。列表 10.20 仅显示了运行中的答案节点输出;这些结果可能与您看到的不同,但应该是相似的。没有返回文本的节点要么评估失败,要么其父节点无效。
列表 10.20 tree-of-thought-evaluation 流程的输出
{
"answer_1_1": "", #1
"answer_1_2": "",
"answer_1_3": "",
"answer_2_1": "Alex spends a total of 29 days in the past before he
sees the end of the battle.",
"answer_2_2": "", #2
"answer_2_3": "Alex spends a total of 29 days in the past before he
sees the end of the battle.",
"answer_3_1": "", #3
"answer_3_2": "Alex spends a total of 29 days in the past before he
sees the end of the battle.",
"answer_3_3": "Alex spends a total of 9 days in the past before he
sees the end of the battle.",
#1 表示第一个节点的计划无效且未执行
#2 节点 2 的计划和答案 2 评估失败,未运行。
#3 此节点的计划评估失败,未运行。
列表 10.20 中的输出显示了仅评估了一组选定的节点。在大多数情况下,评估的节点返回了一个可能有效的答案。没有输出产生,意味着该节点本身或其父节点无效。当所有兄弟节点都返回空值时,父节点评估失败。
如我们所见,ToT 对复杂问题有效,但可能不太实用。此流程的执行可能需要多达 27 次调用 LLM 生成输出。在实践中,可能只需调用一半那么多,但那仍然是回答一个问题的十几次调用。
10.4 练习
使用以下练习来提高您对材料的了解:
- 练习 1—创建直接提示、少量样本提示和零样本提示
目标—为 LLM 创建三个不同的提示,以总结最近的一篇科学文章:一个使用直接提示,一个使用少量样本提示,最后一个使用零样本提示。
任务:
-
-
比较每种方法生成的摘要的有效性。
-
比较每种方法生成的摘要的准确性。
-
-
练习 2—制作推理提示
目标—设计一组提示,要求 LLM 解决逻辑谜题或谜语。
任务:
-
-
关注您的提示结构如何影响 LLM 的推理过程。
-
关注相同的方法如何影响其答案的正确性。
-
-
练习 3—评估提示技术
目标—开发一个评估提示,要求 LLM 预测一个假设实验的结果。
任务:
-
- 创建一个后续提示,以评估 LLM 的预测准确性,并提供对其推理过程的反馈。
摘要
-
直接解决方案提示是使用提示引导 LLM 解决特定问题或任务的基础方法,强调清晰的问题和答案结构的重要性。
-
少样本提示为 LLM 提供少量示例,以指导他们处理新或未见过的内容,突出了其使模型能够适应未知模式的能力。
-
零样本学习和提示展示了 LLM 如何从其训练中泛化以解决问题,而无需显式示例,展示了它们在理解并应用新情境中的知识的能力。
-
思维链提示引导 LLM 逐步通过推理过程解决复杂问题,说明了如何从模型中提取详细推理。
-
提示链将问题分解为一系列相互构建的提示,展示了如何将复杂的问题解决过程结构化为 LLM 可管理的步骤。
-
自洽性是一种提示技术,它为问题生成多个解决方案,并通过评估选择最一致的答案,强调了在实现可靠结果中一致性的重要性。
-
思维树提示结合自我评估和提示链,为解决复杂问题创建了一种全面的策略,允许系统地探索多个解决方案路径。
-
高级提示工程策略提供了对诸如与 CoT 和 ToT 的自洽性等复杂技术的见解,提供了提高 LLM 生成解决方案的准确性和可靠性的方法。
更多推荐


所有评论(0)