DLAI-DSPy-笔记-全-
DLAI DSPy 笔记(全)
001:引言 🚀

在本课程中,我们将学习如何使用DSPy框架来构建和优化基于大型语言模型的智能应用。DSPy旨在简化提示工程流程,通过自动化优化来提升应用性能,减少手动调试的工作量。
构建复杂的AI应用时,面临的主要挑战之一是为语言模型编写高质量的提示。开发者通常需要尝试数十个提示,反复调整措辞和格式,以期获得更好的结果。这个过程非常耗时,并且当底层模型更新时,精心设计的提示也常常会失效。
DSPy简化并自动化了这一传统流程。你只需定义模型所需的输入和期望的输出,并提供一个包含输入和理想输出的数据集。DSPy随后可以自动优化你的AI程序,以更少的手动工作获得更佳的性能。

我们很高兴地介绍本课程的讲师Zn Tian,他是Databricks的软件工程师,也是DSPy的核心开发者之一。
核心构建模块:签名与模块
在DSPy中构建应用组件主要涉及两个核心概念:签名和模块。
当你尝试构建应用的一个组件时,指定一个签名可以告诉系统该组件期望的输入和输出是什么。例如,一个情感分析程序的签名可以定义为:输入一个字符串,输出一个代表情感倾向的整数。
模块则利用这些签名来实际调用语言模型并获取结果。有时应用可能无法正常工作,但我们不确定原因。在本课程中,你还将学习使用流程追踪功能,它能帮助你清晰地看到应用每一步的执行情况:使用了什么数据、调用了哪些工具、模型返回了什么结果,以及问题出在哪里。只需添加一层代码,你就可以为你的应用启用这些追踪功能。
优化智能代理工作流
DSPy的一个强大应用是优化智能代理的工作流。如果你有一个复杂的工作流,它接收一个输入,经过多步处理(可能涉及多次调用模型或多个工具)来生成输出,那么DSPy的优化器可以派上用场。
优化器会接收你的代理程序、一个评估数据集和一个评估指标,并基于这些信息,自动为所有步骤搜索更好的提示。有时,它能根据你的数据生成非常高质量的提示,其效果远超人类手动调整所能达到的水平。
在本课程中,你将使用DSPy的优化器来优化一个基于维基百科问答的RAG应用中的提示。

课程致谢与展望
许多人为本课程的筹备做出了贡献。在此,我们要感谢Omar、Kat、Kahi、Yin、Trister、Obsalon、Tobo、Hirata、Ash、Gagari和Brendan Brown。
关于DSPy,一个令人惊讶的特点是它所需的代码量非常少,并且它本质上自动化了提示工程的过程。请继续观看下一个视频,了解这是如何实现的。


总结:本节课我们一起学习了DSPy课程的目标与核心价值。我们了解到DSPy通过引入签名和模块这两个核心构建块,以及强大的自动优化与流程追踪功能,旨在解决传统提示工程耗时、脆弱的问题,让开发者能更高效地构建和优化复杂的AI应用。
002:DSPy框架介绍 🚀
在本节课中,我们将学习什么是DSPy,更重要的是,了解DSPy的特殊之处以及为何众多开发者选择使用它来构建智能应用。
概述
DSPy是一个用于构建和优化智能代理应用的框架。本节将介绍DSPy的核心概念、它旨在解决的问题,以及其区别于其他框架的关键特性。
什么是DSPy?
从高层次看,DSPy是一个智能应用创作框架,它简化了基于语言模型的应用程序开发。你可以用它来构建检索增强生成系统或智能代理。
一个很自然的问题是:我们是否在重复造轮子?市场上已有许多类似选项。答案是否定的。接下来,我们将一起探讨DSPy的独特之处。
当前面临的挑战
在深入了解DSPy之前,我们先看看当前开发者面临的问题。自2022年底以来,随着生成式模型的成功,复合AI系统开始兴起。
复合AI系统是由多个模块组成的系统。每个模块处理一个子任务,这些子任务可能涉及调用语言模型,也可能只是常规的函数调用。将这些模块组合起来,就形成了一个能够处理复杂任务(如图中所示的检索增强生成或智能体)的强大AI系统。

在构建复合AI系统时,我们普遍面临来自提示工程的挑战。

