Home 論文解説: TextGrad — テキスト自動微分によるLLMパイプライン最適化フレームワーク
投稿
キャンセル

📄 論文解説: TextGrad — テキスト自動微分によるLLMパイプライン最適化フレームワーク

論文概要(Abstract)

TextGradは、LLMを基盤とした任意の計算グラフに対して「テキストによる自動微分」を実装するフレームワークである。PyTorchがテンソルと自動微分を提供するのと同様に、TextGradはパイプライン内のあらゆる成果物(コード、テキスト、分子構造等)を表現するVariable抽象を提供し、LLMフィードバックによる自動微分をサポートする。ラベルデータ不要のゼロショット最適化を数行のコードで実現し、LeetCode Hardで2.85倍改善(20%→57%)、Big-Bench HardでDSPy比+6.1%を達成した。

この記事は Zenn記事: LLMプロンプト管理CI/CD:Langfuse×LaunchDarklyでA/Bテストと安全ロールアウト の深掘りです。

情報源

  • arXiv ID: 2406.07496
  • URL: https://arxiv.org/abs/2406.07496
  • 著者: Mert Yuksekgonul, Federico Bianchi, Joseph Boen, Sheng Liu, Zhi Huang, Carlos Guestrin, James Zou(Stanford University)
  • 発表年: 2024
  • 分野: cs.LG, cs.AI, cs.CL

背景と動機(Background & Motivation)

LLMパイプラインは複数のLLM呼び出し・ツール実行・外部API呼び出しで構成されるが、パイプライン全体の最適化は困難である。個別のプロンプトを手動チューニングしても、パイプライン全体の品質向上に直結しないことが多い。

ニューラルネットワークの最適化では、自動微分(Autograd)とバックプロパゲーションが決定的に重要だった。TextGradは、この成功パターンをテキスト空間に拡張する。数値勾配の代わりに「テキスト勾配」(自然言語のフィードバック)を使い、LLMパイプラインの任意の変数を最適化する。

Zenn記事のプロンプト管理CI/CDアーキテクチャにおいて、TextGradはLangfuseの本番フィードバックデータをテキスト損失として活用し、継続的プロンプト自動改善を実現する技術として位置づけられる。DSPyがラベルデータを必要とするのに対し、TextGradはゼロショットで動作するため、データアノテーションのコストがない。

主要な貢献(Key Contributions)

  • テキスト自動微分の統一フレームワーク: 数値勾配の代わりに自然言語フィードバック(テキスト勾配)を用いた、LLMパイプラインへのバックプロパゲーション実装
  • PyTorch準拠API: Variable、Function、Loss、TextualGradientDescentというPyTorchと同名のAPIにより、低い学習コスト
  • ゼロショット・タスク非依存の最適化: ラベルデータを必要とせず、コード生成・創薬・放射線治療計画など多ドメインに適用可能
  • 任意計算グラフのサポート: LLM呼び出し・コード実行・外部ツール呼び出しなど任意のPython関数を計算グラフのノードとして扱える

技術的詳細(Technical Details)

テキスト勾配の数学的基盤

ニューラルネットワークの自動微分では、スカラー損失 $L$ に対してパラメータ $\theta$ の勾配 $\frac{\partial L}{\partial \theta}$ を計算する。TextGradはこれをテキスト空間に拡張する。

関数 $f: \mathcal{X} \rightarrow \mathcal{Y}$($\mathcal{X}, \mathcal{Y}$ はテキスト空間)に対して、テキスト勾配 $\nabla_x L$ を以下のように定義:

\[\nabla_x L = \text{LLM}\left(\text{GradientPrompt}(x, f, y, \nabla_y L)\right)\]

ここで、

  • $x \in \mathcal{X}$: 入力変数(テキスト)
  • $y = f(x) \in \mathcal{Y}$: 出力変数
  • $\nabla_y L$: 下流からのテキスト勾配(フィードバック)
  • $\text{GradientPrompt}$: テキスト勾配を計算するためのプロンプト関数

テキスト勾配計算のプロンプト:

1
2
3
4
5
6
7
8
9
Given the following context:
- The variable x: {x.value}
  Role: {x.role_description}
- The function f applied to x: {f.description}
- The output y = f(x): {y.value}
- The downstream feedback (gradient at y): {∇y L}

