IBM Graniteを使用したReWOO推論エージェントの構築

著者

Jobit Varughese

Technical Content Writer

IBM

大規模言語モデル(LLM)とその強化版である拡張言語モデル(ALM)は、現代のAIシステムの基盤となっています。強力な言語生成と、検索拡張生成(RAG)などの外部の知識検索手法を組み合わせることで、さまざまなドメインにわたる高度な推論、質問応答、タスクの自動化が可能になります。しかし、その優れた機能にもかかわらず、これらのモデルが複雑なタスクに取り組む際には、システム全体で一貫性のない堅牢性、高いトークン使用量、応答時間の遅さ、反復プロンプトや冗長なコンテキストによる非効率性などの課題に直面することがよくあります。このような制限は、運用コストを増加させ、拡張性とリアルタイムの性能を妨げます。

これらの課題を克服するために、ReWOO(観察なしの推論)フレームワークでは、外部の知識検索から推論を切り離すことに焦点を当てたアプローチを採用しています。単一のLLMが推論、行動、観察を交互に繰り返すのではなく、ReWOOはこれらの機能を個別のモジュールに分割します。各モジュールでLLMを活用する可能性はありますが、それぞれが特定の役割を担っています。ReWOOは、このプロセスを個別の計画、証拠収集、合成の段階にモジュール化することで、トークンの効率と精度を向上させます。また、システムのデバッグが容易になり、より合理化され効果的なAIワークフローが可能になります。

ReWOOの背後にある方法論

ReWOOのワークフローは、段階的な推論、ツールの呼び出し、要約という3つの主な構成要素を中心に展開します。これらの構成要素は、プランナー、ワーカー、ソルバーの3つの部分で構成されるモジュール構造で実装されています。

プランナー

プランナーはメイン・タスクを一連の焦点を絞ったサブ質問に分割し、明確な青写真を作成します。トークンの使用や混乱につながる可能性のある複雑な質問に一度に回答するようLLMに求める代わりに、プランナーは青写真やロードマップを作成します。この段階的な分解によってワークフローがガイドされ、推論プロセスの構造化が維持されます。

ワーカー

ワーカーは、検索エンジンやデータベースなどの外部ツールを呼び出して、サブ質問に答えるために必要な関連情報や証拠を取得します。また、LLMを使用して、取得した情報のみに基づいて、明確で簡潔な回答を作成します。この外部観測フェーズは、プロンプトの不必要な繰り返しを回避し、トークンの消費を削減するために、推論プロセスから切り離されます。

ソルバー

ソルバーは、収集したすべての洞察を統合し、新鮮で適切に構造化された最終応答を生成します。このモジュール式分離は、大規模言語モデルによる効率的で正確かつスケーラブルな推論の実現に役立ちます。

LangChainLangGraphなどのフレームワークは、OpenAIやIBM Graniteのモデルや、検索用のSerperやTavilyなどの専用ツールを使用して、ReWOOアーキテクチャーを実装するための強力なツールを提供します。

このチュートリアルでは、コンテンツの要約タスクを実行するReWOOエージェントを構築する方法について説明します。このエージェントは次のことができます。

  • 高レベルのタスクをサブ質問に分割する
  • Web検索を使用して、各サブ質問に関連するコンテキストを収集する
  • IBM Graniteを使用して回答を生成する
  • 成果を最終応答にまとめる

このアーキテクチャーは、次の場合に役立ちます。

  • 要約タスク
  • 外部の知識を活用した質問応答
  • 動的なツール拡張型の推論

使用したテクノロジー

このステップバイステップのチュートリアルでは、次の最先端AIテクノロジーを活用しています。

  1. IBM Granite Instruct:一般的な指示に従うための強力なLLMで、ビジネスやその他の分野におけるAIアシスタントに最適です。
  2. Transformer:IBM Graniteなどの言語モデルの読み込み、トークン化、実行をするためのツールを提供する、広く使用されているPythonライブラリーです。テキスト・インプットの処理とモデル・アウトプットの生成を効率的に行います。

ステップ

ステップ1: 環境を設定する

このチュートリアルでは、Jupyter Notebookを使用して、ReWOOスタイルの推論パイプラインを実行するようにローカル開発環境をセットアップする方法を説明します。ライブのWeb検索の取得には、IBM Graniteの言語モデルとSerper.devを使用します。

