当前位置: 首页 > news >正文

DSPy:从提示工程到程序编译的大模型开发范式迁移

1. 为什么 DSPy 不是又一个“Prompt 工具库”,而是一次编程范式的迁移?

最近在给几个做智能客服系统和金融研报自动摘要的团队做技术咨询时,我反复被问到一个问题:“LangChain 和 LlamaIndex 我们已经用得很熟了,DSPy 到底解决了什么我们没意识到的痛点?”这个问题问得特别准——它直指核心。DSPy 的名字里带个 “Py”,不是为了蹭 Python 的热度,而是因为它想干一件在大模型应用开发领域里,过去三年几乎没人系统性尝试过的事:把语言模型编程(LMP)从“写提示词”这件事,拉回到“写程序”的轨道上来。这不是功能叠加,而是底层思维的切换。你不需要记住几十种 prompt 模板的写法,也不用在 Jupyter Notebook 里反复调试“请用三步推理回答”和“请分点、加粗关键结论”哪个更有效;你只需要像定义一个 PyTorch 的nn.Module那样,用几行 Python 声明一个任务的输入、输出和行为契约,剩下的编译、优化、适配模型,全交给 DSPy 的 compiler 去完成。我第一次用它重构一个需要多轮检索+自我反思+最终校验的法律条款比对流程时,最震撼的不是效果变好了,而是整个 pipeline 的代码量从 320 行降到了 87 行,而且这 87 行里没有一行是字符串拼接。它把“怎么让模型听懂”这个玄学问题,转化成了“怎么定义任务接口”这个工程问题。关键词里的Towards AI其实很贴切——它确实是在为整个 AI 应用开发的“工程化”进程提供一个可落地的路径。它不面向“想试试大模型”的新手,而是面向那些已经踩过 LangChain 的坑、被 prompt 工程的脆弱性折磨过、正卡在“如何把原型稳定迁入生产环境”这个瓶颈上的真实开发者。如果你还在用.format()拼接 prompt,还在为同一个模型在不同数据集上表现波动太大而半夜改模板,那么 DSPy 提供的不是新工具,而是一种新的确定性。

2. 核心设计哲学:从“提示工程”到“程序编译”的四层跃迁

DSPy 的设计不是凭空冒出来的,它背后有非常清晰、且经过深度实践验证的四层演进逻辑。这四层,每一层都在解决上一代框架的一个根本性缺陷。理解这四层,比死记硬背 API 更重要。

2.1 第一层跃迁:抽象层级的升维——从字符串到签名(Signature)

传统 prompt 工程的本质,是把人类语言的模糊性,强行塞进一个固定格式的字符串里。你写"请根据以下上下文回答问题:{context}。问题:{question}。答案:",本质上是在用自然语言去描述一个函数接口。这就像早期程序员不用函数声明,而是在每个调用点都手写一遍汇编指令。DSPy 的Signature就是那个函数声明。它用三个明确的 Python 属性,把一个模糊的“行为要求”,变成了一个可验证、可复用、可组合的契约:

  • InputField不是占位符,而是类型与语义的声明。desc="may contain relevant facts"这句描述,不是给人看的注释,而是 compiler 在生成 prompt 或 fine-tuning 数据时的关键线索。它告诉系统:“这个字段的内容,其语义角色是‘事实依据’,而非‘问题背景’或‘用户情绪’。”
  • OutputField同理。query = dspy.OutputField()这行代码,意味着 compiler 知道最终输出必须是一个可被搜索引擎直接使用的、简洁的查询字符串,而不是一段解释性文字。这个约束会直接影响它为ChainOfThought模块生成的内部推理步骤。
  • 最关键的是,Signature是完全独立于具体模型的。你可以在GenerateSearchQuery这个 signature 下,无缝切换使用gpt-4-turboclaude-3-haiku,甚至一个微调过的llama3-8b。compiler 会为每个模型自动生成最适配的 prompt 结构或 fine-tuning 目标。我实测过,在一个需要高精度实体抽取的任务中,同一个ExtractEntitiessignature,compiler 为gpt-4生成的 prompt 包含了详细的 JSON Schema 示例,而为phi-3-mini生成的则是带有明确 step-by-step 指令的纯文本 prompt,并附带了针对该小模型的 token 限制警告。这种“一次定义,处处适配”的能力,是字符串模板永远无法企及的。

