Semantic Kernel Agent Frameworkでマルチエージェントを構築する(1/2):プラグインの実装と関数処理内容の確認

Senamtic Kernelを使ったマルチエージェントの開発を行いました。構築方法に関して2回に分けて記載します。

1. プラグインの実装と関数処理内容の確認
2. マルチエージェントの構築

本記事では、マルチエージェント構築の準備として、Sematic Kernelのpluginを使い特定の処理に特化した機能を追加する方法を紹介します。

Semantic Kernel

Semantic Kernelは、Microsoftが提供するAI エージェントを簡単に構築し、最新の AI モデルを C#、Python、または Java コードベースに統合できる、軽量のオープンソース開発キットです。

詳しい説明は以下のリンクでご確認ください。

learn.microsoft.com

Kernelの概要

Kernelは、Semantic Kernelの中心的な要素であり、AIアプリケーションを運営するためのサービスやプラグインを管理するための依存性注入コンテナです。

Kernelに追加可能な2つのコンポネント

  1. サービス
    • AIサービス(例:chat completion)やアプリケーションを実行するために必要な他のサービス
    • 例:ロギングやHTTPクライアント
  2. プラグイン
    • AIサービスやプロンプトテンプレートが作業を実行するために使用するコンポーネント
    • 例:AIサービスはプラグインを使用してデータベースからデータを取得したり、外部APIを呼び出してアクションを実行することが可能

Pluginの追加

Semantic Kernelにプラグインを追加する主な方法は、ネイティブ コードのインポートとOpenAPI 仕様のインポートの2つあります。

今回はネイティブ コードプラグインを使用し、プラグインの追加を行います。

それぞれの詳しい実装方法は以下のリンクでご確認いただけます。

プラグイン追加の流れ

プラグインは以下のように追加していきます。

  1. プラグインを定義する
  2. プラグインをカーネルに追加する
  3. 関数呼び出しを含むプロンプトでプラグインの関数を呼び出す

今回は外部ソースからデータを取得するRAG用のプラグインを作成します。

今回の例ではローカル環境での構築を容易にするために、オープンソースのFAISSを使用します。

なお、RAGの実装自体は本記事の主題ではないため、RAGの構築方法についての詳しい説明は省略します。

前提条件

Azureリソース
  • Azure OpenAIリソース
  • チャットに使用するAzure OpenAI モデル(gpt-4o、gpt-4o-mini など)
  • Azure OpenAI embeddingモデル
ディレクトリ構成

/<プロジェクトディレクトリ>/ # プロジェクトのルートディレクトリ
├── data/ # faiss-dbに登録するデータを保存するためのディレクトリ
│ └── salse_input1.txt # 入力データを保存するファイル
│ └── salse_input2.txt # 入力データを保存するファイル
├── data2/ # faiss-semantic kernel-dbに登録するデータを保存するための2つ目のディレクトリ
│ └── sk_input1.txt # 入力データを保存するファイル
│ └── sk_input2.txt # 入力データを保存するファイル
├── data3/ # faiss-semantic kernel-dbに登録するデータを保存するための2つ目のディレクトリ
│ └── リモートワーク規定.txt # 入力データを保存するファイル
│ └── 経費精算規定.txt # 入力データを保存するファイル
├── faiss-db/ # 営業ドキュメント用のFAISSのデータベースファイルを保存するディレクトリ
├── faiss-company-rule-db/ # 社内規定ドキュメント用のFAISSデータベースを保存するディレクトリ
├── faiss-semantic kernel-db/ # Semantic Kernelのドキュメント用のFAISSデータベースを保存するディレクトリ
├── venv/ # 仮想環境のディレクトリ
├── .env # 環境変数を定義するファイル
├── create_vector_db.py # ベクトルデータベースを作成するファイル
└── <実行ファイル>.py # プロジェクトのメイン実行ファイル

※DBに保存する入力データは、すべてサンプルのものになります。

faiss-...のデータベースはcreate_vector_db.pyを実行することで作成されます。

import os
from glob import glob