注:GPUは不要ですが、CPUベースのシステムでは実行速度が低下する可能性があります。このステップでは、このチュートリアルのコードをコピーできるノートブック環境を開きます。このチュートリアルは、GitHubでも公開されています。

ステップ2: 必要な依存関係をインストールする

ReWOOパイプラインを実行し、外部ツールとやり取りするためには、以下のライブラリーが必要です。

transformer:IBM Graniteの大規模言語モデルを読み込んで実行します。

torch:モデルを効率的に実行するために必要なディープラーニング(深層学習)フレームワークです。

accelerate:ハードウェア全体でモデルのパフォーマンスを最適化します(オプション)。

requests:HTTP POSTリクエストを外部のAPI(Serperなど)に送信します。

!pip install transformers accelerate torch requests

ステップ3: 必要なライブラリーをインポートする

このステップでは、ReWOOパイプラインのコア・コンポーネントの構築に必要なPythonライブラリーをインポートします。

transformers.AutoTokenizer:テキストを言語モデルと互換性のあるトークンに変換するトークナイザーをロードします。

transformers.AutoModelForCausalLM:応答を生成するための、事前トレーニング済み言語モデル、IBM Graniteをロードします。

transformers.pipeline:トークナイザーとモデルを使用してテキスト生成パイプラインを迅速に作成するための、高レベルのインターフェースを提供します。

import requests
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline

ステップ4: IBM Graniteモデルを読み込み、テキスト生成パイプラインを作成する

このステップでは、IBM Granite言語モデルをロードし、Hugging Faceのトランスフォーマー・ライブラリーを使用して、テキスト生成パイプラインを初期化します。Hugging FaceのGranite 3.2 2B Instructモデルはこちらでご覧ください。

model_id = "ibm-granite/granite-3.2-2b-instruct":Hugging FaceでホストされているIBM Graniteモデルのチェックポイントの名前を指定します。このモデルは、指示に従うタスク用に最適化されています。

AutoTokenizer.from_pretrained(model_id):指定されたモデルに関連付けられたトークナイザーを読み込みます。インプット・テキストをトークンに変換し、アウトプット・トークンをテキストにデコードします。

AutoModelForCausalLM.from_pretrained(model_id):質問への回答や要約などのテキスト生成タスクのための言語モデル(Granite 3.2 2B instruct)を読み込みます。

pipeline("text- Generation", model=model, tokenizer=tokenizer): モデルとトークナイザーを組み合わせた高レベルのテキスト生成パイプラインを作成し、プロンプトからの応答を簡単に生成できるようにします。

model_id = "ibm-granite/granite-3.2-2b-instruct"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
generator = pipeline("text-generation", model=model, tokenizer=tokenizer)

ステップ5: Web検索の取得用にSerper APIを設定する

このステップでは、ReWOOアーキテクチャーのワーカーとして機能する関数を定義します。このワーカーは、Web検索ツールであるSerper.devを使用して、インターネットから関連する最新情報を取得し、推論と回答の生成をサポートします。Serper.devは、Google検索の成果を構造化された形式で提供する高速で軽量なAPIで、AIワークフローでのリアルタイムの情報検索に最適です。

この設定により、LLMが最終的な決定を下す前に外部の知識ソースに照会することで、ReWOOシステムは現実世界を「観察」できます。

ReWOOパイプラインでSerperを使用するには、次の操作を行います。

  1. https://serper.devにアクセスして、無料アカウントを作成します。
  2. 登録後、ダッシュボードに移動し、APIキーをコピーします。
  3. コード内にAPIキーを安全に保管します。ここでは、次の後に示すように直接割り当てます。

SERPER_API_KEY ="<YOUR_API_KEY>" #実際のキーに置き換えてください

注:APIキーをパブリック・リポジトリーにアップロードしないでください。本番環境またはチーム設定の場合は、.envを使用します。ファイルまたは環境変数を使用して安全に保ちます。

ReWOOのスクリーンショット

def query_serper(question, num_results=3):検索の質問を受け取り、上位の検索結果から関連するスニペットを返す関数を定義します。

payload = {"q": question, "num": num_results}:検索用語と返される成果の数を含むクエリー・ペイロードを準備します。

response = requests.post(...):指定したクエリーとヘッダーを付加して、Serper APIに対してPOSTリクエストを送信します。

response.raise_for_status():API応答が無効または失敗した場合にエラーを発生させます。