我们进行提示工程是因为我们知道,更好的提示能从语言模型中获得更好的结果。但提示工程可能非常混乱,因为我们是在调整字符串,并且并不真正知道哪些改变在实践中能产生实际效果。这通常意味着我们需要迭代50、100甚至更多个提示,而每个提示可能非常长,达到数万字。
更糟糕的是,如果我们更换模型,就需要重新开始,因为提示工程严重依赖于特定的语言模型。总而言之,提示工程既脆弱又耗时。
另一个问题来自框架本身。框架非常强大,因为它们简化和标准化了构建智能体或RAG等应用的经验。

但越来越多的抱怨指出,用户不喜欢某些框架,因为它们带来的麻烦多于价值。



一个非常实际的担忧是,用户被迫学习框架的特定约定,这有时意味着不必要的开销,而不是让用户专注于业务逻辑。此外,如果决定切换到另一个框架,将现有代码迁移出来也非常困难。

DSPy的解决方案

为了解决上述两个问题,DSPy应运而生。现在,让我们更详细地回答这个问题:什么是DSPy?
DSPy是一个灵活、轻量级的框架,它简化了与语言模型的交互,并通过DSPy优化器提供自动程序优化,包括提示优化和语言模型权重优化。
DSPy还提供无缝的产品化支持,例如流式处理、异步操作等。DSPy为你提供构建AI应用的基础模块,但不会限制你构建应用的方式。无论是迁移到DSPy还是从DSPy迁移出去,都同样容易。
DSPy内部有三个特别之处。以下是详细介绍:
1. 模型无关编程 vs. 模型偏向提示


本质上,DSPy不是进行提示工程,而是通过定义DSPy上下文中的输入字段和输出字段来与语言模型交互。你可以将语言模型和提示视为一个精心设计的REST API,但与传统API不同的是,输入和输出是在客户端定义的。如果现在还不完全清楚,请不要担心,我们将在下一课中详细讲解。
2. 通过原生功能与MLflow实现无缝产品化
DSPy通过其原生功能(如缓存)以及与MLflow的紧密集成,实现无缝产品化。MLflow是一个ML和AI运维工具,可简化AI应用程序的端到端开发。例如,它通过MLflow Tracing帮助调试AI程序,通过MLflow Experiments跟踪开发,并通过MLflow Deployments部署应用。我们将在第3课中了解更多关于它的内容。
3. 自动程序优化
在第4课中,你将学习自动程序优化。当你使用DSPy构建好AI应用程序后,可以创建一个DSPy优化器并将其应用于你的程序,从而自动获得质量提升。
行业信任
DSPy受到行业的信任。我们与许多企业用户成功集成。请在DSPy.ai社区查看更多使用案例。
总结
本节课我们一起学习了DSPy框架的基本介绍。我们了解了当前AI应用开发中提示工程和框架锁定带来的挑战,并探讨了DSPy如何通过其模型无关编程、无缝产品化支持和自动优化功能来解决这些问题。
在下一课中,你将学习如何在DSPy中构建一个AI应用程序。我们下节课见。




003:签名与模块 🧩


在本节课中,你将学习如何使用DSPy构建AI应用。我们将深入代码,通过一个实验来掌握DSPy的两个核心抽象:签名和模块。签名定义了AI任务的输入输出格式,而模块则是利用签名与语言模型交互的执行单元。
签名:定义输入输出合约 📝
在DSPy中,与语言模型的交互类似于调用一个具有明确定义输入输出格式的RESTful API,但这个格式定义发生在客户端。这个定义就是通过DSPy签名来完成的。
签名定义了输入和输出字段,以及它们的类型和注释。定义签名主要有两种方式。
基于类的签名

这是推荐的方式。你需要从dspy.Signature类继承,并使用dspy.InputField和spy.OutputField来标记输入输出字段。
import dspy
class SentimentSignature(dspy.Signature):
"""分析给定文本的情感倾向。"""
text: str = dspy.InputField(desc="待分析的文本")
sentiment: int = dspy.OutputField(desc="情感分数,范围0-10", gt=0, lt=10)
一个基于类的签名包含五个重要部分:
- 文档字符串:签名指令,用几句话概述任务目的。
- 字段名:用于传递输入数据和访问输出数据。
- 字段类型:标记是输入字段还是输出字段。
- 描述:当字段名不能自解释时,提供关于字段的实际信息。
- 类型信息:可以是基本类型或任何自定义类,对于输出字段尤其有用,可以确保返回值的类型。
基于字符串的签名

