Skip to content

DSPy

编程—而非提示—语言模型

DSPy 是一个用于编程而非提示语言模型的框架。它允许你快速迭代构建模块化AI系统,并提供算法来优化它们的提示和权重,无论你是在构建简单的分类器、复杂的RAG管道还是Agent循环。

DSPy 代表声明式自我改进的 Python。与其使用脆弱的提示,不如编写组合式的 Python 代码,并使用 DSPy 来 教你的语言模型生成高质量的输出。这个 讲座 是一个很好的概念介绍。加入社区,寻求帮助,或通过我们的 GitHub 仓库Discord 服务器 开始贡献。

入门指南 I: 安装 DSPy 并设置你的语言模型

> pip install -U dspy

您可以通过设置OPENAI_API_KEY环境变量或在下面传递api_key来进行身份验证。

1
2
3
import dspy
lm = dspy.LM('openai/gpt-4o-mini', api_key='YOUR_OPENAI_API_KEY')
dspy.configure(lm=lm)

您可以通过设置ANTHROPIC_API_KEY环境变量或在下面传递api_key来进行身份验证。

1
2
3
import dspy
lm = dspy.LM('anthropic/claude-3-opus-20240229', api_key='YOUR_ANTHROPIC_API_KEY')
dspy.configure(lm=lm)

如果您在Databricks平台上,通过他们的SDK认证是自动的。如果不是,您可以设置环境变量DATABRICKS_API_KEYDATABRICKS_API_BASE,或者在下面传递api_keyapi_base

1
2
3
import dspy
lm = dspy.LM('databricks/databricks-meta-llama-3-1-70b-instruct')
dspy.configure(lm=lm)

首先,安装 Ollama 并使用您的 LM 启动其服务器。

> curl -fsSL https://ollama.ai/install.sh | sh
> ollama run llama3.2:1b

然后,从您的DSPy代码中连接到它。

1
2
3
import dspy
lm = dspy.LM('ollama_chat/llama3.2', api_base='http://localhost:11434', api_key='')
dspy.configure(lm=lm)

首先,安装 SGLang 并使用您的语言模型启动其服务器。

> pip install "sglang[all]"
> pip install flashinfer -i https://flashinfer.ai/whl/cu121/torch2.4/ 

> CUDA_VISIBLE_DEVICES=0 python -m sglang.launch_server --port 7501 --model-path meta-llama/Llama-3.1-8B-Instruct

如果您没有从Meta获取下载meta-llama/Llama-3.1-8B-Instruct的权限,可以使用Qwen/Qwen2.5-7B-Instruct作为示例。

接下来,从您的DSPy代码中将本地LM连接为OpenAI兼容的端点。

1
2
3
4
lm = dspy.LM("openai/meta-llama/Llama-3.1-8B-Instruct",
             api_base="http://localhost:7501/v1",  # ensure this points to your port
             api_key="local", model_type='chat')
dspy.configure(lm=lm)

在DSPy中,您可以使用任何由LiteLLM支持的LLM提供商。只需按照他们的说明设置{PROVIDER}_API_KEY,并了解如何将{provider_name}/{model_name}传递给构造函数。

一些例子:

  • anyscale/mistralai/Mistral-7B-Instruct-v0.1, 使用 ANYSCALE_API_KEY
  • together_ai/togethercomputer/llama-2-70b-chat, 使用 TOGETHERAI_API_KEY
  • sagemaker/, 使用 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, 和 AWS_REGION_NAME
  • azure/,带有 AZURE_API_KEYAZURE_API_BASEAZURE_API_VERSION,以及可选的 AZURE_AD_TOKENAZURE_API_TYPE

如果您的提供商提供与OpenAI兼容的端点,只需在您的完整模型名称前添加一个openai/前缀。

1
2
3
import dspy
lm = dspy.LM('openai/your-model-name', api_key='PROVIDER_API_KEY', api_base='YOUR_PROVIDER_URL')
dspy.configure(lm=lm)
Calling the LM directly.

习惯性的DSPy涉及使用模块,我们将在本页的其余部分定义这些模块。然而,仍然可以轻松地直接调用上面配置的lm。这为您提供了一个统一的API,并让您受益于自动缓存等实用功能。

lm("Say this is a test!", temperature=0.7)  # => ['This is a test!']
lm(messages=[{"role": "user", "content": "Say this is a test!"}])  # => ['This is a test!']

1) 模块 帮助你用 代码 而不是字符串来描述AI行为。

要构建可靠的AI系统,你必须快速迭代。但维护提示使得这变得困难:它迫使你每次更改LM、指标或管道时都要调整字符串或数据。自2020年以来,我们已经构建了十几个一流的复合LM系统,我们通过艰难的方式学到了这一点——因此我们构建了DSPy,以将AI系统设计与关于特定LM或提示策略的混乱附带选择解耦。

