guidance ライブラリ徹底解説:LLMの挙動を自在に操る新時代のプロンプトエンジニアリング 🚀

プログラミング

Microsoftが開発した革新的なPythonライブラリで、大規模言語モデル(LLM)の制御を新たなレベルへ引き上げましょう。

近年、ChatGPTをはじめとする大規模言語モデル(LLM)の進化は目覚ましく、様々な分野での応用が期待されています。しかし、LLMはその性質上、常に期待通りの出力をするとは限りません。プロンプトエンジニアリングによってある程度の制御は可能ですが、より複雑なタスクや厳密な出力形式が求められる場合、限界が生じることがありました。

そこで登場したのが、Microsoft Researchが開発したPythonライブラリ guidance です。guidanceは、LLMのプロンプトと制御ロジックをシームレスに統合し、まるでプログラミング言語のようにLLMの挙動を細かく制御できる画期的なフレームワークです。従来のプロンプトやチェイニング(複数のプロンプトを連鎖させる手法)よりも効率的かつ効果的にLLMを制御できると注目を集めています。

この記事では、guidanceライブラリの基本的な考え方から主要な機能、具体的な使い方、そしてその利点と注意点まで、網羅的に解説します。guidanceを使いこなすことで、あなたのLLMアプリケーション開発は新たな次元へと進化するでしょう! ✨

guidanceの基本

インストール

guidanceのインストールは非常に簡単です。pipコマンドを使ってインストールできます。

pip install guidance

依存関係によっては追加のライブラリが必要になる場合がありますが、基本的な利用はこのコマンドで開始できます。

基本的な考え方:Guidanceプログラム

guidanceの中核となるのは「Guidanceプログラム」という概念です。これは、PythonのコードとLLMへの指示(プロンプトテキストや生成コマンド)を組み合わせたものです。F-stringのような構文を使い、テキスト生成、選択、条件分岐、ループなどをプログラム内に記述できます。

従来のプロンプトエンジニアリングでは、プロンプトは単なるテキスト文字列でしたが、guidanceではプロンプト自体が実行可能なプログラムとなります。これにより、LLMの生成プロセス中にPythonのロジックを介入させ、より動的で精密な制御が可能になります。

Guidanceプログラムは、モデルオブジェクト(LLMへのインターフェース)に対してテキストやguidanceの関数を追加していく形で構築されます。実行すると、guidanceがLLMとのやり取りを管理し、指定された制御に従ってテキストを生成します。

簡単なコード例:Hello, guidance!

まずは、guidanceを使って簡単なテキスト生成を行ってみましょう。ここではOpenAIのモデルを使う例を示します(他のモデルも利用可能です)。

import guidance

# OpenAIモデルをロード(APIキーの設定が必要)
# guidance.llm = guidance.llms.OpenAI("text-davinci-003")
# または、ローカルモデルを使う場合 (例: LlamaCpp)
# guidance.llm = guidance.models.LlamaCpp("/path/to/your/model.gguf")

# Guidanceプログラムを定義
program = guidance(
'''こんにちは! guidanceライブラリを使って、{{topic}} について短い説明文を生成してください。
{{gen 'explanation' max_tokens=50}}'''
)

# プログラムを実行し、変数を渡す
executed_program = program(topic="プロンプトエンジニアリング")

# 生成されたテキストを表示
print(executed_program['explanation'])

この例では、guidance(...) でプログラムテンプレートを作成し、{{topic}} という変数と {{gen 'explanation' max_tokens=50}} という生成コマンドを含めています。program(topic="...") で変数を渡し、プログラムを実行します。{{gen ...}} の部分でLLMによるテキスト生成が行われ、その結果が executed_program['explanation'] に格納されます。

実行結果は、guidance がテンプレートの {{gen ...}} 部分をLLMの生成結果で置き換えた形になります。Jupyter Notebookなどで実行すると、生成された部分が色付きで表示され、どの部分が生成されたか分かりやすくなっています。