snippets = [...]:オーガニック検索結果からスニペット・テキストを抽出します。

return "\n".join(snippets):スニペットを単一の文字列として結合して返し、言語モデルのコンテキストとして機能します。

注:この機能は、ReWOOの「観察」ステップの基盤を成すもので、さらなる推論のために外部証拠を収集する役割を果たします。テストの際には、APIキーが有効であり、レート制限がないことを確認してください。

SERPER_API_KEY = "your_serper_api_key_here" # Replace with your actual key
def query_serper(question, num_results=3):
    url = "https://google.serper.dev/search"
    headers = {
            "X-API-KEY": SERPER_API_KEY,
            "Content-Type": "application/json"
}
    payload = {"q": question, "num": num_results}
    response = requests.post(url, headers=headers, json=payload)
    response.raise_for_status()
    data = response.json()
    snippets = [item.get("snippet", "") for item in data.get("organic", [])]
    return "\n".join(snippets)

ステップ6: expert関数を使用して、情報に基づいた回答を生成する

このステップでは、ReWOOアーキテクチャーのソルバーとして機能するexpert()関数を定義します。ソルバーは、取得した外部証拠を合成し、言語モデルを使用して最終的な応答を生成します。

def expert(question: str) -> str:expert()関数は、質問(文字列)を受け取り、Graniteモデルによって生成された回答(文字列)を返します。Serper.devでWebを検索することで機能し、適切な情報を収集したら、それを利用して明確で完全な応答を生成します。

context = query_serper(question):Serper Web検索ツールを使用して関連する情報を取得します(ワーカー)。

prompt = f"""...""":取得したコンテキストのみを使用して、モデルに回答するように指示するプロンプトを構築します。

generator(...):入力されたプロンプトに基づいて回答を生成するGraniteモデルを呼び出します。

for _ in range(5):このループでは、モデルが回答をチャンク単位で生成し、最大5回まで繰り返します。答えが長く、一度に完了できない場合に役立ちます。

generated_text += new_text:新しい各テキストのチャンクを連結して、完全な回答を形成します。

if new_text.endswith(...):回答が完全な形式(句点、疑問符、または感嘆符で終わる)であり、かつ十分な単語数(50語を超える)を満たしている場合、ループを終了します。

return generated_text.strip():最終的なクリーンアップされた回答を返します。

注: プロンプト形式は重要であり、それによってモデルの"ハルシネーション"がなくなったり、本題から外れたりしなくなります。コンテキストの内容を維持しなければなりません。世代されるチャンクを120トークンに制限することで、アウトプットの長さを制御し、リソースの使用量を効率的に管理しながら、過剰なトークンの使用を防ぐことができます。

def expert(question: str) -> str:
    context = query_serper(question) # your retrieval function
    prompt = f"""You are a knowledgeable expert. Based ONLY on the context below, answer the question clearly and concisely in your own words.
Do NOT mention any sources or references.
Context:
{context}
Question: {question}
Answer:"""
    input_prompt = prompt
    generated_text = ""
    last_generated = ""
    for _ in range(5): # up to 5 chunks
        outputs = generator(
            input_prompt,
            max_new_tokens=120,
            do_sample=False,
            eos_token_id=tokenizer.eos_token_id,
            # no invalid flags like 'temperature' here
        )
        text = outputs[0]["generated_text"]
        new_text = text[len(input_prompt):].strip()
        # Stop if no new content
        if new_text == last_generated:
            break
        generated_text += new_text + " "
        input_prompt = prompt + generated_text
        last_generated = new_text
        if new_text.endswith(('.', '!', '?')) and len(generated_text.split()) > 50:
            break
    return generated_text.strip()

ステップ7: プランナー・モジュールを定義する

このステップでは、ReWOO段階的推論の中核的原則である、広範なインプット・タスクをより小さく明確に定義されたサブ質問に分解するPlanner関数を定義します。

def planner(task: str): これで、単一の引数タスク(実行するタスクを説明する文字列)を受け取るPlanner関数が定義されます。

topic = task.replace("Summarize", "").replace("the novella", "").strip(): タスクから主な主題(タイトルやテーマなど)を抽出します。"要約して"や"小説の"などの一般的なプロンプト・フレーズを削除することでインプットをクリーンアップし、前後の空白を排除して核となるトピックのみを分離します。