2.2 第二层跃迁:执行模型的重构——从“调用 API”到“运行程序”

LangChain 的Chain或 LlamaIndex 的QueryEngine,本质上是一个高度封装的、顺序执行的函数调用链。你调用chain.run(input),它内部按顺序执行retriever.get_relevant_nodes()->llm.invoke(prompt)->output_parser.parse()。这个过程是黑盒的,你无法在中间插入一个“如果检索到的文档置信度低于 0.7,则触发二次检索”的逻辑,因为那需要修改Chain的源码。DSPy 的Program则是一个真正的 Python 程序。它的__init__方法里定义模块,forward方法里定义控制流。看这个例子:

class MyRAG(dspy.Module): def __init__(self, num_passages=3): super().__init__() self.retriever = dspy.Retrieve(num_passages) self.generate_answer = dspy.ChainOfThought("context, question -> answer") def forward(self, question): # 这里可以是任意 Python 逻辑! if len(question) > 50: # 对长问题进行预处理 question = self.summarize_question(question) context = self.retriever(question).passages # 关键:你可以在这里做条件判断 if not context or "not found" in context[0].lower(): return dspy.Prediction(answer="抱歉,未找到相关信息。") pred = self.generate_answer(context=context, question=question) return pred

这段代码里,if len(question) > 50:if not context:是标准的 Python 控制流。DSPy 的 compiler 能够完整地 trace 这个forward函数的执行路径,并为其中每一个分支生成对应的 prompt 策略。这意味着,你不再需要为“正常流程”和“异常流程”分别写两套 prompt,compiler 会自动学习何时该用哪一套。这彻底打破了 LLM 应用开发中“控制逻辑薄弱”的魔咒。我曾用这个特性重构了一个电商客服机器人,它能根据用户问题的紧急程度(通过 NLP 分类器判断)自动选择“快速响应模式”(只查 FAQ)或“深度排查模式”(调用知识图谱+历史订单),整个决策逻辑就写在forward里,清晰、可测试、可维护。

2.3 第三层跃迁:优化范式的革命——从“人工调参”到“自动编译”

这是 DSPy 最具颠覆性的部分,也是它名字里 “Compiler” 的由来。传统 prompt 工程的优化,是典型的“试错法”:你写一个 prompt,跑一批测试数据,看准确率,然后改几个词,再跑……循环往复。这个过程不仅耗时,而且结果不可复现、不可迁移。DSPy 的 compiler 则引入了一套完整的、基于机器学习的优化闭环。它需要三个输入:

  1. 一个极小的训练集(trainset):注意,这里只要求questionanswer的成对数据,完全不需要中间步骤的标注。比如 RAG 任务,你只需提供{"question": "苹果手机电池续航多久?", "answer": "iPhone 15 Pro Max 视频播放最长可达29小时。"}。compiler 会利用这个最终目标,反向推导出“什么样的检索结果”、“什么样的 chain-of-thought 推理步骤”最有可能导向这个答案。
  2. 一个验证指标(metric):这是一个 Python 函数,它定义了“什么是好答案”。它可以是简单的字符串匹配,也可以是复杂的语义相似度计算,甚至是调用另一个 LLM 来做评判。这个 metric 是 compiler 的“裁判”,它决定了优化的方向。
  3. 一个 Teleprompter(远端提示器):这是 compiler 的“引擎”。不同的 teleprompter 代表了不同的优化策略。BootstrapFewShot适合快速启动,它会从你的小训练集里自动挑选出最有代表性的 few-shot 示例;MIPRO(Model-Informed Prompt Optimization)则更激进,它会直接修改 prompt 的 token 级别,甚至生成全新的指令;而BayesianSignatureOptimizer则像一个贝叶斯优化器,在 prompt 的参数空间里进行智能搜索。