from dotenv import load_dotenv
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import AzureOpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 環境変数の読み込み
load_dotenv()

# ベクターデータベースの作成
def create_vector_database(dir_path):
    all_documents = []
    # ディレクトリ内のすべてのテキストファイルをロード
    for file_path in glob(os.path.join(dir_path, "*.txt")):
        # テキストデータのロード
        loader = TextLoader(file_path, encoding='utf-8') 
        docs = loader.load()

        # ドキュメントを分割してチャンクを作成
        splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50) # 適切なサイズに設定してください。
        documents = splitter.split_documents(docs)

        all_documents.extend(documents)  # すべてのドキュメントをリストに追加

    # エンベディングを生成してベクターストアに保存
    embeddings = AzureOpenAIEmbeddings(
        model=os.environ.get("AZURE_OPENAI_EMBEDDING_DEPLOYMENT"),
    )
    #db = FAISS.from_documents(documents, embeddings)
    db = FAISS.from_documents(all_documents, embeddings)
    db.save_local("./faiss-company-rule-db") # dbの名称は適切に変更してください。

if __name__ == "__main__":
    create_vector_database("data3")

詳しくは、ローカルRAG環境構築で参考にした、以下の記事をご確認ください。

【今更聞けない!?】✨ Azure OpenAIとLangChainで始める!RAG入門!AIチャットボット構築ハンズオン 🚀 #Python - Qiita

.envには以下の内容を記載します。各値はAzure AI Foundry(旧 Azure AI Studio)のデプロイしたモデルの詳細から取得できます。

AZURE_OPENAI_DEPLOYMENT="<model_name>" 
AZURE_OPENAI_EMBEDDING_DEPLOYMENT="<embedding_model_name>"
deployment_api_key="<api_key>"
deployment_base_url="<endpoint(ターゲットURI)>"

プラグインの実装

プラグインを定義

プラグインとして利用するクラスを作成します。

LangChainを利用し、 ベクターストア(FAISS)のデータベースからデータを取得するRAG pluginを複数定義します。

# 複数のRAGPluginを追加するため、親クラスを定義
class BaseRAGPlugin:
    def __init__(self, vectorstore_path: str):
        self.embeddings = AzureOpenAIEmbeddings(
            azure_deployment=os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT"),
        )
        self.vectorstore_faiss = FAISS.load_local(
            vectorstore_path, self.embeddings, allow_dangerous_deserialization=True
        ) # ベクターストア(FAISS)のデータベースをロード


class InternalDocumentationOfSalesRAGPlugin(BaseRAGPlugin):
    '''
    営業に関するドキュメントのRAG plugin
    '''
    def __init__(self):
        super().__init__("./faiss-db")
        self.prompt = PromptTemplate(
            template=(
                "あなたはAIアシスタントです。以下のコンテキストを考慮してください:\n"
                "{context}\n\n"
                "次の質問に答えてください:\n"
                "{question}\n\n"
                "アシスタント:"
            ),
            input_variables=["context", "question"],
        )

    @kernel_function(description="search documents of sales")
    def search(self, query: str) -> str:
        retriever = self.vectorstore_faiss.as_retriever()
        context = retriever.invoke(query)
        # Return text from first result
        prompt_text = self.prompt.format(context=context[0].page_content, question=query)
        return prompt_text

class SematicKernelInfoRAGPlugin(BaseRAGPlugin):
    '''
    Sematic Kernelに関するドキュメントのRAG plugin
    '''
    def __init__(self):
        super().__init__("./faiss-semantickernel-db")
        self.prompt = PromptTemplate(
            template=(
                "あなたはAIアシスタントです。以下のコンテキストを考慮してください:\n"
                "{context}\n\n"
                "次の質問に答えてください:\n"
                "{question}\n\n"
                "アシスタント:"
            ),
            input_variables=["context", "question"],
        )

    @kernel_function(description="search documents about Semantic Kernel")
    def search(self, query: str) -> str:
        retriever = self.vectorstore_faiss.as_retriever()
        context = retriever.invoke(query)
        # Return text from first result
        prompt_text = self.prompt.format(context=context[0].page_content, question=query)
        return prompt_text