DSPy 将您的注意力从调整提示字符串转移到使用结构化和声明性自然语言模块进行编程。对于系统中的每个 AI 组件,您可以将输入/输出行为指定为签名,并选择一个模块来分配调用 LM 的策略。DSPy 将您的签名扩展为提示并解析您的类型化输出,因此您可以将不同的模块组合在一起,形成符合人体工程学、可移植和可优化的 AI 系统。

入门指南 II:为各种任务构建 DSPy 模块

在配置好上面的lm后,尝试下面的示例。调整字段以探索您的LM可以很好地完成哪些任务。下面的每个选项卡都设置了一个DSPy模块,如dspy.Predictdspy.ChainOfThoughtdspy.ReAct,并带有特定任务的签名。例如,question -> answer: float告诉模块接受一个问题并生成一个float类型的答案。

math = dspy.ChainOfThought("question -> answer: float")
math(question="Two dice are tossed. What is the probability that the sum equals two?")

可能的输出:

Prediction(
    reasoning='当两个骰子被掷出时,每个骰子有6个面,总共有6 x 6 = 36种可能的结果。只有当两个骰子都显示1时,两个骰子上的数字之和才等于2。这是一个特定的结果:(1, 1)。因此,只有1个有利的结果。和为2的概率是有利结果的数量除以总可能结果的数量,即1/36。',
    answer=0.0277776
)

1
2
3
4
5
6
7
8
def search_wikipedia(query: str) -> list[str]:
    results = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')(query, k=3)
    return [x['text'] for x in results]

rag = dspy.ChainOfThought('context, question -> response')

question = "What's the name of the castle that David Gregory inherited?"
rag(context=search_wikipedia(question), question=question)

可能的输出:

Prediction(
    reasoning='上下文提供了关于苏格兰医生和发明家大卫·格雷戈里的信息。特别提到他在1664年继承了金奈尔迪城堡。这一细节直接回答了关于大卫·格雷戈里继承的城堡名称的问题。',
    response='Kinnairdy Castle'
)

from typing import Literal

class Classify(dspy.Signature):
    """Classify sentiment of a given sentence."""

    sentence: str = dspy.InputField()
    sentiment: Literal['positive', 'negative', 'neutral'] = dspy.OutputField()
    confidence: float = dspy.OutputField()

classify = dspy.Predict(Classify)
classify(sentence="This book was super fun to read, though not the last chapter.")

可能的输出:

Prediction(
    sentiment='positive',
    confidence=0.75
)
class ExtractInfo(dspy.Signature):
    """Extract structured information from text."""

    text: str = dspy.InputField()
    title: str = dspy.OutputField()
    headings: list[str] = dspy.OutputField()
    entities: list[dict[str, str]] = dspy.OutputField(desc="a list of entities and their metadata")

module = dspy.Predict(ExtractInfo)

text = "Apple Inc. announced its latest iPhone 14 today." \
    "The CEO, Tim Cook, highlighted its new features in a press release."
response = module(text=text)

print(response.title)
print(response.headings)
print(response.entities)

可能的输出:

Apple Inc. 宣布 iPhone 14
['介绍', "CEO的声明", '新功能']
[{'name': 'Apple Inc.', 'type': '组织'}, {'name': 'iPhone 14', 'type': '产品'}, {'name': 'Tim Cook', 'type': '人物'}]

def evaluate_math(expression: str):
    return dspy.PythonInterpreter({}).execute(expression)

def search_wikipedia(query: str):
    results = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')(query, k=3)
    return [x['text'] for x in results]

react = dspy.ReAct("question -> answer: float", tools=[evaluate_math, search_wikipedia])

pred = react(question="What is 9362158 divided by the year of birth of David Gregory of Kinnairdy castle?")
print(pred.answer)

可能的输出:

5761.328
class Outline(dspy.Signature):
    """Outline a thorough overview of a topic."""

    topic: str = dspy.InputField()
    title: str = dspy.OutputField()
    sections: list[str] = dspy.OutputField()
    section_subheadings: dict[str, list[str]] = dspy.OutputField(desc="mapping from section headings to subheadings")

class DraftSection(dspy.Signature):
    """Draft a top-level section of an article."""

    topic: str = dspy.InputField()
    section_heading: str = dspy.InputField()
    section_subheadings: list[str] = dspy.InputField()
    content: str = dspy.OutputField(desc="markdown-formatted section")