主要な機能と制御構文 🛠️

guidanceが提供する強力な制御機能を見ていきましょう。

guidanceの真価は、その豊富な制御構文にあります。これらを組み合わせることで、複雑なLLMの振る舞いをデザインできます。

gen テキスト生成の基本

{{gen 'variable_name' ...}} は、最も基本的なコマンドで、LLMにテキスト生成を指示します。生成されたテキストは指定された変数名(例: 'variable_name')に格納されます。

program = guidance(
'''Q: 宇宙について面白い事実を教えてください。
A: {{gen 'fact' temperature=0.7 max_tokens=100}}'''
            )
executed_program = program()
print(executed_program['fact'])

temperaturemax_tokensstop(停止文字列)などのパラメータを指定して、生成プロセスを細かく制御できます。

select 選択肢からの生成

{{select 'variable_name' options=list_of_options}} は、与えられた選択肢の中からLLMに一つを選ばせます。これは、特定の形式や語彙を強制したい場合に非常に便利です。

program = guidance(
'''この文章の感情はポジティブですか、ネガティブですか、それともニュートラルですか?
文章: {{text}}
感情: {{select 'sentiment' options=['ポジティブ', 'ネガティブ', 'ニュートラル']}}'''
            )
executed_program = program(text="今日は素晴らしい天気だ!")
print(executed_program['sentiment']) # 出力例: ポジティブ

guidanceは、選択肢のリストをLLMが理解できる形式に変換し、指定された選択肢以外のトークンが生成されないように制御します(トークンヒーリングやマスキング技術を利用)。

if / unless 条件分岐