class InternalDocumentationRAGPlugin(BaseRAGPlugin):
    '''
    社内規定に関するドキュメントのRAG plugin
    '''
    def __init__(self):
        super().__init__("./faiss-company-rule-db")
        self.prompt = PromptTemplate(
            template=(
                "あなたはAIアシスタントです。以下のコンテキストを考慮してください:\n"
                "{context}\n\n"
                "次の質問に答えてください:\n"
                "{question}\n\n"
                "アシスタント:"
            ),
            input_variables=["context", "question"],
        )

    @kernel_function(description="search documents about internal company rules")
    def search(self, query: str) -> str:
        retriever = self.vectorstore_faiss.as_retriever()
        context = retriever.invoke(query)
        # Return text from first result
        prompt_text = self.prompt.format(context=context[0].page_content, question=query)
        return prompt_text
プラグインをカーネルに追加する

定義したpluginをkernelに追加します。

    # Initialize the kernel
    kernel = Kernel()

    # RAGのpluginを追加
    kernel.add_plugin(plugin=InternalDocumentationOfSalesRAGPlugin(), plugin_name="InternalDocumentationOfSalesRAGPlugin")
    kernel.add_plugin(plugin=SematicKernelInfoRAGPlugin(), plugin_name="SematicKernelInfoRAGPlugin")
    kernel.add_plugin(InternalDocumentationRAGPlugin(), plugin_name="InternalDocumentaionRAG")
関数呼び出しを含むプロンプトでプラグインの関数を呼び出す(コード全体)
import asyncio
import os
from dotenv import load_dotenv
from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
    AzureChatPromptExecutionSettings,
)

from langchain_core.prompts import PromptTemplate
from langchain_openai import AzureOpenAIEmbeddings
from langchain_community.vectorstores import FAISS


# 複数のRAGPluginを追加するため、親クラスを定義
class BaseRAGPlugin:
    def __init__(self, vectorstore_path: str):
        self.embeddings = AzureOpenAIEmbeddings(
            azure_deployment=os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT"),
        )
        self.vectorstore_faiss = FAISS.load_local(
            vectorstore_path, self.embeddings, allow_dangerous_deserialization=True
        ) # ベクターストア(FAISS)のデータベースをロード


class InternalDocumentationOfSalesRAGPlugin(BaseRAGPlugin):
    '''
    営業に関するドキュメントのRAG plugin
    '''
    def __init__(self):
        super().__init__("./faiss-db")
        self.prompt = PromptTemplate(
            template=(
                "あなたは営業に関するAIアシスタントです。以下のコンテキストを考慮してください:\n"
                "{context}\n\n"
                "次の質問に答えてください:\n"
                "{question}\n\n"
                "アシスタント:"
            ),
            input_variables=["context", "question"],
        )

    @kernel_function(description="search documents of sales")
    def search(self, query: str) -> str:
        retriever = self.vectorstore_faiss.as_retriever()
        context = retriever.invoke(query)
        # Return text from first result
        prompt_text = self.prompt.format(context=context[0].page_content, question=query)
        return prompt_text

class SematicKernelInfoRAGPlugin(BaseRAGPlugin):
    '''
    Sematic Kernelに関するドキュメントのRAG plugin
    '''
    def __init__(self):
        super().__init__("./faiss-semantickernel-db")
        self.prompt = PromptTemplate(
            template=(
                "あなたはSematic Kernelに関するAIアシスタントです。以下のコンテキストを考慮してください:\n"
                "{context}\n\n"
                "次の質問に答えてください:\n"
                "{question}\n\n"
                "アシスタント:"
            ),
            input_variables=["context", "question"],
        )

    @kernel_function(description="search documents about Semantic Kernel")
    def search(self, query: str) -> str:
        retriever = self.vectorstore_faiss.as_retriever()
        context = retriever.invoke(query)
        # Return text from first result
        prompt_text = self.prompt.format(context=context[0].page_content, question=query)
        return prompt_text


