Microsoft Semantic KernelとPythonでAIアプリケーションを構築する:エンタープライズAIエージェントのためのプラグイン、メモリ、プランニング

AI tutorial - IT technology blog
AI tutorial - IT technology blog

エンタープライズ開発で「とりあえずLangChain」では通用しない理由

ほとんどのAIチュートリアルはLangChainから始めるよう誘導する。私もそうだった――Pythonサービスを立ち上げ、いくつかのツールをチェーンで繋いでリリースし、完了したつもりでいた。ところが半年後、LangChainのマイナーバージョンアップで統合の半数が壊れてしまった。コードはほぼテスト不可能な状態で、別チームの.NET開発者をオンボーディングするのは、まったく異質なコードベースを渡すようなものだった。

その経験から、私はMicrosoft Semantic Kernelへと移行した。MicrosoftのAIアプリケーション構築用オープンソースSDKで、1年以上本番環境で運用した経験から断言できるが、エンタープライズの信頼性を重視するなら習得必須だ。プラグインアーキテクチャはすっきりしており、プランニングシステムは明示的で、同じSDKがPython、.NET、Javaで動作する。最後の点は思った以上に重要で、.NET開発者のオンボーディングが1週間ではなく半日で済むようになった。

このガイドでは、AIエージェントをゼロから構築する手順を解説する――カーネルのセットアップ、ネイティブプラグイン、セマンティックプラグイン、ベクトルメモリ、そして自動呼び出しプランニングまで。

まず理解すべきコアコンセプト

カーネル:AIのための依存性注入コンテナ

Semantic Kernelのすべてはカーネルオブジェクトを中心に展開する。依存性注入コンテナのように考えるとわかりやすい――LLM接続、プラグイン、メモリストアを登録すれば、カーネルがすべてをオーケストレートしてくれる。この設計のおかげでユニットテストがすっきりする:実際のLLMをモックに差し替えて、テストを実行するだけで完了だ。

チェーンの代わりにプラグインを使う

LangChainが「チェーン」と「ツール」を使うのに対し、Semantic Kernelはプラグインを使う。重要なタイプは2種類ある:

  • ネイティブプラグイン@kernel_functionデコレータを付けた通常のPython関数。APIの呼び出し、データベースのクエリ、計算など、実際のコードを実行する。
  • セマンティックプラグイン:テキストファイルとして保存されたプロンプトテンプレート。プロンプトを書けばSKがLLMの呼び出しを処理する。要約や翻訳といった言語タスクに最適で、Gitでクリーンにバージョン管理できる。

プランニング:実際に機能する自動呼び出し

SKのプランナーはユーザーのゴールを受け取り、どのプラグインをどの順序で呼び出すかを判断する。SK 1.xでは、FunctionChoiceBehaviorを通じてLLMのネイティブなツール呼び出し機能を使うアプローチが推奨されている。以前のフレームワークが頼っていたカスタムのChain-of-Thoughtの工夫と比べて、実用上の信頼性が明らかに高い――幻覚的なツール呼び出しが減り、マルチステップのリクエストへの対応も改善されている。

ハンズオン:AIエージェントをステップバイステップで構築する

インストールとセットアップ

pip install semantic-kernel openai python-dotenv

.envファイルを作成する:

OPENAI_API_KEY=sk-...
OPENAI_CHAT_MODEL_ID=gpt-4o

最初のカーネル

import asyncio
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from dotenv import load_dotenv

load_dotenv()

async def main():
    kernel = Kernel()
    kernel.add_service(OpenAIChatCompletion())

    result = await kernel.invoke_prompt(
        "マイクロサービスにおけるKubernetesの主なメリットを3つ挙げてください。"
    )
    print(result)

asyncio.run(main())

まだ地味に見えるかもしれないが、プラグインを追加した瞬間にこの基本アーキテクチャの価値が発揮される。

ネイティブプラグインを構築する

以下は、サーバーメトリクスの取得をシミュレートする実用的なプラグインだ――本番環境ではPrometheusやDatadogと連携させる類のものだ:

import random
from semantic_kernel.functions import kernel_function

class ServerMetricsPlugin:

    @kernel_function(
        name="get_cpu_usage",
        description="指定されたサーバーホスト名の現在のCPU使用率を取得する"
    )
    def get_cpu_usage(self, hostname: str) -> str:
        # 本番環境:Prometheus、Datadogなどにクエリを実行する
        usage = random.uniform(10, 95)
        return f"サーバー {hostname} のCPU使用率: {usage:.1f}%"

    @kernel_function(
        name="get_memory_usage",
        description="指定されたサーバーホスト名の現在のメモリ使用率を取得する"
    )
    def get_memory_usage(self, hostname: str) -> str:
        usage = random.uniform(20, 90)
        return f"サーバー {hostname} のメモリ使用率: {usage:.1f}%"

自動呼び出しプランニングを接続する

from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import (
    OpenAIChatCompletion,
    OpenAIChatPromptExecutionSettings
)
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior

async def main():
    kernel = Kernel()
    kernel.add_service(OpenAIChatCompletion())
    kernel.add_plugin(ServerMetricsPlugin(), plugin_name="ServerMetrics")

    settings = OpenAIChatPromptExecutionSettings(
        function_choice_behavior=FunctionChoiceBehavior.Auto()
    )

    result = await kernel.invoke_prompt(
        "web-server-01のCPUとメモリを確認してください。問題があれば指摘してください。",
        settings=settings
    )
    print(result)

asyncio.run(main())

舞台裏では、カーネルがget_cpu_usageget_memory_usageの両方が必要だと判断し、両方を呼び出してから結果をLLMに渡して分析させる。手動でのチェーニングは不要。壊れやすいシーケンス定義も不要だ。

ベクトル検索でメモリを追加する

Semantic Kernelのメモリ層はQdrant、Pinecone、Azure AI Searchなどと連携できる。プロトタイピングには、インメモリの揮発性ストアが数分で使い始められる:

from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAITextEmbedding
from semantic_kernel.memory import SemanticTextMemory, VolatileMemoryStore

async def setup_memory():
    kernel = Kernel()
    kernel.add_service(OpenAIChatCompletion(service_id="chat"))
    kernel.add_service(OpenAITextEmbedding(service_id="embedding"))

    memory = SemanticTextMemory(
        storage=VolatileMemoryStore(),
        embeddings_generator=kernel.get_service(service_id="embedding")
    )

    await memory.save_information(
        collection="runbooks",
        id="restart-nginx",
        text="nginxを再起動するには: 'sudo systemctl restart nginx'。状態を確認するには: 'systemctl status nginx'。"
    )

    await memory.save_information(
        collection="runbooks",
        id="disk-full",
        text="ディスク容量不足: 'df -h'で確認し、'du -sh /* | sort -rh | head -20'で大きなファイルを探し、/var/logをクリーンアップする。"
    )

    results = await memory.search("runbooks", "ディスク問題後にサーバーが起動しない")
    for r in results:
        print(f"[{r.relevance:.2f}] {r.text[:80]}...")

asyncio.run(setup_memory())

本番環境に移行する準備ができたら、VolatileMemoryStoreをQdrantやAzure AI Searchに差し替えればいい。インターフェースはまったく同じ――それが抽象化の仕事だ。

セマンティックプラグイン:バージョン管理されたプロンプト

言語タスクでは、セマンティックプラグインがプロンプトをプレーンなファイルとして保存する。プロンプトの変更は他のdiffと同様にプルリクエストに表示され、レビューやコメントが可能で、ロールバックも簡単だ:

plugins/
  SummaryPlugin/
    SummarizeText/
      skprompt.txt
      config.json

skprompt.txtの内容:

以下のテキストを、実行可能なインサイトに焦点を当てて2〜3文に要約してください:

{{$input}}

要約:

config.jsonの内容:

{
  "schema": 1,
  "description": "テキストを実行可能な2〜3文に要約する",
  "execution_settings": {
    "default": {
      "max_tokens": 256,
      "temperature": 0.3
    }
  }
}

読み込んで呼び出す:

plugin = kernel.add_plugin(
    parent_directory="./plugins",
    plugin_name="SummaryPlugin"
)

result = await kernel.invoke(
    plugin["SummarizeText"],
    input="長いインシデントレポートやログテキストをここに入力..."
)
print(result)

Semantic Kernel vs LangChain:正直な比較

これはどちらかのフレームワークが常に優れているという議論ではない。両方ともコンテキストによって明確な強みがある:

  • Semantic Kernelを選ぶべき場面:Azureを使っている、チームがPythonと.NETにまたがっている、四半期をまたいで安定したAPIサーフェスが必要、またはエンタープライズの監査要件のために説明可能なプランニングステップが必要な場合。
  • LangChainを使い続けるべき場面:最新の実験的なベクトルデータベースとの統合が必要、高速でプロトタイピングをしている、またはチームが安定した固定バージョンと深い専門知識を持っている場合。

Azure OpenAI、Azure AI Search、エンタープライズのセキュリティ要件――SKはこれらのシナリオに他のほとんどの代替手段よりも自然にフィットする。ユニットテストも話が変わる:チェーン全体と格闘する代わりに、個々のプラグインをモックするだけで済む。

次のステップ

ここまで構築したものは、デモではなく実際にスケールするアーキテクチャパターンという、堅牢でテスト可能な基盤だ。ここから先、3つの方向性が考えられる。QdrantやAzure AI Searchのような永続的なベクトルストアを接続して、実際のRAGワークロードに対応する。ネイティブプラグインに認証とレート制限を追加してから本番環境に投入する。そして、別々のカーネルがお互いに作業を引き渡すマルチエージェントパターンを調べる。

プラグインアーキテクチャが理解できると、複雑なワークフローの構築が驚くほど扱いやすくなる。デバッグも別次元の体験になる――どこかの途中で状態が変化した不透明なオブジェクトのチェーンを解きほぐすのではなく、個々のプラグインの呼び出しをトレースすればいい。

Share: