几个月前,我正在开发一个内容处理工具——它能读取原始的会议记录,提取出待办事项,然后自动起草跟进邮件。一开始,我直接调用 OpenAI 的 API,结果不到一周,我的代码就变成了一团乱麻:到处都是字符串拼接、手动解析 JSON,还有十几个文件里散落着脆弱的提示词模板。每次想给处理流程加个新步骤,我都得再接一次 API 调用,处理一套新的报错,还得想办法在各步骤之间传递上下文。
就在那时,我偶然发现了 Semantic Kernel。微软把它做成了一个编排层——用来规范代码与 AI 模型交互的方式,免得你的代码库变成一团乱麻。它提供了一个用于管理 AI 服务的 kernel 对象,一个用来整理提示词和函数的插件系统,还有能自动把多个步骤串联起来的规划器。花了一个周末用重构了我的工具后,我彻底被圈粉了。接下来我带你上手配置,你亲自感受一下就知道了。
第一步:安装 SDK 并配置密钥
我主要用 Python 开发,所以这里重点讲 Python,不过 Semantic Kernel 也支持 C# 和 Java。Python 的安装很简单:
pip install semantic-kernel
C# 的话是这样:
dotnet add package Microsoft.SemanticKernel
接下来,你需要配置 API 密钥。Semantic Kernel 开箱即支持 OpenAI 和 Azure OpenAI。我在生产环境中用的是 Azure OpenAI,但入门的话,普通的 OpenAI 更简单。在项目根目录创建一个 .env 文件:
OPENAI_API_KEY=sk-your-key-here
OPENAI_ORG_ID=your-org-id-if-applicable
如果你用的是 Azure OpenAI,.env 文件内容就不一样了:
AZURE_OPENAI_API_KEY=your-azure-key
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=gpt-4
说说我踩过的第一个坑:一开始我直接把 API 密钥硬编码在了脚本里。能跑是能跑,但那天晚些时候我把代码推到 GitHub 之后——对,你懂的,我只好去重置密钥。从一开始就老老实实用环境变量吧。如果你用了 python-dotenv,Semantic Kernel 会自动读取 .env 文件。
第二步:创建你的第一个 Kernel 并进行对话
kernel 是核心对象,负责管理你的 AI 服务和插件。下面演示如何实现一个基础的来回对话:
import asyncio
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
async def main():
# 初始化 kernel
kernel = Kernel()
# 添加 AI 服务
kernel.add_service(
OpenAIChatCompletion(
service_id="chat",
ai_model_id="gpt-4o",
)
)
# 简单的聊天调用
from semantic_kernel.contents import ChatHistory
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
chat_service = kernel.get_service(type=ChatCompletionClientBase)
history = ChatHistory()
history.add_user_message("我有一份会议记录,你能帮我提取待办事项吗?")
response = await chat_service.get_chat_message_contents(
chat_history=history,
settings=chat_service.get_prompt_execution_settings_from_service_id("chat"),
)
print(response[0].content)
asyncio.run(main())
我第一次跑这段代码时报错了,提示找不到 API 密钥,因为我忘了加载 .env 文件。在脚本顶部加上 from dotenv import load_dotenv 并调用 load_dotenv() 就行了。加上之后,代码顺利跑通——GPT-4o 给出了关于提取待办事项的有用回复。
拿来做演示是没问题,但这跟直接调用 OpenAI API 没太大区别。真正的大招在后面。
第三步:赋予 AI 运行你代码的能力
这才是 Semantic Kernel 让我顿悟的地方。你可以写普通的 Python 函数,加上装饰器,AI 就能在对话过程中调用它们。这就是所谓的“原生函数”或插件。
from semantic_kernel.functions import kernel_function
class ActionItemsPlugin:
@kernel_function(
description="从会议文本中提取待办事项",
name="extract_actions"
)
def extract_actions(self, text: str) -> str:
# 实际开发中,这里会有更复杂的逻辑
# 比如查询数据库、调用其他 API 等
lines = text.split(".")
action_items = [line.strip() for line in lines if "need to" in line.lower() or "will" in line.lower()]
return "\n".join(f"- {item}" for item in action_items)
class EmailPlugin:
@kernel_function(
description="根据待办事项起草跟进邮件",
name="draft_email"
)
def draft_email(self, action_items: str, recipient: str) -> str:
return f"Hi {recipient},\n\nHere are the action items from our meeting:\n{action_items}\n\nBest regards"
# 将插件添加到 kernel 中
kernel.add_plugin(ActionItemsPlugin(), plugin_name="actions")
kernel.add_plugin(EmailPlugin(), plugin_name="email")
@kernel_function 装饰器就是施展魔法的地方。description 参数会告诉 AI 这个函数是干嘛的。当你开启函数调用(也叫工具调用)时,AI 就能根据用户的请求,自己决定什么时候调用这些函数。
开启方法如下:
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
settings = chat_service.get_prompt_execution_settings_from_service_id("chat")
settings.function_choice_behavior = FunctionChoiceBehavior.Auto()
response = await chat_service.get_chat_message_contents(
chat_history=history,
settings=settings,
kernel=kernel, # 传入 kernel,让它知道有哪些可用函数
)
设置了 FunctionChoiceBehavior.Auto() 之后,AI 会查看可用的插件函数,根据用户消息自行决定调用哪些,并自动执行。当我发送“从这份记录中提取待办事项,然后给 Sarah 起草一封邮件”时,AI 按顺序调用了 extract_actions 和 draft_email,并把前一个的输出作为后一个的输入。这就是编排,而且我完全没写任何胶水代码。
第四步:看 AI 临场生成计划
上面的函数调用能处理简单的链式操作。面对更复杂的场景,Semantic Kernel 提供了规划器。规划器接收一个目标,然后自己琢磨该调用哪些插件函数、按什么顺序、传什么参数。
说实话,我第一次试用规划器时是持怀疑态度的。让 AI 自己决定运行我的哪个函数,感觉挺冒险。但对于范围明确、函数描述清晰的任务,它的效果出奇地好。
from semantic_kernel.connectors.ai.open_ai import OpenAIPromptExecutionSettings
# 设定一个目标
goal = "拿这份会议记录,提取待办事项,然后给团队负责人起草一封跟进邮件"
# 规划器会利用 AI 算出该调用哪些函数
# 以及按什么顺序来实现这个目标
新版 Semantic Kernel 在规划上重度依赖函数调用机制,而不是以前那种 Handlebars 规划器。这其实是件好事——因为 AI 用的是结构化的工具调用,而不是生成一堆自由文本再去解析,所以更可靠。
我碰到过一个意外情况:如果你的函数描述很模糊,规划器有时会以奇怪的顺序调用函数。我一开始把 extract_actions 描述成“处理文本”,结果 AI 试图先把原始记录塞进邮件起草器里。所以在写 @kernel_function 的描述时一定要具体。它们不仅是写给人看的文档,更是给 AI 看的提示词。
实用建议与诚实的局限性
用了 Semantic Kernel 几个月后,这是我总结的经验:
函数描述一定要具体。 AI 完全依赖你的描述来决定何时以及如何调用函数。“从会议文本中提取待办事项”可比“处理文本”好太多了。我曾花了一个小时调试一个奇怪的规划失败问题,最后发现全是因为描述太模糊。
从函数调用开始,别上来就用规划器。 FunctionChoiceBehavior.Auto() 方法比完整的规划器更可预测、更容易调试。只有当你遇到真正需要动态编排的复杂多步工作流时,再考虑用规划器。
独立测试你的插件。 像对待其他代码一样,为你的插件函数写单元测试。AI 层只是你函数的消费者——如果函数本身有 bug,AI 也会把错误结果往下传。
留意 Token 消耗。 开启函数调用后,每个函数的 schema 都会随请求一起发送。如果你有几十个插件,参数类型又复杂,Token 数量会迅速膨胀。我就因为函数定义占用了大量上下文窗口,比预期更早触发了频率限制。
文档还在努力跟上节奏。 Semantic Kernel 发展很快,网上有些教程已经过时了。微软官方文档是目前最好的参考,但就连它也落后于最新版本。想看最新的用法,最好去 GitHub 仓库的 examples 文件夹里找找。
它不是银弹。 如果你只是简单调调 API,根本不需要编排,那用 Semantic Kernel 只会徒增复杂性。写一次性脚本时,我依然会直接裸调 OpenAI API。只有当你需要复用函数、处理多步工作流,或者需要在不同的 AI 供应商之间切换时,Semantic Kernel 才真正大放异彩。
对我来说,最大的收获就是它的插件系统。我再也不用把提示词和 API 调用散落在代码库的各个角落了,所有东西都被组织成具有清晰接口的插件。后来因为成本原因,我需要把模型从 GPT-4o 换成别的,我只改了 kernel 配置里的一行代码,所有插件照常运行。光凭这一点,就值得我去花时间踩这个学习曲线了。