在当今的人工智能世界中,构建智能代理已经不再是遥不可及的梦想。无论是自动化的客户服务机器人,还是能够解决复杂问题的搜索助手,这些技术都在逐步改变我们的生活方式。而在这个过程中,DSPy(Declarative Signature Programming for LLMs)作为一个强大的框架,正在重新定义如何高效地构建和优化语言模型程序。
本文将带您深入探索如何使用 DSPy 构建一个智能代理,并优化其在复杂任务中的表现。我们将从基础开始,逐步搭建一个能够进行多跳搜索(multi-hop search)的 ReAct 代理,并通过优化提升其性能。无论您是初学者还是经验丰富的开发者,这篇文章都将为您提供实用的指导和深刻的启发。
🌟 什么是 DSPy?
在进入实践之前,让我们先了解一下 DSPy 的核心理念。DSPy 是一个专为语言模型(LLM)设计的编程框架,它的目标是通过模块化的方式,让开发者能够高效地构建、优化和部署复杂的语言模型程序。与传统的提示工程(prompt engineering)不同,DSPy 提供了一个更系统化的方式来定义任务、优化模型和管理整个工作流。
DSPy 的核心概念包括:
- Signatures(签名):定义输入输出的结构化接口。
- Modules(模块):将任务分解为多个可组合的子任务。
- Optimizers(优化器):通过数据驱动的方法优化提示和模型权重。
- Assertions(断言):为模型输出提供约束和验证机制。
通过这些工具,DSPy 不仅让开发者能够快速构建复杂的程序,还能通过优化器自动提升模型的性能。
🚀 第一步:安装 DSPy 和设置环境
在开始之前,请确保您已经安装了 DSPy。您可以通过以下命令安装最新版本:
pip install -U dspy
此外,我们还推荐使用 MLflow 进行实验跟踪和可视化。MLflow 是一个强大的 LLMOps 工具,可以帮助您记录模型的优化过程并进行深入分析。以下是设置 MLflow 的步骤:
- 安装 MLflow:
pip install mlflow>=2.20
- 启动 MLflow UI:
mlflow ui --port 5000
- 在代码中连接 MLflow:
import mlflow mlflow.set_tracking_uri("http://localhost:5000") mlflow.set_experiment("DSPy")
- 启用自动跟踪:
mlflow.dspy.autolog()
通过这些设置,您可以在 MLflow 的界面中直观地查看每次程序执行的详细信息。
🛠️ 第二步:构建一个简单的 ReAct 代理
在本教程中,我们将使用 DSPy 构建一个 ReAct(Reasoning + Acting)代理。这个代理的任务是处理复杂的多跳搜索问题,即从一个复杂的声明中推导出需要检索的多个维基百科页面。
1. 配置语言模型
我们选择了一个小型的语言模型 Llama-3.2-3B-Instruct
作为主要模型。尽管它的性能不如大型模型,但它的速度快且成本低,非常适合快速迭代。我们还将使用一个更大的模型 GPT-4o
作为教师模型,帮助优化小型模型的表现。
以下是配置代码:
import dspy
llama3b = dspy.LM('<provider>/Llama-3.2-3B-Instruct', temperature=0.7)
gpt4o = dspy.LM('openai/gpt-4o', temperature=0.7)
dspy.configure(lm=llama3b)
2. 加载数据集
我们将使用 HoVer 数据集,它是一个多跳任务数据集,要求模型从复杂的声明中推导出需要检索的维基百科页面。以下是加载数据的代码:
import random
from dspy.datasets import DataLoader
kwargs = dict(fields=("claim", "supporting_facts", "hpqa_id", "num_hops"), input_keys=("claim",))
hover = DataLoader().from_huggingface(dataset_name="hover-nlp/hover", split="train", trust_remote_code=True, **kwargs)
hpqa_ids = set()
hover = [
dspy.Example(claim=x.claim, titles=list(set([y["key"] for y in x.supporting_facts]))).with_inputs("claim")
for x in hover
if x["num_hops"] == 3 and x["hpqa_id"] not in hpqa_ids and not hpqa_ids.add(x["hpqa_id"])
]
random.Random(0).shuffle(hover)
trainset, devset, testset = hover[:100], hover[100:200], hover[650:]
3. 定义搜索工具
为了让代理能够进行搜索,我们需要定义两个工具:search_wikipedia
和 lookup_wikipedia
。前者用于检索维基百科页面,后者用于查看特定页面的内容。
def search_wikipedia(query: str) -> list[str]:
"""返回前 5 个结果以及第 5 到第 30 个结果的标题。"""
topK = search(query, 30)
titles, topK = [f"`{x.split(' | ')[0]}`" for x in topK[5:30]], topK[:5]
return topK + [f"Other retrieved pages have titles: {', '.join(titles)}."]
def lookup_wikipedia(title: str) -> str:
"""返回维基百科页面的文本内容。"""
if title in DOCS:
return DOCS[title]
results = [x for x in search(title, 10) if x.startswith(title + " | ")]
if not results:
return f"No Wikipedia page found for title: {title}"
return results[0]
4. 定义 ReAct 代理
接下来,我们定义一个简单的 ReAct 代理,它将接收一个声明(claim),并输出需要检索的维基百科页面列表。
instructions = "Find all Wikipedia titles relevant to verifying (or refuting) the claim."
signature = dspy.Signature("claim -> titles: list[str]", instructions)
react = dspy.ReAct(signature, tools=[search_wikipedia, lookup_wikipedia], max_iters=20)
📊 第三步:评估代理的性能
在优化之前,我们需要评估代理的初始性能。我们定义了一个 top5_recall
指标,用于衡量代理在前 5 个返回结果中找到正确页面的比例。
def top5_recall(example, pred, trace=None):
gold_titles = example.titles
recall = sum(x in pred.titles[:5] for x in gold_titles) / len(gold_titles)
if trace is not None:
return recall >= 1.0
return recall
evaluate = dspy.Evaluate(devset=devset, metric=top5_recall, num_threads=16, display_progress=True, display_table=5)
运行评估:
evaluate(react)
初始结果显示,代理的 top5_recall
仅为 8%。这表明模型在当前配置下的性能较差,需要进一步优化。
🔧 第四步:优化代理
DSPy 提供了强大的优化器,可以帮助我们提升代理的性能。在这里,我们使用 MIPROv2
优化器,它能够通过教师模型(如 GPT-4o)生成更好的提示并优化代理的表现。
kwargs = dict(teacher_settings=dict(lm=gpt4o), prompt_model=gpt4o, max_errors=999)
tp = dspy.MIPROv2(metric=top5_recall, auto="medium", num_threads=16, **kwargs)
optimized_react = tp.compile(react, trainset=trainset, max_bootstrapped_demos=3, max_labeled_demos=0)
优化过程可能需要一些时间,并会调用 GPT-4o 生成提示。优化完成后,我们再次评估代理的性能:
evaluate(optimized_react)
优化后的结果显示,代理的 top5_recall
提升到了 41.7%,性能显著提高。
💾 第五步:保存和加载优化后的代理
为了方便后续使用,我们可以将优化后的代理保存到文件中,并在需要时加载:
optimized_react.save("optimized_react.json")
loaded_react = dspy.ReAct("claim -> titles: list[str]", tools=[search_wikipedia, lookup_wikipedia], max_iters=20)
loaded_react.load("optimized_react.json")
通过这种方式,您可以轻松地在不同项目中复用优化后的代理。
🔍 第六步:深入分析优化结果
优化后的代理不仅性能更强,还能够生成更合理的提示和推理过程。您可以使用以下代码查看优化后的提示历史:
dspy.inspect_history(n=2)
此外,如果您启用了 MLflow 跟踪,还可以在 MLflow 的界面中查看每一步的详细信息,包括模型调用、提示内容和工具执行结果。
🎉 总结
通过本文的教程,我们成功构建了一个基于 DSPy 的 ReAct 代理,并通过优化显著提升了其在多跳搜索任务中的表现。从最初的 8% 提升到 41.7%,这一过程展示了 DSPy 在构建和优化语言模型程序方面的强大能力。
无论您是希望构建智能搜索助手,还是探索更复杂的语言模型应用,DSPy 都是一个值得尝试的工具。希望本文能为您的开发之旅提供灵感和指导!