这是一种更轻量的方式,适合快速原型设计。
signature = "text -> sentiment"
你只需在箭头前写输入字段,在箭头后写输出字段,用逗号分隔。虽然简洁,但为了获得更好的灵活性和功能支持,我们通常推荐使用基于类的签名。
模块:与语言模型交互的执行单元 ⚙️
上一节我们介绍了如何静态地定义任务格式,本节我们来看看如何利用签名来与语言模型对话,这就是DSPy模块的用途。
模块是DSP程序的最小构建块,通常都附带着一个签名。最简单的模块是dspy.Predict,它根据附带的签名将用户查询格式化为提示词,并解析语言模型的响应。
模块除了签名外,还有可配置的属性,例如demos属性用于携带示例。DSPy模块可以自定义以实现复杂逻辑,并且一个模块可以由多个子模块组成。

DSPy提供了一系列构建模块,让用户能轻松上手。以下是几个重要的内置模块:
dspy.Predict:最基本的模块,执行语言模型交互,是所有复杂模块的构建基础。dspy.ChainOfThought:要求模型在给出答案前进行推理。dspy.ReAct:代表“推理与行动”,是构建AI智能体的常用范式,我们将在下一课详细介绍。dspy.ProgramOfThought:类似于ReAct,但工具调用是通过代码完成的。dspy.Retry:用户可以设置奖励函数和阈值,如果未达到阈值则重试。
使用内置模块
要使用内置模块,只需将签名传递给模块构造函数,然后通过设置关键字参数来调用它。

# 创建一个带有签名的问题生成器模块
qa_module = dspy.ChainOfThought("question -> answer")
# 调用模块
result = qa_module(question="天空是什么颜色的?")
print(result.answer) # 访问输出字段
你可以在DSPy文档站点找到完整的构建模块列表。
创建自定义模块
更常见的情况是,你需要的复杂逻辑无法完全由预构建模块覆盖。这时,你需要编写自定义模块。
这类似于PyTorch,你需要从dspy.Module类继承,并在forward方法中实现你的自定义逻辑。
class CustomGameModule(dspy.Module):
def __init__(self):
super().__init__()
self.question_gen = dspy.ChainOfThought("past_questions, past_answers -> new_question, guess_made")
def forward(self, celebrity_name):
# 在这里实现你的游戏逻辑
# 可以调用子模块、其他Python函数、任何框架或工具
questions = []
answers = []
for _ in range(20):
result = self.question_gen(past_questions=questions, past_answers=answers)
# ... 处理结果,询问用户,更新列表 ...
return final_result
这种方式非常灵活。你可以在forward方法中调用任何Python函数、框架(如LangChain、LlamaIndex)或工具(如搜索引擎、文件系统处理器)。
实战:构建情感分析器与猜名人游戏 🎮
现在,让我们通过编码来更好地理解签名和模块系统是如何工作的。我们将构建一个简单的情感分析器,然后是一个更复杂的“猜名人”游戏。
第一步:设置语言模型
DSPy编程的第一步是选择你的语言模型。在本实验中,我们使用gpt-4o-mini。
import dspy
# 配置语言模型
lm = dspy.OpenAI(model='gpt-4o-mini')
dspy.configure(lm=lm)
第二步:构建情感分类器
我们使用之前定义的SentimentSignature,并创建一个dspy.Predict模块。
# 定义签名
class SentimentSignature(dspy.Signature):
"""分析给定文本的情感倾向。"""
text: str = dspy.InputField(desc="待分析的文本")
sentiment: int = dspy.OutputField(desc="情感分数,范围0-10", gt=0, lt=10)

# 创建模块
classifier = dspy.Predict(SentimentSignature)

# 调用模块
result = classifier(text="这部电影真是太精彩了!")
print(result.sentiment) # 输出情感分数

result是一个dspy.Prediction对象,类似于字典,但支持关键字和点号两种访问方式。
我们可以查看语言模型的交互历史来理解幕后发生了什么:
print(lm.inspect_history(n=1))