{{#if condition}} ... {{/if}}{{#unless condition}} ... {{/unless}} を使うことで、Pythonの変数に基づいてプログラムのフローを分岐させることができます。

program = guidance(
'''ユーザーのリクエスト: {{request}}
{{#if use_expert_mode}}
専門家モードで回答します: {{gen 'expert_answer'}}
{{else}}
通常モードで回答します: {{gen 'normal_answer'}}
{{/if}}'''
            )
# 専門家モードを有効にして実行
executed_program_expert = program(request="量子力学について", use_expert_mode=True)
# 通常モードで実行
executed_program_normal = program(request="今日の天気", use_expert_mode=False)

これにより、外部の条件やLLM自身の判断(例: select の結果)に基づいて、実行するプロンプト部分を動的に変更できます。

each ループ

{{#each list_variable}} ... {{/each}} を使うと、リストの各要素に対してプロンプトの一部を繰り返し実行できます。ループ内では {{this}} で現在の要素にアクセスできます。

program = guidance(
'''以下の各項目について短い説明を生成してください:
{{#each items}}- {{this}}: {{gen 'description' max_tokens=30}}
{{/each}}'''
            )
executed_program = program(items=["リンゴ", "バナナ", "オレンジ"])
# 出力例:
# - リンゴ: 赤または緑色の果物で、シャキシャキとした食感が特徴です。
# - バナナ: 黄色い皮を持つ細長い果物で、甘くて柔らかいです。
# - オレンジ: 丸い柑橘系の果物で、ビタミンCが豊富です。

await 関数/ツール呼び出し

{{await 'variable_name' function_call}} は、Pythonの非同期関数や外部ツールを呼び出し、その結果を変数に格納します。これにより、LLMの生成プロセス中に外部の情報を取得したり、計算を実行したりできます。これは、Function Calling や ReAct (Reasoning and Acting) パターンの実装に役立ちます。

import datetime

# 非同期で現在時刻を取得する関数 (例)
async def get_current_time():
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

program = guidance(
'''現在の時刻を調べます。
{{await 'current_time' get_current_time()}}
現在時刻は {{current_time}} です。
この時刻に基づいて、ユーザーへの挨拶を生成してください:
{{gen 'greeting'}}'''
)

# guidanceプログラムは非同期実行に対応
import asyncio
executed_program = asyncio.run(program())
print(executed_program['greeting'])

await を使うことで、LLMは単なるテキスト生成器ではなく、外部環境と相互作用するエージェントとしての能力を獲得します。

role チャットロール

{{#system}}...{{/system}}, {{#user}}...{{/user}}, {{#assistant}}...{{/assistant}} といったタグを使うことで、ChatGPTのようなチャットモデルに対応したロールベースの対話を簡単に構築できます。guidanceはモデルに応じて適切な特殊トークンを自動的に付与します。

program = guidance(
'''{{#system}}あなたは親切なAIアシスタントです。{{/system}}

{{#user}}日本の首都はどこですか?{{/user}}

{{#assistant}}{{gen 'answer'}}{{/assistant}}

{{#user}}そこでおすすめの観光地を3つ教えてください。{{/user}}

{{#assistant}}{{gen 'recommendations' max_tokens=100}}{{/assistant}}'''
            )
executed_program = program()
print(f"回答1: {executed_program['answer']}")
print(f"おすすめ: {executed_program['recommendations']}")

これにより、会話履歴の管理や役割に応じた応答生成が容易になります。

高度な制約:正規表現とJSONスキーマ

guidanceの強力な機能の一つが、生成されるテキストの形式を厳密に制約できる点です。

  • 正規表現 (Regex): gen コマンドに regex=r"..." パラメータを指定することで、生成されるテキストが特定の正規表現パターンに一致するように強制できます。これは、電話番号、メールアドレス、特定のIDフォーマットなどを生成させたい場合に役立ちます。
  • JSONスキーマ: gen コマンドで json_schema=... を指定すると、生成されるテキストが指定されたJSONスキーマに準拠した有効なJSONオブジェクトであることを保証します。これは、構造化データをLLMに生成させる際に非常に強力です。
# JSONスキーマを使った例
import json

character_schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "class": {"type": "string", "enum": ["Warrior", "Mage", "Rogue"]},
        "level": {"type": "integer", "minimum": 1, "maximum": 99},
        "hp": {"type": "integer"}
    },
    "required": ["name", "class", "level", "hp"]
}

program = guidance(
'''RPGキャラクターのプロフィールをJSON形式で生成してください。
```json
{{gen 'character_json' json_schema=character_schema temperature=0}}
```'''
)

executed_program = program()
try:
    # 生成されたJSONをパース
    character_data = json.loads(executed_program['character_json'])
    print(json.dumps(character_data, indent=2, ensure_ascii=False))
except json.JSONDecodeError:
    print("生成された出力は有効なJSONではありませんでした。")
    print(executed_program['character_json'])

guidanceは、トークンレベルでの制御(マスキングやバイアス調整)を行うことで、これらの制約を効率的に実現します。これにより、無効な形式の出力を生成してしまい、後処理で修正したり再生成したりするコストを削減できます。

具体的なユースケース 💡

guidanceがどのように活用できるか、いくつかの例を見てみましょう。

  • 🤖 高度なチャットボット開発:

    role タグを使って対話の流れを管理し、ifawait を使ってユーザーの発言内容に応じた条件分岐や外部API連携(天気情報取得、データベース検索など)を組み込むことで、単なる応答生成を超えたインテリジェントなチャットボットを構築できます。会話履歴もguidanceプログラム内で自然に扱えます。

  • 📄 構造化データの抽出:

    非構造化テキスト(メール、レポート、記事など)から特定の情報を抽出し、JSON形式で出力させることが容易になります。JSONスキーマ制約を使えば、抽出結果が必ず定義したフォーマットに従うことを保証できます。これにより、後続のシステムでのデータ処理が格段に楽になります。

    # 請求書テキストから情報を抽出しJSONにする例
    invoice_text = """
    請求書番号: INV-00123
    発行日: 2025年4月1日
    支払期日: 2025年4月30日
    請求元: 株式会社サンプルテック
    請求先: ABC株式会社 御中
    商品: 製品X, 数量: 2, 単価: 10000
    商品: 製品Y, 数量: 1, 単価: 5000
    合計金額: 25000円 (税抜)
    """
    
    extraction_schema = {
        "type": "object",
        "properties": {
            "invoice_number": {"type": "string"},
            "issue_date": {"type": "string", "format": "date"},
            "due_date": {"type": "string", "format": "date"},
            "biller_name": {"type": "string"},
            "client_name": {"type": "string"},
            "total_amount": {"type": "integer"}
        },
        "required": ["invoice_number", "issue_date", "due_date", "biller_name", "client_name", "total_amount"]
    }
    
    program = guidance(
    '''以下の請求書テキストから指定された情報を抽出し、JSON形式で出力してください。
    テキスト:
    {{invoice_text}}
    
    抽出結果:
    ```json
    {{gen 'extracted_data' json_schema=extraction_schema temperature=0}}
    ```'''
    )
    
    executed_program = program(invoice_text=invoice_text)
    print(executed_program['extracted_data'])
    
  • 📝 多様な出力形式の生成:

    select や正規表現制約、ループ構文 (each) などを組み合わせることで、箇条書きリスト、表形式のテキスト、特定のフォーマットに従ったコードスニペットなど、様々な形式のテキストを確実に生成させることができます。

  • 🤔 思考連鎖 (Chain-of-Thought) の実装:

    複雑な問題解決プロセスをLLMに段階的に実行させ、その思考プロセス(中間的な推論)も出力させることができます。genset を使って中間結果を保持し、if で条件分岐させることで、より信頼性の高い推論を実現できます。

  • 🔧 外部ツールとの連携 (エージェント):

    await を活用して、LLMが判断したタイミングで外部の計算ツール(電卓、コード実行環境)、情報検索API、データベースなどを呼び出すエージェントを構築できます。LLMは思考や計画を行い、実際の計算や情報取得は外部ツールに任せる、といった分業が可能になります。

guidanceの利点と注意点 🤔

👍 利点

  • 柔軟かつ強力な制御: プロンプト内にロジック(条件分岐、ループ、変数)を埋め込めるため、LLMの挙動を非常に細かく制御できます。
  • 出力形式の保証: 正規表現やJSONスキーマにより、生成されるテキストが特定の形式に確実に準拠するように強制できます。これにより、後処理の手間やエラーが大幅に削減されます。
  • プロンプトとロジックの統合: 従来のプロンプトとそれを制御するPythonコードが分離していたのに対し、guidanceではこれらが一つのプログラムとして統合されるため、見通しが良く、管理しやすくなります。
  • 効率性: トークンヒーリングやマスキングといった技術により、制約を満たすための不要なトークン生成を抑制し、APIコール数や生成トークン数を削減できる可能性があります。特に、制約が多い場合や選択肢が限定される場合に効果を発揮します。
  • 再利用性とモジュール性: guidanceプログラムを関数としてカプセル化し、再利用可能なコンポーネントを作成できます。
  • デバッグの容易さ: プログラムの実行過程や中間生成結果を確認しやすく、問題の特定が比較的容易です。Jupyter Notebookなどでのインタラクティブな開発にも適しています。

⚠️ 注意点

  • 学習コスト: guidance独自の構文({{gen}}, {{select}} など)を習得する必要があります。従来のプロンプトエンジニアリングとは異なるアプローチです。
  • 複雑な制御の記述: 非常に複雑なロジックをguidanceプログラム内に記述すると、可読性が低下する可能性があります。適切な粒度での関数化が重要です。
  • 対応モデルと機能制限: 最も強力な制御機能(トークンレベルでの制約など)は、TransformersライブラリやLlamaCppなど、内部実装にアクセスしやすいモデルバックエンドで最も効果を発揮します。OpenAIのようなAPIベースのモデルでも利用可能ですが、一部機能(特に高度な制約)は限定的になるか、効率が低下する場合があります。
  • 開発途上のライブラリ: guidanceは比較的新しいライブラリであり、活発に開発が進められています。APIの変更や機能追加・削除が行われる可能性がある点に留意が必要です。

他のライブラリとの比較 (LangChainなど)

LLMアプリケーション開発フレームワークとしては、LangChain も非常に有名です。guidanceとLangChainは目的が一部重なりますが、アプローチに違いがあります。

guidance vs LangChain

特徴 guidance LangChain
主な焦点 LLMの生成プロセスに対する細かな制御出力形式の保証。プロンプトと制御ロジックの統合。 LLMを中心としたアプリケーションコンポーネントの連鎖 (Chaining)統合。エージェント、メモリ、外部ツール連携など、広範な機能を提供。
制御の粒度 トークンレベルでの制御が可能。プロンプト内に直接制御構文を埋め込む。 コンポーネント(LLM、PromptTemplate、OutputParserなど)を組み合わせて制御。制御は主にコンポーネント間のインターフェースで行われる。
プロンプト記述 独自のテンプレート構文 (F-string like + guidanceコマンド)。プロンプト自体がプログラム。 PromptTemplateクラスなどを使用。プロンプトは主にデータ構造として扱われる。
出力形式の保証 正規表現やJSONスキーマによる強力な保証機能が組み込まれている。 OutputParserを使用するが、LLMの生成後にパースするため、失敗する可能性がある。Function Callingを利用する場合はより確実。
複雑さ 独自の構文を学ぶ必要はあるが、プロンプトとロジックが一体化しているため、特定のタスクではシンプルになることも。 多くの概念(Agent, Tool, Memory, Chainなど)があり、全体像を把握するのに学習コストがかかる場合がある。
柔軟性 プロンプト内での自由なロジック記述が可能。 豊富なコンポーネントと抽象化により高い柔軟性を持つが、内部実装のカスタマイズはguidanceより複雑になる場合がある。

どちらを選ぶべきか?

  • LLMの出力を厳密なフォーマットに制御したい、または生成プロセス自体に細かく介入したい場合は、guidance が強力な選択肢となります。
  • 様々なツールやデータソースを連携させ、複雑なアプリケーションフローを構築したい場合は、コンポーネントが豊富な LangChain が適している場合が多いでしょう。

両者は競合するというよりは、異なる強みを持つツールであり、プロジェクトの要件に応じて使い分けたり、部分的に組み合わせたりすることも考えられます。

また、構造化出力に特化した Outlines や、OpenAIのFunction Callingを使いやすくする Instructor といったライブラリも存在します。guidanceはこれらの機能を取り込みつつ、より汎用的な制御フレームワークを目指していると言えるでしょう。

まとめ

Microsoftが開発した guidance ライブラリは、LLMの制御を新たなレベルに引き上げる可能性を秘めた、非常に強力で革新的なツールです。プロンプトと制御ロジックをシームレスに統合し、PythonコードのようにLLMの挙動を記述できるため、開発者はより意図した通りにLLMを動作させることができます。

特に、出力形式の厳密な制御条件に応じた動的なプロンプト生成外部ツールとの連携といった点で、従来のプロンプトエンジニアリングや単純なAPIコールでは難しかった高度なタスクを実現可能にします。

学習コストや一部モデルでの機能制限といった注意点もありますが、そのメリットは非常に大きく、LLMアプリケーション開発の効率と品質を大幅に向上させることが期待できます。今後のLLM開発において、guidanceのような制御フレームワークはますます重要な役割を担っていくでしょう。

ぜひ、guidanceを試してみて、そのパワーを体験してください! 🚀

学習リソース

さらに詳しく学びたい方は、以下の公式リソースを参照することをお勧めします。

コメント

タイトルとURLをコピーしました