2.1 从手工调优到声明式编程


2.1 从手工调优到声明式编程

本节摘要:本节承担范式转折的叙事任务:从手工拼字符串的命令式写法,讲到 DSPy"声明做什么、编译器决定怎么写"的声明式主张,并用同一个问答任务的两版代码对照,说明范式转变在工程上到底换来了什么。读完本节,你应当能判断自己的项目适不适合做这次迁移。

从"改一句提示词要重新部署整个应用"到"提示词根本不写在代码里",中间隔着的不是某个技巧,而是一次立场调换:手工时代的问题是"这句提示词怎么写才好",声明式范式的问题是"这个任务的输入输出和步骤结构是什么"。问题一经调换,工作重心就变了——前者比拼语感与耐心,后者比招建模与数据。

两种范式的代码对照

先看手工范式下的问答程序。它把任务知识全部编码进一个字符串常量:

# 手工范式:提示词即源码,模型一换、需求一变就得重写 PROMPT = """请根据你的知识回答问题。要求: 1. 答案尽量简短,是事实型问题就给出事实; 2. 先简要分析再作答; 3. 答不上来就说不知道。""" def manual_qa(question: str, client) -> str: resp = client.chat( messages=[{"role": "user", "content": PROMPT + "\n问题:" + question}] ) return resp.text # 格式全靠模型自觉,解析靠运气

再看声明式范式下同一件事。注意代码里完全找不到"怎么写提示词":

import dspy class BasicQA(dspy.Signature): """回答问题,给简短的事实型答案。""" question = dspy.InputField() answer = dspy.OutputField(desc="通常不超过五个词") class BasicQAModule(dspy.Module): def __init__(self): super().__init__() self.generate = dspy.Predict(BasicQA) def forward(self, question): return self.generate(question=question) # 配置语言模型后即可运行;换模型只改这一行配置 lm = dspy.LM("openai/gpt-4o-mini", max_tokens=256) dspy.configure(lm=lm) qa = BasicQAModule() print(qa(question="法国的首都是哪里?").answer)

对照的要点藏在细节里。手工版的"先简要分析再作答"是写给模型的祈祷,模型听不听看缘分;声明式版里,"要不要分析、如何分析"由 dspy.Predict 换成 dspy.ChainOfThought 这类模块选择来表达——这是结构化决策,不是文字游戏。手工版的输出格式靠模型自觉,声明式版的字段由签名约束、解析由框架负责,pred.answer 拿到的就是干净的答案字段。手工版换模型要重新审读全部措辞,声明式版只改一行 LM 配置,因为提示词文本是编译时根据当前模型生成的。

声明式不等于变魔术

需要立刻校准预期:声明式范式没有消除"提示词质量"这个问题,而是把它从"人的语感问题"转化为"编译器的搜索问题"。你的声明(签名、模块、指标)就是搜索的约束条件,约束写得越准,编译产物越好。这带来一个反直觉的推论:迁移到 DSPy 后,写好签名描述和指标定义变得比写提示词更重要——它们是新的"源码"。很多团队迁移失败,不是框架不行,而是把写提示词的旧习惯带进来了:随便写个签名、随便给个指标,然后抱怨编译不出好结果。

还有一个常见误读要澄清:声明式不意味着黑盒。恰恰相反,DSPy 的编译过程全程可检查——编译完可以查看每个预测器学到的指令与示范(第 5 章会专门讲检查方法),dspy.inspect_history(n=3) 能翻出最近几次模型调用的完整提示词。透明度是声明式范式被工程团队接受的重要原因:你能审计每一行提示词的来历。

范式转变换来与付出的清单

换来的:模型可移植(提示词随模型自动再生成);逻辑可组合(模块嵌套代替字符串拼接);质量可搜索(编译器在示范与指令空间里自动探索);资产可沉淀(源码加数据集加指标,全部可版本化)。付出的:需要准备少量标注数据(哪怕几十条);需要定义可计算的指标;编译本身消耗额外的模型调用预算;团队需要建立新的调试直觉。总体判断标准很简单:项目会长期迭代、任务可以积累数据、模型或供应商存在变数——三条中两条,就值得迁移;一次性脚本、纯玩票的 demo,手工写提示词反而更快。

一个务实的迁移路径:不要推倒重来。先用 DSPy 把现有手工提示词包成 dspy.Predict 的自定义指令跑起来(保持行为不变),再逐模块替换为可编译形态,最后打开编译器。渐进式迁移让每一步的收益都可度量。

迁移案例:一次真实的改造记录

用一个压缩版的真实迁移案例把判断标准落到地面。某团队的合同审查助手,手工提示词维护了一年多,体量约两千字的指令加五条示例,遇到三重困境:换底层模型后错误率翻倍、法务同事每次补充审查要点都要排工程师的期、没人说得清当前版本到底比三个月前好多少。迁移按渐进路径走了三步。第一步包装:把现有提示词原样塞进自定义指令跑通 DSPy 流程,行为不变但从此有了统一的调用入口;这一步没有收益,是铺路。第二步建评估:从历史审查记录整理出一百二十条带结论的样本,切分训练与验证,第一次拿到了可信的基线分数。第三步编译:签名按审查要点重新声明,BootstrapFewShot 编译,验证集分数比手工版本高出可观的一段;最意外的收获是法务同事从此直接改签名的任务描述——描述改完重编译,不用再排工程师的期。

这个案例里最值得咀嚼的是最后一点:迁移的深层收益往往不在分数,而在协作结构的改变。领域专家第一次能直接参与"程序行为"的定义(通过签名描述与标注数据),工程师从"提示词翻译官"回到真正的工程角色。评估一个团队是否该迁移,与其测量当前的分数差距,不如评估当前的协作摩擦——摩擦越大,迁移的隐性收益越大。

本节要点回顾

  • 立场调换:从"提示词怎么写"变成"任务是什么",工作重心从语感转向建模与数据。
  • 代码对照的三个细节:行为由模块类型表达、字段由签名约束、提示词是编译产物。
  • 声明式的边界:它把提示词质量变成搜索问题,签名与指标成为新的关键源码;全程可审计,不是黑盒。
  • 迁移判断:长期迭代、可积累数据、供应商有变数,三条占两条再动手;迁移宜渐进不宜推倒。
  • 通向下一节:对照里出现的 Signature、Module、LM、编译器,正是下一节要全景梳理的核心组件。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U