🎉

Ollamaとllama3.1 8bで始める関数呼び出し機能の実装チュートリアル

2024/12/19に公開

はじめに

近年、ローカル環境でLLMを動作させる需要が高まっています。その中で、Ollamaは手軽に使えるオープンソースのLLMプラットフォームとして注目を集めています。本記事では、Ollamaを使用した関数呼び出し(Function Calling)機能の実装方法について、実践的なコード例を交えながら解説します。

🎯 この記事のゴール

  • Ollama Toolsの基本的な使い方の理解
  • 関数呼び出し機能の実装方法の習得
  • 実践的なユースケースの把握

Ollama Toolsの基本機能

主要コンポーネント

  1. ロガー機能(loguru)

    • 詳細なログ出力
    • デバッグ情報の可視化
    • エラーハンドリングのサポート
  2. 関数定義システム

    • 数値計算機能
    • 型アノテーションによる安全性確保
    • JSONスキーマベースの関数定義

実装手順の詳細解説

環境セットアップ

from loguru import logger
from ollama import Client
from ollama import ChatResponse

基本関数の実装

def add_two_numbers(a: int, b: int) -> int:
    logger.debug(f"Adding numbers: {a} + {b}")
    result = a + b
    logger.info(f"Add result: {result}")
    return result

ツール定義の設定

add_two_numbers_tool = {
    'type': 'function',
    'function': {
        'name': 'add_two_numbers',
        'description': '2つの数値の足し算を行う',
        'parameters': {
            'type': 'object',
            'required': ['a', 'b'],
            'properties': {
                'a': {'type': 'integer', 'description': '1つ目の数値'},
                'b': {'type': 'integer', 'description': '2つ目の数値'},
            },
        },
    },
}

実装のポイントとベストプラクティス

エラーハンドリング

  • try-except文による例外処理
  • 詳細なログ出力
  • グレースフルな失敗処理

ログ管理のベストプラクティス

  1. 適切なログレベルの使用

    • DEBUG: 詳細なデバッグ情報
    • INFO: 一般的な情報
    • WARNING: 警告
    • ERROR: エラー情報
  2. 構造化ログの活用

    logger.info(f"プロンプト: {messages[0]['content']}")
    

セキュリティ考慮事項

  • 入力値のバリデーション
  • 適切な型チェック
  • セキュアな関数実行環境の確保

まとめと発展的な使用方法

主要な学習ポイント

  1. Ollama Toolsの基本構成
  2. 関数呼び出し機能の実装方法
  3. エラーハンドリングとログ管理の重要性

発展的な使用方法

  • 複雑な計算処理の実装
  • カスタム関数の追加
  • 外部APIとの連携

次のステップ

  1. より複雑な関数の実装
  2. エラーハンドリングの強化
  3. パフォーマンス最適化

参考情報

全体コード


from loguru import logger
from art import text2art
from ollama import Client
from ollama import ChatResponse
import sys
from typing import Dict, Any, Callable

def print_banner():
    """アプリケーションバナーを表示"""
    art = text2art("Ollama Tools", font='block')
    logger.info("\n\033[94m" + art + "\033[0m")
    logger.info("\033[92m" + "=" * 50 + "\033[0m")
    logger.info("\033[93mFunction Calling with Ollama LLM\033[0m")
    logger.info("\033[92m" + "=" * 50 + "\033[0m\n")


def add_two_numbers(a: int, b: int) -> int:
    """
    2つの数値を足し算する関数

    Args:
        a (int): 1つ目の数値
        b (int): 2つ目の数値

    Returns:
        int: 2つの数値の合計
    """
    logger.debug(f"Adding numbers: {a} + {b}")
    result = a + b
    logger.info(f"Add result: {result}")
    return result


def subtract_two_numbers(a: int, b: int) -> int:
    """
    2つの数値を引き算する関数

    Args:
        a (int): 引かれる数
        b (int): 引く数

    Returns:
        int: aからbを引いた結果
    """
    logger.debug(f"Subtracting numbers: {a} - {b}")
    result = a - b
    logger.info(f"Subtract result: {result}")
    return result


def main():
    """メイン処理"""
    
    # バナーの表示
    print_banner()

    # ツールは手動で定義してchatに渡すことができます
    add_two_numbers_tool = {
        'type': 'function',
        'function': {
            'name': 'add_two_numbers',
            'description': '2つの数値の足し算を行う',
            'parameters': {
                'type': 'object',
                'required': ['a', 'b'],
                'properties': {
                    'a': {'type': 'integer', 'description': '1つ目の数値'},
                    'b': {'type': 'integer', 'description': '2つ目の数値'},
                },
            },
        },
    }

    subtract_two_numbers_tool = {
        'type': 'function',
        'function': {
            'name': 'subtract_two_numbers',
            'description': '2つの数値の引き算を行う',
            'parameters': {
                'type': 'object',
                'required': ['a', 'b'],
                'properties': {
                    'a': {'type': 'integer', 'description': '引かれる数'},
                    'b': {'type': 'integer', 'description': '引く数'},
                },
            },
        },
    }

    # チャットの初期メッセージを設定
    messages = [{'role': 'user', 'content': '3足す1は何ですか?'}]
    logger.info(f'プロンプト: {messages[0]["content"]}')

    # 利用可能な関数を辞書として定義
    available_functions: Dict[str, Callable] = {
        'add_two_numbers': add_two_numbers,
        'subtract_two_numbers': subtract_two_numbers,
    }

    try:
        # クライアントの初期化
        logger.info("Ollamaクライアントの初期化")
        client = Client(host='http://localhost:11434')

        # モデルとチャットを開始
        logger.info("チャットセッションの開始")
        response: ChatResponse = client.chat(
            'llama3.1',
            messages=messages,
            tools=[add_two_numbers_tool, subtract_two_numbers_tool],  # 両方のツールを渡す
        )

        # ツール呼び出しの処理
        if response.message.tool_calls:
            logger.info("ツール呼び出しの検出")
            # レスポンスには複数のツール呼び出しが含まれる可能性があります
            for tool in response.message.tool_calls:
                # 関数が利用可能であることを確認して呼び出し
                if function_to_call := available_functions.get(tool.function.name):
                    logger.info(f'関数の呼び出し: {tool.function.name}')
                    logger.debug(f'引数: {tool.function.arguments}')
                    output = function_to_call(**tool.function.arguments)
                    logger.info(f'関数の出力: {output}')
                else:
                    logger.warning(f'関数 {tool.function.name} が見つかりません')

            # ツール呼び出しの結果を使ってモデルとチャットを続ける場合
            # モデルが使用するメッセージにツールの応答を追加
            messages.append(response.message)
            messages.append({'role': 'tool', 'content': str(output), 'name': tool.function.name})

            # 関数の出力を含めた最終的な応答を取得
            logger.info("最終応答の取得")
            final_response = client.chat('llama2', messages=messages)
            logger.success(f'最終応答: {final_response.message.content}')

        else:
            logger.warning('モデルからツール呼び出しが返されませんでした')

    except Exception as e:
        logger.error(f"エラーが発生しました: {str(e)}")
        raise


if __name__ == "__main__":
    main()


<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Discussion