Zenn
📘

Tree of Thoughts (ToT):わかりやすい実践ガイド

2025/03/26に公開

1. ToTとは何か? - 基本概念

Tree of Thoughts(ToT)は、言語AIがより複雑な問題を解決するための手法です。通常のAIの使い方では一度に答えを出そうとしますが、ToTでは人間のように「考えるプロセス」を段階的に分けて、複数の可能性を検討しながら最良の解決策を見つけます。

簡単に言えば、ToTは「一度に一本道で考える」のではなく、「木の枝のように複数の可能性を広げながら考える」方法です。

ToTの特徴

  1. 複数の思考経路を同時に検討: 一つの答えに直進するのではなく、複数の可能性を「枝」として広げる
  2. 自己評価能力: AIが自分の思考プロセスの良し悪しを判断できる
  3. 探索とバックトラック: 行き詰まったら別の可能性に戻って再検討できる

従来手法との比較

手法 どう考えるか 例え
通常プロンプト 直線的に一発で回答 目的地までの一本道
Chain of Thought 一本道だが段階的に考える 道順を書きながら進む一本道
Tree of Thoughts 複数の可能性を枝分かれして検討 地図を見ながら複数ルートを比較

2. ToTの4つの基本ステップ

ToTを実装するには、以下の4つのポイントを考える必要があります:

2.1 問題を適切なステップに分解する

問題を解きやすい単位に分けます。たとえば:

  • 数学パズル: 各計算ステップ
  • 文章作成: 段落やプロット
  • クロスワード: 単語や文字

重要なのは、AIにとって「考えられる大きさ」であることです。書籍全体は大きすぎますし、単語一つは小さすぎるかもしれません。

2.2 複数の可能性(思考)を生成する

各ステップで複数の選択肢を考えます。方法としては:

  1. バラバラに複数案を生成する: 創造的な問題に向いています
  2. 関連性を持たせながら複数案を出す: 制約のある問題(数学、クロスワードなど)に向いています

2.3 思考の良し悪しを評価する

生成した思考の中から良いものを選びます:

  1. 個別評価: 各アイデアを個別に「良い/普通/ダメ」などと評価
  2. 比較評価: 複数のアイデアを比較して「どれが一番良いか」を判断

2.4 探索方法を選ぶ

問題によって適した探索方法を選びます:

  1. 幅優先探索: 各ステップで複数の良い案を同時に進める(浅く広く)

    • 短い手順で解ける問題に向いている
    • 例:簡単な数学パズル、文章の構成を考える
  2. 深さ優先探索: 一つの案を最後まで試し、ダメなら別の案に戻る(深く掘り下げる)

    • 複雑な制約がある問題に向いている
    • 例:クロスワードパズル、複雑な計画立案

3. ToTの実装方法

ToTは難しく聞こえますが、実際には3つの方法で実装できます。

3.1 コードによる実装(TypeScript版)

コードを使うと最も自由度が高く細かく制御できます:

// 思考ノードを表すクラス
class ThoughtNode {
  content: string;
  parent: ThoughtNode | null;
  children: ThoughtNode[];
  value: number | null;

  constructor(content: string, parent: ThoughtNode | null = null) {
    this.content = content;
    this.parent = parent;
    this.children = [];
    this.value = null;
  }

  addChild(content: string): ThoughtNode {
    const child = new ThoughtNode(content, this);
    this.children.push(child);
    return child;
  }
}

// 思考を生成する関数
async function generateThoughts(
  client: any, 
  state: string, 
  numThoughts: number = 5
): Promise<string[]> {
  const prompt = `
    現在の考え方は以下の通りです:
    ${state}
    
    可能性のある次の${numThoughts}つの異なる思考を生成してください。
  `;
  
  const response = await client.messages.create({
    model: "gpt-4",
    max_tokens: 1000,
    temperature: 0.7,
    messages: [
      { role: "system", content: "あなたは問題解決の専門家です。" },
      { role: "user", content: prompt }
    ]
  });
  
  // レスポンスから思考を抽出
  const thoughts = parseThoughts(response.content);
  return thoughts;
}

// 思考を評価する関数
async function evaluateThoughts(
  client: any, 
  thoughts: string[]
): Promise<number[]> {
  const prompt = `
    以下の思考が問題解決にどれだけ有望かを評価してください:
    ${thoughts.join('\n\n')}
    
    各思考を1〜10の尺度で評価し、10が最も有望です。
  `;
  
  const response = await client.messages.create({
    model: "gpt-4",
    max_tokens: 500,
    temperature: 0.2,
    messages: [
      { role: "system", content: "あなたは評価の専門家です。" },
      { role: "user", content: prompt }
    ]
  });
  
  // レスポンスから評価値を抽出
  const evaluations = parseEvaluations(response.content);
  return evaluations;
}