Provide specific feedback on how x should be improved
to reduce the loss.
graph LR
    subgraph 順伝播["順伝播 Forward Pass"]
        V1["Variable x\n最適化対象のプロンプト\nrequires_grad=True"] -->|"Function f"| V2["Variable y\nLLM出力テキスト"]
        V2 -->|"Loss Function"| L["Loss Variable\n品質評価フィードバック"]
    end

    subgraph 逆伝播["逆伝播 Backward Pass"]
        L2["Loss.backward()"] -->|"GradientPrompt"| G2["テキスト勾配 ∇y L\n出力改善のフィードバック"]
        G2 -->|"GradientPrompt"| G1["テキスト勾配 ∇x L\n入力改善のフィードバック"]
    end

    subgraph 更新["パラメータ更新 Step"]
        TGD["TextualGradientDescent\nooptimizer.step()"] -->|"LLMで改善"| V1_new["Variable x 更新版\n改善されたプロンプト"]
    end

    L --> L2
    G1 --> TGD

Variable/Function/Loss API

TextGradの3つの基本構成要素:

Variable(変数)

1
2
3
4
5
6
7
8
class Variable:
    def __init__(self, value: str, role_description: str,
                 requires_grad: bool = False):
        self.value = value           # テキストコンテンツ
        self.role_description = role_description  # パイプラインでの役割
        self.gradients = []          # 蓄積されたテキスト勾配
        self.predecessors = []       # グラフ走査用の前ノード
        self.requires_grad = requires_grad

Variableは文字列シリアライズ可能な任意のコンテンツを保持できる:

  • Pythonコード
  • プロンプト文字列
  • 分子のSMILES表記
  • Base64エンコードされた画像

Function(関数)

任意のPython関数をVariablesからVariablesへのマッピングとして定義。組み込みFunction:

  • BlackboxLLM: LLM APIコールのラッパー
  • MultimodalLLMCall: マルチモーダル入力対応
  • カスタム関数: コード実行・外部API等

Loss(損失)

Functionの合成として定義。数値スカラーではなくテキストで品質を表現する。

バックプロパゲーションアルゴリズム

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def text_backpropagation(computation_graph, loss_variable):
    """TextGradのバックプロパゲーション

    Args:
        computation_graph: 順伝播で構築された計算グラフ
        loss_variable: 損失Variable

    Returns:
        各requires_grad Variableのテキスト勾配
    """
    # 損失の初期勾配を設定
    loss_variable.gradient = "Improve this output"

    # 逆トポロジカル順で各Functionを処理
    for function in reverse_topological_order(computation_graph):
        for input_var in function.inputs:
            if input_var.requires_grad:
                # テキスト勾配計算プロンプトを構築
                gradient_prompt = build_gradient_prompt(
                    variable=input_var,
                    function=function,
                    output=function.output,
                    downstream_gradient=function.output.gradient
                )
                # LLMがテキスト勾配を生成
                text_gradient = llm.generate(gradient_prompt)
                input_var.gradients.append(text_gradient)

TextualGradientDescent(TGD)オプティマイザ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class TextualGradientDescent:
    """テキスト勾配降下法

    PyTorchのSGDに対応するテキスト空間のオプティマイザ
    """
    def __init__(self, parameters: list, engine: str = "gpt-4o"):
        self.parameters = parameters
        self.engine = engine

    def zero_grad(self):
        """勾配をクリア"""
        for param in self.parameters:
            param.gradients = []

    def step(self):
        """テキスト勾配に基づいてVariableを更新"""
        for param in self.parameters:
            # 全テキスト勾配を集約
            aggregated = self._aggregate_gradients(param)

            # LLMに改善を依頼
            new_value = self.engine.generate(
                f"""Variable: {param.value}

                Feedback (text gradients):
                {aggregated}

                Provide an improved version of the variable."""
            )
            param.value = new_value

完全な使用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import textgrad as tg

# バックエンドLLMを設定
tg.set_backward_engine("gpt-4o", override=True)

# 最適化するプロンプトを変数として定義
system_prompt = tg.Variable(
    "あなたは技術記事の要約エキスパートです。",
    role_description="最適化対象のシステムプロンプト",
    requires_grad=True
)

# 損失関数を定義(LLMによる品質評価)
evaluator = tg.BlackboxLLM(
    engine="gpt-4o",
    system_prompt="""要約の品質を評価してください。
    基準: 1.技術用語の保持 2.簡潔さ 3.数値データの維持
    改善点を具体的に指摘してください。"""
)

# オプティマイザ設定
optimizer = tg.TextualGradientDescent(
    parameters=[system_prompt],
    engine="gpt-4o"
)

# 最適化ループ(5回反復)
for iteration in range(5):
    optimizer.zero_grad()

    # Forward pass: プロンプトで要約を生成
    summary = llm_call(system_prompt, article_text)

    # Loss: 品質評価
    loss = evaluator(summary)

    # Backward pass: テキスト勾配を計算
    loss.backward()

    # Update: プロンプトを改善
    optimizer.step()

    print(f"Iteration {iteration}: {system_prompt.value[:100]}...")

実装のポイント(Implementation)

インストールと基本設定