输出会展示系统消息(包含签名、字段格式)、用户消息(格式化后的输入)和助手的响应(格式化后的输出)。DSPy将签名、模块信息和实际输入组合成一个多轮对话提示词,并根据签名解析语言模型的响应,使得语言模型就像一个具有明确定义输入输出的API。
第三步:构建“猜名人”游戏
现在,让我们构建一个更复杂的自定义模块来实现“猜名人”游戏。游戏规则是:玩家一想一个名人名字,玩家二(语言模型)开始问是/否问题,直到猜出名字或用完所有问题机会(设为20次)。
class CelebrityGame(dspy.Module):
def __init__(self):
super().__init__()
# 子模块1:问题生成器
self.question_gen = dspy.ChainOfThought("past_questions, past_answers -> new_question, guess_made")
# 子模块2:游戏后反思
self.reflector = dspy.Predict("correct_name, final_guess, qa_log -> reflection")
def forward(self):
celebrity_name = input("请想一个名人名字并输入: ")
questions = []
answers = []
for i in range(20):
# 生成新问题
gen_result = self.question_gen(past_questions=questions, past_answers=answers)
new_q = gen_result.new_question
guess_flag = gen_result.guess_made
print(f"问题 {i+1}: {new_q}")
answer = input("你的回答 (是/否): ").strip().lower()
answers.append(answer)
questions.append(new_q)
# 检查是否直接猜出了名字
if "yes" in guess_flag.lower():
print(f"模型猜测是: {guess_flag}")
if input("猜对了吗? (是/否): ").strip().lower() == '是':
print("游戏结束!模型猜对了。")
break
# 游戏结束后进行反思
final_guess = guess_flag if i < 19 else "未猜出"
reflection = self.reflector(correct_name=celebrity_name, final_guess=final_guess, qa_log=list(zip(questions, answers)))
print("\n=== 游戏反思 ===")
print(reflection.reflection)
return {"correct_name": celebrity_name, "guessed": final_guess}
# 运行游戏
game = CelebrityGame()
game()
这个例子展示了DSPy模块的灵活性。你可以在forward方法中编写任何Python逻辑,并且签名系统使得与语言模型的交互变得简单可靠,无需手动解析输出字段。
模块的保存与加载 💾
DSPy提供了两种保存和加载模块的方式。
1. 仅保存状态
只保存模块的内部状态(如参数、演示示例)。
# 保存
game.save("game_state.json", save_program=False)
# 加载(需要先重新创建实例)
loaded_game = CelebrityGame()
loaded_game.load("game_state.json")
2. 保存整个程序
保存整个程序,包括其结构和依赖。
# 保存
game.save("my_game_program")
# 加载
loaded_program = dspy.load("my_game_program")
# 可以直接调用
loaded_program()
总结 📚
在本节课中,我们一起学习了DSPy编程的核心概念。我们首先了解了签名,它像一份合约,明确定义了AI任务的输入和输出格式。接着,我们探索了模块,它是利用签名与语言模型交互、并可以包含自定义逻辑的执行单元。我们通过构建情感分析器和“猜名人”游戏,实践了如何使用内置模块和创建自定义模块。最后,我们还了解了如何保存和加载DSPy模块。

DSPy通过将提示词工程抽象为可组合、可优化的模块,让构建复杂的AI应用变得更加简单和可靠。在下一节课中,你将学习如何使用Ammoflow追踪来调试你的DSPy程序。
004:使用MLflow追踪调试你的DSPy智能代理 🐛


在本节课中,我们将学习如何使用MLflow追踪功能来帮助调试DSPy程序。我们将构建一个航空客户服务代理,并通过MLflow清晰地观察其内部复杂的多步推理过程。
概述:什么是追踪及其必要性
上一节我们介绍了DSPy的基础模块。本节中,我们来看看如何调试这些模块。
追踪意味着记录AI程序中内部函数的输入和输出,并捕获一个层次化的调用栈,例如模块A调用模块B。
AI应用内部可能非常复杂,但通常只暴露最终输出。因此,当出现问题时,很难追溯到根本原因。例如,如果你构建了一个由五个子模块组成的DSPy程序,其中一个语言模型调用因无法理解提示词而失败,即使DSPy提供了检查历史记录的API,追溯问题根源仍然困难。
追踪为可解释性和调试提供了一种简便的方法。
什么是MLflow
MLflow是一个开源的AI运维包,可以简化你的AI应用开发流程。MLflow有助于构建AI应用的全生命周期管理,确保每个阶段都是可追踪和可复现的。