// 幅優先探索によるToT実装
async function bfsSearch(
  client: any, 
  rootNode: ThoughtNode, 
  maxDepth: number = 3, 
  beamWidth: number = 5
): Promise<string> {
  let currentLevel: ThoughtNode[] = [rootNode];
  
  for (let depth = 0; depth < maxDepth; depth++) {
    let nextLevel: ThoughtNode[] = [];
    
    // 現在のレベルの各ノードに対して子ノードを生成
    for (const node of currentLevel) {
      const thoughts = await generateThoughts(client, node.content);
      
      // 各思考に対して子ノードを作成
      for (const thought of thoughts) {
        const child = node.addChild(thought);
        nextLevel.push(child);
      }
    }
    
    // 子ノードを評価
    if (nextLevel.length > 0) {
      const evaluations = await evaluateThoughts(
        client, 
        nextLevel.map(node => node.content)
      );
      
      // 評価に基づいて上位ノードを選択
      const nodeEvalPairs = nextLevel.map((node, i) => ({
        node,
        eval: evaluations[i]
      }));
      
      nodeEvalPairs.sort((a, b) => b.eval - a.eval);
      nextLevel = nodeEvalPairs
        .slice(0, beamWidth)
        .map(pair => pair.node);
    }
    
    currentLevel = nextLevel;
  }
  
  // 最終レベルの最良ノードを返す
  if (currentLevel.length > 0) {
    const finalEvaluations = await evaluateThoughts(
      client, 
      currentLevel.map(node => node.content)
    );
    
    const bestNodeIndex = finalEvaluations.indexOf(
      Math.max(...finalEvaluations)
    );
    const bestNode = currentLevel[bestNodeIndex];
    
    // 最良パスを再構築
    return reconstructPath(bestNode);
  }
  
  return "解決策が見つかりませんでした";
}

3.2 LangChainを使用した実装(TypeScript)

LangChainというライブラリを使えば、より簡単に実装できます:

import { ChatOpenAI } from "langchain/chat_models/openai";
import { PromptTemplate } from "langchain/prompts";
import { LLMChain } from "langchain/chains";

// 思考生成用のプロンプト
const thoughtGenerationPrompt = new PromptTemplate({
  inputVariables: ["currentState"],
  template: `
    現在の考え方:
    {currentState}
    
    可能性のある次の5つの異なる思考を生成してください。それぞれの思考について、なぜ有望かを説明してください。
  `
});

// 思考評価用のプロンプト
const thoughtEvaluationPrompt = new PromptTemplate({
  inputVariables: ["thoughts"],
  template: `
    以下の思考が問題解決にどれだけ有望かを評価してください:
    {thoughts}
    
    各思考について:
    1. 1〜10の評価
    2. 評価の理由を簡潔に
    3. 「有効」「無効」「部分的に有効」のいずれかに分類
    
    最後に、思考を最も有望なものから順にランク付けしてください。
  `
});

// LLMの初期化
const llm = new ChatOpenAI({
  modelName: "gpt-4",
  temperature: 0.7
});

// チェーンの作成
const generationChain = new LLMChain({
  llm,
  prompt: thoughtGenerationPrompt
});

const evaluationChain = new LLMChain({
  llm,
  prompt: thoughtEvaluationPrompt
});

// LangChainを使ったToT実装
async function totWithLangchain(
  initialState: string, 
  maxDepth: number = 3, 
  beamWidth: number = 3
): Promise<string> {
  let currentStates: string[] = [initialState];
  
  for (let depth = 0; depth < maxDepth; depth++) {
    let allNewStates: string[] = [];
    
    // 現在の各状態に対して新しい思考を生成
    for (const state of currentStates) {
      // 思考の生成
      const generationResult = await generationChain.call({
        currentState: state
      });
      const newThoughts = parseThoughts(generationResult.text);
      
      // 新しい状態を作成
      const newStates = newThoughts.map(thought => 
        updateState(state, thought)
      );
      allNewStates.push(...newStates);
    }
    
    // 生成された全ての新しい状態を評価
    if (allNewStates.length > 0) {
      const evaluationResult = await evaluationChain.call({
        thoughts: allNewStates.join("\n\n")
      });
      const rankings = parseRankings(evaluationResult.text);
      
      // 評価に基づいて上位の状態を選択
      const rankedStates = allNewStates
        .map((state, index) => ({
          state,
          rank: rankings[index]
        }))
        .sort((a, b) => b.rank - a.rank);
      
      currentStates = rankedStates
        .slice(0, beamWidth)
        .map(item => item.state);
    }
  }
  
  // 最終的な解決策を生成
  return generateSolution(currentStates[0]);
}

