はじめに
近年、ローカル環境でLLMを動作させる需要が高まっています。その中で、Ollamaは手軽に使えるオープンソースのLLMプラットフォームとして注目を集めています。本記事では、Ollamaを使用した関数呼び出し(Function Calling)機能の実装方法について、実践的なコード例を交えながら解説します。
OllamaのK/V Context量子化でだいぶ運用が楽になったので、エージェント向けにtoolsで遊んでみた!
llama3.1 8b で単純なタスクのtoolsは普通に動いた!
*さらなる軽量モデルの検証などは後日。。。 https://t.co/SmTPGLo9ap pic.twitter.com/OragDG1DtB— Maki@Sunwood AI Labs. (@hAru_mAki_ch) December 5, 2024
🎯 この記事のゴール
- Ollama Toolsの基本的な使い方の理解
- 関数呼び出し機能の実装方法の習得
- 実践的なユースケースの把握
Ollama Toolsの基本機能
主要コンポーネント
-
ロガー機能(loguru)
- 詳細なログ出力
- デバッグ情報の可視化
- エラーハンドリングのサポート
-
関数定義システム
- 数値計算機能
- 型アノテーションによる安全性確保
- 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文による例外処理
- 詳細なログ出力
- グレースフルな失敗処理
ログ管理のベストプラクティス
-
適切なログレベルの使用
- DEBUG: 詳細なデバッグ情報
- INFO: 一般的な情報
- WARNING: 警告
- ERROR: エラー情報
-
構造化ログの活用
logger.info(f"プロンプト: {messages[0]['content']}")
セキュリティ考慮事項
- 入力値のバリデーション
- 適切な型チェック
- セキュアな関数実行環境の確保
まとめと発展的な使用方法
主要な学習ポイント
- Ollama Toolsの基本構成
- 関数呼び出し機能の実装方法
- エラーハンドリングとログ管理の重要性
発展的な使用方法
- 複雑な計算処理の実装
- カスタム関数の追加
- 外部APIとの連携
次のステップ
- より複雑な関数の実装
- エラーハンドリングの強化
- パフォーマンス最適化
参考情報
全体コード
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()
コメント