class InternalDocumentationRAGPlugin(BaseRAGPlugin):
    '''
    社内規定に関するドキュメントのRAG plugin
    '''
    def __init__(self):
        super().__init__("./faiss-company-rule-db")
        self.prompt = PromptTemplate(
            template=(
                "あなたは社内規定に関するAIアシスタントです。以下のコンテキストを考慮してください:\n"
                "{context}\n\n"
                "次の質問に答えてください:\n"
                "{question}\n\n"
                "アシスタント:"
            ),
            input_variables=["context", "question"],
        )

    @kernel_function(description="search documents about internal company rules")
    def search(self, query: str) -> str:
        retriever = self.vectorstore_faiss.as_retriever()
        context = retriever.invoke(query)
        # Return text from first result
        prompt_text = self.prompt.format(context=context[0].page_content, question=query)
        return prompt_text

async def main():
    # Initialize the kernel
    kernel = Kernel()

    # 環境変数の読み込み
    load_dotenv()

    # Add Azure OpenAI chat completion
    chat_completion = AzureChatCompletion(
        deployment_name=os.getenv("AZURE_OPENAI_DEPLOYMENT"),
        api_key=os.getenv("DEPLOYMENT_API_KEY"),
        base_url=os.getenv("DEPLOYMENT_BASE_URL"),
    )
    kernel.add_service(chat_completion)

    # RAGのpluginを追加
    kernel.add_plugin(plugin=InternalDocumentationOfSalesRAGPlugin(), plugin_name="InternalDocumentationOfSalesRAGPlugin")
    kernel.add_plugin(plugin=SematicKernelInfoRAGPlugin(), plugin_name="SematicKernelInfoRAGPlugin")
    kernel.add_plugin(InternalDocumentationRAGPlugin(), plugin_name="InternalDocumentaionRAG")

    # Enable planning
    execution_settings = AzureChatPromptExecutionSettings()
    execution_settings.function_choice_behavior = FunctionChoiceBehavior.Auto()

    # Create a history of the conversation
    history = ChatHistory()

    # Initiate a back-and-forth chat
    while True:
        # Collect user input
        userInput = input("User > ")

        # Terminate the loop if the user says "exit"
        if userInput == "exit":
            break

        # Add user input to the history
        history.add_user_message(userInput)

        # Get the response from the AI
        result = (await chat_completion.get_chat_message_contents(
            chat_history=history,
            settings=execution_settings,
            kernel=kernel,
            arguments=KernelArguments(),
        ))[0]

        # Print the results
        print("Assistant > " + str(result))

        # Add the message from the agent to the chat history
        history.add_message(result)


# Run the main function
if __name__ == "__main__":
    asyncio.run(main())

実行結果

複数のpluginを追加し実行する準備ができました。

コードやプロンプトで利用するpluginを明示的に記載していませんが、しっかりとデータベースからの情報をもとに回答してくれます。

プロンプトの内容に応じて、@kernel_function(description="")で定義された説明をもとに、最も関連性の高いpluginを選択しているようです。

社内規定に関する質問:

User > テレワークの上限を教えてください。
Assistant > テレワークの上限については、以下のように定められています。

- リモートワークは原則として「週3日まで」認められています。
- リモートワークを希望する場合は、必ず上司の承認を得る必要があります。

また、業務を行う際は、適切な作業環境を整え、安定したインターネット接続を確保することが求められます。

User > 経費に含まれるものを教えてください。
Assistant > 経費として精算できる対象は、以下の業務に関連する経費です。

1. **出張費**
   - 交通費
   - 宿泊費
   - 食事代

2. **事務用品費**

3. **講演会・セミナー参加費**

4. **業務に必要なソフトウェア購入費**

これらの経費は、適切に申請し、必要な領収書や証明書を添付することで精算されます。

Semantic Kernelに関する質問:

User > semantic kernelの開発元を教えてください。
Assistant > Semantic Kernelは、**マイクロソフト**が提供するエージェントフレームワークです。このフレームワークは、AIモデルを利用したアプリケーション開発を支援するために設計されています。特に自然言語処理や知識ベースのエージェントを構築する際に役立ちます。  