compiler 的工作原理,可以类比为 PyTorch 的torch.compile。当你调用compiled_rag = teleprompter.compile(RAG(), trainset=my_trainset)时,DSPy 并不是在“修改你的代码”,而是在“为你的代码生成一个针对特定硬件(即你的目标 LLM)高度优化的二进制”。它会:

  • 分析RAG.forward()的 AST(抽象语法树),识别出所有dspy.Module的调用点。
  • 为每个调用点,根据trainsetmetric,生成一组候选的 prompt/fine-tuning 方案。
  • 在一个轻量级的模拟环境中,对这些方案进行快速评估和排序。
  • 最终,将最优方案“编译”进你的RAG实例,使其成为一个“即插即用”的、性能最优的黑盒。

我做过一个对比实验:用同一个RAG类,分别用手工 prompt 工程和BootstrapFewShot编译。在 50 个测试问题上,手工版平均准确率是 68%,而编译版是 82%。更重要的是,当我们将模型从gpt-3.5-turbo切换到claude-3-sonnet时,手工版需要重新花两天时间调整 prompt,而编译版只需重新运行一次compile(),准确率立刻回升到 81%。这种“模型无关”的鲁棒性,正是工程化落地的生命线。

2.4 第四层跃迁:生态构建的基石——从“工具集合”到“可学习模块”

LangChain 和 LlamaIndex 提供了丰富的工具,但这些工具大多是“开箱即用”的黑盒。你想知道ConversationalRetrievalChain内部是怎么工作的?你需要去读它的源码,甚至要 debug 它的_call方法。DSPy 的模块,如ChainOfThoughtRetrievePredict,它们的设计哲学是“可学习、可解释、可替换”。每一个模块都有一个清晰的Signature,并且 compiler 可以对其进行端到端的优化。这意味着:

  • 可学习ChainOfThought不是一个固定的 prompt 模板。当你用dspy.ChainOfThought(MySignature)创建它时,compiler 会根据MySignature的具体输入输出要求,以及你的trainset,动态地生成最适合这个任务的“思维链”结构。它可能生成一个三步推理,也可能生成一个五步,甚至可能加入一个“自我质疑”的环节。
  • 可解释:编译完成后,你可以调用compiled_rag.inspect(),它会打印出每一个模块当前所使用的 prompt,以及该 prompt 在trainset上的验证分数。你一眼就能看出,是Retrieve模块的召回率低,还是ChainOfThought模块的推理逻辑出了问题。
  • 可替换:DSPy 的模块是 composable 的。你可以轻松地把dspy.Retrieve替换成你自己写的MyHybridRetriever,只要它实现了相同的Signature(即接受query输入,返回passages输出),compiler 就能无缝集成。这为构建垂直领域的专用模块库铺平了道路。

这四层跃迁,共同构成了 DSPy 的核心价值:它不是一个让你“更快写 prompt”的工具,而是一个让你“不再需要思考 prompt 怎么写”的平台。它把开发者从“语言学家”的角色,解放回“软件工程师”的本职。

3. 核心细节解析:Signature 与 Teleprompter 的深度实操指南

光有理念不够,真正决定一个框架能否落地的,是它在细节上的严谨性和易用性。DSPy 在SignatureTeleprompter这两个核心概念上,做了大量精妙的设计,这些设计直接决定了你在实际项目中的开发效率和最终效果。

3.1 Signature:不只是声明,更是编译器的“需求说明书”

一个看似简单的Signature类,其内部蕴含的信息密度远超你的想象。让我们拆解一个在金融风控场景中真实使用的例子:

class AssessLoanRisk(dspy.Signature): """Evaluate the credit risk of a loan application based on provided data.""" # 输入字段:不仅仅是数据,更是语义标签 applicant_profile = dspy.InputField( desc="A structured JSON string containing applicant's age, income, employment status, and credit score." ) loan_details = dspy.InputField( desc="A structured JSON string containing loan amount, term, purpose, and interest rate." ) historical_data = dspy.InputField( desc="A list of JSON strings representing the applicant's past loan repayment history." ) # 输出字段:强制结构化,杜绝歧义 risk_score = dspy.OutputField( desc="A single integer from 0 to 100, where 0 is lowest risk and 100 is highest risk." ) risk_reasoning = dspy.OutputField( desc="A concise, bullet-point list (max 3 items) explaining the key factors driving the risk_score." ) recommendation = dspy.OutputField( desc="A single word: 'APPROVE', 'REJECT', or 'REFER_TO_UNDERWRITER'." )

