论文
Prompt 优化
★ 必读
TextGrad: Automatic "Differentiation" via Text
TextGrad:把自然语言批评当梯度反向传播
Mert Yuksekgonul, Federico Bianchi, Joseph Boen, Sheng Liu, Pan Lu, Zhi Huang, Carlos Guestrin, James Zou · Stanford (Zou Group) · Nature (2025) / arXiv · 2024-06 · 被引 209
一句话 用 LLM 写的自然语言批评充当"梯度",沿计算图反传去改 prompt / 解答 / 代码 / 分子 SMILES / 放疗权重,API 照抄 PyTorch(Variable、loss.backward()、optimizer.step())。战绩:GPQA-Diamond 51%→55%、LeetCode Hard 完成率 31%→36%、BBH Object Counting 77.8%→91.9%;2025 年以 Optimizing generative AI by backpropagating language model feedback 发表于 Nature 639:609-616,GitHub 3.7k star。但五个招牌应用里,分子设计和放疗计划两个的代码至今没进仓库。
这是什么 2024 年前后,做得好的 AI 系统已经不是单个模型,而是多次 LLM 调用 + 检索 + 工具 + 评估器串起来的复合系统。这类系统里能调的"参数"是文本(prompt、指令、中间解),没有可导路径,于是每个人都在手工试 prompt。作者的类比很直接:神经网络早期也是各家手搓梯度,直到 backprop + autodiff 把优化变成 turn-key,复合 AI 系统缺的就是这一层。这条线上前面已有 ProTeGi("textual gradients" 这个词就是它的)和 DSPy(主要优化 few-shot demo 的选择),TextGrad 的定位是把它做成一个通用的、和领域无关的 autograd 引擎。
具体交付物是一个 pip 装得上的 Python 库加一套抽象:tg.Variable(文本变量,带 role_description 和 requires_grad)、tg.BlackboxLLM(前向就是一次 LLM 调用)、tg.TextLoss(用一段自然语言指令当损失函数)、tg.TGD(Textual Gradient Descent 优化器)。反向传播时,LLM 被要求写出"这个变量该怎么改才能让下游目标更好",这段文本连同变量出现的上下文一起构成"梯度";优化器再拿变量 + 梯度去问一次 LLM,要它输出改写后的变量。整个框架里没有任何数值运算。
论文把问题分成两类:instance optimization(直接把某个具体解答/代码/分子当变量,在 test time 迭代改它,不要求泛化)和 prompt optimization(优化一条 system prompt,要在整个任务分布上泛化)。两类都用同一套 gradient operator 和 TGD,不针对任务改框架 —— 这是它主打的卖点。论文用 5 个应用铺开:代码(LeetCode Hard)、解答优化(GPQA/MMLU)、prompt 优化(BBH/GSM8k)、分子设计(DOCKSTRING 58 靶点)、前列腺癌放疗计划(5 位患者)。S2 记录引用 209 次。
全文最该先看的一张。(a)(b) 并排给出核心类比:左边是数值梯度沿网络回传,右边是把 ∂Loss/∂Prompt 换成 "this prompt can be improved by…" 这样的自然语言反馈,沿着 LLM/搜索引擎/工具调用组成的黑箱图回传。(c) 是抽象对照表,Tensor→Variable、ResNet50→BlackboxLLM、CrossEntropyLoss→TextLoss、SGD→TGD,前向/反向/更新三行代码与 PyTorch 完全一致 —— 这就是它主打的"会 PyTorch 就会 80%"。(d)(e)(f)(g) 各给一个应用的一步优化实例:分子(Vina -4.3→-7.5 kcal/mol)、代码(补上 nums[i]==k 的边界情况)、放疗(调高膀胱/直肠权重)、prompt(Object Counting 77.8%→91.9%)。 机制与做法 "梯度"到底是什么对象
形式上 ∂L/∂v = LLM_backward(v, w, ∂L/∂w),其中 w 是 v 的后继。当 v 有多个后继时,把所有后继传回来的反馈取并集(实现上就是拼接)—— 对应"一个变量在多处被使用,就从每个使用场景各收一份反馈"。
落到代码里,梯度对象就是一段格式化文本。textgrad/prompts.py 的 GRADIENT_TEMPLATE 是:<CONVERSATION>{变量出现的上下文}</CONVERSATION> + "这段对话可能是更大系统的一部分,输出被用作 {下游角色}" + <FEEDBACK>{LLM 写的批评}</FEEDBACK>。也就是说"梯度"= 上下文 + 批评,前半截提供"这个变量在链条里的位置",后半截才是方向信息。
计算量按边算:图里有 n 条边,每轮优化最多多出 n 次 LLM 调用(每条边一次 gradient operator)。instance optimization 的典型开销是每轮 3 次 gpt-4o 调用(算 loss、收梯度、更新变量)。
分子优化的完整证据链,也是 Nature 叙事的主要支柱。(a) 从一个苯环片段出发,三轮"梯度"(加能形成氢键的官能团 → 加疏水基/芳环)把 Vina 从 -4.2 推到 -7.5 kcal/mol、QED 从 0.44 推到 0.79。(b) 看整体分布:灰色是起始片段,蓝色是 TextGrad 产物,红色是 DrugBank 已上市药 —— 蓝色云团整体偏右(QED 更高),纵向与红色重叠(Vina 相当),这就是"与临床药物竞争性"的原始依据,注意它只是分布重叠而非逐靶点胜出。(c) 单个靶点(PPARA)的十轮轨迹与三个已上市药的位置对比。(d) 结构相似度低(Tanimoto 0.38),用来论证"不是抄药"。(e) 对接姿态。全部为 in silico,无湿实验验证。 TGD 优化器本身就是一个改写 prompt
textgrad/optimizer/optimizer_prompts.py 把优化器整个写死成一段 prompt:系统提示是"你是优化系统的一部分,会收到反馈,用反馈改进这个变量……反馈可能有噪声,自己判断哪些重要哪些正确",然后 TGD_PROMPT_PREFIX 把 <ROLE>(变量角色)、<VARIABLE>(当前值)、<CONTEXT>(即梯度)拼进去,要求把改进后的变量放在 <IMPROVED_VARIABLE> 标签里,"标签之间的文本会直接替换该变量"。
论文里那些优化技巧的实现同样朴素:momentum = 把过去几轮的变量值贴进 prompt,并加一句"如果多轮反馈都相似,说明改动不够,请改得更狠一点"(MOMENTUM_PROMPT_ADDITION);constrained optimization = 把自然语言约束贴进 <CONSTRAINTS>;batch = tg.sum 把多个 loss 求和,反传时把各 loss 的反馈拼起来。读这个文件比读论文的公式更快理解这个方法的真实分量。
放疗计划优化,是全文样本量最小(5 位前列腺癌患者)但结论最抢眼的一节。(a) 剂量分布随迭代变化,初始时剂量四处外溢,5 轮后收拢到 PTV。(b)(c) 是要重点看的两条轨迹:PTV 平均剂量从 79 Gy 降到贴住 70.2 Gy 的临床目标线;而膀胱/直肠剂量在第 1 轮先被顶到 31 Gy 冲破临床上限(红线 30 Gy),之后几轮才压回来 —— 直观展示了这种"顾此失彼再回摆"的贪心过程,只跑 5 轮的话中途是可能停在违反约束的状态上的。(d)(e) 与医生方案的定量对比(PTV 平均剂量偏差 +0.51 vs +1.97 Gy;直肠平均剂量 17.18 vs 23.88 Gy),括号里是 5 例的标准差,直肠那项达 4.20,样本量小要注意。这一节对应的代码至今未开源(issue #186、#203)。 训练循环里有一个论文只提了一句的关键部件
prompt 优化的设置是:batch size 3、12 步,总共只看 36 个训练样本(有放回随机采样),前向模型 gpt-3.5-turbo-0125,反传/优化引擎 gpt-4o。这是典型的"用贵模型的反馈把便宜模型调好,之后只付便宜模型的推理钱"。
关键在于 evaluation/prompt_optimization.py 每一步 optimizer.step() 之后会调用 run_validation_revert:在验证集(GSM8k 是 300 题)上重新评测,只有变好才保留新 prompt,变差就回滚。论文正文只用一句"if the performance is better than the previous iteration we update the Prompt"带过,但这实质上是一个验证集上的贪心选择机制,把"文本梯度可能给出坏方向"这件事挡掉了 —— 评估 TextGrad 的收益时必须把这部分算进去。
instance optimization(GPQA/MMLU)的循环不同:loss 是 LLM(问题 + 当前解 + test-time instruction),做 3 轮更新,然后对 4 个版本的解答做多数投票得到最终答案。
两个科学应用怎么接进来
分子设计:变量就是 SMILES 字符串,loss 由两个外部工具算 —— AutoDock Vina(经 DOCKSTRING 标准化流程)给结合亲和力,RDKit 的 Chem.QED 给成药性;两个目标天然冲突(docking 偏爱大分子,QED 偏爱小分子)。DOCKSTRING 58 个靶点,每个靶点从 3 种起始官能团片段各跑 10 轮。梯度就是 LLM 看到两个分数后写的"加个能形成氢键的官能团"之类的建议。
放疗计划:变量是一串超参字符串 "weight for PTV: [..], weight for bladder: [..], weight for rectum: [..]…";内层用数值优化器 matRad 求出剂量分布 P(θ),外层用 TextGrad 调这些权重,loss 由 LLM 比较 P(θ) 与临床目标得出。为了让 LLM 理解权重和结果的关系,TGD 步骤里还额外塞了 N 组 (计划, 超参) 配对当上下文。
两个应用抽掉包装其实是同一件事:LLM 当黑箱超参/结构搜索器,领域模拟器当 loss。这也解释了为什么它们不需要训练集就能出结果,以及为什么结论都只是 in silico(论文 Discussion 自己写明"真正的检验需要实验和临床评估,超出本文范围")。
关键结果 解答优化(gpt-4o,3 轮 test-time 更新 + 多数投票):GPQA-Diamond 从 CoT 的 51.0% 到 55.0%(OpenAI 官方报告 53.6%);MMLU-Machine Learning 85.7%→88.4%;MMLU-College Physics 91.2%→95.1%。三个子集分别只有 198 / 112 / 92 题,表里没有误差棒。 代码优化(LeetCode Hard,39 题,5 seed 平均):zero-shot 0.26,Reflexion(1 个示例、5 轮)0.31±0.012,TextGrad(0 示例、5 轮)0.36±0.018。这是全文唯一给了多 seed 标准差的表。 prompt 优化(gpt-4o 反传、gpt-3.5-turbo-0125 前向,只改 instruction 不加示例):Object Counting 77.8%→91.9%(DSPy BFSR + 8 示例是 84.9%);Word Sorting 76.7%→79.8%(与 DSPy 打平);GSM8k 72.9%→81.1%(与 DSPy 打平)。作者指出两者互补:把 DSPy 挑的示例接到 TextGrad 优化后的指令上,GSM8k 可到 82.1%。 分子设计:DOCKSTRING 全部 58 个靶点上,Vina 和 QED 都能同时改善,且与起始片段无关;在 29 个有已上市药物的靶点上,生成分子的 QED 高于临床药物、Vina 相当,与最相似的临床药物结构相似度低(示例中 Tanimoto 0.38 / Tversky 0.36)。全部是 in silico 评估。 放疗计划(5 位前列腺癌患者,与临床医生方案对比):PTV 平均剂量偏差 +0.51 Gy(医生 +1.97),D95 偏差 0.00 Gy(医生 -0.10);膀胱平均剂量 20.92 Gy vs 医生 22.39,直肠 17.18 Gy vs 医生 23.88。样本 5 例,标准差都不小(直肠 4.20)。 工程侧:MIT 许可,PyPI 与 conda-forge 都有,readthedocs 文档 + 5 个 Colab 教程,3707 star。仓库 main 分支最后一次 push 是 2025-07-25 —— 到 2026-08 已停更约 13 个月,期间积压了 10 个 open issue/PR。 实证核查
有水分 框架本身是真东西:代码可装、抽象清晰、prompt/解答/代码三类任务的评测脚本都在仓库里跑得通,方法论也被后续工作(GEPA、各类 APO 评测)当成标准基线,这部分扎实。打折的是宣传口径:Discussion 说 "fully open-source",但分子和放疗这两个最出彩、最能支撑 Nature 叙事的应用没有代码;benchmark 上的领先幅度落到题目数上只有几题,而 GSM8k 基线被用户复现出高 6.6pp 的结果、GPQA 的增益混入了多数投票,都没有被解释清楚。
论文 Discussion 明确列出三条原则,第三条是 "It is fully open-source";五个应用中包含分子设计和放疗计划。
仓库里没有这两个应用的代码。GitHub code search 在 zou-group/textgrad 里搜 vina、smiles、matRad 命中数都是 0;evaluation/ 目录只有 code_optimization/、prompt_optimization.py、solution_optimization.py(+ 多模态版),textgrad/tasks/ 只有 big_bench_hard、gpqa、gsm8k、leetcode、mmlu。issue #186 有人问放疗代码,作者 shengliu66 回复"有开源计划但不是近期,可能 2025 年底";issue #203(至今 open,无人回复)还在问放疗教程。仓库 main 最后 push 2025-07-25,依然没有。
GSM8k 上 0-shot CoT 基线 72.9%,TextGrad 优化后 81.1%(+8.2pp)。
issue #198 有用户按 README 给的命令原样运行(python prompt_optimization.py --task=GSM8K_DSPy --run_validation --do_not_run_larger_model --evaluation_engine=gpt-4o --test_engine=gpt-3.5-turbo-0125),用论文那条初始 prompt 在 1319 题测试集上得到 79.5%,比论文基线高 6.6pp;维护者 vinid 只回了"可能是评测噪声",随后 issue 关闭,没有给出结论或复核。若 79.5% 才是真实基线,TextGrad 的净增益从 8.2pp 缩到 1.6pp。
prompt 优化表里 TextGrad 大幅超过 0-shot CoT、在 Object Counting 上超 DSPy 7pp。
两个折扣。(1) 仓库自己承认不稳:evaluation/results/prompt_optimization/README.md 的 Notes 写 "since we are running prompt optimization with only a handful of examples from the training set, the training procedure has high variance",而 prompt 优化那张表既没有 seed 数也没有标准差(同一篇论文的 LeetCode 表给了 5 seed ±)。(2) 成绩包含验证集选择:evaluation/prompt_optimization.py 每步调用 run_validation_revert,在验证集上评测并在变差时回滚 prompt —— 这部分收益不能归给"文本梯度指对了方向"。
TextGrad 把 GPQA 的 zero-shot 准确率从 51% 提到 55%,是当时该数据集最好结果。
口径不对齐。论文 §3.2 的方法段写明 TextGrad 侧是"3 轮 test-time 更新 + 对所有解做多数投票",而 CoT 基线是单次生成;全文 grep majority / self-consistency 只有这一处,没有做"同等采样预算的多数投票"对照,所以这 4pp 里有多少来自 self-consistency 而非文本梯度无法区分。另外 appendix 写明 GPQA-Diamond 只有 198 题,4pp ≈ 8 题,且是单次运行无误差棒。
在 LeetCode Hard 上把 SOTA(Reflexion)的 31% 提到 36%,摘要写 "20% relative performance gain"。
三处需要打折。(1) 附录 code_optimization.tex 自述"我们不得不用作者的 pipeline 重新抽取 LeetCodeHard 数据集,因此我们用的很可能不是 Reflexion 论文用的那个数据集" —— 跨论文数字不可直接比。(2) 同一附录写明数据集只有 39 题,0.36 与 0.31 之差约等于 2 题。(3) 摘要那个 "20% relative" 对不上表里任何一对数:0.31→0.36 是 +16% 相对,0.26→0.36 是 +38%。
反向传播实现照抄 PyTorch,batch 优化对应 backprop through addition,可靠。
batch 路径里有个未修的重复累加。textgrad/autograd/algebra.py 的 Sum.backward 在同一次循环里对 variable.gradients 连续 add 了两个内容完全相同的反馈 Variable(约 L94 和 L101),导致 batch prompt 优化时每条 loss 的反馈在 TGD prompt 里出现两遍。issue #202 提问、PR #200(2025-08-24 提交)给了修复,截至 2026-08 两者都还是 open 状态未合。这条路径正是论文 prompt 优化结果所用的路径。
框架"general and performant",不针对领域手工调就能开箱用。
第三方评测里表现是"有效但不最强"。知识图谱三元组抽取的系统性 APO 评测(arXiv 2506.19773)在 SynthIE 上:baseline prompt 三元组 F1 0.62,DSPy 0.72,APE 0.69,TextGrad 0.67 —— 三个优化器都超基线,TextGrad 最弱。多目标场景更明显:ACL 2026 CustomNLP4U workshop 的 failure-modes 研究(textgrad-failure-modes.github.io)把 TextGrad 扩到 4 个 LLM-judge 评分标准,发现让 gradient LLM 一次处理多标准时梯度的针对性从 9.0 掉到 3.7(-59%,两组分布无重叠),把各自单目标优化好的指令拼进一个 prompt 会让 SummEval 的 Spearman ρ 从 0.305 降到 0.220。
与我们方向的关系 对 ai4ai 这条线,TextGrad 真正值得拿走的是抽象而不是数字。它把"一段带上下文的批评文本"确立为可沿图传播的一等对象,把优化器实现成一个固定的改写 prompt,于是任何"多次 LLM 调用 + 外部工具/模拟器打分"的 pipeline 都能套上 Variable / backward / step 三件套 —— 我们要搭自动改进 pipeline 的骨架,直接拿 textgrad 当地基的成本很低(MIT、pip 可装、教程齐)。它的放疗那一节尤其值得看:变量是一串超参字符串,内层照旧用数值优化器,LLM 只负责"读懂结果、调权重",这是把 LLM 塞进现有科学计算流程最省事的接法,我们组自己的模拟器也可以照这个模式接。但要注意仓库已停更约 13 个月、batch 路径还有未合的重复累加 bug,真上生产得自己 fork。
另一半价值是反面教材。这篇论文的评测把该踩的坑都踩了一遍:小测试集(39/92/112/198 题)上报 4pp 的单次结果、关键增益的基线没对齐采样预算(多数投票 vs 单次)、把验证集回滚这种强部件写在一句话里、摘要的相对提升与表格对不上、最亮眼的两个应用不放代码。我们组写自己的实验章节时可以照这个清单反查:每个数字给几个 seed、基线是否同预算、有没有把"选择机制"的收益错记到"方法"头上。另外在方法选择上,同方向的 GEPA(反思式进化 + Pareto 前沿 + 候选合并)对搜索的处理明显更成熟,而 TextGrad 只有一条贪心轨迹加验证集回滚,在多目标下已被第三方证明会退化 —— 比较务实的组合是用 TextGrad 的计算图抽象承载"什么在被优化",用 GEPA 一类的搜索策略决定"怎么走"。
阅读笔记 几个读的时候容易被绕过去的地方:(1) arXiv 版和 2025 年 Nature 版(639:609-616)的数字口径不完全一致,引用时务必标明是哪一版,本笔记的表格数字取自 arXiv 的 tex 源码。(2) 摘要里 "20% relative performance gain" 在正文表里找不到对应,正文文字还出现过 "23% for gpt-4o zero-shot" 与表中 0.26 不一致,说明数字在改版过程中没同步干净。(3) 库正在把 engine 迁到 litellm,新引擎要用 "experimental:gpt-4o" 这种前缀,README 自己标了 experimental 并说旧引擎将废弃 —— 起新项目建议直接用新引擎,但注意 issue #204 报了新引擎的 sqlite 缓存报错。(4) 社区口碑两极:HN 讨论 41640812 里有人批评它"让人误以为这不只是一个 prompt 在优化另一个 prompt",另一位说 DSPy 已经够绕、TextGrad 干脆看不懂 —— 抽象层的心智成本是真实存在的,给组里同学介绍时最好直接从 optimizer_prompts.py 那几段 prompt 讲起,比讲公式快。
材料清单 TeX 源码 已存档:Raw/textgrad/source/
同类条目