3.5 TextGrad:文本梯度
用 LLM 生成的自然语言反馈作为梯度,反向传播优化任意变量(prompt、代码、分子、治疗方案)
从数值梯度到文本梯度
神经网络的反向传播靠数值梯度——每个参数都有一个"往哪个方向调能让 loss 变小"的向量。但复合 AI 系统里的大多数变量不是数:prompt 是字符串、分子是 SMILES、治疗方案是权重表格。怎么给它们算"梯度"?
TextGrad(Yuksekgonul et al., Nature 2025)给出的答案很激进——用自然语言批评作为梯度。
神经网络通过数值通信,现代 AI 系统通过文本、代码、图像通信。TextGrad 把"微分"视为一种隐喻:LLM 以信息丰富且可解释的自然语言批评形式提供反馈——"文本梯度"——描述每个变量应如何修改以改进整个系统。 —— Yuksekgonul et al. 2025
形式化地,考虑一个两步系统:
数值梯度要求每个函数可微。TextGrad 不要求可微——它定义一个文本梯度算子 ,对变量 产生一条批评:
输出形如:"该预测可以通过澄清单位换算来改进;步骤 3 中把 mL 当成了 L。"
文本梯度下降(TGD)
对数值优化:
对文本优化:
更新算子本身也是一次 LLM 调用——给它旧变量和批评,让它产出修改后的新变量。这就是 Textual Gradient Descent(TGD)。
整个计算图按"正向-反向"两阶段执行,反向过程中每条边都调用一次 LLM 来生成批评并传播。
TextGrad 的 PyTorch 风格 API
TextGrad 的 API 刻意模仿 PyTorch,让从神经网络迁移过来的研究者零门槛上手:
import textgrad as tg
# 声明待优化的"参数"
system_prompt = tg.Variable(
"You are a helpful assistant solving math problems.",
requires_grad=True,
role_description="system prompt"
)
# 构造前向调用
model = tg.BlackboxLLM(llm_engine="gpt-3.5-turbo", system_prompt=system_prompt)
# 定义 loss(也是一个 LLM 调用)
loss_fn = tg.TextLoss(
"Rate the correctness of the answer given the ground truth. "
"If incorrect, describe how to fix the system prompt."
)
# 训练循环
optimizer = tg.TGD(parameters=[system_prompt])
for x, y in train_data:
prediction = model(x)
loss = loss_fn(prediction, y)
loss.backward() # 反向传播文本梯度
optimizer.step() # 用梯度更新 prompt注意与 PyTorch 几乎相同的五件事:Variable、requires_grad、loss.backward()、optimizer.step()、训练循环。
两类任务:测试时优化 vs. 提示优化
TextGrad 把问题分成两类:
| 任务类型 | 待优化变量 | 目标 | 举例 |
|---|---|---|---|
| 测试时优化(Test-time Optimization) | 问题的解本身(代码、答案、分子) | 当前问题得分最大化 | LeetCode Hard 题解、GPQA 答案 |
| 提示优化(Prompt Optimization) | 系统 prompt 或 Few-shot 示例 | 在一批问题上泛化 | 为 gpt-3.5 找一个能跟 gpt-4o 媲美的 prompt |
论文中的一些数字:
- LeetCode Hard:gpt-4o zero-shot 26% → TextGrad 36%(超过 Reflexion 的 31%)
- GPQA:51% → 55%
- MMLU-Physics:91.2% → 95.1%
- 分子优化:在 58 个靶蛋白上,结合亲和力 + 类药性联合指标优于手工设计
TextGrad vs. DSPy
两个框架常被放在一起讨论,但它们的"梯度"来源完全不同:
| 维度 | DSPy | TextGrad |
|---|---|---|
| 优化信号 | 来自数据(指标 + Bootstrap 采样) | 来自 LLM 反馈(自然语言批评) |
| 参数形式 | Few-shot 示例 / 指令 | 任何文本变量(prompt、代码、SMILES) |
| 需要标注 | 需要指标(EM/F1 等) | 需要 evaluator(可以是 LLM-as-Judge) |
| 抽象层次 | Signature / Module / Teleprompter | Variable / Loss / Optimizer |
| 编程风格 | 声明式(像函数签名) | 命令式(像 PyTorch 训练循环) |
| 典型场景 | 多阶段 pipeline 的系统性优化 | 单变量或计算图的精细迭代 |
两者不互斥。实际工程中常见的模式是:用 DSPy 搭 pipeline 结构,用 BootstrapFewShot 完成粗调;再对关键 prompt 用 TextGrad 做精细打磨。TextGrad 论文本身也把 DSPy 列为 SOTA 基线之一。
一个最小的优化循环示例
用 TextGrad 优化一个"解数学题的解答"(测试时优化):
import textgrad as tg
# 1. 设置引擎
tg.set_backward_engine("gpt-4o", override=True)
# 2. 待优化的解答
question = "一个游泳池每小时进水 50 升, 漏水 12 升. 800 升需要多久?"
solution = tg.Variable(
"直接除 800 / 50 = 16 小时", # 初始错误答案
requires_grad=True,
role_description="solution to math problem"
)
# 3. 评估器(loss)
loss_fn = tg.TextLoss(
f"Evaluate this solution to: {question}\n"
"Is it correct? If not, identify the mistake and suggest how to fix it."
)
# 4. 优化 3 步
optimizer = tg.TGD(parameters=[solution])
for step in range(3):
loss = loss_fn(solution)
loss.backward()
optimizer.step()
print(f"Step {step}: {solution.value}")
# 典型输出:
# Step 0: "进水 50 减去漏水 12, 净进 38 升/小时, 800/38 ≈ 21.05 小时"模型通过一步 LLM-as-Judge 发现"你忘了漏水",把反馈反向传回 solution 变量,下一轮就自动修正。没有写任何规则,也没有标注正确答案——评估器本身就是一个 LLM。
局限与风险
TextGrad 不是魔法:
- 依赖评估器质量——如果 LLM-as-Judge 的反馈本身有偏,优化会朝错误方向跑
- 成本——每个变量每步都要 2 次 LLM 调用(前向 + 反向),长流水线成本陡增
- 收敛性——文本空间没有凸性保证,可能陷入局部震荡
- 可重现性——LLM 的随机性让同一 loop 两次可能输出不同的 prompt
实践建议:
- 把 evaluator prompt 设计得具体(不是 "is this good" 而是 "does this satisfy constraints X, Y, Z")
- 每 N 步做snapshot,保留验证集上最好的版本
- 对关键任务用多个 seed 跑多次,取众数
本节小结
| 概念 | 要点 |
|---|---|
| Textual Gradient | LLM 生成的自然语言批评,描述"如何改进变量" |
| TGD | 文本梯度下降——用批评指导 LLM 产出新变量 |
| 测试时优化 | 直接优化答案/代码/分子本身 |
| 提示优化 | 优化系统 prompt,让弱模型接近强模型水平 |
| 与 DSPy 对比 | DSPy 梯度来自数据指标;TextGrad 梯度来自 LLM 反馈 |
| 核心价值 | 把 PyTorch 式的"定义-反向-更新"范式迁移到非可微 AI 系统 |