MLflow服务器和客户端都是完全开源的,你可以轻松设置以开始追踪DSPy程序。使用MLflow追踪DSPy程序,你只需要在程序中添加一行代码:mlflow.dspy.autolog() 或简写为 mlflow.autolog()。之后,你的程序将被自动追踪,并且追踪记录将保存在MLflow服务器中,你可以在生成后随时访问。
MLflow追踪的内容
以下是MLflow在DSPy程序中追踪的四个核心部分:
- 追踪每个模块调用:无论是顶层模块还是内部模块。
- 追踪所有对适配器的调用:你可以看到适配器如何格式化用户查询并解析语言模型的响应。
- 追踪对语言模型的调用:你可以检查实际的提示词和语言模型的响应。
- 追踪DSPy工具调用:这是对DSPy工具调用的封装,同样会被MLflow追踪。
我们不仅可以解释DSPy程序每个子模块的输入和输出,还可以看到这些模块的层次结构以及其他重要信息,如耗时。当出现问题时,会有一个“X”标记指出有问题的模块。
幻灯片中的截图是一个真实ReAct模块的追踪记录。让我们将其映射到我们追踪的组件:
react是我们的顶层模块,它调用了一些表示为predict的子模块。我们可以看到react和predict都被追踪了。- 在
predict内部,chat_adapter.format_and_parse承载了适配器的追踪。 - 中间的
LM traces包含了来自语言模型的实际提示词和响应。 - 在底部,我们还可以看到工具调用的追踪,在本例中是调用文件。
在上一课中,我们看到了如何使用 dspy.inspect_history API 来查看实际提示词。现在有了MLflow追踪,这变得更加容易。只需点击LM追踪记录,你就可以看到实际的提示词和LM响应。
开始编码:构建航空客户服务代理
让我们开始编码。与之前的实验类似,我们需要设置API密钥,此外还需要设置MLflow环境。
import mlflow
# 设置MLflow跟踪UI。在本实验中,我们已经为你设置了MLflow跟踪服务器。
# 现在,让我们给实验一个唯一的标识符。
mlflow.set_experiment("DSPy_Lesson_4")
# 正如幻灯片中提到的,我们可以用一行代码开启追踪功能。
mlflow.dspy.autolog()
# 然后,我们可以像之前的实验一样选择我们的语言模型。
现在,让我们开始使用DSPy的ReAct构建一个航空客户服务代理,并使用MLflow追踪来辅助这个过程。
这个代理将能够接收用户请求并为用户预订航班,也可以为用户修改行程。
我们首先需要定义数据。在实际生产中,会有一个数据库模式。这里我们有用户档案、航班信息、行程信息和客户支持工单。现在我们有了领域数据和数据格式。
让我们定义几个工具(模块):
fetch_flight_information:根据日期、出发地、目的地获取航班信息。fetch_itinerary:获取行程。pick_flight:从几个候选航班中挑选一个航班。book_itinerary:代表用户预订行程。cancel_itinerary:取消行程。get_user_info:获取用户信息。file_customer_ticket:如果我们无法自动解决问题,则提交客户工单。
要使用DSPy上下文定义工具或函数,你需要指定一个描述来说明此函数的功能,并为输入参数提供类型提示,以便语言模型可以帮助设置参数。
现在我们有了工具和数据,让我们定义一个签名,以便我们知道这个程序的输入和输出是什么。
输入将是一个字符串,代表用户请求。输出是一个 ProcessResult,它是一个给用户的消息。如果预订成功,它将包含确认号;如果我们无法解决问题,我们将链接到一个工单号。
现在我们有了签名和工具。我们可以将它们组合成一个DSPy的ReAct模块。那么什么是ReAct?ReAct代表推理和行动。基本上,我们给语言模型一个签名(这是程序的目标)以及一个工具列表,语言模型可以决定是否要调用工具来获取实际信息以回答用户问题。如果它不需要实际信息,可以直接回答问题。
现在我们已经构建了一个ReAct实例。让我们调用它。
# 我们可以简单地将用户请求放入。
user_request = "Book a flight from SFO to JFK on 2024-01-01 for John Doe."
# 运行代理
result = react_agent(user_request)
现在我们将得到结果以及MLflow追踪记录。这是MLflow追踪UI。我们可以看到被追踪的内容以及追踪的属性。
让我们看一下追踪的属性。我们捕获了输入和输出以及属性(这些是函数调用时的参数,如温度、最大令牌数和其他可配置属性)。如果存在任何错误,事件选项卡将保存错误消息。
我们可以看到顶层模块 react 被追踪,其输入输出就是我们的最终输入和最终输出。可以看到输入是我们的用户查询,最终输出(表示为输出字段 process_result)显示“已成功预订航班,这是确认号”。
我们可以深入查看某个子模块,了解过程中发生了什么。当我们与语言模型对话时,我们给它一个用户请求并询问下一步行动。下一个行动可能是工具调用,也可能是流程结束。最初,它只会说“我想获取航班信息,因为我现在没有任何信息”,并且它还决定了函数调用的参数,以提供我们想要获取航班的日期。
收到这个后,我们继续调用工具 fetch_flight_information,其输入参数由语言模型决定:日期、出发地、目的地。然后我们获取匹配请求的航班列表,得到两个候选航班。
然后我们收集所有信息(如两个候选航班以及工具调用信息),将它们发送回语言模型以决定下一个工具调用,或者我们可以结束流程。
我们在轨迹字段中表示所有内容:我们有上次工具调用的参数和结果。语言模型说:“好的,我有一堆航班,我想挑选最好的航班。”参数是航班候选列表。我们调用 pick_flight 工具。
我们为这个 pick_flight 工具编写了相当精细的逻辑:我们总是选择最短的航班;如果时长相同,我们选择最便宜的。
这里我们获取航班信息作为输入,然后从中挑选一个航班。我们收集所有信息再次调用语言模型。这次语言模型看到:“好的,我们有航班了,我可以获取用户信息,以便代表用户预订航班。”我们获取用户信息,并将所有信息再次传递给语言模型。
下一个想法是:“我拥有所有信息,我可以直接预订航班了。”然后我们调用 book_flight 工具,将其写入数据库并记录。这次语言模型说:“好的,这似乎完成了,所以我们可以调用 finish(一个标记ReAct工具调用结束的虚拟工具)。”
在所有过程之后,我们仍然需要找到一种方法来生成 process_result(输出字段)。所以我们只需调用一个链式模块,给它所有的工具调用历史记录和用户请求,以形成最终输出。然后,之后我们结束流程。
通过MLflow追踪,我们可以清晰地解释ReAct模块内部发生的非常复杂的多跳调用过程。如果出现任何问题,我们可以点击特定的模块,查找其输入输出来决定如何调试。
总结与扩展
顺便说一下,MLflow也与LangChain、LlamaIndex和其他框架集成。你也可以在其他框架中获得自动追踪功能。
在实验中,我们为你设置了一个MLflow服务器。在实际开发中,你需要自己完成。如果你没有时间设置自己的MLflow服务器,或者想探索更多功能,Databricks提供了托管的MLflow服务,你可以直接连接它来开始使用。你可以通过Databricks Lighthouse尝试,它提供免费试用。试试看,在 databricks.com 注册。

