关于 dotnet-semantic-kernel
介绍如何在 .NET 应用中使用 Microsoft Semantic Kernel 编排 AI 与大模型。内容涵盖插件、提示词、向量存储检索、Agent,以及云端和本地 AI 服务连接。
主要功能
- 01配置 Kernel 与 AI 服务
- 02将 .NET 方法作为可调用插件
- 03创建 Handlebars 和 Liquid 提示词
- 04使用向量存储实现 RAG
- 05构建 Agent 与群聊
- 06流式传输聊天响应
适用场景
- 01让模型回答基于应用数据
- 02构建文档检索与问答
- 03协调专用 Agent 执行任务
- 04在聊天应用中流式返回响应
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-semantic-kernel安装后在 Claude Code 中输入/dotnet-semantic-kernel确认已加载。
详细介绍
dotnet-semantic-kernel
用于在 .NET 应用程序中协调 AI 和大模型的 Microsoft Semantic Kernel。涵盖内核设置和配置、插件/函数调用、使用 Handlebars 和 Liquid 语法的提示词模板、记忆和向量存储集成、规划器、Agent 框架,以及与 Azure OpenAI、OpenAI 和本地模型的集成。
不在范围内: 通用 async/await 模式和取消令牌传递——请参阅 [skill:dotnet-csharp-async-patterns]。DI 容器机制和服务生命周期管理——请参阅 [skill:dotnet-csharp-dependency-injection]。HTTP 客户端弹性和重试策略——请参阅 [skill:dotnet-resilience]。配置绑定(选项模式、密钥)——请参阅 [skill:dotnet-csharp-configuration]。
交叉引用:[skill:dotnet-csharp-async-patterns] 介绍聊天补全中使用的异步流模式;[skill:dotnet-csharp-dependency-injection] 介绍 ASP.NET Core 中的内核服务注册;[skill:dotnet-resilience] 介绍 AI 服务调用的重试策略;[skill:dotnet-csharp-configuration] 介绍 API 密钥和模型配置的管理。
内核设置
Kernel 是 Semantic Kernel 的核心对象。它管理 AI 服务连接、插件和函数调用。
包概览
| 包 | 用途 |
|---|---|
Microsoft.SemanticKernel | 核心内核、函数调用、提示词模板 |
Microsoft.SemanticKernel.Connectors.AzureOpenAI | Azure OpenAI 聊天/嵌入/图像服务 |
Microsoft.SemanticKernel.Connectors.OpenAI | OpenAI 聊天/嵌入/图像服务 |
Microsoft.SemanticKernel.Connectors.Ollama | Ollama 本地模型集成 |
Microsoft.SemanticKernel.Plugins.Core | 内置插件(时间、数学、文本) |
Microsoft.SemanticKernel.Agents.Core | Agent 框架(聊天 Agent、群组聊天) |
Microsoft.Extensions.VectorData.Abstractions | 向量存储抽象层 |
Microsoft.SemanticKernel.Connectors.Qdrant | Qdrant 向量存储连接器 |
Microsoft.SemanticKernel.Connectors.AzureAISearch | Azure AI Search 向量存储连接器 |
基本内核配置
using Microsoft.SemanticKernel;
var builder = Kernel.CreateBuilder();
// Azure OpenAI
builder.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!,
apiKey: Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY")!);
var kernel = builder.Build();
与 ASP.NET Core 集成 DI
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKernel();
builder.Services.AddAzureOpenAIChatCompletion(
deploymentName: builder.Configuration["AI:DeploymentName"]!,
endpoint: builder.Configuration["AI:Endpoint"]!,
apiKey: builder.Configuration["AI:ApiKey"]!);
// Register plugins
builder.Services.AddSingleton<OrderPlugin>();
builder.Services.AddSingleton(sp =>
{
var kernel = sp.GetRequiredService<Kernel>();
kernel.Plugins.AddFromObject(sp.GetRequiredService<OrderPlugin>());
return kernel;
});
多个 AI 服务
注册多个 AI 服务,并通过服务 ID 进行选择:
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: endpoint,
apiKey: apiKey,
serviceId: "gpt4o");
builder.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o-mini",
endpoint: endpoint,
apiKey: apiKey,
serviceId: "gpt4o-mini");
var kernel = builder.Build();
// Select service at invocation time
var settings = new PromptExecutionSettings { ServiceId = "gpt4o-mini" };
var result = await kernel.InvokePromptAsync("Summarize: {{$input}}", new(settings)
{
["input"] = longDocument
});
使用 Ollama 的本地模型
#pragma warning disable SKEXP0070 // Ollama connector is experimental
var builder = Kernel.CreateBuilder();
builder.AddOllamaChatCompletion(
modelId: "llama3.2",
endpoint: new Uri("http://localhost:11434"));
var kernel = builder.Build();
插件和函数调用
插件将 .NET 方法公开为 AI 模型可以调用的函数。这是让大模型响应基于真实数据和操作的主要机制。
定义插件
using Microsoft.SemanticKernel;
using System.ComponentModel;
public sealed class OrderPlugin
{
private readonly IOrderRepository _repository;
public OrderPlugin(IOrderRepository repository) => _repository = repository;
[KernelFunction("get_order")]
[Description("Retrieves an order by its ID")]
public async Task<OrderSummary?> GetOrderAsync(
[Description("The unique order identifier")] string orderId,
CancellationToken ct = default)
{
var order = await _repository.GetByIdAsync(orderId, ct);
return order is null ? null : new OrderSummary(order);
}
[KernelFunction("list_recent_orders")]
[Description("Lists the most recent orders for a customer")]
public async Task<IReadOnlyList<OrderSummary>> ListRecentOrdersAsync(
[Description("The customer ID")] string customerId,
[Description("Maximum number of orders to return")] int limit = 10,
CancellationToken ct = default)
{
var orders = await _repository.GetRecentAsync(customerId, limit, ct);
return orders.Select(o => new OrderSummary(o)).ToList();
}
}
注册插件
var kernel = builder.Build();
// From an object instance (DI-friendly)
kernel.Plugins.AddFromObject(new OrderPlugin(orderRepo), "Orders");
// From a type (kernel creates the instance)
kernel.Plugins.AddFromType<TimePlugin>("Time");
// From functions directly
kernel.Plugins.AddFromFunctions("Math",
[
KernelFunctionFactory.CreateFromMethod(
([Description("First number")] double a, [Description("Second number")] double b) => a + b,
"Add",
"Adds two numbers")
]);
自动函数调用
启用模型,使其在聊天过程中自动调用函数:
var settings = new AzureOpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var chatHistory = new ChatHistory();
chatHistory.AddUserMessage("What's the status of order ORD-12345?");
var result = await kernel.GetRequiredService<IChatCompletionService>()
.GetChatMessageContentAsync(chatHistory, settings, kernel);
// The model calls get_order("ORD-12345") automatically and responds with the result
Console.WriteLine(result.Content);
函数筛选器
拦截函数调用以进行日志记录、授权或修改:
public sealed class AuthorizationFilter : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(
FunctionInvocationContext context,
Func<FunctionInvocationContext, Task> next)
{
// Check authorization before function execution
if (context.Function.Name == "get_order")
{
var orderId = context.Arguments["orderId"]?.ToString();
// Validate access...
}
await next(context);
// Post-execution: log or modify result
}
}
// Register the filter
builder.Services.AddSingleton<IFunctionInvocationFilter, AuthorizationFilter>();
提示词模板
提示词模板支持在结构化提示词中进行变量替换和函数调用。
内联提示词
var result = await kernel.InvokePromptAsync(
"Summarize the following text in {{$style}} style:\n\n{{$input}}",
new KernelArguments
{
["input"] = articleText,
["style"] = "concise bullet points"
});
Handlebars 模板
Handlebars 模板支持条件判断、循环和函数调用:
var templateString = """
<message role="system">
You are a helpful customer service agent.
{{#if isVip}}You are speaking with a VIP customer. Be extra attentive.{{/if}}
</message>
<message role="user">
Customer: {{customerName}}
Query: {{query}}
Recent orders:
{{#each orders}}
- Order {{this.Id}}: {{this.Status}} ({{this.Date}})
{{/each}}
</message>
""";
var factory = new HandlebarsPromptTemplateFactory();
var template = factory.Create(new PromptTemplateConfig(templateString)
{
TemplateFormat = HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat
});
var result = await template.RenderAsync(kernel, new KernelArguments
{
["customerName"] = "Alice",
["query"] = "Where is my order?",
["isVip"] = true,
["orders"] = recentOrders
});
YAML 提示词配置
将提示词定义为 YAML 文件,以分离关注点:
# prompts/summarize.yaml
name: Summarize
description: Summarizes text to a specified length
template_format: handlebars
template: |
<message role="system">
Summarize the following text in approximately {{maxWords}} words.
Focus on key facts and actionable items.
</message>
<message role="user">{{input}}</message>
input_variables:
- name: input
description: The text to summarize
is_required: true
- name: maxWords
description: Target word count
default: "100"
execution_settings:
default:
temperature: 0.3
max_tokens: 500
var yamlContent = File.ReadAllText("prompts/summarize.yaml");
var function = kernel.CreateFunctionFromPromptYaml(yamlContent);
var result = await kernel.InvokeAsync(function, new KernelArguments
{
["input"] = longText,
["maxWords"] = "50"
});
记忆和向量存储
Semantic Kernel 提供向量存储抽象,可用于实现检索增强生成(RAG)模式。
向量存储抽象
using Microsoft.Extensions.VectorData;
public sealed class DocumentRecord
{
[VectorStoreRecordKey]
public string Id { get; set; } = string.Empty;
[VectorStoreRecordData(IsFilterable = true)]
public string Source { get; set; } = string.Empty;
[VectorStoreRecordData(IsFullTextSearchable = true)]
public string Content { get; set; } = string.Empty;
[VectorStoreRecordVector(Dimensions: 1536)]
public ReadOnlyMemory<float> Embedding { get; set; }
}
注册向量存储
using Microsoft.SemanticKernel.Connectors.Qdrant;
var builder = Kernel.CreateBuilder();
// Register embedding generation
builder.AddAzureOpenAITextEmbeddingGeneration(
deploymentName: "text-embedding-3-small",
endpoint: endpoint,
apiKey: apiKey);
// Register vector store
builder.Services.AddQdrantVectorStore("localhost", 6334);
RAG 模式
public sealed class RagService
{
private readonly IVectorStoreRecordCollection<string, DocumentRecord> _collection;
private readonly ITextEmbeddingGenerationService _embeddingService;
private readonly IChatCompletionService _chatService;
public RagService(
IVectorStore vectorStore,
ITextEmbeddingGenerationService embeddingService,
IChatCompletionService chatService)
{
_collection = vectorStore.GetCollection<string, DocumentRecord>("documents");
_embeddingService = embeddingService;
_chatService = chatService;
}
public async Task<string> AskAsync(string question, CancellationToken ct = default)
{
// 1. Generate embedding for the question
var questionEmbedding = await _embeddingService
.GenerateEmbeddingAsync(question, cancellationToken: ct);
// 2. Search for relevant documents
var searchResults = _collection.VectorizedSearchAsync(
questionEmbedding,
new VectorSearchOptions { Top = 5 },
ct);
// 3. Build context from search results
var contextBuilder = new StringBuilder();
await foreach (var result in searchResults)
{
contextBuilder.AppendLine(result.Record.Content);
contextBuilder.AppendLine("---");
}
// 4. Generate answer with context
var chatHistory = new ChatHistory();
chatHistory.AddSystemMessage(
$"Answer based on the following context:\n\n{contextBuilder}");
chatHistory.AddUserMessage(question);
var response = await _chatService
.GetChatMessageContentAsync(chatHistory, cancellationToken: ct);
return response.Content ?? string.Empty;
}
}
导入文档
public async Task IngestAsync(
string documentId,
string content,
string source,
CancellationToken ct = default)
{
await _collection.CreateCollectionIfNotExistsAsync(ct);
var embedding = await _embeddingService
.GenerateEmbeddingAsync(content, cancellationToken: ct);
await _collection.UpsertAsync(new DocumentRecord
{
Id = documentId,
Content = content,
Source = source,
Embedding = embedding
}, cancellationToken: ct);
}
Agent 框架
Semantic Kernel Agent 框架支持构建多 Agent 系统,让专用 Agent 协作完成任务。
聊天补全 Agent
#pragma warning disable SKEXP0110 // Agents framework is experimental
using Microsoft.SemanticKernel.Agents;
var agent = new ChatCompletionAgent
{
Name = "OrderAssistant",
Instructions = """
You are an order management assistant. Help customers check order status,
process returns, and answer questions about their orders.
Always verify the customer's identity before sharing order details.
""",
Kernel = kernel,
Arguments = new KernelArguments(new AzureOpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
})
};
// Invoke via a thread (required -- agents do not accept bare strings)
var thread = new ChatHistoryAgentThread();
await foreach (var message in agent.InvokeAsync(
"What's the status of my order ORD-12345?", thread))
{
Console.WriteLine(message.Content);
}
Agent 群组聊天
多个 Agent 可以在群组聊天中协作,并设置终止条件:
var analyst = new ChatCompletionAgent
{
Name = "DataAnalyst",
Instructions = "You analyze data and provide insights. Present findings clearly.",
Kernel = kernel
};
var writer = new ChatCompletionAgent
{
Name = "ReportWriter",
Instructions = "You take analytical findings and write clear, actionable reports.",
Kernel = kernel
};
var chat = new AgentGroupChat(analyst, writer)
{
ExecutionSettings = new AgentGroupChatSettings
{
TerminationStrategy = new ApprovalTerminationStrategy
{
MaximumIterations = 6
}
}
};
chat.AddChatMessage(
new ChatMessageContent(AuthorRole.User, "Analyze Q4 sales trends and write a summary report."));
await foreach (var message in chat.InvokeAsync())
{
Console.WriteLine($"[{message.AuthorName}]: {message.Content}");
}
OpenAI 助手 Agent
用于带有内置工具(代码解释器、文件搜索)的有状态对话:
#pragma warning disable SKEXP0110
// Create the assistant via the builder pattern
OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync(
kernel,
new OpenAIAssistantDefinition("gpt-4o")
{
Name = "DataProcessor",
Instructions = "You process CSV data and generate insights.",
EnableCodeInterpreter = true
});
try
{
// Assistant agents use threads for stateful conversations
var thread = await agent.CreateThreadAsync();
await foreach (var message in agent.InvokeAsync(
"Analyze the attached sales data.", thread))
{
Console.WriteLine(message.Content);
}
}
finally
{
await agent.DeleteAsync();
}
注意:Agent 框架目前处于实验阶段(SKEXP0110)。Semantic Kernel 的各个版本之间 API 变化频繁。采用时,请对照最新示例核实方法签名。
流式响应
对于聊天应用,逐个 Token 流式传输响应:
var chatService = kernel.GetRequiredService<IChatCompletionService>();
var chatHistory = new ChatHistory("You are a helpful assistant.");
chatHistory.AddUserMessage(userInput);
var settings = new AzureOpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
await foreach (var chunk in chatService.GetStreamingChatMessageContentsAsync(
chatHistory, settings, kernel))
{
Console.Write(chunk.Content);
}
核心原则
- 优先使用函数调用,而不是在提示词中塞入大量内容——让模型调用插件获取实时数据,而不是将所有内容注入提示词
- 让插件各司其职——每个插件应代表一个领域;在函数和参数上使用
[Description]特性,让模型了解何时以及如何调用它们 - 在生产环境中使用 YAML 提示词——将提示词内容与代码分离,便于迭代并允许非开发人员编辑
- 不要在代码中存储 API 密钥——使用环境变量、Azure Key Vault 或 .NET 机密管理器(参见 [skill:dotnet-csharp-configuration])
- 优先使用向量存储抽象——基于
IVectorStore编写代码,以便在 Qdrant、Azure AI Search 和其他提供程序之间切换 - 明确处理实验性 API——按调用抑制
SKEXP*警告,而不要全局抑制,这样 API 转为稳定版时你能及时注意到
Agent 注意事项
- 不要在 Kernel 构建器调用中硬编码 API 密钥或端点——使用
builder.Configuration或环境变量。硬编码的机密会泄漏到源代码管理中,并妨碍针对不同环境进行配置。 - 不要全局抑制所有
SKEXP*警告——实验性 API 变化频繁。应按用法抑制(#pragma warning disable SKEXP0110),这样编译器就能标记新的实验性 API 使用位置。 - 不要在 ASP.NET Core 中为每个请求创建新的
Kernel实例——将 Kernel 作为单例注册到 DI 中(它是线程安全的);如果需要每个请求的状态,请使用kernel.Clone()克隆。 - 不要在插件函数中忽略
CancellationToken——用户或超时策略可能会取消 AI 函数调用。始终通过插件方法签名传递CancellationToken。 - 不要从插件函数返回大型对象——模型会将序列化结果作为上下文接收。应返回摘要 DTO,而不是完整的实体关系图,以免超出 Token 限制。
- 不要在没有
serviceId的情况下混用AddAzureOpenAIChatCompletion和AddOpenAIChatCompletion——如果没有服务 ID,最后注册的服务会生效。注册多个 AI 服务时,请使用显式的serviceId。
前提条件
Microsoft.SemanticKernelNuGet 包(稳定版 1.x)- AI 服务端点(Azure OpenAI、OpenAI API 密钥或用于本地模型的 Ollama)
- 对于向量存储:所选提供程序的运行实例(Qdrant、Azure AI Search 等)