1
pip install textgrad
1
2
import textgrad as tg
tg.set_backward_engine("gpt-4o", override=True)

role_descriptionの重要性

TextGradの最適化品質はrole_descriptionの質に強く依存する:

role_descriptionLeetCode Hard Pass Rate
指定なし44%
“The code to optimize”50%
“Python code that solves the given LeetCode problem”57%

13ポイントの差。role_descriptionはテキスト勾配の計算に使われるため、LLMが何を改善すべきか理解するための重要な情報源である。

コスト計算

$n$ 回反復、パイプラインに $k$ 個のLLM呼び出しがある場合:

\[\text{Total LLM calls} = O\left(n \cdot \frac{k(k+1)}{2}\right)\]
パイプライン構成k5回反復のLLM呼び出し数
単一プロンプト15
2段パイプライン215
3段RAG330

Langfuse統合パターン

Langfuseの本番スコアデータをTextGradの損失として活用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Langfuseから低スコアのトレースを取得
low_score_traces = langfuse.get_traces(
    filters={"score": {"$lt": 0.7}},
    limit=10
)

# TextGradの損失としてフィードバックを注入
for trace in low_score_traces:
    user_feedback = trace.scores[0].comment
    loss = tg.Variable(
        user_feedback,
        role_description="ユーザーからの品質フィードバック"
    )
    loss.backward()

optimizer.step()
# → ユーザーフィードバックに基づいてプロンプトが自動改善

実験結果(Results)

LeetCode Hard(コード最適化)

手法Pass Rate改善倍率
GPT-4o ゼロショット20%
TextGrad (1回反復)35%1.75x
TextGrad (3回反復)48%2.40x
TextGrad (5回反復)57%2.85x

5回の反復で、ゼロショットの2.85倍の改善。各反復で実行結果のフィードバックをテキスト勾配として使用している。

GPQA Diamond(大学院レベル問題)

手法Accuracy
GPT-4o ゼロショット51%
TextGrad (3回反復)55%

プロンプト最適化(Big-Bench Hard)

手法Accuracy
DSPy83.0%
ProTeGi84.2%
TextGrad89.1%

DSPy比で+6.1ポイント。TextGradはラベルデータなしでこの結果を達成している点が重要。

DSPyとの比較

比較軸TextGradDSPy
最適化信号テキストフィードバックラベル付きデータ
データ要件ゼロショットラベル付きデータ必須
対象範囲任意のテキスト変数プロンプト・デモ
計算グラフ任意の構造構造化LLMモジュール
BBH精度89.1%83.0%

実運用への応用(Practical Applications)

Zenn記事アーキテクチャとの統合

Zenn記事の3層防御にTextGradを統合する:

  1. Layer 0(TextGrad): Langfuseの本番フィードバックからプロンプトを継続的自動改善
  2. Layer 1(Promptfoo CI/CD): TextGrad最適化後のプロンプトを品質ゲートで検証
  3. Layer 2(LaunchDarkly段階ロールアウト): 検証済みプロンプトを段階的にデプロイ
  4. Layer 3(Langfuse本番監視): 新プロンプトのメトリクスをTextGradにフィードバック

DSPyとの使い分け

  • ラベルデータがある場合: DSPy(BootstrapFewShot)で最適化。より安定した結果
  • ラベルデータがない/少ない場合: TextGrad。ゼロショットで動作
  • 両方を併用: DSPyで初期最適化 → TextGradで本番フィードバックベースの継続改善

関連研究(Related Work)

  • DSPy (Khattab et al., 2023): 宣言的LLMパイプラインのコンパイル。ラベルデータが必要な点でTextGradと相補的
  • OPRO (Yang et al., 2023): メタプロンプトベースの最適化。TextGradは計算グラフ全体を対象とするため適用範囲が広い
  • ProTeGi (Pryzant et al., 2023): TextGradの先行手法。テキスト勾配のアイデアを提案したが、計算グラフへの一般化はTextGradが初

まとめと今後の展望

TextGradは「ニューラルネットワークの自動微分」をテキスト空間に拡張した重要な研究である。PyTorch準拠のAPIにより導入障壁が低く、ゼロショットで動作するためデータ収集コストがない。

Zenn記事のLangfuse×LaunchDarkly基盤と組み合わせることで、「本番フィードバック→テキスト勾配→プロンプト改善→品質ゲート→段階ロールアウト」の完全自動ループが実現する。特にLangfuseに蓄積された低スコアトレースをTextGradの損失関数として直接活用できる点が、既存のDSPy/OPROにはない独自の強みである。

参考文献

この投稿は CC BY 4.0 でライセンスされています。

LaunchDarkly公式解説: AI ConfigsによるLLMプロンプトの段階的ロールアウトとランタイム制御

-