1. 为什么你需要Semantic Kernel来构建企业级AI应用?
如果你正在尝试将大语言模型(LLM)的能力集成到你的企业应用中,你很可能已经体验过那种“拼接感”——写了一大堆胶水代码,就为了把用户的问题传给AI,再把AI的回答塞回你的业务流程里。更头疼的是,当你想让AI不只是聊天,而是能真正“做事”,比如查询数据库、分析图片、调用内部API时,代码复杂度会直线上升。这还没算上多轮对话的记忆管理、不同AI模型的切换、以及生产环境下的监控和稳定性问题。
我自己在早期项目里就踩过这些坑,直到遇到了 Semantic Kernel(SK)。简单说,SK是微软开源的一个AI应用编排框架,它不是一个AI模型,而是一个“中间件”或者说“胶水层”。它的核心价值在于,让你能用熟悉的编程语言(C#、Python、Java),以模块化、可插拔的方式,轻松构建出能理解、推理并执行复杂任务的AI代理(AI Agent)。
想象一下,你要开发一个智能客服。传统做法是写一堆if-else规则。用SK,你可以创建一个“客服代理”,它背后是一个大语言模型(比如GPT-4)。你告诉这个代理:“你是我们的客服,这是产品手册(作为知识库),这是订单查询API(作为一个工具/插件)。” 当用户问“我的订单12345到哪了?”,代理会自己理解问题,决定调用“订单查询”插件,拿到数据后,组织成自然语言回复给用户。整个过程,你只需要定义好插件和代理的职责,剩下的复杂编排SK帮你搞定。
对于企业来说,SK的吸引力在于企业级就绪。它原生支持Azure OpenAI服务,与微软云生态无缝集成;提供了插件化架构,能把你现有的业务系统(CRM、ERP)快速封装成AI可用的工具;内置了记忆、规划、多代理协作等高级模式;并且从设计上就考虑了可观测性、安全性和向后兼容。这意味着你可以快速构建原型,并放心地将其部署到生产环境。
2. 快速上手:5分钟构建你的第一个AI代理
理论说了不少,咱们直接动手。这里我用Python来演示,因为语法更简洁。放心,C#和Java的API设计思路几乎一样。
首先,安装Semantic Kernel。确保你的Python版本在3.10以上。
pip install semantic-kernel
接下来,你需要一个大语言模型的服务。这里以Azure OpenAI为例(你也可以用OpenAI官方接口)。你需要准备好终结点(Endpoint)、API密钥和部署名称。
import asyncio
import os
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
# 通常建议通过环境变量管理密钥,这里为了演示直接写入
os.environ["AZURE_OPENAI_ENDPOINT"] = "https://你的资源名.openai.azure.com/"
os.environ["AZURE_OPENAI_API_KEY"] = "你的密钥"
os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"] = "你的聊天模型部署名"
async def main():
# 1. 创建Kernel核心实例
kernel = Kernel()
# 2. 添加AI服务
chat_service = AzureChatCompletion(
deployment_name=os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"),
endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
api_version="2024-02-01" # 使用较新的API版本
)
kernel.add_service(chat_service)
# 3. 创建一个简单的提示函数(Prompt Function)
# 这相当于给AI一个固定的任务模板
prompt = """
你是一个专业的文案助手。
请根据用户提供的产品描述,生成一段吸引人的广告文案。
产品描述:{{$product_description}}
文案风格:{{$style}}
"""
# 将提示模板注册到Kernel,成为一个可调用的函数
generate_ad = kernel.create_function_from_prompt(
prompt_template=prompt,
function_name="GenerateAd",
description="根据产品描述生成广告文案"
)
# 4. 调用这个AI函数
arguments = kernel.create_new_arguments()
arguments["product_description"] = "一款新型智能咖啡机,支持语音控制,10种口味定制,5分钟快速煮好。"
arguments["style"] = "科技感、简洁"
result = await kernel.invoke(function=generate_ad, arguments=arguments)
print(f"生成的广告文案:\n{result}")
if __name__ == "__main__":
asyncio.run(main())
运行这段代码,你应该能得到一段关于智能咖啡机的广告文案。虽然简单,但你已经完成了一个核心链路:配置服务 -> 定义任务 -> 执行并获得AI输出。这里的 Kernel 对象是整个SK的枢纽,它管理着所有服务(AI模型、插件等)和函数的执行。
3. 核心进阶:模块化插件设计与实战
让AI真正强大的,不是它自己有多能说,而是它能调用多少工具。在SK里,工具(Tool)以**插件(Plugin)**的形式存在。一个插件就是一组相关函数的集合。这些函数可以是:
- 语义函数(Semantic Function):基于自然语言提示词(Prompt)的AI函数,就像上面的
generate_ad。 - 原生函数(Native Function):用编程语言(Python、C#)写的传统函数,用于执行确定性的操作,比如计算、数据库查询、调用API。
实战场景:我们来构建一个“智能会议室助手”代理的一部分。它需要能查询会议室预定情况。
首先,我们创建一个原生的“会议室管理”插件:
from semantic_kernel.functions import kernel_function
from typing import Annotated
from datetime import datetime
import json
class ConferenceRoomPlugin:
"""一个模拟的会议室管理插件"""
# 模拟的会议室数据
_rooms = {
"A101": {"capacity": 10, "bookings": [("09:00", "11:00")]},
"B202": {"capacity": 20, "bookings": [("14:00", "16:00")]},
"C303": {"capacity": 8, "bookings": []},
}
@kernel_function(
name="GetAvailableRooms",
description="根据时间、人数和时长查询可用的会议室。"
)
def get_available_rooms(
self,
start_time: Annotated[str, "查询开始时间,格式 HH:MM"],
duration_minutes: Annotated[int, "会议时长(分钟)"],
min_capacity: Annotated[int, "最小容纳人数"] = 5
) -> str:
"""
核心逻辑:遍历会议室,检查时间冲突和容量。
返回JSON格式的字符串,方便AI解析。
"""
available = []
for room_id, info in self._rooms.items():
if info["capacity"] < min_capacity:
continue
# 简单的冲突检查(真实场景需要更复杂的日期时间处理)
conflict = False
for (book_start, book_end) in info["bookings"]:
# 这里简化了时间冲突逻辑
if start_time < book_end: # 假设会议时间有重叠
conflict = True
break
if not conflict:
available.append({
"room_id": room_id,
"capacity": info["capacity"],
"available_until": "17:00" # 模拟数据
})
return json.dumps({"available_rooms": available}, ensure_ascii=False)
@kernel_function(
name="BookRoom",
description="预定指定的会议室。"
)
def book_room(
self,
room_id: Annotated[str, "要预定的会议室ID"],
date: Annotated[str, "预定日期,格式 YYYY-MM-DD"],
start_time: Annotated[str, "开始时间 HH:MM"],
end_time: Annotated[str, "结束时间 HH:MM"]
) -> str:
# 这里应该实现真实的预定逻辑,写入数据库等
# 现在只是模拟
if room_id not in self._rooms:
return json.dumps({"success": False, "error": "会议室不存在"})
# 检查冲突...
self._rooms[room_id]["bookings"].append((start_time, end_time))
return json.dumps({"success": True, "message": f"会议室 {room_id} 预定成功"})
然后,在Kernel中注册这个插件,并让AI代理学会使用它:
async def agent_with_plugin_demo():
kernel = Kernel()
# ... 添加AI服务(同上例)
# 将插件类实例添加到Kernel
room_plugin = ConferenceRoomPlugin()
kernel.add_plugin(room_plugin, plugin_name="ConferenceRoom")
# 现在,我们可以创建一个更智能的代理,它能自动决定何时调用插件
# 使用SK的Agent框架(更高级的用法)
from semantic_kernel.agents import ChatCompletionAgent
agent = ChatCompletionAgent(
service=chat_service, # 复用之前的chat_service
name="RoomAssistant",
instructions="""
你是一个会议室助手。帮助用户查询和预定会议室。
当用户想找会议室时,使用GetAvailableRooms工具。
当用户明确要预定时,使用BookRoom工具。
工具返回的是JSON数据,请用友好、自然的话总结给用户。
""",
kernel=kernel, # 将带有插件的Kernel传给Agent
)
# 模拟用户对话
response = await agent.get_response(messages="今天下午3点,我们需要一个能坐15个人的会议室,开1小时的会。")
print(f"助手回复: {response.content}")
# 预期助手会调用 GetAvailableRooms(start_time="15:00", duration_minutes=60, min_capacity=15)
# 然后根据返回的JSON,组织成自然语言回复,例如:“找到符合您要求的会议室B202,可容纳20人,目前下午3点后可用...”
# 继续对话
response = await agent.get_response(messages="好的,就预定B202,日期是2025-10-27,从15:00到16:00。", thread=response.thread)
print(f"助手回复: {response.content}")
# 预期助手会调用 BookRoom(room_id="B202", date="2025-10-27", start_time="15:00", end_time="16:00")
通过这个例子,你看到了插件如何将企业内部系统能力“暴露”给AI。AI代理根据你的指令(instructions)和对话上下文,自动规划(Planning) 并决定调用哪个工具,拿到结构化数据后再组织回复。这种“思考-行动”的循环,正是智能代理的核心。
4. 处理多模态数据:让AI看懂图片和听懂声音
现代AI应用早已不限于文本。SK通过集成不同的AI服务,支持多模态(Multimodal) 处理,这意味着你的代理可以同时理解文本、图像甚至音频。
实战场景:构建一个“智能内容审核助手”,它能分析用户上传的图片和文本,判断是否违规。
这里我们需要用到支持图像识别的AI服务,比如Azure OpenAI的GPT-4V模型,或者Azure AI Vision。以下是一个集成示例:
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import ChatHistory, ImageContent
import base64
async def multimodal_analysis_demo():
kernel = Kernel()
# 添加支持多模态的聊天服务(例如GPT-4 Turbo with Vision)
# 注意:需要确保你的Azure OpenAI部署支持视觉模型
chat_service = AzureChatCompletion(
deployment_name="gpt-4-vision-deployment", # 你的视觉模型部署名
endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
api_version="2024-02-01"
)
kernel.add_service(chat_service)
# 准备图像内容:可以从本地文件读取,也可以来自网络URL
def encode_image_to_base64(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# 假设我们有一张本地图片
image_data = encode_image_to_base64("path/to/user_uploaded_image.jpg")
# 构建聊天历史,包含图像和文本
chat_history = ChatHistory()
# 系统指令
chat_history.add_system_message("你是一个内容安全审核助手。请分析用户提供的图片和文本,判断是否存在违规内容(如暴力、色情、不当标语等)。只回答‘合规’或‘违规’,并简要说明原因。")
# 用户消息:混合了文本和图像
user_content = [
"请分析这张图片和附带的文字:‘清仓大甩卖!’",
ImageContent.from_base64(image_data, detail="high") # detail参数控制发送给模型的图像细节程度
]
chat_history.add_user_message(user_content)
# 调用模型进行分析
settings = {"max_tokens": 300}
result = await chat_service.get_chat_message_content(
chat_history=chat_history,
settings=settings
)
print(f"审核结果:{result.content}")
# 可能的输出:“违规。原因:图片中包含疑似不雅内容,且促销文字未标明具体商品,可能涉及虚假宣传。”
对于音频处理,思路类似。你可以使用Azure AI Speech等服务先将音频转录成文本,再将文本送入SK的流程中。SK的插件体系允许你轻松集成这些外部服务。关键是设计好工作流:是先语音转文本,还是直接让多模态模型处理音频?SK的任务链(Chain) 或流程框架(Process Framework) 正是用来编排这种复杂多步骤任务的。
5. 构建高可用企业级架构:与Azure云服务深度集成
当你的AI代理从Demo走向生产,高可用、可扩展、可观测、安全就成了必须考虑的问题。SK与Azure云服务的原生集成,让这些变得相对简单。
5.1 部署为Azure Function(无服务器架构)
将你的SK应用部署为Azure Function,可以让你无需管理服务器,按需伸缩,并且通过HTTP触发器轻松提供API服务。
// 这是一个C#的Azure Function示例,展示如何包装一个SK智能代理
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using System.Net;
public class AIChatAgentFunction
{
private readonly IKernel _kernel;
private readonly ChatCompletionAgent _agent;
public AIChatAgentFunction(IKernel kernel, ChatCompletionAgent agent)
{
_kernel = kernel;
_agent = agent;
}
[Function("Chat")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
// 1. 解析用户请求
var requestBody = await req.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
string userMessage = data?.message;
string sessionId = data?.sessionId ?? "default-session";
if (string.IsNullOrEmpty(userMessage))
{
var badResponse = req.CreateResponse(HttpStatusCode.BadRequest);
await badResponse.WriteStringAsync("Please pass a 'message' in the request body");
return badResponse;
}
// 2. 这里应该根据sessionId从持久化存储(如Azure Cosmos DB)中恢复对话历史
// 为了简化,我们每次都是新对话
var chatHistory = new ChatHistory();
// 3. 调用SK代理获取回复
var response = await _agent.GetChatMessageContentAsync(userMessage, chatHistory);
// 4. 将本次对话历史保存回持久化存储(以实现多轮对话记忆)
// 5. 返回响应
var okResponse = req.CreateResponse(HttpStatusCode.OK);
await okResponse.WriteAsJsonAsync(new { reply = response.Content });
return okResponse;
}
}
在Startup或Program.cs中,你需要配置依赖注入,创建单例的Kernel和Agent实例,确保Function每次执行时复用它们,避免重复初始化开销。
5.2 利用Azure AI Search实现检索增强生成(RAG)
企业AI应用的核心价值往往在于其独有的知识和数据。SK内置了与Azure AI Search(原名Azure Cognitive Search)等向量数据库的集成,轻松实现RAG模式。
from semantic_kernel.connectors.memory.azure_ai_search import AzureAISearchMemoryStore
from semantic_kernel.memory import SemanticTextMemory
async def setup_rag_with_azure_search():
kernel = Kernel()
# ... 添加AI服务
# 1. 初始化Azure AI Search记忆存储
memory_store = AzureAISearchMemoryStore(
vector_size=1536, # 通常对应text-embedding-ada-002的维度
search_endpoint=os.getenv("AZURE_AI_SEARCH_ENDPOINT"),
admin_key=os.getenv("AZURE_AI_SEARCH_ADMIN_KEY"),
index_name="company-knowledge-base"
)
# 2. 创建语义文本记忆,并绑定一个文本嵌入生成服务
from semantic_kernel.connectors.ai.open_ai import AzureTextEmbedding
embedding_gen = AzureTextEmbedding(
deployment_name="text-embedding-ada-002-deployment",
endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
api_key=os.environ["AZURE_OPENAI_API_KEY"]
)
kernel.add_service(embedding_gen)
memory = SemanticTextMemory(storage=memory_store, embeddings_generator=embedding_gen)
# 3. 假设我们已经提前将公司文档切片并存储到了索引中
# memory.save_information(collection="handbook", id="doc1", text="公司年假政策是...")
# 4. 在代理或函数中使用记忆进行检索
# 定义一个语义函数,其提示词中包含从记忆检索的步骤
prompt = """
基于以下公司知识库内容回答问题。
[知识库内容开始]
{{recall $query}}
[知识库内容结束]
问题:{{$query}}
答案:
"""
qa_function = kernel.create_function_from_prompt(prompt)
# 调用时,SK会自动执行“recall”操作,从memory中搜索与$query相关的片段,并注入到提示词中
arguments = kernel.create_new_arguments()
arguments["query"] = "请问年假有多少天?"
# 需要将memory实例与函数调用关联(具体API可能随版本变化)
# result = await kernel.invoke(function=qa_function, arguments=arguments, memory=memory)
这样,当员工询问公司政策时,代理的回答不再是基于模型的通用知识,而是精准地从你上传的内部手册中检索并生成,极大提高了准确性和可靠性。
5.3 实现可观测性与监控
在生产环境中,你需要知道AI代理的表现:它调用了哪些函数?耗时多久?消耗了多少Token?是否触发了敏感内容过滤器?
SK提供了过滤器(Filters) 和遥测(Telemetry) 支持。你可以为函数调用、提示词渲染、模型调用等环节添加前置和后置钩子。
// C# 示例:创建一个日志过滤器
public class LoggingFilter : IFunctionInvocationFilter
{
private readonly ILogger _logger;
public LoggingFilter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<LoggingFilter>();
}
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
_logger.LogInformation($"开始调用函数: {context.Function.Name}");
var startTime = DateTimeOffset.UtcNow;
// 调用下一个过滤器或实际函数
await next(context);
var duration = DateTimeOffset.UtcNow - startTime;
_logger.LogInformation($"函数调用完成: {context.Function.Name}, 耗时: {duration.TotalMilliseconds}ms");
}
}
// 在Kernel构建时注册过滤器
var kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.Services.AddSingleton<IFunctionInvocationFilter, LoggingFilter>();
// ... 添加其他服务
var kernel = kernelBuilder.Build();
结合Azure Application Insights,你可以轻松地将这些日志和指标发送到云端,创建仪表盘,监控成本、延迟和错误率。
6. 避坑指南:从开发到部署的常见问题
在我实际的项目交付过程中,总结了一些容易踩坑的地方,希望能帮你少走弯路。
1. 插件设计要“原子化”且描述清晰
插件的函数应该职责单一。GetUserProfile 就比 GetAndUpdateUserData 要好。同时,description 参数至关重要,这是AI决定是否调用该函数的主要依据。描述要像给一个实习生写说明书一样清晰,说明输入是什么、输出是什么、这个函数是干什么的。
2. 管理好对话状态(记忆)
对于多轮对话应用,必须持久化对话历史。简单的场景可以用ChatHistory对象并自己存到数据库。复杂的场景可以考虑SK的ChatCompletionAgent及其线程管理,它会自动维护对话上下文。注意,无服务器环境(如Azure Function)下,需要将会话状态外置到Cosmos DB或Redis中。
3. 处理AI的“不确定性” AI模型可能拒绝回答、产生幻觉或调用错误的工具。一定要在关键业务路径上设置人工审核回退机制。例如,在客服场景中,当AI置信度低或涉及退款等敏感操作时,自动转交人工坐席。
4. 成本与性能优化
- 缓存:对频繁且结果不变的AI调用(如某些内容总结)进行缓存。
- 限制Token:在提示词中明确限制输出长度,在调用模型时设置
max_tokens参数。 - 模型选择:不是所有任务都需要GPT-4。文本分类、简单提取可以用更便宜、更快的模型(如GPT-3.5 Turbo)。SK的模型无关性让你可以轻松配置多个模型,并根据任务路由。
5. 版本升级
SK目前迭代较快,API可能会有变动。建议在项目中锁定主要的版本号(如semantic-kernel==1.*),并在升级前仔细阅读发行说明。将SK相关的代码进行良好封装,有助于降低升级成本。
从我个人的经验来看,Semantic Kernel最大的优势在于它提供了一套统一的、企业级的抽象,让你能专注于业务逻辑和AI能力的设计,而不是陷入底层的通信、编排和运维细节。它可能不是最简单的入门工具,但绝对是构建复杂、可靠、可维护的企业AI应用时最有力的框架之一。当你需要将AI从演示玩具变成生产系统中的一个核心部件时,SK的价值就会真正凸显出来。
901



被折叠的 条评论
为什么被折叠?



