セマンティック カーネルの入門: C# での AI オーケストレーション
.NET アプリケーションを構築し、AI 環境の進化を見てきた人なら、おそらく次のことを疑問に思ったことがあるでしょう: コードベースをスパゲッティにせずに大規模な言語モデルを C# プロジェクトに統合する最善の方法は何ですか? それはまさに Microsoft のセマンティック カーネルが解決する問題であり、昨年これを使用して実稼働アプリケーションを構築することに費やした後、セマンティック カーネルは私の開発者ツールキットの中で最も重要なツールの 1 つになったと言えます。
この投稿では、中心となる概念の理解から現実世界の AI アシスタントの構築まで、セマンティック カーネルを始めるために必要なすべてのことを説明します。 AI 開発に少しだけ足を踏み入れている場合でも、既存の .NET アプリケーションで LLM 呼び出しを調整するための構造化された方法を探している場合でも、このガイドは役に立ちます。
セマンティック カーネルとは何ですか?
セマンティック カーネル (SK) は、Microsoft のオープンソース SDK であり、アプリケーション コードと GPT-4o、Azure OpenAI、またはその他の AI サービスなどの大規模な言語モデルの間の オーケストレーション レイヤー として機能します。これは、従来の C# コードと AI 機能をクリーンで構成可能な方法で組み合わせることができる軽量のミドルウェアと考えてください。
しかし、なぜ OpenAI API を直接呼び出さないのでしょうか?絶対に可能です。単純な使用例の場合はそれで問題ありません。しかし、次のことが必要になった瞬間に:
- ユーザー入力に基づいて どの関数を呼び出すかを AI に決定させます
- 複数の AI 呼び出しをパイプライン内の従来のコードと結合します
- メモリとコンテキストを追加して、AI が以前のインタラクションを記憶できるようにします
- 複雑なタスクを推論する マルチステップ エージェントを構築する
…車輪の再発明をしていることに気づくでしょう。セマンティック カーネルは、ファーストクラスの .NET サポート、依存関係注入の統合、および C# 開発者にとって自然に感じられるプラグイン アーキテクチャを備え、これらすべてをすぐに提供します。
このプロジェクトは GitHub の microsoft/semantic-kernel リポジトリに存在し、C#、Python、Java 用の SDK があります。 C# SDK は最も成熟しており、ここではこれに焦点を当てます。
コアコンセプト
コードを書く前に、構成要素を理解しましょう。
カーネル
Kernel は、セマンティック カーネルの中心的なオブジェクトです。これはオーケストレーターであり、AI サービス、プラグイン、構成を結び付けるものです。これを作成し、サービスとプラグインを登録し、それを使用してプロンプトを実行したり、関数を呼び出したりします。 ASP.NET Core での依存関係の挿入に慣れている場合、カーネルは非常に親しみのあるものに感じられるでしょう。カーネルは本質的に AI の強力な機能を備えたサービス コンテナーです。
プラグインと関数
プラグインは、カーネルが呼び出すことができる関連する関数のコレクションです。関数には 2 つの種類があります。
- プロンプト関数 — LLM に送信される自然言語テンプレートとして定義されます。
- ネイティブ関数 — カーネルが検出して呼び出すことができる属性で装飾された通常の C# メソッド
たとえば、ネイティブ関数 GetCurrentWeather(string city) と気象データをわかりやすく要約するプロンプト関数を備えた WeatherPlugin があるとします。### AI コネクタ
コネクタは、セマンティック カーネルが AI サービスと通信する方法です。最も一般的なものは次のとおりです。
AzureOpenAIChatCompletion— Azure OpenAI サービス用OpenAIChatCompletion— OpenAI の API を直接使用する場合- ベクトル検索とメモリ用の埋め込みコネクタ
これらを起動時にカーネルに登録すれば、その他はすべて正常に動作します。
プロジェクトのセットアップ
手を汚してみましょう。まず、新しいコンソール アプリケーションを作成します。
dotnet new console -n SemanticKernelDemo
cd SemanticKernelDemo
次に、セマンティック カーネル NuGet パッケージを追加します。
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI
Azure OpenAI ではなく OpenAI を直接使用している場合:
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI
メモリと埋め込みのサポートの場合 (これは後で使用します):
dotnet add package Microsoft.SemanticKernel.Plugins.Memory
dotnet add package Microsoft.Extensions.VectorData.Abstractions
.csproj は .NET 8 以降をターゲットにする必要があります。 Semantic Kernel の最新バージョンは、最新の .NET 機能を最大限に活用しています。
最初のカーネル
最も単純な例から始めましょう。カーネルを作成し、AI サービスに接続し、それに質問します。
using Microsoft.SemanticKernel;
// Build the kernel with Azure OpenAI
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: "https://your-resource.openai.azure.com/",
apiKey: "your-api-key"
);
var kernel = builder.Build();
// Invoke a simple prompt
var result = await kernel.InvokePromptAsync(
"Explain dependency injection in C# in three sentences."
);
Console.WriteLine(result);
OpenAI を直接使用している場合は、サービス登録を交換します。
builder.AddOpenAIChatCompletion(
modelId: "gpt-4o",
apiKey: "your-openai-api-key"
);
それだけです。実行すると、依存関係の注入についての簡潔な説明が得られます。しかし、これはほんの表面をなぞっただけです。
プロンプト テンプレートの使用
プロンプト テンプレートを使用すると、ハンドルバー スタイルの構文を使用して変数でプロンプトをパラメータ化できます。
var prompt = """
You are a technical writer. Write a brief summary of {{$topic}}
aimed at developers with {{$experienceLevel}} experience.
Keep it under 200 words.
""";
var function = kernel.CreateFunctionFromPrompt(prompt);
var arguments = new KernelArguments
{
["topic"] = "gRPC in .NET",
["experienceLevel"] = "intermediate"
};
var result = await kernel.InvokeAsync(function, arguments);
Console.WriteLine(result);
ここで、セマンティック カーネルが輝き始めます。再利用可能なプロンプト テンプレートを定義し、バージョンを付けて、より大きなワークフローを構成できます。
プラグインとネイティブ関数
プラグインは、セマンティック カーネルが AI と既存の C# コードの間のギャップを埋める場所です。ネイティブ関数は、カーネルに公開する通常のメソッドです。
using Microsoft.SemanticKernel;
using System.ComponentModel;
public class TimePlugin
{
[KernelFunction("get_current_time")]
[Description("Gets the current date and time in UTC")]
public string GetCurrentTime()
{
return DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss UTC");
}
[KernelFunction("get_time_in_timezone")]
[Description("Gets the current time in a specific timezone")]
public string GetTimeInTimezone(
[Description("The IANA timezone identifier, e.g. 'America/New_York'")] string timezone)
{
var tz = TimeZoneInfo.FindSystemTimeZoneById(timezone);
var time = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
return time.ToString("yyyy-MM-dd HH:mm:ss");
}
}
[KernelFunction] 属性と [Description] 属性に注目してください。これらは重要です。説明は、AI が関数をいつどのように呼び出すかを理解するために読み取るものです。適切な説明は、ツールを効果的に使用する AI と混乱している AI の違いを生み出します。
カーネルにプラグインを登録します。
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: "https://your-resource.openai.azure.com/",
apiKey: "your-api-key"
);
builder.Plugins.AddFromType<TimePlugin>();
var kernel = builder.Build();
サービスを挿入する、より複雑なプラグインを構築することもできます。セマンティック カーネルは Microsoft.Extensions.DependencyInjection と統合されているため、プラグインは他のサービスと同様にコンストラクターの依存関係を受け取ることができます。
public class OrderPlugin
{
private readonly IOrderRepository _repository;
public OrderPlugin(IOrderRepository repository)
{
_repository = repository;
}
[KernelFunction("get_order_status")]
[Description("Retrieves the status of an order by its ID")]
public async Task<string> GetOrderStatus(
[Description("The order ID to look up")] string orderId)
{
var order = await _repository.GetByIdAsync(orderId);
return order is null
? $"No order found with ID {orderId}"
: $"Order {orderId}: {order.Status}, placed on {order.CreatedAt:d}";
}
}
関数呼び出しと自動呼び出し
ここからが本当に興味深いことになります。 関数呼び出し (ツール呼び出しとも呼ばれる) を使用すると、会話コンテキストに基づいて、登録された関数のどれを呼び出すかを AI モデルに決定させます。モデルはコードを実行しません。「これらの引数を使用して関数 X を呼び出したい」という構造化されたリクエストを返し、カーネルが実際の呼び出しを処理します。
自動関数呼び出しを有効にする方法は次のとおりです。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: "https://your-resource.openai.azure.com/",
apiKey: "your-api-key"
);
builder.Plugins.AddFromType<TimePlugin>();
builder.Plugins.AddFromType<WeatherPlugin>();
var kernel = builder.Build();
// Enable automatic function calling
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var result = await kernel.InvokePromptAsync(
"What time is it in Tokyo and what's the weather like there?",
new KernelArguments(settings)
);
Console.WriteLine(result);
FunctionChoiceBehavior.Auto() を使用すると、カーネルは次のことを行います。
- 利用可能なすべての機能の説明とともにプロンプトを AI に送信します
- AI は、
get_time_in_timezoneおよびget_weatherを呼び出す必要があると判断します。 - カーネルはこれらの関数を自動的に実行します。
- 結果は AI に送信されます
- AI は関数の結果を使用して自然言語応答を作成しますこのループは 1 回の呼び出しで複数回発生する可能性があります。AI は必要な情報をすべて収集するために複数の関数を順番に呼び出すことがあります。
FunctionChoiceBehavior.Required()を使用して、AI に少なくとも 1 つの関数の呼び出しを強制したり、使用を許可する関数の特定のリストを提供したりすることもできます。
履歴付きチャット完了
会話型アプリケーションの場合は、 ChatCompletionService を ChatHistory オブジェクトで直接使用する必要があります。
using Microsoft.SemanticKernel.ChatCompletion;
var chatService = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory();
history.AddSystemMessage("""
You are a helpful developer assistant. You have access to tools
for checking the time and weather. Be concise and friendly.
""");
while (true)
{
Console.Write("You: ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input)) break;
history.AddUserMessage(input);
var response = await chatService.GetChatMessageContentAsync(
history,
executionSettings: settings,
kernel: kernel
);
history.AddAssistantMessage(response.Content ?? "");
Console.WriteLine($"Assistant: {response.Content}");
}
これにより、会話履歴を保持し、必要に応じてプラグインを呼び出すことができる完全にインタラクティブなチャットボットが得られます。
メモリと埋め込み
AI アプリケーションで最も強力なパターンの 1 つは 検索拡張生成 (RAG) です。これは、データをベクトル空間に埋め込み、クエリ時に関連するチャンクを取得することで、AI に独自のデータへのアクセスを提供します。
セマンティック カーネルは、ベクトル ストアと埋め込みを操作するための抽象化を提供します。開発用にインメモリ ベクター ストアを設定する方法は次のとおりです。
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Embeddings;
#pragma warning disable SKEXP0010
// Create an embedding generation service
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAITextEmbeddingGeneration(
deploymentName: "text-embedding-ada-002",
endpoint: "https://your-resource.openai.azure.com/",
apiKey: "your-api-key"
);
var kernel = builder.Build();
var embeddingService = kernel.GetRequiredService<ITextEmbeddingGenerationService>();
// Generate embeddings for your documents
var documents = new[]
{
"Semantic Kernel is an open-source SDK for AI orchestration.",
"Azure OpenAI provides enterprise-grade AI models.",
"Plugins in SK allow you to expose C# methods to AI models."
};
var embeddings = await embeddingService.GenerateEmbeddingsAsync(documents);
運用シナリオの場合は、これらの埋め込みを Azure AI Search、Qdrant、Pinecone などの専用のベクター データベースに保存します。セマンティック カーネルには、Microsoft.Extensions.VectorData 抽象化を介したこれらすべてのコネクタがあります。
一般的な RAG フローは次のようになります。
- 取り込み: ドキュメントをチャンク化し、埋め込みを生成し、ベクトル データベースに保存します。
- 取得: ユーザーが質問すると、クエリを埋め込んで最も類似したドキュメントを検索します。
- 生成: 取得したドキュメントを、ユーザーの質問とともにコンテキストとして LLM に渡します。
[KernelFunction("search_knowledge_base")]
[Description("Searches the internal knowledge base for relevant information")]
public async Task<string> SearchKnowledgeBase(
[Description("The search query")] string query)
{
var queryEmbedding = await _embeddingService.GenerateEmbeddingAsync(query);
var results = await _vectorStore.SearchAsync(queryEmbedding, limit: 3);
return string.Join("\n\n", results.Select(r => r.Text));
}
RAG パイプラインをカーネル関数として公開することで、AI はナレッジ ベースをいつ検索する必要があるかを自動的に決定できるため、オーケストレーションをクリーンな状態に保ち、モデルに最適な動作をさせることができます。
プランナーとエージェント
AI アプリケーションがより複雑になると、複数ステップのタスクを計画して実行する AI が必要になります。ここで、セマンティック カーネルのエージェント フレームワークが登場します。
基本: チャット完了エージェント
最も単純なエージェント タイプは、チャット完了モデルを命令とプラグインでラップします。
using Microsoft.SemanticKernel.Agents;
#pragma warning disable SKEXP0110
var agent = new ChatCompletionAgent
{
Name = "DevAssistant",
Instructions = """
You are a senior .NET developer assistant. Help users with code
reviews, architecture decisions, and debugging. Always provide
code examples when relevant. Use your available tools to look up
current information when needed.
""",
Kernel = kernel,
Arguments = new KernelArguments(settings)
};
var history = new ChatHistory();
history.AddUserMessage("How should I structure a clean architecture project in .NET 8?");
await foreach (var message in agent.InvokeAsync(history))
{
Console.WriteLine(message.Content);
history.Add(message);
}
マルチエージェントのコラボレーション
複数のエージェントが連携している場合、物事が非常に強力になります。セマンティック カーネルは、さまざまな専門分野を持つエージェントが協力するグループ チャット パターンをサポートしています。
#pragma warning disable SKEXP0110
var codeReviewer = new ChatCompletionAgent
{
Name = "CodeReviewer",
Instructions = """
You review C# code for bugs, performance issues, and best practices.
Be specific about what you find and suggest concrete fixes.
""",
Kernel = kernel
};
var securityAuditor = new ChatCompletionAgent
{
Name = "SecurityAuditor",
Instructions = """
You focus exclusively on security vulnerabilities in code.
Look for injection attacks, authentication issues, data exposure,
and OWASP Top 10 violations.
""",
Kernel = kernel
};
var groupChat = new AgentGroupChat(codeReviewer, securityAuditor)
{
ExecutionSettings = new AgentGroupChatSettings
{
TerminationStrategy = new MaximumIterationTerminationStrategy(4)
}
};
groupChat.AddChatMessage(
new ChatMessageContent(AuthorRole.User, "Review this code: ...")
);
await foreach (var message in groupChat.InvokeAsync())
{
Console.WriteLine($"[{message.AuthorName}]: {message.Content}");
}
このパターンは、さまざまな視点や専門分野が関与する必要がある複雑なタスクに非常に役立ちます。各エージェントは独自のシステム プロンプトで動作し、独自のプラグイン セットを持つことができます。
実際の例: プロジェクト ドキュメント アシスタントの構築
すべてを実際の例で結び付けてみましょう。ファイルを読み取って質問に答えることで、開発者がコードベースを理解できるように支援する AI アシスタントです。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.ComponentModel;
// Define our plugins
public class FileSystemPlugin
{
private readonly string _rootPath;
public FileSystemPlugin(string rootPath)
{
_rootPath = rootPath;
}
[KernelFunction("read_file")]
[Description("Reads the contents of a file from the project directory")]
public async Task<string> ReadFile(
[Description("Relative path to the file")] string path)
{
var fullPath = Path.Combine(_rootPath, path);
if (!File.Exists(fullPath))
return $"File not found: {path}";
var content = await File.ReadAllTextAsync(fullPath);
// Truncate very large files
if (content.Length > 8000)
content = content[..8000] + "\n... [truncated]";
return content;
}
[KernelFunction("list_files")]
[Description("Lists files in a directory, optionally filtered by extension")]
public string ListFiles(
[Description("Relative directory path")] string directory,
[Description("File extension filter like '.cs' or '.json'")] string? extension = null)
{
var fullPath = Path.Combine(_rootPath, directory);
if (!Directory.Exists(fullPath))
return $"Directory not found: {directory}";
var files = Directory.GetFiles(fullPath, "*.*", SearchOption.AllDirectories)
.Select(f => Path.GetRelativePath(_rootPath, f))
.Where(f => extension is null || f.EndsWith(extension))
.Take(50);
return string.Join("\n", files);
}
}
public class DocumentationPlugin
{
[KernelFunction("generate_summary")]
[Description("Generates a structured markdown summary for documentation")]
public string GenerateSummaryTemplate(
[Description("Name of the component")] string componentName,
[Description("Brief description")] string description,
[Description("Key responsibilities as comma-separated values")] string responsibilities)
{
var items = responsibilities.Split(',', StringSplitOptions.TrimEntries);
var bullets = string.Join("\n", items.Select(r => $"- {r}"));
return $"""
## {componentName}
{description}
### Responsibilities
{bullets}
---
*Generated documentation — review and expand as needed.*
""";
}
}
// Wire it all up
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: "https://your-resource.openai.azure.com/",
apiKey: "your-api-key"
);
builder.Plugins.AddFromObject(new FileSystemPlugin("./src"));
builder.Plugins.AddFromType<DocumentationPlugin>();
var kernel = builder.Build();
var chatService = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory();
history.AddSystemMessage("""
You are a codebase documentation assistant. You help developers understand
projects by reading source files and explaining architecture, patterns,
and design decisions.
When asked about code, use your tools to read the actual files rather
than guessing. Be specific and reference actual code when possible.
Generate documentation artifacts when asked.
""");
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
Console.WriteLine("Documentation Assistant ready. Ask me about your codebase!");
Console.WriteLine("Type 'exit' to quit.\n");
while (true)
{
Console.Write("You: ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
break;
history.AddUserMessage(input);
var response = await chatService.GetChatMessageContentAsync(
history,
executionSettings: settings,
kernel: kernel
);
Console.WriteLine($"\nAssistant: {response.Content}\n");
history.AddAssistantMessage(response.Content ?? "");
}
このアシスタントは次のことができます。- プロジェクト ディレクトリからファイルを一覧表示して読み取る
- 実際のソース ファイルを読んでコードベースに関する 質問に答える
- マークダウン形式でドキュメントを生成
- 会話のコンテキストを維持して、フォローアップの質問が自然に機能するようにします
AI は、ユーザーの質問に基づいて、read_file、list_files、または generate_summary をいつ呼び出すかを自動的に決定します。 「OrderService は何をするのですか?」と尋ねてください。ファイルを読み取り、分析し、説明します。 「認証モジュールのドキュメントを生成する」ように依頼すると、ファイルを調べて構造を理解し、フォーマットされた概要を生成します。
制作のヒント
セマンティック カーネル アプリケーションを出荷する前に、私が苦労して学んだことがいくつかあります。
依存関係の挿入を適切に使用します。 ASP.NET Core アプリでは、カーネルとサービスをインラインで作成するのではなく、DI コンテナーに登録します。
builder.Services.AddKernel()
.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: configuration["AzureOpenAI:Endpoint"]!,
apiKey: configuration["AzureOpenAI:ApiKey"]!
)
.Plugins.AddFromType<TimePlugin>()
.AddFromType<OrderPlugin>();
エラーを適切に処理します。 LLM 呼び出しは失敗したり、タイムアウトになったり、予期しない結果を返したりする可能性があります。呼び出しを try-catch ブロックでラップし、Polly または組み込みの復元機能を使用して再試行ポリシーを実装します。
トークンの使用状況を監視します。 すべてのプロンプト、すべての機能の説明、およびすべてのチャット履歴でトークンが消費されます。フィルターを使用して使用状況をログに記録し、追跡します。
kernel.FunctionInvocationFilters.Add(new LoggingFilter());
関数の説明は正確にしてください。 曖昧な説明は、AI による関数の誤った呼び出しにつながります。 「説明だけを読めば、この機能をいつ、どのように使用するか正確にわかりますか?」と質問して、説明をテストします。
結論
Semantic Kernel は、アプリケーションの構築に関する考え方を根本的に変えるライブラリの 1 つです。これは単なる API ラッパーではなく、保守可能、テスト可能、実稼働対応の方法で従来のコードを使用して AI 機能を構築できるオーケストレーション フレームワークです。
私が最も気に入っている点は、.NET エコシステムを尊重していることです。これは、依存関係の注入、属性、非同期/待機、インターフェイスなど、すでに知られているパターンを使用し、それらを AI の世界に拡張します。まったく新しいパラダイムを学ぶ必要はありません。 AI をツールキットの別の機能として追加するだけです。
.NET アプリケーションを構築していて、まだセマンティック カーネルを試していない場合は、今がその時期です。 SDK は安定しており、コミュニティは活発で、シンプルなプロンプト オーケストレーションからマルチエージェントのコラボレーションまで、SDK によって可能になるパターンは、現代の開発者にとって必須のスキルになりつつあります。
小さなことから始めましょう。カーネルを作成し、プラグインを登録し、AI がコードを呼び出すのを観察します。それがうまくいくと、アプリケーションのあらゆる場所にインテリジェンスを追加できる機会が見え始めるでしょう。
公式ドキュメント と GitHub リポジトリ は、旅を続けるための優れたリソースです。ハッピービルディング!