在本节课中,你使用了MLflow追踪来构建一个复杂的航空客户服务ReAct模块。在下一课中,你将学习如何使用DSPy优化器自动优化程序质量。我们下节课见。
005:使用DSPy优化器优化代理


在本节课中,你将学习如何使用DSPy优化器自动提升你的DSP程序质量。

本节课附带一个实验。在实验中,你将亲身体验使用DSPy优化器自动提升一个基于维基百科数据源的问答代理的质量。

优化后,你将看到你的代理质量得到显著提升。

什么是优化?
在讨论DSPy优化器之前,我们先思考一下,当我们谈论语言模型应用时,“优化”意味着什么。
优化通常可以指三件事:
- 优化提示模板。
- 构建高质量的少样本示例。
- 微调语言模型的权重。
前两部分都属于提示优化或提示工程的范畴。在DSPy中,我们支持以上所有三种优化方式。
DSPy优化器概览

现在,让我们概览一下如何使用DSPy优化器。
首先,你需要选择一个优化器。关于如何选择优化器,请参考我们的官方文档网站 dspy.ai。
在本节课和实验中,我们将使用 MIPROVE 优化器。这是一个适用于提示模板优化和构建少样本示例的优秀优化器。

选择优化器后,你需要告诉优化器评估指标函数,以及训练和验证数据集。其核心思想是让优化器知道什么是好的程序,什么是不好的程序。
与常规的机器学习任务不同,这个数据集可以小到只有20条记录。如果你没有验证数据集,优化器会自动划分一部分数据作为验证集。

