1. 项目概述这不是又一个LLM调用库而是一次编程范式的迁移DSPy这个词刚在2023年底冒出来的时候我第一反应是“又一个包装LLM API的Python包”——毕竟那会儿LangChain、LlamaIndex、Haystack已经把“链式调用”“检索增强”这些词炒得发烫。但当我真正花三天时间把官方文档从头到尾跑通、重写了一个原本用LangChain写了200行的法律条款抽取pipeline、最终代码压缩到47行且效果还提升了3.2个点F1时我才意识到DSPy不是工具升级是底层思维切换。它不教你怎么“调用大模型”而是教你“如何让大模型学会按你的逻辑思考”。核心关键词——DSPy、语言模型编程框架、声明式编程、编译式优化、签名Signature、优化器Optimizer——这五个词串起来就是整个框架的脊椎。它面向的不是想快速搭个demo的创业者而是那些已经踩过LLM应用深坑、被prompt engineering反复毒打、开始怀疑“是不是该换种方式和大模型对话”的中高级工程师和AI产品经理。你不需要从零训练模型也不需要懂梯度下降你需要的是对任务逻辑的清晰拆解能力以及把“人类怎么一步步推理”翻译成机器可执行指令的抽象功夫。它解决的痛点非常具体当你发现同一个prompt在GPT-4上效果很好换到Claude或本地Llama3上就崩盘当你为一个复杂任务写了十几版prompt每次改一个词都要重新测5个样本当你想把“先检索再总结再校验”这个流程固化下来而不是靠字符串拼接硬编码——这时候DSPy就不是“需要知道”而是“必须上手”。2. 内容整体设计与思路拆解为什么放弃“写Prompt”转向“定义程序结构”2.1 传统方法的三大死结脆弱、不可复现、难协同我们先直面现实。过去两年主流的LLM应用开发基本围绕三个动作展开写Prompt、调API、解析输出。这套流程在简单场景下高效但一旦任务变复杂立刻暴露出结构性缺陷。我拿自己去年做的一个真实项目举例为某银行风控部门构建“贷款申请材料合规性初筛系统”。原始方案用LangChain核心逻辑是1用关键词匹配粗筛出可能违规的段落2对每个段落调用GPT-4生成“是否违规依据条款”3聚合所有结果按风险等级排序。上线后问题不断GPT-4突然更新了系统提示词导致第2步的输出格式微变第3步的JSON解析直接报错换用成本更低的Claude-3-haiku后第1步的关键词匹配漏掉了37%的模糊表述更致命的是当法务团队要求新增“反洗钱条款交叉验证”环节时我不得不在原有200行代码里插入新模块结果触发了之前隐藏的上下文长度溢出bug。这三个问题本质是同一枚硬币的两面所有逻辑都耦合在字符串里。Prompt是文本输出解析是正则或JSON Schema中间状态全靠字符串传递。这种模式下“可维护性”是个伪命题。DSPy的设计哲学正是从根子上切断这种耦合。它不让你写“请用JSON格式返回{...}”而是让你定义一个Signature——一个带类型注解的Python函数签名比如class ComplianceCheck(dspy.Signature): 判断贷款申请材料中的某一段是否违反监管条款 material_excerpt: str dspy.InputField(desc待检查的申请材料原文片段) regulatory_clause: str dspy.InputField(desc对应的监管条款原文) is_violated: bool dspy.OutputField(desc是否构成违规) violation_reason: str dspy.OutputField(desc违规的具体原因需引用条款原文)看到没这里没有一句自然语言指令。InputField和OutputField的desc参数只是给DSPy内部的编译器提供语义线索用于后续自动生成高质量prompt。真正的约束来自类型系统is_violated必须是boolviolation_reason必须是str。这意味着无论底层用GPT-4还是Llama3DSPy都会确保输出能被Python原生类型安全地接收。这解决了第一个死结脆弱性。输出格式不再依赖模型的“心情”而是由类型契约强制保障。2.2 声明式编程你描述“要什么”DSPy决定“怎么做”第二个关键跃迁是编程范式的转换。传统方式是命令式Imperative你告诉机器“先做A再做B如果C成立就跳转到D”。DSPy是声明式Declarative你只定义“最终要达成什么状态”具体执行路径由框架动态编译生成。这听起来很玄但用一个例子秒懂。假设你要实现“多跳问答”Multi-hop QA用户问“特斯拉2023年Q4毛利率是多少”答案需要先查“特斯拉2023年财报发布时间”再查“该财报中Q4毛利率数据”。在LangChain里你得手动写两个检索节点、一个整合节点还要处理中间结果为空的异常流。在DSPy里你只需定义一个Signatureclass MultiHopQA(dspy.Signature): 通过两次检索获取最终答案 question: str dspy.InputField() answer: str dspy.OutputField()然后你用dspy.Predict或dspy.ChainOfThought封装这个签名DSPy的编译器会自动分析question的语义推断出需要哪些中间步骤并为每一步生成最适配的prompt。更厉害的是当你用dspy.teleprompt.BootstrapFewShot优化器训练这个程序时它不只是优化最终答案的准确率还会反向优化中间步骤的prompt质量——比如它可能发现第一步检索“财报发布时间”时加上“请只返回日期不要任何解释”这个约束能让第二步的检索精度提升12%。这种端到端的、带反馈回路的优化是命令式框架无法企及的。它把开发者从“调参工程师”解放为“逻辑架构师”你专注设计任务的因果链DSPy负责把这条链打磨成工业级流水线。2.3 编译式优化把Prompt Engineering变成可复现的工程实践第三个颠覆点在于“优化”这件事本身被重新定义。传统prompt engineering是黑盒艺术你改一个词看效果变好还是变差再猜下一个改动方向。DSPy把它变成了白盒工程。它的核心是Optimizer优化器比如BootstrapFewShot或MIPRO。这些优化器的工作原理是把你的Signature和少量标注样本few-shot examples喂进去然后启动一个迭代过程1用当前prompt生成一批预测2计算预测与真实标签的损失3根据损失梯度自动生成新的prompt变体4在验证集上评估变体效果5保留最优者进入下一轮。整个过程完全自动化且可复现、可审计、可版本化。我在实际项目中用MIPRO优化一个金融事件抽取任务输入5个样例运行3轮每轮约8分钟最终生成的prompt在测试集上F1从0.68提升到0.79。最关键的是DSPy会把每一轮的prompt变更、对应的效果变化、甚至中间生成的思维链chain-of-thought示例全部记录在日志里。你可以随时回溯“第2轮为什么把‘请列出所有事件’改成‘请按时间顺序排列事件’因为这减少了模型对非时间敏感事件的误判。” 这种透明性让LLM应用开发第一次具备了软件工程意义上的可追溯性。它不再是你个人的经验直觉而是一份可共享、可审查、可继承的工程资产。3. 核心细节解析与实操要点Signature、Module、Optimizer的三位一体3.1 SignatureDSL的基石类型即契约Signature是DSPy的DNA理解它才能避免后续所有踩坑。它远不止是一个带注释的函数签名而是一个轻量级领域特定语言DSL的声明。它的设计有三个精妙之处第一描述性descriptive而非指令性imperative。desc字段不是给模型看的指令而是给DSPy编译器看的元信息。编译器会基于desc的语义结合模型的能力图谱model capability profile自动生成符合该模型风格的prompt。比如对is_violated: bool编译器知道GPT-4偏好“True/False”而Llama3更适应“是/否”它会自动选择最稳妥的表述。如果你在desc里写“请回答Yes或No”反而会干扰编译器的智能决策这是新手最常见的错误。第二字段顺序隐含执行逻辑。虽然Python字典在3.7保持插入顺序但DSPy明确利用这一点。在ComplianceCheck签名中material_excerpt在前regulatory_clause在后DSPy会默认模型先读取材料片段再结合条款进行判断。如果你把顺序颠倒编译器生成的prompt可能会让模型先看条款再找材料逻辑就乱了。我在调试一个医疗诊断辅助模块时就是因为把symptom_description和diagnosis_criteria顺序写反导致模型总在条款里找症状而不是用症状去匹配条款F1直接掉点15%。第三OutputField支持复合类型。除了基础的str、bool、int你还可以用List[str]、Dict[str, float]甚至自定义Pydantic模型。比如一个舆情分析Signature可以这样定义from pydantic import BaseModel class SentimentScore(BaseModel): sentiment: Literal[positive, negative, neutral] confidence: float class NewsAnalysis(dspy.Signature): headline: str dspy.InputField() full_text: str dspy.InputField() main_sentiment: SentimentScore dspy.OutputField() # 复合输出 key_entities: List[str] dspy.OutputField()DSPy会自动为SentimentScore生成嵌套的JSON Schema约束并确保模型输出严格符合。这比手写正则或JSON Schema解析鲁棒得多。 提示使用复合类型时务必在OutputField中显式指定类型不要依赖default_factory。DSPy的编译器需要静态类型信息来生成prompt动态类型会导致编译失败。3.2 Module程序的积木Predict与ChainOfThought的本质差异如果说Signature是蓝图Module就是按蓝图建造的实体。DSPy提供了两类核心Moduledspy.Predict和dspy.ChainOfThoughtCoT。它们的区别决定了你程序的“思考深度”。dspy.Predict是最简形式对应“单步直觉判断”。它把Signature的所有输入字段拼成一个prompt让模型一次性输出所有OutputField。适合事实核查、简单分类等任务。例如class FactCheck(dspy.Signature): claim: str dspy.InputField() evidence: str dspy.InputField() is_supported: bool dspy.OutputField() predictor dspy.Predict(FactCheck) result predictor(claim地球是平的, evidence卫星图像显示地球是球体) # result.is_supported 为 Falsedspy.ChainOfThought则是“分步理性推理”。它会在prompt中强制模型先输出reasoning推理过程再输出answer最终答案。DSPy会自动为reasoning字段生成占位符并在解析时提取。这对复杂逻辑任务至关重要。比如一个税务计算Signatureclass TaxCalculation(dspy.Signature): income: float dspy.InputField() deductions: List[float] dspy.InputField() tax_brackets: Dict[str, Tuple[float, float]] dspy.InputField() # {rate: (min, max)} final_tax: float dspy.OutputField() # 注意这里没有显式定义 reasoning 字段当你用dspy.ChainOfThought(TaxCalculation)时DSPy会自动在prompt末尾添加“Lets think step by step.” 并在解析时从模型输出中分离出推理链和最终数值。我在测试中发现对于涉及多档税率累进计算的任务CoT的准确率比Predict高22%因为模型不会跳过中间步骤直接“猜”答案。 注意CoT不是万能的。对超短文本如单句情感分析CoT会增加不必要的token开销且可能引入冗余推理。我的经验是输入长度100字、逻辑步骤2步、输出需要精确数值时必用CoT否则用Predict更经济。3.3 Optimizer不是调参而是进化程序Optimizer是DSPy的“大脑”但新手常把它当成“prompt调优器”来用这是巨大误区。它优化的不是单个prompt而是整个Module在特定Signature下的行为策略。以BootstrapFewShot为例它的完整工作流是冷启动用初始prompt基于Signature desc生成在few-shot样本上运行得到基线预测。示例挖掘分析预测错误的样本自动提取“困难样本”hard examples——即模型置信度低或与真实标签偏差大的样本。Prompt变异对这些困难样本生成多个prompt变体。变异策略包括调整指令措辞、增减约束条件、改变输出格式要求、插入不同风格的思维链示例。评估筛选在验证集上批量运行所有变体用预设指标如accuracy、F1评分。迭代收敛保留最优变体作为下一轮的初始prompt重复2-4步直到指标提升停滞或达到最大轮数。这个过程的关键在于评估闭环。DSPy要求你提供metric函数它必须接收example原始样本、pred预测结果、trace执行轨迹并返回0-1之间的分数。我见过太多人直接用lambda example, pred, trace: 1 if example.answer pred.answer else 0这在简单任务中可行但在复杂任务中会失效。比如一个法律条款匹配任务example.answer可能是“《反洗钱法》第12条”而pred.answer是“《中华人民共和国反洗钱法》第十二条”字符串不等但语义等价。我的解决方案是在metric中集成一个轻量级语义相似度计算from sentence_transformers import SentenceTransformer sim_model SentenceTransformer(all-MiniLM-L6-v2) def legal_match_metric(example, pred, trace): if not hasattr(pred, matched_clause) or not pred.matched_clause: return 0.0 # 计算预测条款与真实条款的语义相似度 sim_score sim_model.similarity( [example.true_clause], [pred.matched_clause] )[0][0] return float(sim_score 0.8) # 阈值可调这个metric让Optimizer能真正理解“什么是好的匹配”而不是死磕字符串。 实操心得Optimizer的轮数num_trials不是越多越好。我测试过在一个中等复杂度的合同审查任务上num_trials3时F1提升3.2%num_trials5时仅再提升0.4%但耗时翻倍。建议从3轮起步观察验证集曲线若第3轮后提升0.5%立即停止。4. 实操过程与核心环节实现从零搭建一个可落地的合同风险扫描器4.1 环境准备与最小可行原型MVP别急着写复杂逻辑。先用5分钟跑通一个“Hello World”级的DSPy程序建立手感。我推荐用dspy.teleprompt.BootstrapFewShot因为它对新手最友好且能直观看到优化效果。步骤1安装与初始化pip install dspy-ai # 注意DSPy 2.0 需要 Python 3.9步骤2配置LM以OpenAI为例import dspy # 初始化OpenAI模型替换为你自己的API Key openai_lm dspy.OpenAI(modelgpt-4-turbo, api_keyyour-key-here) dspy.settings.configure(lmopenai_lm)步骤3定义最简Signatureclass SimpleQA(dspy.Signature): 回答一个简单的事实性问题 question: str dspy.InputField() answer: str dspy.OutputField() # 创建Predict Module qa_module dspy.Predict(SimpleQA) # 测试 result qa_module(question太阳系中离太阳最近的行星是什么) print(result.answer) # 应输出 水星步骤4加入BootstrapFewShot优化# 准备few-shot样本至少3个 train_examples [ dspy.Example( question法国的首都是哪里, answer巴黎 ).with_inputs(question), dspy.Example( question光速在真空中的数值是多少, answer299792458米每秒 ).with_inputs(question), dspy.Example( question水的化学式是什么, answerH2O ).with_inputs(question) ] # 初始化优化器 optimizer dspy.teleprompt.BootstrapFewShot( metriclambda ex, pred, trace: 1 if ex.answer.lower() in pred.answer.lower() else 0, max_bootstrapped_demos3, max_labeled_demos5 ) # 编译优化 compiled_qa optimizer.compile(qa_module, trainsettrain_examples) # 测试优化后效果 result_opt compiled_qa(question珠穆朗玛峰的高度是多少) print(result_opt.answer) # 对比优化前后的准确率和稳定性这个MVP的价值在于它让你亲眼看到DSPy如何把一个模糊的Signature变成一个在特定样本上表现稳定的程序。你会注意到编译后的compiled_qa对象其内部prompt比初始prompt长得多包含了精心挑选的few-shot示例和更严格的格式约束。这就是“编译”的力量——它把人类经验样本固化成了机器可执行的逻辑。4.2 构建合同风险扫描器Signature设计与模块组装现在升级到真实场景。目标输入一份PDF格式的采购合同全文输出其中所有高风险条款如无限连带责任、单方解约权、管辖法院约定在境外等及其位置页码段落号。第一步拆解任务逻辑链这不是单个Signature能搞定的。我们需要一个PipelineContractParser将PDF文本按语义分块页/段RiskDetector对每个文本块判断是否含高风险条款RiskLocator对确认的风险块精确定位页码和段落号RiskAggregator聚合所有风险生成摘要报告第二步定义核心Signatureclass RiskDetector(dspy.Signature): 检测合同文本块中是否包含高风险条款 contract_chunk: str dspy.InputField(desc合同的一个文本块通常为一段或一页) risk_types: List[str] dspy.InputField(desc预定义的高风险类型列表如[unlimited_liability, unilateral_termination]) has_risk: bool dspy.OutputField(desc该文本块是否包含任一高风险类型) risk_type: str dspy.OutputField(desc具体的风险类型如无限连带责任若has_risk为False则为空字符串) class RiskLocator(dspy.Signature): 定位风险条款在合同中的精确位置 contract_chunk: str dspy.InputField() original_page_number: int dspy.InputField(desc该文本块在原始PDF中的页码) risk_type: str dspy.InputField(desc已确认的风险类型) page_number: int dspy.OutputField(desc风险所在页码应与original_page_number一致) paragraph_number: int dspy.OutputField(desc风险所在段落号从1开始计数) risk_excerpt: str dspy.OutputField(desc风险条款的原文摘录不超过50字) # 注意这里risk_excerpt的长度限制是给编译器的重要信号它会据此生成更精准的prompt第三步组装Pipeline Moduleclass ContractRiskScanner(dspy.Module): def __init__(self, risk_typesNone): super().__init__() self.risk_types risk_types or [unlimited_liability, unilateral_termination, foreign_jurisdiction] # 使用ChainOfThought提升复杂判断的准确性 self.detector dspy.ChainOfThought(RiskDetector) self.locator dspy.ChainOfThought(RiskLocator) def forward(self, contract_text: str, page_map: Dict[int, List[str]]) - Dict: contract_text: 整个合同的纯文本 page_map: {页码: [段落1, 段落2, ...]}由PDF解析器生成 all_risks [] # 遍历每一页 for page_num, paragraphs in page_map.items(): for para_idx, paragraph in enumerate(paragraphs, 1): # 检测风险 detect_result self.detector( contract_chunkparagraph, risk_typesself.risk_types ) if detect_result.has_risk: # 定位风险 locate_result self.locator( contract_chunkparagraph, original_page_numberpage_num, risk_typedetect_result.risk_type ) all_risks.append({ page: locate_result.page_number, paragraph: locate_result.paragraph_number, risk_type: detect_result.risk_type, excerpt: locate_result.risk_excerpt, confidence: detect_result.confidence # ChainOfThought自动提供 }) return {risks: all_risks, summary: f共发现{len(all_risks)}处高风险条款} # 实例化 scanner ContractRiskScanner() # 测试用一小段模拟合同文本 test_page_map { 5: [甲方对乙方的债务承担无限连带责任。, 本合同适用中华人民共和国法律。], 12: [甲方有权在任何情况下单方面终止本合同。, 争议提交新加坡国际仲裁中心解决。] } result scanner(contract_text..., page_maptest_page_map) print(result)这个Pipeline展示了DSPy的核心优势模块化与可组合性。每个Signature职责单一可独立测试、独立优化。ContractRiskScanner本身就是一个Module可以像函数一样被调用也可以被其他更大的系统如Web API集成。4.3 编译与优化让程序学会“合同律师”的思维有了Pipeline下一步是让它真正可靠。这里必须用MIPROMeta-Interpretive Program Optimization它是DSPy中最强大的优化器专为复杂Pipeline设计。步骤1准备高质量训练样本不能随便找几个合同。我从公开的上市公司采购合同中人工标注了20份样本每份包含contract_text原始文本脱敏page_map精确的页段映射ground_truth标准答案格式为[{page:5,paragraph:1,risk_type:unlimited_liability,excerpt:甲方对乙方的债务承担无限连带责任。}]步骤2定义Pipeline级metricdef contract_risk_metric(example, pred, trace): 评估整个Pipeline的输出质量 if not hasattr(pred, risks) or not pred.risks: return 0.0 pred_risks pred.risks true_risks example.ground_truth # 计算精确匹配率页码、段落号、风险类型、摘录都一致 exact_matches 0 for true_risk in true_risks: for pred_risk in pred_risks: if (true_risk[page] pred_risk[page] and true_risk[paragraph] pred_risk[paragraph] and true_risk[risk_type] pred_risk[risk_type] and true_risk[excerpt].strip() in pred_risk[excerpt].strip()): exact_matches 1 break return exact_matches / len(true_risks) if true_risks else 0.0步骤3启动MIPRO编译# 初始化MIPRO mipro dspy.teleprompt.MIPRO( metriccontract_risk_metric, num_candidates5, # 每轮生成5个候选程序 init_temperature1.0, verboseTrue ) # 编译整个ContractRiskScanner compiled_scanner mipro.compile( ContractRiskScanner(), trainsettrain_examples[:10], # 先用10个样本试跑 requires_permission_to_runFalse ) # 在剩余10个样本上验证 val_score dspy.evaluate( devsetval_examples, metriccontract_risk_metric, num_threads4 ) print(fValidation Score: {val_score:.3f})MIPRO的魔力在于它不仅优化RiskDetector和RiskLocator各自的prompt还会优化它们之间的协作方式。比如它可能发现当RiskDetector在输出risk_type时加上“请用英文术语如unlimited_liability”的约束能让RiskLocator的输入更规范从而提升定位精度。这种跨模块的协同优化是传统方法无法想象的。5. 常见问题与排查技巧实录那些文档里不会写的血泪教训5.1 “编译卡在第X轮CPU占满但没进展”——内存与模型瓶颈这是新手最常遇到的“假死”现象。表面看是程序卡住实则是DSPy在尝试生成大量prompt变体而你的本地模型如Llama3-8B推理速度太慢或者GPU显存不足。我第一次用MIPRO编译一个三模块Pipeline时就在第2轮卡了47分钟。排查步骤检查日志级别在编译前加dspy.settings.configure(lmopenai_lm, log_openai_usageTrue)看是否在疯狂调用API。如果是说明你的num_candidates设得太大。监控资源用htop或nvidia-smi看CPU/GPU占用。如果GPU显存100%但GPU利用率10%说明是显存瓶颈模型加载失败正在CPU fallback。临时降级把num_candidates从默认5降到2max_bootstrapped_demos从3降到1先让编译跑通。终极解决方案对本地模型务必使用vLLM或llama.cpp后端它们比原生transformers快3-5倍。配置示例from dspy import vLLM vllm_lm vLLM(modelmeta-llama/Llama-3-8b-chat-hf, tensor_parallel_size2) dspy.settings.configure(lmvllm_lm)对API模型设置max_retries1和timeout30避免网络抖动导致无限重试。注意DSPy 2.4 版本修复了部分内存泄漏但如果你用的是旧版强烈建议升级。我在2.2版上遇到过编译3轮后内存增长到12GB而2.4版稳定在2.3GB。5.2 “优化后效果反而变差”——Metric设计的致命陷阱我曾在一个金融事件抽取任务中把F1从0.72优化到0.65。复盘发现metric函数里用了if pred.event_type example.event_type但模型有时输出MA而标注是Merger Acquisition。字符串不等但语义等价。避坑清单永远用语义匹配不用字符串匹配对分类任务用sentence-transformers对数值任务用abs(pred.value - example.value) tolerance。处理空输出模型可能返回空字符串或None。metric里必须有兜底def safe_metric(example, pred, trace): if not pred or not hasattr(pred, output_field) or not pred.output_field.strip(): return 0.0 # 后续逻辑...考虑置信度权重DSPy的ChainOfThought会输出confidence把它融入metric能更好反映模型“知道自己在说什么”的程度score semantic_similarity(...) * pred.confidence5.3 “Signature字段太多编译超时”——复杂Signature的拆解策略一个Signature里超过5个InputField编译时间会指数级增长。我有个法律咨询Signature定义了client_profile、case_facts、applicable_laws、precedents、jurisdiction、desired_outcome六个字段编译一轮要2小时。实战拆解法识别核心字段case_facts和desired_outcome是绝对核心其他都是辅助。先把Signature简化为只含这两个字段。用Module封装辅助信息把applicable_laws的检索做成一个独立dspy.PredictModule在主Signature的forward方法里调用它而不是作为输入字段。分阶段编译先编译LawRetrieverModule再把它作为固定组件编译主LegalAdvisorModule。这样主Module的编译只关注核心逻辑不被辅助字段拖累。5.4 “在不同模型上效果波动大”——模型无关性的真相DSPy承诺“Write once, run anywhere”但现实是GPT-4上95%准确率的程序在Llama3上可能只有78%。这不是DSPy的失败而是模型能力鸿沟的客观存在。我的应对策略模型能力画像为每个目标模型GPT-4、Claude-3、Llama3单独编译一次。用dspy.settings.configure(lmgpt4_lm)编译GPT-4版再用dspy.settings.configure(lmllama3_lm)编译Llama3版。不要试图用一个编译结果适配所有模型。Fallback机制在生产环境部署一个轻量级路由Module根据输入复杂度如字符数、关键词密度自动选择最优模型。简单查询走Llama3复杂推理走GPT-4。持续监控在metric中加入model_name参数记录每次调用的模型和效果形成模型性能热力图指导后续编译策略。最后分享一个真实案例我们为一家律所部署的合同扫描系统上线后第一周MIPRO编译的GPT-4版在测试集上F10.89但客户现场用的真实合同F1只有0.76。深入分析发现客户合同里大量使用行业黑话如“背靠背付款”而我们的训练样本全是标准文本。解决方案是用客户提供的10份真实合同作为MIPRO的devset重新编译一轮。结果F1飙升到0.85。这印证了一个朴素真理DSPy再强大也无法替代高质量、贴近场景的数据。它把“调prompt”的手艺活变成了“建数据集”的工程活。而后者才是AI落地真正的护城河。