3.3 プロンプトだけで実装する方法

プログラミングなしで、単一のプロンプトでToTを簡易的に実装できます:

【専門家による思考プロセス】

この問題について3人の異なる専門家が考えていると想像してください。
各専門家は段階的に思考し、意見を共有します。
間違いに気づいた専門家は、その時点で議論から抜け、他の専門家の考えを聞きます。

問題は次の通りです:
[問題の詳細]

専門家たちは以下の手順で考えてください:

ステップ1: 各専門家は問題に対する最初の考えを述べます。
ステップ2: お互いの考えを分析し、最も有望なアプローチを発展させます。
ステップ3: さらに考えを深め、最終的な解決策を導き出します。

専門家たちは常に理由を説明し、他の専門家の考えも尊重してください。
最も説得力のある解決策を最終的な答えとしてください。

4. ToTの実際の応用例

4.1 数学パズル「24ゲーム」

4つの数字(例:4, 9, 10, 13)を使って、四則演算だけで「24」という数を作るゲームです。

ToTアプローチ:

  1. 最初のステップで5つの異なる計算方法を考える(例:13-9=4、4+9=13など)
  2. 各計算結果から次のステップの計算方法を考える
  3. 各ステップで「確実に24になる」「たぶん24になる」「絶対に24にならない」と評価
  4. 有望なパスを優先的に探索

コード実装イメージ:

// 24ゲームの思考評価関数
async function evaluate24GameThought(
  client: any, 
  numbersLeft: number[], 
  currentResult: number
): Promise<string> {
  const prompt = `
    残りの数字: ${numbersLeft.join(', ')}
    現在の計算結果: ${currentResult}
    
    これらの数字を使って24を作れるかどうか評価してください:
    - 「確実」:確実に24を作れる
    - 「たぶん」:有望だがまだ確信できない
    - 「不可能」:絶対に24にならないと証明できる
    
    理由も簡潔に説明してください。
  `;
  
  // 省略(評価ロジック)
  return evaluation;
}

結果:

  • 通常のChain of Thoughtでは4%の成功率
  • ToTでは74%の成功率を達成

4.2 クリエイティブライティング

4つのランダムな文を末尾に持つ4段落の文章を作成するタスクです。

ToTアプローチ:

  1. 複数の文章構成プランを考える
  2. プラン同士を比較して最良のものを選ぶ
  3. 選んだプランに基づいて複数の文章案を作る
  4. 文章案を比較して最良のものを選ぶ

実装イメージ:

// クリエイティブライティングのToT実装
async function creativeWritingTot(
  client: any, 
  sentences: string[]
): Promise<string> {
  // 1. 複数の文章構成プランを生成
  const plans = await generateWritingPlans(client, sentences, 5);
  
  // 2. プランを評価して最良のものを選択
  const bestPlan = await voteForBestPlan(client, plans);
  
  // 3. 選択されたプランに基づく文章案を生成
  const passages = await generatePassages(client, bestPlan, sentences, 5);
  
  // 4. 文章案を評価して最良のものを選択
  const bestPassage = await voteForBestPassage(client, passages);
  
  return bestPassage;
}

結果:

  • 人間の評価で、ToTが生成した文章の方が一貫性が高いと判断

4.3 クロスワードパズル

ヒントを元に5×5のクロスワードパズルを解くタスクです。

ToTアプローチ:

  1. 最も確信度の高い単語から埋めていく
  2. 既に入力した文字との整合性を常にチェック
  3. 行き詰まったら別の単語に戻ってやり直す(バックトラック)

実装イメージ:

// クロスワードパズルのDFS実装
async function crosswordDfs(
  client: any, 
  clues: {[key: string]: string}, 
  currentBoard: string[][], 
  depth: number = 0, 
  maxDepth: number = 10
): Promise<string[][] | null> {
  // 完成またはステップ上限に達したらボードを返す
  if (depth >= maxDepth || isBoardComplete(currentBoard)) {
    return currentBoard;
  }
  
  // 次に埋める単語ヒントを選択
  const nextClue = selectNextClue(clues, currentBoard);
  
  // 候補となる単語を生成
  const wordCandidates = await proposeWords(client, nextClue, currentBoard);
  
  // 確信度に基づいて並べ替え
  const sortedCandidates = sortByConfidence(wordCandidates);
  
  // 各候補を試す
  for (const [word, confidence] of sortedCandidates) {
    // 単語を配置した新しいボードを作成
    const newBoard = placeWord(currentBoard, nextClue, word);
    
    // ボードが有効かどうか評価
    if (await isBoardValid(client, newBoard, clues)) {
      // 探索を続行
      const result = await crosswordDfs(
        client, 
        clues, 
        newBoard, 
        depth + 1, 
        maxDepth
      );
      
      if (result !== null) {
        return result;
      }
    }
  }
  
  // すべての候補が失敗したらバックトラック
  return null;
}

結果:

  • 通常手法の単語成功率15.6%に対し、ToTは60%を達成

5. 実用的なヒントとベストプラクティス

タスク別の最適なアプローチ

タスクタイプ おすすめの探索方法 思考の単位 評価方法
数学/論理パズル 幅優先探索 式/ステップ 確実/たぶん/不可能
文章作成 浅い木+投票 段落/構成 人間らしさ/一貫性
検索問題(クロスワード等) 深さ優先+バックトラック 単語/制約 制約満足度
計画問題 幅優先/深さ優先 アクション 目標達成度

実装時の注意点

  1. 思考単位の選択:

    • 大きすぎず、小さすぎない適切なサイズを選ぶ
    • AIが評価できる具体性を持たせる
  2. 評価方法の工夫:

    • 明確な評価基準を設定する
    • 理由付けを求めることで質を高める
    • 複数の評価を集約する
  3. リソース管理:

    • 探索の幅と深さのバランスを取る
    • 明らかに不適切な思考は早めに排除する
    • 適切なモデルを選択する(温度設定も重要)

6. ToTが他のプロンプト手法より優れている根拠

ToTが従来のプロンプト手法よりも大幅に優れていることは、複数の実験結果や性能比較から明らかになっています。

具体的な性能比較データ

タスク 通常プロンプト (IO) Chain of Thought (CoT) ToT (Tree of Thoughts)
Game of 24(成功率) 7.3% 4.0% 74%
クロスワード(単語成功率) 14% 15.6% 60%
クリエイティブライティング(人間評価) 21% 38% 41% (「より良い」と評価された割合)

これらの数値は、Princeton大学とGoogle DeepMindの研究者による原論文の実験結果から得られたものです。特にGame of 24では、ToTが従来手法に対して約18倍もの成功率向上を示しており、複雑な問題解決におけるToTの圧倒的な優位性を証明しています。

ToTが優れている理由

  1. 探索能力の向上:

    • 従来の手法は直線的に思考するため、初期の誤りがそのまま最終結果に影響する
    • ToTは複数の思考経路を同時に検討するため、より良い解決策を見つけやすい
  2. 自己修正能力:

    • 標準的なAIの使い方では、一度間違えると修正が難しい
    • ToTでは各ステップで評価を行い、有望でない経路を早期に捨てられる
  3. バックトラッキング:

    • 行き詰まりに対処する能力がある(クロスワードパズルで特に効果的)
    • これは人間の問題解決アプローチに近い方法
  4. 問題の性質に合わせた柔軟性:

    • 探索アルゴリズムを問題に合わせて選択できる
    • 思考の粒度を適切に調整できる

特に効果を発揮する問題タイプ

ToTは以下のような問題タイプで特に優れた性能を発揮します:

  • 戦略的先読みが必要な問題: 数学パズル、チェスなどのゲーム
  • 複雑な制約がある問題: クロスワード、スケジューリング問題
  • 初期の決定が重要な問題: 複数の解決策がある創造的タスク
  • 試行錯誤が必要な問題: 一度の推論では解けない複雑な問題

7. 結論

Tree of Thoughts(ToT)は、AIの問題解決能力を大幅に向上させる実用的な手法です。通常のプロンプト技術では難しい複雑な問題でも、複数の思考経路を系統的に探索することで、より質の高い解決策を見つけることができます。

ToTの実装は、コード、LangChain、単純なプロンプトなど、様々な方法で可能です。問題の性質に合わせて適切な思考分解、生成方法、評価基準、探索アルゴリズムを選択することで、AIの潜在能力を最大限に引き出すことができます。

数学パズル、文章作成、クロスワードパズルなどの実例では、ToTが従来手法を大幅に上回る性能を示しており、複雑な問題解決のための強力なツールとして活用できます。

Discussion

ログインするとコメントできます