优化器如何工作?
本节课我们专注于提示工程,并以MIPROVE优化器为例进行演示。关于如何使用DSPy优化器进行微调以及其他优化器的工作原理,请阅读 dspy.ai 上的教程文档。
从高层次来看,优化过程如下:
- 首先,构建多组少样本示例。
- 基于这些少样本示例和你的程序信息,我们让语言模型生成多个提示模板候选。这些模板对应签名中的指令。
- 我们从少样本示例集和指令集中采样,组合成候选程序。
- 在验证数据集上运行候选程序并进行评估。评估基于用户定义的指标函数,将程序输出与标准答案进行比较。
- 候选程序的最终得分是所有验证数据得分的平均值。
- 在整个过程中,我们会持续选择得分最高的候选组合。
重要的是,在优化器中,我们既不进行穷举搜索,也不尝试所有组合。相反,我们使用一种称为贝叶斯采样的统计方法,智能地向最优组合方向采样。
生成少样本示例和指令候选
上一节我们介绍了优化流程,本节我们来看看如何生成少样本示例和指令候选。
少样本示例是通过一个称为 自举 的过程生成的。这个过程很简单:
- 我们从训练数据集中获取数据,并将其输入到一个DSP程序中。
- 如果根据指标函数,该程序在用户设定的阈值之上获得了分数,我们就捕获每个模块的输入和输出轨迹。
- 这些轨迹将成为该模块的少样本示例候选。
请注意,一条数据可能生成多个候选,因为我们通过设置非零的 temperature 参数为每次调用添加了随机性。
现在,我们来谈谈如何构建指令候选:
- 我们获取程序代码和描述,连同少样本示例以及一些通用提示(如“要全面”或“要简洁”)。
- 通过一个称为 DSPy提议 的机制,将所有内容发送给语言模型,生成一批指令候选。
现在我们有了指令(即提示模板)和少样本示例的候选,就可以通过选取一个少样本示例候选和一个指令候选来组合生成候选程序。然后我们可以评估候选程序,并根据用户指定的试验次数持续采样。
优化效果与过程追踪
我们已经对MIPROVE优化器的性能进行了全面的实验。我们看到,在多项任务中,MIPROVE的表现都大幅超越了原始提示。更多细节请查阅论文《Optimizing Instructions and Demonstrations for Multi-Stage Language Model Programs》。
我们还可以利用 MLflow 来解读优化过程。只需设置几个标志来开启 mlflow.dspy.autolog,优化过程就会被追踪。当我们尝试候选程序时,会对其进行评估,所有这些评估结果以及候选程序的信息(如指令是什么、少样本示例是什么)都将保存到MLflow中,从而完整记录整个过程中尝试过的所有内容。
动手实践:优化问答代理


现在,让我们通过编码来看看优化器如何工作。


和之前的实验一样,我们首先设置语言模型API。我们还需要设置一个MLflow追踪服务器,并给它一个唯一的标识符,我们称之为 dspy-course-2。
我们可以设置自动日志记录,并开启比之前更多的标志,以便追踪优化过程。我们继续使用 gpt-4 作为语言模型。
这个问答代理将基于维基百科数据。我们使用 ReAct 模块,但不固定检索调用次数,而是使用 ReAct 的生成模式,让语言模型决定在得到最终答案前是否需要从数据源获取更多数据。
首先定义我们的工具,即维基百科搜索。我们使用公开可用的 ColBERTv2 接口来获取维基百科数据,返回值将是维基百科数据源的文本块。
我们仍然使用 dspy.ReAct 作为我们的程序。这次输入输出非常简单,只有问题和答案。所以我们使用一个基于字符串的签名,并且只有一个工具:搜索维基百科。
现在,回顾一下我们需要为优化器准备什么:数据和评估指标。我们需要训练数据集和验证数据集。我们为你准备了一个数据集,它是 HotpotQA 数据集的一个子集,这是一个基于维基百科的问答数据集。
加载数据后,让我们看看数据是什么样子。它非常简单,一个问题对应一个答案。一个输入案例就是问题。
是时候创建我们的优化器了。我们将使用MIPROVE优化器。如前所述,我们需要准备一个指标函数,以便优化器知道如何评估我们的候选程序。对于这个任务,我们只使用答案完全匹配作为指标。
配置优化器时,我们建议你使用自动模式,它有 light、medium 和 heavy 三种模式,这些模式经过精心调校以获得良好性能。但如果你想自定义MIPROVE优化器,可以在文档网站的搜索框中搜索 DSPy 或 MIPROVE 来找到所有可用的配置选项。
优化过程可能需要相当长的时间。为了加速这个过程,我们为你记录了缓存,因此优化将命中缓存而变得快得多。在实际生产中,你不需要记录任何缓存,只需进行语言模型调用。
现在,让我们启动优化过程。我们使用优化器的 compile 函数,将程序、训练集和验证集传递给它。
优化完成后,我们可以查看日志。基本上,我们持续获取候选程序并对其进行评估,最终从整个过程中选出最佳程序。