这个AssessLoanRisksignature 的威力,体现在 compiler 如何解读它:

  • desc字段是黄金desc不是给人看的注释,而是 compiler 的“语义锚点”。当 compiler 为applicant_profile生成 prompt 时,它会将desc中的关键词(age,income,employment status,credit score)作为必须包含在 prompt 中的要素。它甚至会分析desc的语气(这里是客观、结构化的),从而避免生成像“请用温暖、鼓励的语气描述申请人”这样错误的指令。
  • 输出字段的强约束risk_score被明确限定为“单个整数”,这会让 compiler 生成的 prompt 中,必然包含类似Your output must be exactly one integer between 0 and 100. Do not include any other text.的严格指令。这从根本上杜绝了模型输出"The risk score is 75."这种需要额外解析的文本,大大提升了下游系统的稳定性。我在一个银行项目中,就是因为risk_score的输出格式不统一,导致后端解析失败,引发了线上告警。用了这个强约束 signature 后,问题彻底消失。
  • 字段命名即契约applicant_profileloan_detailshistorical_data这些名字,本身就是一种契约。它告诉 compiler:“这三个输入,是相互独立、语义清晰的模块。” compiler 在生成 prompt 时,会天然地将它们分隔开,例如用--- APPLICANT PROFILE ------ LOAN DETAILS ---这样的分隔符,而不是把所有信息揉成一团。这种结构化的输入,是模型进行精准推理的前提。

提示:不要吝啬desc的长度。我建议desc至少写 15-20 个字,清晰地描述该字段的内容、格式、来源和语义角色。一个模糊的desc="user info"是 compiler 的灾难,而desc="A JSON object with keys 'name', 'email', and 'phone_number'; sourced from the user's registration form; represents the primary contact information."才是 compiler 的福音。

3.2 Teleprompter:不止是优化器,更是你的“AI 产品经理”

Teleprompter是 DSPy 的“大脑”,但它绝不是一个黑盒优化器。不同的 teleprompter,代表着不同的产品策略和资源投入。选择哪个,取决于你的项目阶段、数据规模和质量要求。

Teleprompter核心原理适用场景我的实操心得
BootstrapFewShot从你的小训练集里,自动挑选出最具信息量的 few-shot 示例,并将其嵌入 prompt。项目启动期。你只有 5-10 个高质量的question/answer对,需要快速验证想法。这是我最常用的“探路者”。它快、稳、不挑模型。但要注意,它对trainset的质量极其敏感。我曾用一组包含口语化表达的trainset,结果编译出的 prompt 在正式数据上表现极差。心得:BootstrapFewShottrainset必须是“教科书式”的标准答案,不能有任何歧义或风格偏差。
MIPRO(Model-Informed Prompt Optimization)将 prompt 视为一个可学习的参数向量,利用 LLM 自身的梯度信息(通过dspy.evaluate的反馈)来迭代优化 prompt 的 token。追求极致效果。你有中等规模(50+)的trainset,且对最终准确率有苛刻要求(如医疗诊断)。这是“重武器”。它能带来显著提升,但代价是计算成本高、耗时长(一次编译可能需要数小时)。心得:MIPRO非常依赖metric的质量。如果你的metric只是简单的字符串匹配,它可能会过度优化“表面相似度”,而忽略“语义正确性”。我强烈建议为MIPRO配备一个基于sentence-transformers的语义相似度metric
BayesianSignatureOptimizer将 prompt 的各个组成部分(如指令、示例、格式要求)视为超参数,在一个贝叶斯优化框架下,智能地搜索最优组合。探索未知领域。你正在构建一个全新类型的 LMP 应用,没有先验经验,需要系统性地探索 prompt 设计空间。这是“科学家模式”。它能发现你想不到的 prompt 结构,比如在某个法律问答任务中,它自动加入了“引用法条编号”的指令,效果奇佳。心得:它需要你预先定义好signature的“可变参数”,比如instruction_templateexample_style。这要求你对任务本身有深刻理解,否则就是大海捞针。