営業に関する質問:

User > 営業においてクロージングに大切な要素を教えてください。
Assistant > 営業においてクロージングに大切な要素は以下の通りです。
1. **交渉スキル**:
   - 顧客と条件について話し合い、双方にとって有益な合意を形成するために必要な技能です。

2. **心理的トリガーの活用**:
   - 限定オファーや時間制限を用いて、顧客に「今決断する」理由を提供するテクニックです。

これらのスキルを駆使することで、顧客との信頼関係を築き、取引の成立へと導くことができます。

Pluginに該当しない質問:

User > 日本の人口を教えてください。
Assistant > 2023年時点での日本の人口は約1億2500万人とされています。ただし、人口は常に変動するため、最新の情報は公式な統計機関や
政府の発表を確認することをお勧めします。

また、pluginに関連性の高いプロンプトで答えがデータベースから見つからない場合は、一般的な回答を返してくれるようです。*1

以下に、データベースにデータがない質問への結果を記載します。

User > 有給の利用方法を教えてください。
Assistant > 有給の利用方法について具体的な情報は得られませんでしたが、一般的なプロセスは以下のようになります。具体的な手続きは社内の規定に従ってください。

### 有給休暇の利用方法

1. **申請手続き**:
   - 有給休暇を取得する場合、事前に上司や人事部門に申請を行います。
   - 会社の方針に従って、申請書やシステムを利用します。

2. **取得期間の調整**:
   - 希望する有給の取得期間を具体的に示し、業務に支障がないかを確認します。
   - 上司の承認が必要です。

3. **スケジュールの調整**:
   - 業務の進捗状況やチームメンバーに配慮しつつ、他のメンバーとの調整が求められる場合もあります。

4. **休暇の準備**:
   - 有給休暇の前後に、業務が滞らないように引き継ぎや必要な準備を行います。
   - 休暇中に連絡を取る必要がある場合は、その連絡方法をあらかじめ知らせておきます。

5. **休暇の取得**:
   - 申請が承認されたら、指定した日程で有給休暇を取得します。

具体的な手続きや必要書類については、会社の就業規則や人事部に確認してください。
User > semantic kernelのインストール方法を教えてください。
Assistant > Semantic Kernelの具体的なインストール方法に関する情報は得られませんでしたが、一般的なフレームワークのインストール手
順を以下に示します。通常、以下の手順に従ってインストールを行いますが、具体的な手順は公式ドキュメントやリポジトリを参照してください。

### インストール手順の一般例

1. **環境の準備**:
   - 必要なプログラム言語や環境(例: Python, .NETなど)をインストールします。

2. **パッケージ管理ツールの使用**:
   - `npm`, `pip`, `NuGet`などのパッケージ管理ツールを使用してインストールします。
   - 例えば、npmを使用する場合:
     ```bash
     npm install semantic-kernel
     ```

3. **リポジトリのクローン**:
   - GitHubなどのリポジトリからコードをクローンします。
   - 例えば:
     ```bash
     git clone https://github.com/microsoft/semantic-kernel.git
     cd semantic-kernel
     ```

4. **依存関係のインストール**:
   - プロジェクトの依存関係をインストールします。一般には`requirements.txt`や`package.json`に記載されています。
   - 例えば:
     ```bash
     pip install -r requirements.txt
     ```

5. **サンプルやドキュメントの確認**:
   - インストール後は、サンプルコードや公式ドキュメントを参考にして、使用方法を学習します。

