很多 Agent 框架喜欢让你「写一堆 prompt 描述该调哪个工具」。微软的 Semantic Kernel(SK) 走的是另一条更工程化的路:把能力写成一个个带类型注解的原生函数(Plugin),框架负责把它们注册成「可被模型调用的工具」,再用 Function Calling / Planner 自动决定调用顺序。好处是:能力是普通 Python 函数,可单测、可复用、可跨语言(C# / Python 同一套概念)。
先搞懂:Plugin 和 Planner 各自干嘛?
| 概念 | 作用 | 本文落点 |
|---|---|---|
| Plugin / Function | 一个可被模型调用的能力(原生函数) | Step 2 写文件处理三件套 |
| Kernel | 把模型 + 插件 + 服务「装配」在一起 | Step 3 装配 |
| Function Calling | 模型自动决定「调哪个、传什么」 | Step 4 自动多步 |
| Planner | 显式生成「执行计划」再跑 | Step 5 规划式 |
SK 的 Python API 迭代很快。下文代码基于较新版本;若你装的版本里 FunctionChoiceBehavior、kernel.invoke 的入参或 Planner 类名对不上,请以你 pip show semantic-kernel 对应的官方文档为准(概念不变,主要是命名/入参微调)。
Step 1:安装
pip install semantic-kernel
export OPENAI_API_KEY=sk-... # 演示用 OpenAI;也可换兼容端点
Step 2:写一个 Plugin(@kernel_function)
import semantic_kernel as sk
from semantic_kernel.functions import kernel_function
class FilePlugin:
@kernel_function(name="read_file", description="读取文本文件内容")
def read_file(self, path: str) -> str:
with open(path, encoding="utf-8") as f:
return f.read()
@kernel_function(name="clean_text", description="去除空行与多余空白")
def clean_text(self, text: str) -> str:
return "\n".join(
line.strip() for line in text.splitlines() if line.strip()
)
@kernel_function(name="write_file", description="把内容写入文件")
def write_file(self, path: str, content: str) -> str:
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return f"已写入 {path}"
@kernel_function 装饰;② 函数名 + 参数类型 + docstring 就是给模型看的「工具说明书」,写得含糊模型就调不准;③ 函数体是普通 Python,能单测、能复用。Step 3:装配 Kernel(模型 + 插件)
import os
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
kernel = sk.Kernel()
kernel.add_service(
OpenAIChatCompletion(
service_id="default",
api_key=os.environ["OPENAI_API_KEY"],
ai_model_id="gpt-4o-mini",
)
)
kernel.add_plugin(FilePlugin(), plugin_name="file")
别硬编码 Key。用环境变量 OPENAI_API_KEY 注入;上线时改从配置/密钥管理读取,不要把密钥写进源码或提交到仓库。
Step 4:让模型自动规划多步任务(Function Calling)
import asyncio
from semantic_kernel.connectors.ai.open_ai import OpenAIChatPromptExecutionSettings
async def main():
settings = OpenAIChatPromptExecutionSettings(service_id="default")
settings.function_choice_behavior = FunctionChoiceBehavior.Auto()
result = await kernel.invoke(
service_id="default",
input="读取 data.txt,清洗后告诉我省了多少空行,并把清洗结果写回 clean.txt",
settings=settings,
)
print(result)
asyncio.run(main())
模型会自己拆成:read_file(data.txt) → clean_text(...) → write_file(clean.txt, ...),并把每一步结果传给下一步。你没写任何「先调 A 再调 B」的胶水代码——这是 SK 自动 Function Calling 的威力。
FunctionChoiceBehavior.Auto() = 模型自主决定调工具;还有 AutoInvokeKernelFunctions 等选项控制「模型只给调用建议」还是「直接执行」。需要人工确认写操作时,选「只建议不执行」更安全。Step 5:用 Planner 显式生成执行计划
当任务复杂、你想「先看它打算怎么做」时,用 Planner 显式生成计划:
from semantic_kernel.planners.function_calling_stepwise_planner import (
FunctionCallingStepwisePlanner,
)
planner = FunctionCallingStepwisePlanner(service_id="default")
plan = await planner.invoke(
"读取 data.txt,清洗后归纳成 3 条要点并写回 summary.txt",
kernel=kernel,
)
print("计划:", plan.full_plan) # 先看它打算怎么走
print("结果:", plan.final_answer) # 再拿最终答案
Planner 类名随版本变动较大。旧版是 SequentialPlanner / FunctionCallingSequentialPlanner;新版多为 FunctionCallingStepwisePlanner。装好后用 python -c "import semantic_kernel.planners as p; print(dir(p))" 看当前可用的 Planner 类名,再决定用哪个。
Step 6:落地汇总与避坑
- 函数即文档:模型靠 name/参数类型/docstring 理解工具,描述写清「做什么、返回什么」能显著提升调用准确率。
- 写操作加护栏:
write_file这类会改文件系统的函数,生产环境建议设为「只建议、需人工确认」或加路径白名单。 - 跨语言一致:同一套 Plugin/Planner 概念在 C# 与 Python 下写法一致,团队混栈时很省心。
- 成本:自动多步会多次调模型;任务固定时优先用显式 Planner 或手工编排,减少试错轮次。
常见问题速查
| 现象 | 原因 & 解决 |
|---|---|
| 模型不调我的函数 | 函数 docstring/参数描述太含糊;把「做什么、返回什么」写具体 |
| Planner import 报错 | 版本命名差异;查当前版 semantic_kernel.planners 可用类名 |
| 报 API Key / 模型错误 | 未设 OPENAI_API_KEY,或 ai_model_id 写错 |
| 自动多步一直调不对 | 任务太复杂;改用 Planner 先看计划,或拆小任务 |