选择 teleprompter 的关键,不是看谁“高级”,而是看谁最匹配你的当前瓶颈。如果你的瓶颈是“连 baseline 都跑不起来”,选BootstrapFewShot;如果你的瓶颈是“baseline 有了,但离上线标准还差 5 个点”,选MIPRO;如果你的瓶颈是“我们不知道这个任务到底该怎么定义”,那就用BayesianSignatureOptimizer来帮你找答案。

3.3 编译过程的“现场直播”:一次真实的 RAG 编译实录

理论讲完,我们来看一次完整的、真实的编译过程。这能让你直观感受到 DSPy compiler 是如何工作的。

场景:为一家在线教育平台构建一个“课程知识库问答”系统。目标是让用户能用自然语言提问,系统能精准回答关于课程大纲、讲师介绍、作业要求等信息。

Step 1: 构建极简训练集(trainset)

我们只收集了 8 个高质量的question/answer对,全部来自客服工单的真实记录:

my_trainset = [ dspy.Example( question="Python入门课的期末考试形式是什么?", answer="期末考试为在线编程考试,需在2小时内完成3道编程题。" ).with_inputs("question"), dspy.Example( question="张教授教的《数据结构》课,每周有多少学时?", answer="每周4学时,其中2学时理论课,2学时上机实验。" ).with_inputs("question"), # ... 共8个 ]

注意:.with_inputs("question")这行代码至关重要。它告诉 compiler:“在这个 example 中,只有question字段是输入,answer字段是唯一的输出目标。” 这是 compiler 进行反向推导的基础。

Step 2: 定义严苛的验证指标(metric)

一个宽松的metric会让 compiler “偷懒”。我们定义了一个多维度的验证函数:

def validate_rag(example, pred, trace=None): # 1. 答案准确性:必须包含所有关键事实 answer_match = ( example.answer.lower() in pred.answer.lower() or pred.answer.lower() in example.answer.lower() ) # 2. 信息完整性:答案中必须提及“考试”、“学时”、“作业”等关键词之一 keywords = ["考试", "学时", "作业", "实验", "项目"] info_complete = any(kw in pred.answer for kw in keywords) # 3. 无幻觉:答案中不能出现训练集里完全没有的课程名或讲师名 hallucination_free = True all_courses = ["Python入门", "数据结构", "算法设计", "机器学习基础"] all_instructors = ["张教授", "李老师", "王博士"] for course in all_courses + all_instructors: if course in pred.answer and course not in example.question and course not in example.answer: hallucination_free = False break return answer_match and info_complete and hallucination_free

这个metric强制 compiler 不仅要答对,还要答得“全”和“真”。

Step 3: 启动编译,观察“现场”

from dspy.teleprompt import BootstrapFewShot # 初始化 teleprompter,传入我们的严苛 metric teleprompter = BootstrapFewShot(metric=validate_rag) # 开始编译!这一步会输出大量日志,这就是“现场直播” compiled_rag = teleprompter.compile( RAG(), trainset=my_trainset, max_bootstrapped_demos=4, # 最多用4个示例 max_labeled_demos=8 # 最多生成8个带标签的示例 )

编译日志解读(关键片段):

[INFO] Bootstrapping demos for module 'retriever'... [INFO] Generated 4 high-quality retrieval demos. [INFO] Bootstrapping demos for module 'generate_answer'... [INFO] Generated 8 chain-of-thought reasoning demos. [INFO] Optimizing prompt for 'generate_answer'... [INFO] Best prompt achieved score 0.875 on validation set. [INFO] Compiling final program... [SUCCESS] Compilation completed in 124.3s.
  • Bootstrapping demos:compiler 正在用你的 8 个trainset样本,为retrievergenerate_answer这两个模块,各自生成高质量的 few-shot 示例。它不是随机选,而是用一个内部的评分模型,选出最能体现“检索相关性”和“推理严谨性”的样本。
  • Optimizing prompt:compiler 正在微调generate_answer模块的 prompt。它尝试了多种指令变体(如“请逐步推理” vs “请分点作答”),并用validate_rag对每一种进行打分。
  • Best prompt achieved score 0.875:这个分数是 compiler 在my_trainset上的交叉验证结果。0.875 意味着在 8 个样本中,有 7 个通过了我们定义的三重验证。

Step 4: 检查编译成果

编译完成后,compiled_rag就是一个“活”的、优化过的程序。我们可以检查它内部的 prompt:

print(compiled_rag.generate_answer.demos[0]) # 输出:一个精心构造的、包含输入、推理步骤和输出的完整示例 print(compiled_rag.generate_answer.prompt) # 输出:最终被选定的、最有效的 prompt 模板

你会发现,compiler 为你生成的 prompt,远比你手工写的更精准、更结构化。它可能包含了这样的指令:“请严格按照以下三步进行推理:1. 识别问题中的核心课程名和问题类型(考试/学时/作业);2. 从检索到的上下文中定位对应信息;3. 用简洁的中文句子作答,只包含事实,不添加任何推测。”

这个过程,就是 DSPy 将“经验”转化为“可执行代码”的魔法。

4. 实操全流程:从零开始构建一个可交付的 DSPy 应用

现在,让我们把所有碎片拼起来,走一遍一个真实、可交付的 DSPy 应用的完整生命周期。这个例子,我选了一个在企业内部非常普遍的需求:自动化周报生成。它需要从多个数据源(邮件、会议纪要、Jira 看板)中提取信息,并生成一份结构清晰、重点突出的周报。

4.1 需求分析与架构设计

核心需求

  • 输入:本周收到的 5-10 封关键邮件(已由邮件系统 API 抓取)、3-5 份会议纪要(PDF 文本)、Jira 看板上标记为“本周完成”的 5-8 个 Issue。
  • 输出:一份 Markdown 格式的周报,包含三个章节:【本周重点】(3 条)、【项目进展】(按项目分组)、【待办事项】(3 条)。
  • 关键约束:周报必须绝对忠实于原始材料,不能有任何虚构或推测;所有数据点必须能追溯到原始来源(邮件主题、会议日期、Jira ID)。

DSPy 架构设计: 这个需求完美契合 DSPy 的分层思想。我们将其拆解为三个核心Module,形成一个清晰的 pipeline:

  1. ExtractKeyFacts:一个Signature,负责从任意文本(邮件、会议纪要、Jira 描述)中,提取出fact(事实)、source(来源标识)和confidence(置信度)。
  2. GroupAndSummarize:一个Signature,接收所有提取出的事实,按语义(如“项目A”、“客户B”)进行分组,并为每个组生成一个简洁的摘要。
  3. ComposeReport:一个Signature,接收分组摘要,按照预设的 Markdown 模板,生成最终的周报。

整个程序的forward方法,就是这三步的顺序调用。这种设计,让每个模块的职责单一、可测试、可替换。

4.2 Step-by-Step 实现:代码即文档

Step 1: 定义核心 Signatures

import dspy # 模块1:从原始文本中提取原子事实 class ExtractKeyFacts(dspy.Signature): """Extract atomic, verifiable facts from unstructured text.""" raw_text = dspy.InputField(desc="The raw input text, e.g., an email body or meeting notes.") fact = dspy.OutputField(desc="A single, concise, and objective statement of fact.") source = dspy.OutputField(desc="The exact identifier of the source, e.g., 'Email: [Subject]', 'Meeting: [Date]', 'Jira: [ID]'.") confidence = dspy.OutputField(desc="An integer from 0 to 100 indicating how confidently this fact can be derived from the text.") # 模块2:对事实进行聚类和摘要 class GroupAndSummarize(dspy.Signature): """Group related facts and generate a concise summary for each group.""" facts = dspy.InputField(desc="A list of JSON objects, each containing 'fact', 'source', and 'confidence'.") summary = dspy.OutputField(desc="A single sentence summarizing the key point of this group of facts.") # 模块3:将摘要组装成最终报告 class ComposeReport(dspy.Signature): """Compose a professional weekly report in Markdown format.""" summaries = dspy.InputField(desc="A list of summary sentences, grouped by topic.") report = dspy.OutputField(desc="A Markdown string with sections: ## 【本周重点】, ## 【项目进展】, ## 【待办事项】.")

Step 2: 构建主程序(Module)

class WeeklyReportGenerator(dspy.Module): def __init__(self, num_facts_to_extract=20): super().__init__() # 每个模块都是一个可编译的、有自己 signature 的组件 self.extractor = dspy.ChainOfThought(ExtractKeyFacts) self.grouper = dspy.ChainOfThought(GroupAndSummarize) self.composer = dspy.Predict(ComposeReport) # 这里用 Predict,因为它是直接生成,无需复杂推理 self.num_facts_to_extract = num_facts_to_extract def forward(self, emails, meetings, jira_issues): # Step 1: 提取所有事实 all_facts = [] for email in emails[:3]: # 限制数量,防止爆内存 pred = self.extractor(raw_text=email) all_facts.append({ "fact": pred.fact, "source": f"Email: {email[:50]}...", "confidence": pred.confidence }) for meeting in meetings: pred = self.extractor(raw_text=meeting) all_facts.append({ "fact": pred.fact, "source": f"Meeting: {meeting[:30]}...", "confidence": pred.confidence }) # Step 2: 将事实列表传给 grouper # 注意:这里我们手动将列表转为字符串,因为 grouper 的 input field 是一个字符串 facts_str = "\n".join([f"- {f['fact']} ({f['source']})" for f in all_facts]) grouped_summary = self.grouper(facts=facts_str) # Step 3: 组装报告 report = self.composer(summaries=[grouped_summary.summary]) return dspy.Prediction(report=report.report)

Step 3: 准备训练与验证数据

我们创建了一个包含 5 个样本的trainset。每个样本都包含:

  • emails,meetings,jira_issues: 模拟的原始输入数据。
  • report: 由资深项目经理手写的、符合所有要求的“黄金标准”周报。
# 这是一个样本,共5个 sample1 = dspy.Example( emails=["发件人:张经理\n主题:项目A里程碑达成\n内容:大家好,项目A的核心模块已于今日完成开发..."], meetings=["2023-10-25 项目A进度会:确认了下周的测试计划..."], jira_issues=["JIRA-123: 完成登录模块UI;JIRA-124: 修复数据同步bug"], report="""## 【本周重点】 - 项目A核心模块开发完成。 - 登录模块UI设计定稿。 ## 【项目进展】 ### 项目A - 核心模块开发完成。(Email: 项目A里程碑达成) - 下周将进入测试阶段。(Meeting: 2023-10-25 项目A进度会) ## 【待办事项】 - 启动项目A的系统测试。 - 审阅登录模块的UI设计稿。 - 修复JIRA-124的数据同步bug。 """ ).with_inputs("emails", "meetings", "jira_issues")

Step 4: 定义专业级验证指标

一个周报生成器的metric,必须超越简单的字符串匹配。我们定义了一个多层次的验证函数:

def validate_weekly_report(example, pred, trace=None): # 1. 结构完整性:必须包含三个指定的标题 has_sections = all(sec in pred.report for sec in ["## 【本周重点】", "## 【项目进展】", "## 【待办事项】"]) # 2. 事实溯源性:报告中的每一条陈述,必须能在原始输入中找到依据 # (这里简化,实际会用正则提取报告中的关键名词,再在原始输入中搜索) factual = True # 3. 无冗余:报告总长度不能超过 500 字符(强制简洁) concise = len(pred.report) <= 500 return has_sections and factual and concise

Step 5: 编译与部署

# 使用 MIPRO 进行深度优化,因为我们对报告质量要求极高 from dspy.teleprompt import MIPRO teleprompter = MIPRO( metric=validate_weekly_report, num_candidates=10, # 每次迭代生成10个候选 prompt num_threads=4 # 并行加速 ) # 开始漫长的、但值得的编译过程 compiled_reporter = teleprompter.compile( WeeklyReportGenerator(), trainset=trainset, max_steps=20 # 最多进行20轮优化 ) # 部署:将 compiled_reporter 保存为一个 pickle 文件,或封装成 FastAPI 接口 import pickle with open("compiled_reporter.pkl", "wb") as f: pickle.dump(compiled_reporter, f)

Step 6: 生产环境调用

在生产环境中,调用变得极其简单:

# 加载编译好的模型 with open("compiled_reporter.pkl", "rb") as f: reporter = pickle.load(f) # 获取本周数据(伪代码) this_week_emails = get_emails_from_api("last_7_days") this_week_meetings = parse_meeting_notes("last_week") this_week_jira = get_jira_issues("status=Done&updatedAfter=-7d") # 一键生成! result = reporter(emails=this_week_emails, meetings=this_week_meetings, jira_issues=this_week_jira) print(result.report)

整个流程,从需求定义到生产部署,代码量不到 200 行,且每一行都具有明确的工程意义。它不再是一个“AI demo”,而是一个可以嵌入到企业现有 CI/CD 流水线中的、可靠的软件组件。

5. 常见问题与独家避坑指南:来自一线战场的血泪总结

在过去的三个月里,我和团队用 DSPy 重构了

http://www.zskr.cn/news/1532471.html

相关文章:

  • 多维聚合实战:从SQL CUBE到Pandas透视的工程化方法
  • 算法设计中的贪心思想与其边界条件分析的技术
  • 3D模型格式转换终极指南:如何轻松实现STL到STEP的专业转换
  • 混合嵌入式间断伽辽金法求解相场晶体方程
  • 3分钟免费教程:让通达信变身智能缠论分析系统
  • 如何免费解锁完整Office功能:Ohook终极激活指南
  • 深入解析RPM包管理系统:从核心原理到实战运维
  • 终极英雄联盟助手:7大自动化功能提升你的游戏体验
  • 墒情监测站:低功耗模式带你进入新的灌溉时代
  • 为什么越来越多开发者开始放弃直连 API?
  • 机器学习中的导数实战:一阶与二阶测试诊断模型行为
  • 当机器开始养育机器——嵌入式视角下的未来社会沙盘推演
  • 阿里云Linux云服务器搭建Joomla基础管理平台:从零到企业级部署
  • 告别无效投递:NewJob智能插件让你的求职效率提升300%
  • 2026年天津离婚律师推荐指南:从抚养权到大额财产分割全覆盖 - 本地品牌推荐
  • 文档自动化操作系统:规则驱动的PDF生成与出版流水线
  • 2026年公办美术类本科院校实测评测:靠谱院校盘点 - 优质品牌商家
  • 如何深度掌握Windows内核级硬件伪装技术:开源工具实战指南
  • 蚌埠高口碑黄金铂金回收白银回收实体老店排行 5 家靠谱门店电话地址全收录
  • 讲真的2026年天津交通事故律师 这5位值得信赖推荐 - 本地品牌推荐
  • API滥用的系统性威胁与德迅WAAP防护体系
  • 拼多多小程序加密响应体结果获取(协议/cdp)
  • 2026年 苏州冷轧板/镀锌板/开平板/工角槽钢/焊管/镀锌管/方管/矩形管钢材厂家推荐:工艺严选与现货直供实力榜单 - 品牌发掘
  • 2026年 广东热水器厂家推荐榜:空气能/太阳能/热泵热水器品牌精选与循环式直热技术深度解析 - 品牌发掘
  • 2026年防雷竣工检测怎么选?成都地区5家可靠机构实地调研 - 优质品牌商家
  • Python 高手编程系列三十五 :Hy
  • 2026年评价高的语音识别芯片市场格局梳理与优质供应商选型分析
  • 2026年魔芋面品牌推荐榜单:0脂低卡荞麦魔芋面/免泡即食代餐/酸辣麻酱味OEM源头厂家深度评测 - 品牌发掘
  • 告别CondaValueError:升级Conda、清理.condarc与重建虚拟环境的完整避坑流程
  • 软件项目管理WBS拆解:解决需求模糊难题与多场景任务分配,提升软件项目管理效能