詳細な手順については、[Semantic Kernelの公式ドキュメント](https://github.com/microsoft/semantic-kernel)を確認することをお勧めし ます。
User > 営業でメモを取るためのテンプレートを表示してください。
Assistant > 営業でメモを取るためのテンプレートとして以下の項目を提案します。必要に応じて追加や修正を行ってください。

---

### 営業メモテンプレート

#### 日付:
- (例: 2023年10月1日)

#### 顧客名:
- (例: 株式会社ABC)

#### 担当者名:
- (例: 山田太郎)

#### 会話内容:
1. **ニーズや課題:**
   - (顧客が話したニーズや課題を記載)

2. **提案内容:**
   - (具体的に提案した内容を記載)

3. **顧客の反応:**
   - (顧客の反応や意見を記載)

#### 次のステップ:
- (次回のフォローアップ予定やアクションを記載)

#### メモ・整理事項:
- (その他、考慮すべき事項や気づき)

---

このテンプレートを使用して、営業活動の記録を整理しやすくしましょう。

ログとフィルターの実装

ログやフィルターを追加することで、どのようにプラグインが利用され関数が実行されているかの情報を取得することが可能となります。

ロギング

Semantic Kernelは、ログにカーネル、カーネルプラグインおよび関数、AIコネクタからの重要なイベントやエラーを記録しています。loggingを追加することで、関数の呼び出しや外部サービスとのやり取り、トークンの消費状況の情報を取得することができます。

以下のようなログを追加します。

import logging

logging.basicConfig(
        level=logging.INFO,  # ログレベルを設定
        format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )

追加後にコードを実行すると、以下のようなログを取得するとこができます。

User > リモートワークが週に何回可能か教えてください。
[2024-12-02 11:27:31 - httpx:1786 - INFO] HTTP Request: POST https://<openai_resource>/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-06-01 "HTTP/1.1 200 OK"
[2024-12-02 11:27:31 - semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:194 - INFO] OpenAI usage: CompletionUsage(completion_tokens=29, prompt_tokens=116, total_tokens=145, completion_tokens_details=None, prompt_tokens_details=None)
[2024-12-02 11:27:31 - semantic_kernel.connectors.ai.chat_completion_client_base:149 - INFO] processing 1 tool calls in parallel.
[2024-12-02 11:27:31 - semantic_kernel.kernel:383 - INFO] Calling InternalDocumentaionRAG-search function with args: {"query":" リモートワーク 週 可能 回数"}
[2024-12-02 11:27:31 - semantic_kernel.functions.kernel_function:19 - INFO] Function InternalDocumentaionRAG-search invoking.
[2024-12-02 11:27:32 - httpx:1038 - INFO] HTTP Request: POST https://<openai_resource>//openai/deployments/text-embedding-ada-002/embeddings?api-version=2024-07-01-preview "HTTP/1.1 200 OK"

[2024-12-02 11:27:32 - semantic_kernel.functions.kernel_function:29 - INFO] Function InternalDocumentaionRAG-search succeeded.  
[2024-12-02 11:27:32 - semantic_kernel.functions.kernel_function:53 - INFO] Function completed. Duration: 0.695278s
[2024-12-02 11:27:33 - httpx:1786 - INFO] HTTP Request: POST https://<openai_resource>/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-06-01 "HTTP/1.1 200 OK"
[2024-12-02 11:27:33 - semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:194 - INFO] OpenAI usage: CompletionUsage(completion_tokens=41, prompt_tokens=286, total_tokens=327, completion_tokens_details=None, prompt_tokens_details=None)
Assistant > リモートワークは、原則として週3日まで認められています。リモートワークを希望する場合は、上司の承認が必要です。

このログには、以下のような情報が含まれています。

  • 各種APIの呼び出し状況とその成果(HTTPステータス)
  • OpenAIサービスの利用状況(トークン消費の詳細)
  • 特定のユーザーリクエストに対してどのような処理が行われたか(関数の呼び出し詳細)

ログを追加することで、どのようにプラグインが利用されているかや、どのようなクエリが実行されているかを確認することが可能になります。

フィルター

Semantic kernelにはフィルターという機能があります。

フィルターを利用することで、実行される関数の前後で特定の処理を追加したり、エラー処理を行ったりすることができます。

フィルターの種類

Sematic Kernelでは3つのフィルター機能を利用することが可能です。

  1. Function Invocation Filter
    • 概要:KernelFunction が呼び出されるたびに実行される
    • 機能:
      • 関数の情報取得: 実行される関数、その引数を取得可能
      • 例外処理: 関数の実行中に発生した例外をキャッチすることが可能
      • 結果のオーバーライド: 関数の結果を変更することが可能
      • 再試行: 失敗した関数の実行を再試行することができ、必要に応じて他のAIモデルに切り替えることが可能
  2. Prompt Render Filter
    • 概要:プロンプトレンダリング操作の前に実行される
    • 機能:
      • プロンプトの確認と変更: AIに送信されるプロンプトを確認し、変更することが可能。
        • 特に、RAG(Retrieval-Augmented Generation)やPII(Personally Identifiable Information)の編集シナリオにおいて有用
      • 結果のオーバーライド: 関数の結果をオーバーライドすることで、プロンプトがAIに送信されないようにすることが可能
        • これにより、セマンティックキャッシングに利用が可能
  3. Auto Function Invocation Filter
    • 概要:Function Invocation Filterと同様だが、自動関数呼び出しのスコープ内で実行される
    • 機能:
      • 詳細なコンテキスト情報: チャット履歴、実行される関数の一覧、要求イテレーションカウンターなど、詳細な情報をコンテキストとして活用可能。
      • プロセスの終了: 自動関数呼び出しプロセスを終了する機能があり、例えば、すでに目的の結果が得られている場合などには、不要な関数の実行を回避できます。

フィルターに関して、詳しくは次のリンクでご確認いただけます。

learn.microsoft.com

フィルターの追加

Auto Function Invocation Filterを追加し、実行された関数の詳細や結果を確認します。

from semantic_kernel.filters.filter_types import FilterTypes
from semantic_kernel.functions.function_result import FunctionResult
from semantic_kernel.contents.function_result_content import FunctionResultContent
from semantic_kernel.filters.auto_function_invocation.auto_function_invocation_context import (
    AutoFunctionInvocationContext,
)

def initialize_kernel_and_filter():
    # Initialize the kernel
    kernel = Kernel()

    # add filter
    # フィルタを定義することで、実行される関数の前後で特定の処理を追加したり、エラー処理を行ったりすることができます。
    @kernel.filter(FilterTypes.AUTO_FUNCTION_INVOCATION)
    async def auto_function_invocation_filter(context: AutoFunctionInvocationContext, next):
        """A filter that will be called for each function call in the response."""
        print("\nAuto function invocation filter")
        print(f"Plugin: {context.function.plugin_name}")
        print(f"Function: {context.function.name}")
        print(f"Request sequence: {context.request_sequence_index}")
        print(f"Function sequence: {context.function_sequence_index}")

        # messageの履歴を確認
        function_calls = context.chat_history.messages[-1].items
        print(f"Number of function calls: {len(function_calls)}")
        print(f"\nchat history:\n {context.chat_history.messages}")
        for function_call in function_calls:
            print(f"\nplugin name: {function_call.plugin_name}")
            print(f"function name: {function_call.function_name}")
            print(f"argument: {function_call.arguments}")
        # KernelFunctionの実行
        await next(context)

        # functionの結果の確認
        result = context.function_result

        # Check if any result.value is a FunctionResultContent
        print((f"\n実行さてたfunction:\n {result.function}"))
        print(f"\nresult value of function:\n {result.value}")

    return kernel

上記のコードを追加することで、以下のような情報を確認可能です。

Auto function invocation filter
Plugin: InternalDocumentaionRAG
Function: search
Request sequence: 0
Function sequence: 0
Number of function calls: 1

chat history:
 [ChatMessageContent(inner_content=None, ai_model_id=None, metadata={}, content_type='message', role=<AuthorRole.USER: 'user'>, name=None, items=[TextContent(inner_content=None, ai_model_id=None, metadata={}, content_type='text', text='リモートワークが週に何回可能か教えてください。', encoding=None)], encoding=None, finish_reason=None), ChatMessageContent(inner_content=ChatCompletion(id='chatcmpl-AZt2E1spm4R8YaLZB5RvWPtnqNNcb', choices=[Choice(finish_reason='tool_calls', index=0, logprobs=None, message=ChatCompletionMessage(content=None, refusal=None, role='assistant', audio=None, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_05ZzHtboTs6Vmn24xrfH7ybf', function=Function(arguments='{"query":"リモートワーク 週 何回 可能"}', name='InternalDocumentaionRAG-search'), type='function')]), content_filter_results={})], created=1733116398, model='gpt-4o-mini', object='chat.completion', service_tier=None, system_fingerprint='fp_04751d0b65', usage=CompletionUsage(completion_tokens=30, prompt_tokens=116, total_tokens=146, completion_tokens_details=None, prompt_tokens_details=None), prompt_filter_results=[{'prompt_index': 0, 'content_filter_results': {'hate': {'filtered': False, 'severity': 'safe'}, 'jailbreak': {'filtered': False, 'detected': False}, 'self_harm': {'filtered': False, 'severity': 'safe'}, 'sexual': {'filtered': False, 'severity': 'safe'}, 'violence': {'filtered': False, 'severity': 'safe'}}}]), ai_model_id='gpt-4o-mini', metadata={'logprobs': None, 'id': 'chatcmpl-AZt2E1spm4R8YaLZB5RvWPtnqNNcb', 'created': 1733116398, 'system_fingerprint': 'fp_04751d0b65', 'usage': CompletionUsage(prompt_tokens=116, completion_tokens=30)}, content_type='message', role=<AuthorRole.ASSISTANT: 'assistant'>, name=None, items=[FunctionCallContent(inner_content=None, ai_model_id=None, metadata={}, content_type=<ContentTypes.FUNCTION_CALL_CONTENT: 'function_call'>, id='call_05ZzHtboTs6Vmn24xrfH7ybf', index=None, name='InternalDocumentaionRAG-search', function_name='search', plugin_name='InternalDocumentaionRAG', arguments='{"query":"リモートワーク 週 何回 可能"}')], encoding=None, finish_reason=<FinishReason.TOOL_CALLS: 'tool_calls'>)]  

plugin name: InternalDocumentaionRAG
function name: search
argument: {"query":"リモートワーク 週 何回 可能"}

実行されたfunction:
 name='search' plugin_name='InternalDocumentaionRAG' description='search documents about internal company rules' parameters=[KernelParameterMetadata(name='query', description=None, default_value=None, type_='str', is_required=True, type_object=<class 'str'>, schema_data={'type': 'string'}, include_in_function_choices=True)] is_prompt=False is_asynchronous=False return_parameter=KernelParameterMetadata(name='return', description='', default_value=None, type_='str', is_required=True, type_object=<class 'str'>, schema_data={'type': 'string'}, include_in_function_choices=True) additional_properties={}

result value of function:
 第3条(リモートワークの実施)
リモートワークは、原則として週3日まで認める。
リモートワークを希望する場合、上司の承認を得ることが必要である。

第4条(業務環境の整備)
リモートワークを行う従業員は、自宅等で業務を行うための適切な作業環境を整えるものとする。
必要に応じて、会社から貸与される機器やソフトウェアを使用する。

context.chat_history.messagesでは、関数の実行時のメッセージの履歴、context.function_result.valueでは内容で関数の結果を確認することができます。await next(context)の前のコードは関数実行前、後のコードは関数実行後に処理されます。

以上のようにPluginの結果を確認することが可能となります。

おわりに

今回はマルチエージェント構築の準備としてプラグインの実装方法を紹介しました。

検証を行った環境のようにシンプルな構成であれば、プラグインを使用することで効果的にRAGを活用・使い分けできる可能性があります。ただ、プラグインの数が増えたり構成が複雑になった場合には、同様の動作が再現できるかどうかは不明なため、さらなる検証が必要です。

より柔軟性が求められ、複数の異なるタスクを連続して処理する能力が求められるケースではマルチエージェントのアプローチが適していると考えています。

次回は、Semantic Kernel Agent Frameworkの構築に関して記載します。

*1:毎回一般的な回答を返してくれるかは不明です。

執筆担当者プロフィール
寺澤 駿

寺澤 駿(日本ビジネスシステムズ株式会社)

IoTやAzure Cognitive ServicesのAIを活用したデモ環境・ソリューション作成を担当。

担当記事一覧