return [ ... ]: ワーカー・モジュールをガイドする特定の質問のリストを返します。

注: インプット・トピックの深さと性質に応じて、このリストをより具体的なサブ質問で拡張することができます。

def planner(task: str):
topic = task.replace("Summarize", "").replace("the novella", "").strip()
return [
f"What is the main plot related to {topic}?",
f"Who are the key characters in {topic}?",
f"What themes are explored in {topic}?"
]

ステップ8: 最終的なサマライザー(ソルバー・モジュール)を定義する

このステップでは、ReWOOパイプラインのソルバーとして機能する、final_summarizer関数を定義します。この関数は、ワーカーが提供した補足回答(証拠)を受け取り、言語モデルを使用して、新たに記述された一貫性のある要約を作成します。

def final_summarizer(task: str, sub_answers: dict) -> str: 元のタスクと補足回答を受け取り、簡潔な要約を返す関数を定義します。

insights = "\n".join(sub_answers.values()): すべての回答を、1つの文字列にまとめて改行で区切り、プロンプトに含めます。

Base_prompt = f"""...": 提供された洞察を要約するようにモデルに指示するベース・プロンプトを構築します。これにより、モデルは補足回答のみに基づいて新しい要約を生成できるようになります。

max_total_tokens = 400: 生成するトークン数の上限を設定し、過度に長いアウトプットを回避します。

max_loops = 5: 最大5回の反復生成を許可し、要約を段階的に構築します。

for in range(maxloops): 言語モデルを使用してテキストのチャンクを生成するためのループ。

response = generator(..., max_new_tokens=100, ...): ジェネレーター(パイプライン・オブジェクト)を使用して、各ループで最大100個の新しいトークンを生成します。サンプリング・モード(do_sample=True)では、応答に変化や創造性を持たせることができます。

if ··summary.endswith(...)またはtotal_tokens_used >= max_total_tokens: 要約が適切な句読点で終わるか、トークンの上限に達した場合に、ループを終了します。

return summary.strip():末尾にスペースを入れずに、洗練された最終的な要約を返します。

def final_summarizer(task: str, sub_answers: dict) -> str:
    insights = "\n".join(sub_answers.values())
    base_prompt = f"""You are an expert summarizer. Based on the following insights, write a fresh, concise summary of the text. The summary must be newly written and must end in a complete sentence with proper punctuation.