让我们看看在这个过程中发生了什么变化。如果你还记得,原始的签名只是一个非常简单的从问题到答案的映射,ReAct 模块没有任何指令。但在优化过程之后,ReAct 子模块将填充一个非常全面的指令,并且还内置了一些少样本示例。




这些示例以列表的形式表示在 demos 属性中。
让我们进一步评估未经优化的 ReAct 问答应用。我们得到了31分,可以在表格中看到一些输入输出示例。
现在,让我们评估优化后的 ReAct 并获取分数。我们得到了54分。可以看到,无需任何人工干预,仅仅通过使用优化器,我们的分数就从31分提升到了54分。这就是DSPy优化器的威力。
正如幻灯片中提到的,我们使用MLflow追踪了优化过程,你可以在MLflow UI中查看。在UI中,优化运行显示为一行,每个子项对应一个候选程序的评估。点击进入运行,你可以看到候选程序的属性,如少样本示例、指令以及其他属性,以及候选程序的评估分数。这样你就完整记录了优化过程的全部记录。

总结

在本节课中,你学习了如何使用DSPy优化器来优化你的DSP程序。我们通过优化基于维基百科数据的问答代理,看到了DSPy优化器的强大能力。
006:总结 🎯

在本课程中,我们学习了DSPy框架的核心概念与应用方法。DSPy是一个轻量级、灵活的框架,旨在简化与大型语言模型的交互以及智能代理应用的开发。它通过自动程序优化和原生MLflow追踪集成,极大地提升了开发效率。
框架概述 🧩
DSPy是一个轻量级且灵活的框架,用于简化与大型语言模型的交互和智能代理应用的开发。它提供了通过DSP优化器实现的自动程序优化功能,并集成了原生的MLflow追踪功能,便于开发过程中的调试与监控。
核心工作流程 🔄
上一节我们介绍了框架的整体概念,本节中我们来看看构建和优化一个DSPy程序的具体步骤。
以下是构建一个DSPy应用的基本步骤:
-
定义签名:使用
dspy.Signature来明确指定程序的输入和输出。class QA(dspy.Signature): """回答基于上下文的问题。""" context = dspy.InputField(desc="相关背景信息") question = dspy.InputField(desc="用户提出的问题") answer = dspy.OutputField(desc="基于上下文的答案") -
创建模块:使用
dspy.Module来封装自定义的逻辑链。class RAG(dspy.Module): def __init__(self): super().__init__() self.generate_answer = dspy.ChainOfThought(QA) def forward(self, question, context): return self.generate_answer(context=context, question=question) -
准备数据与评估:构建一个小型数据集并定义一个匹配函数,用于评估程序输出。
trainset = [ ... ] # 你的训练数据示例 def match_func(example, pred): # 定义如何将预测结果与标准答案比较 return example.answer.lower() == pred.answer.lower() -
优化程序:使用DSPy优化器(如
BootstrapFewShot)来提升AI程序的质量。from dspy.teleprompt import BootstrapFewShot teleprompter = BootstrapFewShot(metric=match_func) optimized_program = teleprompter.compile(RAG(), trainset=trainset) -
集成追踪:通过添加
mlflow.autolog()或dspy.MLflowLogger,轻松集成MLflow进行实验追踪。import mlflow mlflow.autolog() # 或使用 dspy.configure(..., logger=MLflowLogger())
课程总结 📝
本节课中,我们一起学习了DSPy框架的核心价值与使用方法。你了解到DSPy如何通过签名定义接口、通过模块组织逻辑、并利用优化器自动提升程序性能。最后,我们还探讨了如何集成MLflow来追踪实验过程。掌握这些步骤后,你就可以开始构建和优化自己的智能代理应用了。期待看到你运用DSPy构建出的成果。


浙公网安备 33010602011771号