Semantic Kernel 入门:用 C# 十分钟搭建第一个企业级 AI 智能体

📅 2026/7/5 7:24:10
Semantic Kernel 入门:用 C# 十分钟搭建第一个企业级 AI 智能体
摘要很多 .NET 开发者对 AI Agent 的认知停留在“调 API 拼 Prompt”的玩具阶段。Semantic Kernel (SK) 的核心价值是将 LLM 能力以强类型、可测试、可观测的方式融入现有企业架构。本文不讲概念科普直接用一个“智能工单分派助手”的最小完整示例演示 SK 在企业场景下的正确打开方式。从 Plugin 定义、依赖注入、结构化输出到 OpenTelemetry 集成全程 C# 原生体验。代码基于 Semantic Kernel 1.x .NET 9复制即可运行。一、为什么企业级 Agent 不能用“脚本思维”写在动手之前先明确 SK 与 LangChain/裸 API 调用的本质区别维度脚本式 AI 调用Semantic Kernel 企业级 Agent工具定义字符串 JSON Schema强类型 C# 方法 Attribute依赖管理全局变量 / 手动传递.NET DI 容器返回值自由文本正则解析结构化对象编译器保障可测试性必须连真实 LLMPlugin 可独立 Mock 单元测试可观测性手写日志原生 OpenTelemetry Trace多模型切换if-else 硬编码Kernel 抽象 配置驱动核心原则把 LLM 当作一个“不确定性的服务调用”而非“万能的黑盒”。SK 的所有设计都围绕这个原则展开。二、项目准备2 分钟dotnet new console-nTicketAgent--frameworknet9.0cdTicketAgent dotnetaddpackage Microsoft.SemanticKernel dotnetaddpackage Microsoft.SemanticKernel.Connectors.OpenAI dotnetaddpackage Microsoft.Extensions.DependencyInjection dotnetaddpackage OpenTelemetry.Exporter.Consoleappsettings.json{OpenAI:{ApiKey:sk-your-key-here,ModelId:gpt-4o-mini}}生产提示企业环境应使用 Azure OpenAI Managed Identity避免密钥明文。本文用 OpenAI 直连仅为降低入门门槛。三、定义业务服务Agent 要调用的“真逻辑”3 分钟关键认知Plugin 不是 AI 代码而是普通的 C# 业务服务。AI 只是它的调用者之一。// ITicketService.cs — 纯业务接口与 AI 完全解耦publicinterfaceITicketService{TaskTicketInfo?GetTicketAsync(stringticketId,CancellationTokenctdefault);TaskAssignTicketAsync(stringticketId,stringassignee,stringreason,CancellationTokenctdefault);TaskIReadOnlyListstringGetAvailableAgentsAsync(stringcategory,CancellationTokenctdefault);}publicrecordTicketInfo(stringId,stringTitle,stringCategory,// network | database | application | securitystringPriority,// P0 | P1 | P2 | P3stringStatus,// open | assigned | resolvedstring?CurrentAssignee);这里故意不展示实现——因为它可以是 EF Core 查数据库、HTTP 调 Jira、或内存字典。对 SK 来说完全不重要。这正是企业级集成的关键AI 层不侵入业务层。四、编写 Plugin让 LLM “看见”业务能力3 分钟Plugin 是 SK 的核心抽象。每个[KernelFunction]方法对应一个 LLM 可调用的工具。usingSystem.ComponentModel;usingMicrosoft.SemanticKernel;publicclassTicketPlugin(ITicketService_ticketService){[KernelFunction(get_ticket)][Description(根据工单ID查询工单详情包括标题、分类、优先级、状态和当前处理人)]publicasyncTaskTicketInfo?GetTicketAsync([Description(工单编号格式如 TK-20260704-001)]stringticketId,CancellationTokenctdefault){returnawait_ticketService.GetTicketAsync(ticketId,ct);}[KernelFunction(list_available_agents)][Description(查询指定分类下当前可用的值班工程师列表)]publicasyncTaskIReadOnlyListstringGetAvailableAgentsAsync([Description(工单分类network/database/application/security)]stringcategory,CancellationTokenctdefault){returnawait_ticketService.GetAvailableAgentsAsync(category,ct);}[KernelFunction(assign_ticket)][Description(将工单分配给指定工程师。仅在确认工程师可用且工单未关闭时调用)]publicasyncTaskstringAssignTicketAsync([Description(工单编号)]stringticketId,[Description(目标工程师姓名)]stringassignee,[Description(分配理由简述为何选择该工程师)]stringreason,CancellationTokenctdefault){await_ticketService.AssignTicketAsync(ticketId,assignee,reason,ct);return$✅ 工单{ticketId}已成功分配给{assignee};}}⚠️ 三个容易踩的坑Description 是写给 LLM 看的不是给人看的LLM 靠它决定何时调用、传什么参。模糊描述 错误调用。参数名要有语义string id不如string ticketId。LLM 会参考参数名推断含义。返回值要自包含LLM 无法访问上下文变量返回的字符串/对象就是它推理的全部依据。五、组装 Kernel 结构化输出2 分钟usingMicrosoft.Extensions.Configuration;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.SemanticKernel;usingMicrosoft.SemanticKernel.ChatCompletion;usingMicrosoft.SemanticKernel.Connectors.OpenAI;varconfignewConfigurationBuilder().AddJsonFile(appsettings.json).Build();// ✅ 通过 DI 注册Plugin 自动获得 ITicketService 实例varservicesnewServiceCollection();services.AddSingletonITicketService,InMemoryTicketService();// 替换为你的实现services.AddKernel().AddOpenAIChatCompletion(modelId:config[OpenAI:ModelId]!,apiKey:config[OpenAI:ApiKey]!);varkernelservices.BuildServiceProvider().GetRequiredServiceKernel();// 注册 Plugin从 DI 容器解析享受生命周期管理kernel.Plugins.AddFromTypeTicketPlugin();// ✅ 结构化输出让 LLM 返回强类型对象而非自由文本varchatServicekernel.GetRequiredServiceIChatCompletionService();varresponseawaitchatService.GetChatMessageContentAsync(newChatHistory{new(AuthorRole.System, 你是IT运维工单分派助手。你的职责是1.查询工单信息判断分类和优先级2.查找该分类下可用的工程师3.选择最合适的工程师并分配工单4.如果工单已关闭或无可用人员明确告知用户 始终先查询再操作不要猜测工单状态。),new(AuthorRole.User,请帮我把 TK-20260704-003 分配出去)},newOpenAIPromptExecutionSettings{FunctionChoiceBehaviorFunctionChoiceBehavior.Auto(),// 允许自动调用工具ResponseFormattypeof(AssignmentResult)// 结构化输出},kernel:kernel);// 反序列化为强类型对象无需正则解析varresultJsonSerializer.DeserializeAssignmentResult(response.Content!);Console.WriteLine($分配结果:{result?.Success}, 原因:{result?.Reason});// 结构化输出 DTOpublicrecordAssignmentResult(boolSuccess,string?AssignedTo,stringReason,string?SuggestedNextAction);为什么结构化输出对企业至关重要下游代码可以if (result.Success)分支处理而非解析自然语言序列化/存储/审计都有确定 schemaLLM 被迫按格式思考减少幻觉六、可观测性生产环境的必备能力1 分钟企业级 Agent 不能是黑盒。SK 原生支持 OpenTelemetryusingOpenTelemetry.Trace;usingOpenTelemetry.Resources;services.AddOpenTelemetry().ConfigureResource(rr.AddService(TicketAgent)).WithTracing(tracingtracing.AddSource(Microsoft.SemanticKernel*)// SK 内置 Activity Source.AddConsoleExporter()// 替换为 OTLP/Jaeger/Zipkin);启用后每次 LLM 调用、Plugin 执行、Token 消耗都会自动生成 Trace Span[Trace] TicketAgent └─ chat.completion gpt-4o-mini (1.2s, 850 tokens) ├─ tool_call: get_ticket (TK-20260704-003) → 45ms ├─ tool_call: list_available_agents (network) → 12ms └─ tool_call: assign_ticket (TK-..., 张三, ...) → 38ms这解决了企业 AI 最大的运维痛点当 Agent 行为异常时你能像排查微服务一样逐跳定位问题而非对着聊天记录猜。七、完整运行效果用户: 请帮我把 TK-20260704-003 分配出去 [SK 内部流程] → get_ticket(TK-20260704-003) ← { Category: network, Priority: P1, Status: open } → list_available_agents(network) ← [张三, 李四] → assign_ticket(TK-20260704-003, 张三, P1网络故障张三为当日网络组值班负责人) ← ✅ 工单 TK-20260704-003 已成功分配给 张三 输出: 分配结果: True, 原因: P1网络故障张三为当日网络组值班负责人整个过程 LLM 自主决策调用顺序和参数但每一步都在强类型约束和可观测范围内。八、从 Demo 到生产的检查清单完成上述代码后你有了一个可运行的原型。但要上生产还需补齐检查项说明优先级✅ 人机协同审批高风险操作如 P0 工单分配增加 Human-in-the-loop 确认环节P0✅ 速率限制 重试Plugin 内对下游服务做 Polly 熔断防止 LLM 疯狂重试打垮后端P0✅ Prompt 版本管理System Prompt 存入配置中心/Git支持灰度回滚P1✅ 评估体系建立 Golden DatasetCI 中自动跑回归测试P1✅ 权限隔离不同角色用户的 Plugin 可见性不同SK 支持动态 Plugin 过滤P1✅ Token 成本监控OpenTelemetry Metrics 追踪每请求 Token 消耗P2✅ 流式输出长回复启用 Streaming改善用户体验P2九、常见误区纠正❌ “Plugin 越多越好”正解每个 Plugin 方法都是 LLM 的决策负担。超过 15 个工具时准确率显著下降。合并细粒度操作只暴露业务语义完整的动作。❌ “Description 随便写写就行”正解Description 的质量直接决定 Agent 可靠性。把它当作 API 文档来写包含前置条件、副作用、返回值含义。建议团队 Review Plugin 时重点审查 Description。❌ “用 SK 就不用管 Prompt 了”正解SK 降低了 Prompt 工程的频率但没有消除它。System Prompt 仍然是 Agent 行为的“宪法”。好的 System Prompt 好的 Plugin 可靠 Agent差的 System Prompt 好的 Plugin 偶尔可靠的 Agent。❌ “C# 做 Agent 比 Python 慢”正解Agent 的瓶颈是 LLM API 延迟秒级不是本地代码执行毫秒级。SK 的 C# 实现与 Python SDK 调用同一后端 API端到端延迟无差异。而强类型带来的开发效率和运行时稳定性反而缩短了整体交付周期。十、下一步学习路径深入 Plugin 高级用法动态 Plugin、Prompt Template as Plugin、gRPC/REST 自动导入RAG 集成SK Memory Vector StoreAzure AI Search / Qdrant / PostgreSQL pgvector多 Agent 协作SK Agent FrameworkHandoff、Group Chat、Delegation评估与 CISemantic Kernel Eval xUnit 自动化回归生产部署.NET Aspire SK Azure Container Apps 全链路模板参考资料Semantic Kernel 官方文档: https://learn.microsoft.com/en-us/semantic-kernel/SK C# Samples Repository: https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samplesOpenTelemetry .NET Integration: https://opentelemetry.io/docs/languages/net/Structured Outputs Guide: https://platform.openai.com/docs/guides/structured-outputs