class DraftArticle(dspy.Module):
    def __init__(self):
        self.build_outline = dspy.ChainOfThought(Outline)
        self.draft_section = dspy.ChainOfThought(DraftSection)

    def forward(self, topic):
        outline = self.build_outline(topic=topic)
        sections = []
        for heading, subheadings in outline.section_subheadings.items():
            section, subheadings = f"## {heading}", [f"### {subheading}" for subheading in subheadings]
            section = self.draft_section(topic=outline.title, section_heading=section, section_subheadings=subheadings)
            sections.append(section.content)
        return dspy.Prediction(title=outline.title, sections=sections)

draft_article = DraftArticle()
article = draft_article(topic="World Cup 2002")

可能的输出:

一篇1500字的关于该主题的文章,例如。

## Qualification Process

The qualification process for the 2002 FIFA World Cup involved a series of..... [shortened here for presentation].

### UEFA Qualifiers

The UEFA qualifiers involved 50 teams competing for 13..... [shortened here for presentation].

.... [rest of the article]

请注意,DSPy 使得优化像这样的多阶段模块变得非常简单。只要你能评估系统的最终输出,每个 DSPy 优化器都可以调整所有中间模块。

Using DSPy in practice: from quick scripting to building sophisticated systems.

标准提示将界面(“LM应该做什么?”)与实现(“我们如何告诉它这样做?”)混为一谈。DSPy将前者隔离为签名,以便我们可以在更大的程序上下文中推断后者或从数据中学习它。

在你开始使用优化器之前,DSPy的模块允许你将有效的LM系统编写为符合人体工程学、可移植的代码。在许多任务和LM中,我们维护签名测试套件,以评估内置DSPy适配器的可靠性。适配器是在优化之前将签名映射到提示的组件。如果你发现某个任务中,简单的提示始终优于你的LM的习惯性DSPy,请将其视为一个错误并提交问题。我们将利用这一点来改进内置适配器。

2) 优化器 调整您的AI模块的提示和权重。

DSPy 为您提供了将带有自然语言注释的高级代码编译为与您的程序结构和指标一致的低级计算、提示或权重更新的工具。如果您更改了代码或指标,您可以简单地重新编译。

给定几十或几百个代表你任务的输入和一个可以衡量你系统输出质量的指标,你可以使用DSPy优化器。DSPy中的不同优化器通过为每个模块合成良好的少样本示例来工作,例如dspy.BootstrapRS,1 为每个提示提出并智能探索更好的自然语言指令,例如dspy.MIPROv2,2 以及为你的模块构建数据集并使用它们来微调系统中的LM权重,例如dspy.BootstrapFinetune.3

入门 III:在 DSPy 程序中优化 LM 提示或权重

一个典型的简单优化运行成本大约为2美元,耗时约20分钟,但在运行优化器时,如果使用非常大的语言模型或非常大的数据集,请小心。 优化的成本可能低至几美分,也可能高达几十美元,这取决于您的语言模型、数据集和配置。

这是一个最小但完全可运行的示例,展示了如何设置一个通过维基百科搜索回答问题的dspy.ReAct代理,然后使用dspy.MIPROv2在500个从HotPotQA数据集中采样的问答对上以廉价的light模式进行优化。

import dspy
from dspy.datasets import HotPotQA

dspy.configure(lm=dspy.LM('openai/gpt-4o-mini'))

def search_wikipedia(query: str) -> list[str]:
    results = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')(query, k=3)
    return [x['text'] for x in results]

trainset = [x.with_inputs('question') for x in HotPotQA(train_seed=2024, train_size=500).train]
react = dspy.ReAct("question -> answer", tools=[search_wikipedia])

tp = dspy.MIPROv2(metric=dspy.evaluate.answer_exact_match, auto="light", num_threads=24)
optimized_react = tp.compile(react, trainset=trainset)

通过这样的非正式运行,ReAct的得分从24%提高到了51%,这通过教授gpt-4o-mini更多关于任务的具体细节来实现。

给定一个检索索引到search,你最喜欢的dspy.LM,以及一个包含问题和真实答案的小型trainset,以下代码片段可以针对内置的SemanticF1指标优化你的RAG系统,该指标是作为DSPy模块实现的。

class RAG(dspy.Module):
    def __init__(self, num_docs=5):
        self.num_docs = num_docs
        self.respond = dspy.ChainOfThought('context, question -> response')

    def forward(self, question):
        context = search(question, k=self.num_docs)   # defined in tutorial linked below
        return self.respond(context=context, question=question)

tp = dspy.MIPROv2(metric=dspy.evaluate.SemanticF1(decompositional=True), auto="medium", num_threads=24)
optimized_rag = tp.compile(RAG(), trainset=trainset, max_bootstrapped_demos=2, max_labeled_demos=2)

要运行一个完整的RAG示例,请开始这个教程。它相对于StackExchange社区的一个子集,将RAG系统的质量提高了10%的相对增益。