Insights:
{insights}
Summary:"""
    summary = ""
    current_prompt = base_prompt
    max_total_tokens = 400
    total_tokens_used = 0
    max_loops = 5
    for _ in range(max_loops):
        response = generator(current_prompt, max_new_tokens=100, do_sample=True, top_p=0.9, eos_token_id=tokenizer.eos_token_id)
        chunk = response[0]["generated_text"][len(current_prompt):].strip()
        summary += " " + chunk
        summary = summary.strip()
        total_tokens_used += len(chunk.split())
        if summary.endswith(('.', '!', '?')) or total_tokens_used >= max_total_tokens:
            break
        # Prepare prompt for next loop
        current_prompt = base_prompt + summary
    return summary.strip()

ステップ9: solver関数を使用したReWOOエージェントのオーケストレーション

このステップでは、ReWOOパイプラインの最終段階を表すsolver関数を定義します。プランナーを使用し、エキスパート(ワーカー)を呼び出し、final_summarizer(ソルバー)を使用して要約を生成することで、プロセス全体を調整します。ReWOOアーキテクチャーは、プランナーを使用してメイン・タスクをサブ質問に分割することで、マルチステップの推論を可能にします。サブ質問はそれぞれエキスパート・モジュールによって個別に対処され、最終的な要約ではすべての回答が一貫した回答にまとめられます。このモジュール式アプローチにより、システムが複雑なタスクにより効果的に取り組めるようになります。

def solver(task: str): 完全なReWOOワークフローを実行するためのメイン・コントローラー機能を定義します。

subquestions = planner(task): プランナーを使用して、インプット・タスクを焦点を絞ったサブ質問に分割します。

ans = expert(q): サブ質問ごとにexpert関数を呼び出し、Webベースの証拠を取得し、関連性のある回答を生成します。プランナーによって生成されたサブ質問はそれぞれ、ツール・インプットとしてエキスパートに渡されます。エキスパート・モジュールは、言語モデルを使用してインプットを処理します。これは、特定のサブタスクのためのツールの実行とみなすことができます。

answer[q] = ans: 後で要約できるように、対応する質問をキーにして各回答を保存します。

final_summary = final_summarizer(task, answers): 収集したすべての回答をfinal_summarizerに取り込み、無駄のない一貫性のある要約を生成します。

print(final_summary)とreturn final_summary: 元のタスクの完了した要約を表示し、返します。

注: solver()関数の合計実行時間は、CPUの速度、利用可能なRAMの量、異なるハードウェア構成上でモデルを実行した際の効率の違いにより、システム間で異なる場合があります。コードは言語モデルを使用したループ生成ストラテジーを使用しているため、処理能力やメモリーの少ないシステムでは処理時間が大幅に長くなる可能性があります。ネットワークベースの検索やサイズの大きなプロンプトも遅延の原因となることがあります。パフォーマンスを向上させるには、より小規模なモデルや量子化モデルを使用するか、トークナイザーとジェネレーター・パイプラインを最適化するか、Google ColabやKaggle NotebooksなどのGPU対応環境でコードを実行することで、max_loopsを削減することを検討してください。

def solver(task: str):
    print(f"Planner: Breaking down '{task}' into sub-questions...\n")
    subquestions = planner(task)
    answers = {}
    for q in subquestions:
        print(f"🔎 Expert answering: {q}")
        ans = expert(q)
        print(f"➡ Answer: {ans}\n")
        answers[q] = ans
    print("=== Final Summary ===\n")
    final_summary = final_summarizer(task, answers)
    print(final_summary)
    return final_summary

ステップ10: ReWOOパイプラインを実行して最終的な要約を生成する

この最終ステップでは、特定のタスクでsolver関数を呼び出すことにより、ReWOOパイプライン全体を実行します。

solver("小説『変身』を要約して"): ReWOOプロセス全体がトリガーされます。インプット・タスクの要約の計画、取得、生成: データセット『変身』の要約。

このステップでは、最終的な要約をアウトプットし、実際のユースケースでReWOOコンポーネントがどのようにエンドツーエンドで連携するかを示します。

solver("Summarize the novella The Metamorphosis")

重要ポイント

  1. ReWOOエージェントは、タスク(“小説『変身』を要約して“)をプロット、登場人物、テーマに関する意味のあるサブ質問に分解することに成功し、焦点を絞った情報検索ができるようになりました。
  2. サブ質問はそれぞれ、リアルタイムWeb検索(Serper.dev)とIBM Graniteを使用して回答され、テキストの核となる要素を捉えた、関連性の高い、しっかりと構成された回答を生成しました。
  3. 最終的な答えは、一貫性があり、新しく書かれた、正確な答えであり、検索拡張生成が文献分析タスクで高品質で人間のような要約をどのように生成できるかを示しています。

注: ReWOOパイプラインのパフォーマンスと信頼性を向上させるには、要約の品質、一貫性、生成遅延などの評価メトリクスを改善することが重要です。これらのメトリクスは、さまざまなタスクやハードウェアのセットアップ全体でシステムがどの程度適切に実行されるかを評価するのに役立ちます。大きな質問を小さな質問に分割し、最も有用な回答を分類するインテリジェントなアルゴリズムを統合することで、アーキテクチャーを拡張できます。このような機能強化により、より正確で効率的な推論が可能になり、生成時間が短縮され、最終アウトプットの全体的な品質が向上します。

関連ソリューション
ビジネス向けAIエージェント

生成AIを使用してワークフローとプロセスを自動化する強力なAIアシスタントとエージェントを構築、デプロイ、管理しましょう。

    watsonx Orchestrateの詳細はこちら
    IBM AIエージェント・ソリューション

    信頼できるAIソリューションでビジネスの未来を構築します。

    AIエージェント・ソリューションの詳細はこちら
    IBM®コンサルティング AIサービス

    IBMコンサルティングAIサービスは、企業がAIをトランスフォーメーションに活用する方法を再考するのに役立ちます。

    人工知能サービスの詳細はこちら
    次のステップ

    事前構築済みのアプリケーションとスキルをカスタマイズする場合でも、AIスタジオを使用してカスタム・エージェント・サービスを構築し、デプロイする場合でも、IBM watsonxプラットフォームが対応します。

    watsonx Orchestrateの詳細はこちら watsonx.aiの詳細はこちら