著者 | ジュリアン・イップ 編纂者:岳陽 関数呼び出しは新しい概念ではありません。OpenAIは2023年7月にこの機能をGPTモデルに導入し、その後、他の競合他社にも採用されています。例えば、GoogleのGemini APIは最近関数呼び出しのサポートを開始し、AnthropicはClaudeにこれを統合しています。関数呼び出し(特定の関数を呼び出すことでモデルが複雑なタスクを実行できるようにする機能)は、大規模言語モデル(LLM)の重要な機能となり、その応用能力を大幅に向上させています。したがって、この技術を習得することは非常に価値があります。 そのため、基本的な導入部分を超えた内容に焦点を当てた詳細なチュートリアルを作成する予定です(この種のチュートリアルは既に数多く存在します)。このチュートリアルでは、実用的なアプリケーションに焦点を当て、完全自律型AIエージェント(人間の介入なしに自律的に動作し、意思決定を行うことができるAIエージェント)の構築方法と、それをStreamlitと統合してChatGPTのようなWebインターフェースを実現する方法を説明します。このチュートリアルではOpenAIをデモに使用していますが、内容はGeminiなど、関数呼び出しをサポートする他の大規模言語モデルにも同様に当てはまります。 01 関数呼び出しの用途は何ですか?関数呼び出しにより、開発者は関数(ツールとも呼ばれ、数学的計算や注文の発行など、モデルが実行する操作と見なすことができます)を定義し、モデルがこれらの関数を呼び出すために必要なパラメータを含むJSONオブジェクトをインテリジェントに選択して出力することを可能にします。つまり、このテクノロジーは以下の機能を提供します。
この技術は人々にさまざまな新しい機会と可能性をもたらしました。
02 関数呼び出し機能の実行フローGeminiの関数呼び出しドキュメント[1]を参照すると、関数呼び出し関数は次のように実行され、OpenAIにおけるこの関数の動作原理は基本的に同じです。 画像出典: Geminiの関数呼び出しドキュメント [1] 1. ユーザーはアプリケーションにプロンプトを送信します。 2. アプリケーションは、モデルに必要なツールに関する説明情報である、ユーザー提供のプロンプトと関数宣言を提供します。 3. 関数の宣言に基づいて、モデルはツール選択の提案と関連するリクエストパラメータを提供します。モデルは提案されたツールとリクエストパラメータを出力するだけで、実際に関数を呼び出すわけではないことに注意してください。 4. & 5. アプリケーションは、モデルの応答に基づいて関連する API を呼び出します。 6. & 7. API 応答をモデルに入力して、人間が読めるコンテンツを生成します。 8. アプリケーションは最終応答をユーザーに返し、その後ステップ 1 に戻り、このプロセスを繰り返します。 上記の紹介は少し複雑に思えるかもしれませんので、以下の例を使用して概念を詳しく説明します。 03 エージェントの全体的な設計とアーキテクチャ具体的なコードの詳細に入る前に、この記事で説明するエージェントの全体的な設計とアーキテクチャを簡単に紹介しましょう。 3.1 ソリューション: 観光サービスアシスタントこの記事では、旅行中のホテル宿泊客向けのトラベルサービスアシスタントを構築します。この製品では、以下のツール(サービスアシスタントが外部アプリケーションにアクセスできるようにするツール)を使用できます。
3.2 関連技術スタック
それでは紹介を始めましょう! 04. 上記のテクノロジー スタックを使用してエージェントの例を構築します。4.1 事前準備このプロジェクトのコードをクローンするには、Github[7]にアクセスしてください。以下の内容はノートブック 仮想環境を作成してアクティブ化し、「pip install -r requirements.txt」を実行して必要なパッケージをインストールしてください。 4.2 プロジェクトの初期化まずOpenRouterに接続します。OpenAI APIキーをお持ちの場合は、 import osfrom dotenv import load_dotenvfrom haystack.components.generators.chat import OpenAIChatGeneratorfrom haystack.utils import Secretfrom haystack.dataclasses import ChatMessagefrom haystack.components.generators.utils import print_streaming_chunk# thisload_dotenv() を実行する前に、API キーを環境変数として設定してください
OPENROUTER_API_KEY = os.environ.get('OPENROUTER_API_KEY')
chat_generator = OpenAIChatGenerator(api_key=Secret.from_env_var("OPENROUTER_API_KEY"),
api_base_url="https://openrouter.ai/api/v1",
モデル = "openai/gpt-4-turbo-preview",
ストリーミングコールバック=print_streaming_chunk) 次に、chat_generator が正常に呼び出されるかどうかをテストします。 chat_generator.run(messages=[ChatMessage.from_user("このテキストを返します: 'test'")]) ---------- レスポンスは次のようになります ----------{'replies': [ChatMessage(content="'test'", role=<ChatRole.ASSISTANT: 'assistant'>, name=None, meta={'model': 'openai/gpt-4-turbo-preview', 'index': 0, 'finish_reason': 'stop', 'usage': {}})]} 4.3 ステップ1: 適切なデータストレージソリューションを選択して使用するここでは、アプリケーションと 2 つのデータ ソース (非構造化テキストのドキュメント ストアと API 経由で接続されたアプリケーション データベース) 間の接続を確立します。 パイプラインを使用してドキュメントをインデックスする モデルが検索拡張(RAG)を実行するには、システムにサンプルテキストを提供する必要があります。これらのテキストは埋め込み情報に変換され、文書データをメモリに保存するデータストレージスキームを使用して保存されます。 from haystack import Pipeline, Documentfrom haystack.document_stores.in_memory import InMemoryDocumentStorefrom haystack.components.writers import DocumentWriterfrom haystack.components.embedders import SentenceTransformersDocumentEmbedder# サンプルドキュメントdocuments = [
Document(content="コーヒーショップは午前 9 時に開店し、午後 5 時に閉店します。"),
Document(content="ジムのルームは午前 6 時に開き、午後 10 時に閉まります。")
]# ドキュメントストアを作成するdocument_store = InMemoryDocumentStore()# テキストを埋め込みに変換してドキュメントストアに保存するパイプラインを作成するindexing_pipeline = Pipeline()
indexing_pipeline.add_component( "doc_embedder", SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2")
)
indexing_pipeline.add_component("doc_writer", DocumentWriter(document_store=document_store))
indexing_pipeline.connect("doc_embedder.documents", "doc_writer.documents")
indexing_pipeline.run({"doc_embedder": {"documents": documents}}) 上記のプログラムの出力は、入力サンプル ドキュメント データと一致するはずです。 {'doc_writer': {'documents_written': 2}} APIサービスプロセスを開始する db_api.py ファイルに、Flask フレームワークを使用して SQLite データベースに接続するための API サービスを作成します。ターミナルで `python db_api.py` を実行してサービスを起動します。 サービスが正常に実行されると、端末には画像に示す情報が表示されます。 いくつかの初期基本データが db_api.py に事前に設定されていることに気付きました。 データベース内のデータサンプル 4.4 ステップ2: 関数を定義するこのステップでは、モデルが後続の関数呼び出しステップで呼び出して実行する実際の関数を準備します (セクション 02「関数呼び出し機能のフロー」のステップ 4 ~ 5 で説明)。 RAG関数 その一つがRAG関数 haystack.components.embedders から SentenceTransformersTextEmbedder をインポートします。haystack.components.retrievers.in_memory から InMemoryEmbeddingRetriever をインポートします。haystack.components.builders から PromptBuilder をインポートします。haystack.components.generators から OpenAIGenerator をインポートします。
テンプレート = """
与えられたコンテキストに基づいて質問に答えます。
コンテクスト:
{% ドキュメント内のドキュメント %}
{{ドキュメント.コンテンツ}}
{% endfor %}
質問: {{ question }}
答え:
rag_pipe = パイプライン()
rag_pipe.add_component("embedder", SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"))
rag_pipe.add_component("retriever", InMemoryEmbeddingRetriever(document_store=document_store))
rag_pipe.add_component("prompt_builder", PromptBuilder(template=template))# llmへの注意: OpenAIGeneratorではなくOpenAIChatGeneratorを使用しています。後者は入力としてList[str]のみを受け入れ、prompt_builderのstr出力を受け入れることができないためです。rag_pipe.add_component("llm", OpenAIGenerator(api_key=Secret.from_env_var("OPENROUTER_API_KEY"),
api_base_url="https://openrouter.ai/api/v1",
モデル = "openai/gpt-4-turbo-preview"))
rag_pipe.connect("embedder.embedding", "retriever.query_embedding")
rag_pipe.connect("retriever", "prompt_builder.documents")
rag_pipe.connect("prompt_builder", "llm") 機能が正しく動作するかどうかをテストします。 クエリ = 「コーヒーショップはいつ開きますか?」
rag_pipe.run({"embedder": {"text": query}, "prompt_builder": {"question": query}}) モデルによって実行された `rag_pipeline_func` 関数は、以下の出力を生成します。モデルのレスポンスは、先ほど提供したサンプルドキュメントデータに基づいていることに注意してください。 {'llm': {'replies': ['コーヒーショップは午前9時に開店します。'], 'meta': [{'model': 'openai/gpt-4-turbo-preview', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 9, 'prompt_tokens': 60, 'total_tokens': 69, 'total_cost': 0.00087}}]}} 次に、rag_pipe を、他の中間詳細を返さずにクエリに基づいて回答を取得するために必要に応じて rag_pipeline_func(query) を呼び出す関数に変換できます。 定義rag_pipeline_func(query: str): result = rag_pipe.run({"embedder": {"text": query}, "prompt_builder": {"question": query}}) 戻り値{"reply": result["llm"]["replies"][0]} データベースと対話するための API を定義します。 ここでは、データベースと対話するための # Flask のデフォルトのローカル URL。必要に応じて変更してください。db_base_url = 'http://127.0.0.1:5000'# リクエストを使用してデータベースからデータを取得します。import requestimport json# get_categories はプロンプトの一部として提供され、ツールとしては使用されません。def get_categories(): response = requests.get(f'{db_base_url}/category')
data = response.json() return datadef get_items(ids=None,categories=None): params = { 'id': ids, 'category': categories,
}
レスポンス = リクエスト.get(f'{db_base_url}/item', パラメータ = パラメータ)
data = response.json() return datadef purchase_item(id,quantity): headers = { 'Content-type':'application/json',
'承認':'application/json'
}
データ = { 'id': ID, 'quantity': 数量,
}
レスポンス = リクエスト.post(f'{db_base_url}/item/purchase', json=data, headers=headers) レスポンス.json() を返すユーティリティ関数のリストを定義する 関数の定義が完了したので、次のステップは、モデルがこれらの関数の使い方を認識し、理解できるようにすることです。そのためには、これらの関数に関する説明情報を提供する必要があります。 ここではOpenAIを使用しているため、これらのツール(関数)をOpenAI [8]で要求される形式で記述する必要があります。 ツール = [
{ "type": "function", "function": { "name": "get_items", "description": "データベースからアイテムのリストを取得します", "parameters": { "type": "object", "properties": { "ids": { "type": "string", "description": "取得するアイテムIDのカンマ区切りリスト",
}, "categories": { "type": "string", "description": "取得するアイテムカテゴリのカンマ区切りリスト",
},
}、 "必須": []、
},
}
},
{ "type": "function", "function": { "name": "purchase_item", "description": "特定の商品を購入する", "parameters": { "type": "object", "properties": { "id": { "type": "string", "description": "指定された商品ID、商品名はここでは受け入れられません。まずデータベースから商品IDを取得してください。",
}, "quantity": { "type": "integer", "description": "購入する商品の数",
},
}、 "必須": []、
},
}
},
{ "type": "function", "function": { "name": "rag_pipeline_func", "description": "ホテルのパンフレットから情報を取得する", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "検索で使用するクエリ。ユーザーのメッセージから推測します。質問または文である必要があります",
}
}, "必須": ["クエリ"],
},
},
}
] 4.5 ステップ3: すべてのシステムコンポーネントを統合するこれで、関数呼び出し機能をテストするために必要なシステムコンポーネントがすべて揃いました。このステップでは、以下の作業を行う必要があります。
# 1. 初期 promptcontext = f"""あなたはホテルを訪れる観光客のアシスタントです。
観光客が購入できるアイテムのデータベース({get_categories()} を含む)にアクセスでき、ホテルのパンフレットにもアクセスできます。
観光客の質問がデータベースから回答できない場合は、パンフレットを参照できます。
観光客の質問がパンフレットから得られない場合は、ホテルのスタッフに質問するよう観光客に依頼できます。
"""メッセージ = [
ChatMessage.from_system(context), # 2. ユーザーからのサンプルメッセージ ChatMessage.from_user("コーヒーを買ってもいいですか?"),
]# 3. ツールリストを渡してチャットジェネレーターを呼び出すresponse = chat_generator.run(messages=messages, generation_kwargs= {"tools": tools})
応答 - - - - - 応答 - - - - -
{'返信': [ChatMessage(content='[{"index": 0, "id": "call_AkTWoiJzx5uJSgKW0WAI1yBB", "function": {"arguments": "{\"categories\":\"食品と飲料\"}", "name": "get_items"}, "type": "function"}]', role=<ChatRole.ASSISTANT: 'assistant'>, name=None, meta={'model': 'openai/gpt-4-turbo-preview', 'index': 0, 'finish_reason': 'tool_calls', 'usage': {}})]} それでは、モデルの応答を調べてみましょう。 関数呼び出しによって返されるコンテンツには、モデルが呼び出すことを選択した関数自体だけでなく、その関数に渡されるパラメータも含まれることに注意することが重要です。 function_call = json.loads(レスポンス["返信"][0].content)[0]
function_name = function_call["関数"]["名前"]
function_args = json.loads(function_call["関数"]["引数"])
print("関数名:", function_name)
print("関数の引数:", function_args) ---------- レスポンス ---------- 関数名: get_items 関数引数: {'categories': '食品と飲料'} モデルは新しい問題に遭遇すると、その問題を分析し、既存のコンテキスト情報と組み合わせて、利用可能なツール機能のどれが問題の解決に最も役立つかを評価します。 # 別の質問messages.append(ChatMessage.from_user("コーヒーショップはどこですか?"))# チャットジェネレーターを呼び出し、ツールリストを渡しますresponse = chat_generator.run(messages=messages, generation_kwargs= {"tools": tools})
function_call = json.loads(レスポンス["返信"][0].content)[0]
function_name = function_call["関数"]["名前"]
function_args = json.loads(function_call["関数"]["引数"])
print("関数名:", function_name)
print("関数の引数:", function_args) ---------- 応答 ---------- 関数名: rag_pipeline_func 関数引数: {'query': "コーヒーショップはどこですか?"} このステップではまだ関数の呼び出しや実行は行われませんので、ご注意ください。実際の関数呼び出しは次のステップで行われます。 関数の呼び出し このステップでは、選択した関数にパラメータを入力する必要があります。 ## 対応する関数を見つけて、指定された引数で呼び出します。available_functions = {"get_items": get_items, "purchase_item": purchase_item,"rag_pipeline_func": rag_pipeline_func}
function_to_call = available_functions[関数名]
function_response = function_to_call(**function_args)
print("関数の応答:", function_response) ---------- レスポンス ---------- 関数レスポンス: {'reply': '指定されたコンテキストでは、コーヒーショップの物理的な場所が指定されておらず、営業時間のみが示されています。したがって、指定された情報に基づいてコーヒーショップの場所を特定することはできません。'} 次に、 messages.append(ChatMessage.from_function(content=json.dumps(function_response), name=function_name)) レスポンス = chat_generator.run(メッセージ = メッセージ) response_msg = レスポンス["返信"][0] 応答メッセージ.content を印刷します。 ---------- 回答 ---------- ホテル内のカフェの場所につきましては、ホテルスタッフに直接お問い合わせいただくことをお勧めいたします。スタッフが正確なご案内をさせていただきます。 これで、ユーザーと AI 間の完全な対話サイクルが完了しました。 4.6 ステップ4: リアルタイムのインタラクティブチャットシステムに変換する上記のコードは関数呼び出しがどのように実装されるかを示していますが、さらに一歩進んで、これをリアルタイムのインタラクティブなチャット システムに変換したいと考えています。 このセクションでは、2 つの実装方法を説明します。
input() ループ この部分のコードはHaystackチュートリアル[9]からコピーしたもので、モデルを素早くテストするために使用できます。注意:このアプリケーションは関数呼び出しの概念を示すために作成されたものであり、複数のアイテムの同時ソートをサポートしたり、錯覚が全くなかったりするなど、アプリケーションが完全に堅牢であることを意味するものではありません。 JSONをインポート
haystack.dataclassesからChatMessageとChatRoleをインポートします
応答 = なし
メッセージ = [
ChatMessage.from_system(コンテキスト)
]
真の場合:
# OpenAIの応答がツール呼び出しの場合
レスポンスがあり、response["replies"][0].meta["finish_reason"] == "tool_calls"の場合:
function_calls = json.loads(レスポンス["返信"][0].content)
function_calls 内の function_call の場合:
## 関数呼び出し情報を解析する
function_name = function_call["関数"]["名前"]
function_args = json.loads(function_call["関数"]["引数"])
## 対応する関数を見つけて、指定された引数で呼び出します
呼び出す関数 = 利用可能な関数[関数名]
function_response = function_to_call(**function_args)
## `ChatMessage.from_function` を使用して関数応答をメッセージ リストに追加します。messages.append(ChatMessage.from_function(content=json.dumps(function_response), name=function_name))
# 通常の会話
それ以外:
#アシスタントメッセージをメッセージリストに追加する
そうでない場合、メッセージ[-1].is_from(ChatRole.SYSTEM):
メッセージ.append(レスポンス["返信"][0])
user_input = input("メッセージを入力してください👇 情報: 停止するには「exit」または「quit」と入力してください\n")
user_input.lower() == "exit" または user_input.lower() == "quit" の場合:
壊す
それ以外:
メッセージ.append(ChatMessage.from_user(user_input))
レスポンス = chat_generator.run(messages=メッセージ、generation_kwargs={"tools": ツール}) 統合開発環境でインタラクティブチャットアプリを実行する 基本的なインタラクション方法は引き続き機能しますが、より美しくユーザーフレンドリーなインターフェースにより、優れたユーザー エクスペリエンスが実現します。 Streamlitインターフェース Streamlitは、PythonスクリプトとWeb開発技術を巧みに組み合わせ、共有可能なWebサービスアプリケーションに変換し、このインタラクティブな関数呼び出しアプリケーションのための全く新しいWebインターフェースを構築します。上記のコードはStreamlitアプリケーションに適応されており、コードリポジトリのstreamlitフォルダに保存されています。 次の手順でアプリケーションを実行できます。
私が言いたかったのは基本的にこれだけです!この記事を楽しんでいただけたら嬉しいです。 流線型のUI 読んでくれてありがとう! ジュリアン・イップ マルチクラウド データ アーキテクト | Azure、GCP、Databricks 認定 | ML および MLOps 実践者 終わり 参考文献 [1] https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling [2] https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2 [3] https://docs.haystack.deepset.ai/docs/inmemorydocumentstore [4] https://openrouter.ai/models/openai/gpt-4-1106-preview [5] https://haystack.deepset.ai/ [6] https://haystack.deepset.ai/tutorials/40_building_chat_application_with_function_calling [7] https://github.com/yip-kl/llm_function_calling_demo [8] https://cookbook.openai.com/examples/function_calling_with_an_openapi_spec [9] https://haystack.deepset.ai/tutorials/40_building_chat_application_with_function_calling この記事は、原著者の許可を得てBaihai IDPによって翻訳されました。翻訳の転載をご希望の場合は、お問い合わせください。 オリジナルリンク: https://towardsdatascience.com/build-autonomous-ai-agents-with-function-calling-0bb483753975 |