这是一个最小但完全可运行的示例,展示了如何设置一个dspy.ChainOfThought模块,该模块将短文本分类为77个银行标签之一,然后使用dspy.BootstrapFinetune与来自Banking77的2000个文本-标签对来微调GPT-4o-mini的权重以完成此任务。我们使用变体dspy.ChainOfThoughtWithHint,它在引导时接受一个可选的hint,以最大化训练数据的效用。当然,在测试时提示是不可用的。

点击显示数据集设置代码。

import random
from typing import Literal
from dspy.datasets import DataLoader
from datasets import load_dataset

# 加载Banking77数据集。
CLASSES = load_dataset("PolyAI/banking77", split="train", trust_remote_code=True).features['label'].names
kwargs = dict(fields=("text", "label"), input_keys=("text",), split="train", trust_remote_code=True)

# 从数据集中加载前2000个示例,并为每个*训练*示例分配一个提示。
trainset = [
    dspy.Example(x, hint=CLASSES[x.label], label=CLASSES[x.label]).with_inputs("text", "hint")
    for x in DataLoader().from_huggingface(dataset_name="PolyAI/banking77", **kwargs)[:2000]
]
random.Random(0).shuffle(trainset)

import dspy
dspy.configure(lm=dspy.LM('gpt-4o-mini-2024-07-18'))

# Define the DSPy module for classification. It will use the hint at training time, if available.
signature = dspy.Signature("text -> label").with_updated_fields('label', type_=Literal[tuple(CLASSES)])
classify = dspy.ChainOfThoughtWithHint(signature)

# Optimize via BootstrapFinetune.
optimizer = dspy.BootstrapFinetune(metric=(lambda x, y, trace=None: x.label == y.label), num_threads=24)
optimized = optimizer.compile(classify, trainset=trainset)

optimized_classifier(text="What does a pending cash withdrawal mean?")

可能的输出(来自最后一行):

Prediction(
    reasoning='待处理的现金提款表示已发起提款请求,但尚未完成或处理。此状态意味着交易仍在进行中,资金尚未从账户中扣除或提供给用户。',
    label='pending_cash_withdrawal'
)

在DSPy 2.5.29上进行类似的非正式运行,将GPT-4o-mini的得分从66%提高到87%。

What's an example of a DSPy optimizer? How do different optimizers work?

dspy.MIPROv2优化器为例。首先,MIPRO从引导阶段开始。它接受你的程序,此时可能尚未优化,并在不同的输入上多次运行,以收集每个模块的输入/输出行为轨迹。它过滤这些轨迹,只保留那些在你的指标中得分较高的轨迹。其次,MIPRO进入其基础提案阶段。它预览你的DSPy程序的代码、你的数据以及运行程序时的轨迹,并使用它们为程序中的每个提示起草许多潜在的指令。第三,MIPRO启动离散搜索阶段。它从你的训练集中采样小批量数据,提出用于构建管道中每个提示的指令和轨迹组合,并在小批量数据上评估候选程序。使用得到的分数,MIPRO更新一个代理模型,帮助提案随着时间的推移变得更好。

DSPy优化器之所以如此强大,其中一个原因是它们可以组合使用。你可以运行dspy.MIPROv2,并将生成的程序再次作为输入传递给dspy.MIPROv2,或者传递给dspy.BootstrapFinetune以获得更好的结果。这在一定程度上是dspy.BetterTogether的本质。或者,你可以运行优化器,然后提取前5个候选程序,并构建一个dspy.Ensemble。这使你可以以高度系统化的方式扩展推理时间计算(例如,集成)以及DSPy独特的预推理时间计算(即优化预算)。

3) DSPy的生态系统 推动了开源AI研究的发展。

与单一的大型语言模型相比,DSPy的模块化范式使得一个庞大的社区能够以开放、分布式的方式改进组合架构、推理时策略和语言模型程序的优化器。这为DSPy用户提供了更多的控制权,帮助他们更快地进行迭代,并通过应用最新的优化器或模块使他们的程序随着时间的推移变得更好。

DSPy 研究项目于 2022 年 2 月在斯坦福 NLP 启动,基于我们从开发早期复合 LM 系统(如 ColBERT-QABaleenHindsight)中学到的经验。第一个版本于 2022 年 12 月发布为 DSP,并在 2023 年 10 月演变为 DSPy。感谢 250 位贡献者,DSPy 已经向成千上万的人介绍了如何构建和优化模块化 LM 程序。

自那时起,DSPy社区在优化器方面产生了大量工作,如MIPROv2BetterTogetherLeReT,在程序架构方面,如STORMIReRaDSPy Assertions,以及在新问题上的成功应用,如PAPILLONPATHWangLab@MEDIQAUMD的Prompting案例研究Haize的红队计划,此外还有许多开源项目、生产应用和其他用例