用 Semantic Kernel 为 Microsoft Copilot Studio 构建自定义 Skill:从低代码扩展到 Pro-Code 的完整实战
【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel
本篇技术指南以当前仓库中python/samples/demos/copilot_studio_skill示例为核心,系统讲解如何将 Semantic Kernel 以Copilot Studio Skill的形式接入 Microsoft Copilot Studio,通过 Azure Bot Service 与部署在 Azure Container Apps 上的自定义 API 扩展 Agent 能力。读完本文,你将掌握 Copilot Studio Skill 的完整架构、Entra ID(Azure AD)应用注册、azd up一键部署、Bot Framework 技能清单(Manifest)编写,以及基于 Semantic KernelChatCompletionAgent的对话后端实现细节,并能直接照搬源码改造出你自己的生产级 Skill。
为什么需要 Pro-Code 方式扩展 Copilot Studio
Microsoft Copilot Studio 是一个图形化的低代码工具,既可以用于创建 Agent(包括通过 Power Automate 构建自动化流程),也可以将企业自有数据和场景扩展进 Microsoft 365 Copilot。然而在某些场景下,默认 Agent 能力无法满足需求,例如:
- 需要调用企业内部的私有 API、复杂计算或领域算法;
- 需要细粒度的权限控制与多租户安全校验;
- 需要把 LLM 编排(多轮对话、函数调用、插件体系)交给自己完全可控的代码。
此时"Pro-Code 优先"的方式——把 Semantic Kernel 封装成一个可被 Copilot Studio 调用的 Skill——就成为一种自然选择。本示例正是这一思路的最小可运行实现:Copilot Studio 中的 Topic 通过一个 Action 节点调用远程 Skill,Skill 后端由 Semantic Kernel 驱动的聊天 Agent 处理并返回响应。
注意:示例演示了一个"讲笑话"的 Agent(
sk_conversation_agent.py中的指令为 "You invent jokes to have a fun conversation with the user."),但整个框架可以替换为任何业务逻辑,如 RAG 问答、订单查询、内容生成等。
架构总览:一条从 Copilot Studio 到 SK Agent 的请求链路
示例采用Azure Bot Service作为请求入口,负责将请求路由到后端服务——一个运行在Azure Container Apps中、由 Semantic Kernel 驱动的自定义 API。整体时序如下(摘自原文档架构图,此处以 Mermaid 还原):
链路中有两条关键路径:
- 注册路径(一次性):Copilot Studio 直接向 SK App 的
/manifest端点拉取技能清单(Skill Manifest),完成 Skill 注册; - 运行时路径(每次对话):用户消息经 Copilot Studio → Azure Bot Service → SK App 的
/api/messages端点,SK Agent 处理后沿原路返回响应。
从仓库源码看,这个"SK App"实际是一个基于aiohttp的轻量 Web 服务(app.py),路由定义非常清晰:
APP = web.Application() APP.router.add_post("/api/messages", messages) APP.router.add_get("/manifest", copilot_manifest)POST /api/messages:Bot Framework 协议的消息处理入口,处理来自 Copilot Studio 的 Activity;GET /manifest:动态生成并返回 Copilot Studio Skill Manifest(带身份与端点信息)。
提示:原文档特别指出,截至目前 Bot Framework SDK for Python 仅提供
aiohttp支持(不支持 FastAPI/Flask 等框架),这也是示例选择 aiohttp 的原因。见 requirements.txt 中的botbuilder-integration-aiohttp>=4.15.0。
前置条件与环境要求
开始部署前,请确认具备以下环境(与原文档一致):
| 前置项 | 说明 |
|---|---|
| Azure 订阅 | 用于部署 Bot Service、Container Apps、Azure OpenAI 等资源 |
| Azure CLI | 用于创建 Entra ID 应用注册与凭据 |
| Azure Developer CLI(azd) | 用于一键部署基础设施与代码 |
| Python 3.12 或更高 | 后端 API 的运行时(Dockerfile 基于python:3.12-slim) |
| 启用 Copilot Studio 的 Microsoft 365 租户 | 用于注册 Skill 与测试对话 |
关于租户有两个值得注意的细节:
- Azure 订阅与 Microsoft 365 租户不必是同一租户;
- 但必须在启用 Copilot Studio 的租户的 Entra ID(Azure AD)中拥有注册应用的权限,因为 Bot 身份(App Registration)决定了 Copilot Studio 能否安全调用你的 Skill。
端到端部署步骤
第一步:克隆仓库并定位示例
git clone https://gitcode.com/GitHub_Trending/se/semantic-kernel cd semantic-kernel/python/samples/demos/copilot_studio_skill示例目录结构如下(仓库内实际文件):
copilot_studio_skill/ ├── azure.yaml # azd 服务定义(Container Apps + Docker) ├── image.png # 运行效果截图 ├── infra/ # Bicep 基础设施模板(main.bicep、bot.bicep、aca.bicep 等) └── src/api/ # SK Skill 后端 API ├── adapter.py # 带错误处理的 CloudAdapter ├── app.py # aiohttp 入口与路由 ├── auth.py # 调用方 Claims 校验器 ├── bot.py # Teams Application + 消息处理 ├── config.py # 环境变量配置类 ├── copilot-studio.manifest.json # Skill Manifest 模板 ├── dockerfile ├── requirements.txt └── sk_conversation_agent.py # Semantic Kernel ChatCompletionAgent第二步:在 Entra ID 中创建应用注册
Skill 需要以 Bot 身份运行,因此先在 Microsoft 365 租户(Copilot Studio 所在租户)中创建 App Registration,并生成 client secret。原文档给出的 PowerShell 命令如下:
az login --tenant <COPILOT-tenant-id> $appId = az ad app create --display-name "SKCopilotSkill" --query appId -o tsv $secret = az ad app credential reset --id $appId --append --query password -o tsv记下三个值,它们将用于后续部署与配置:
$appId→ 对应环境变量BOT_APPID$secret→ 对应环境变量BOT_PASSWORD<COPILOT-tenant-id>→ 对应环境变量BOT_TENANT_ID
对应关系可从 infra/main.parameters.json 中确认:botAppId、botPassword、botTenantId分别绑定到BOT_APPID、BOT_PASSWORD、BOT_TENANT_ID环境变量。
第三步:使用 azd 一键部署 Azure 资源
登录 Azure 订阅后执行:
azd auth login --tenant <AZURE-tenant-id> azd up交互过程中需要提供以下输入(原文档明确列出):
| 提示项 | 来源 |
|---|---|
botAppId | 上一步的应用注册 App ID |
botPassword | 上一步的 client secret |
botTenantId | Copilot Studio 所在租户 ID |
| 现有的 Azure OpenAI 资源名及其资源组 | 需提前在 Azure 订阅中创建好 |
此外 main.parameters.json 还暴露了模型相关参数,均有默认值:
openAIModel:默认gpt-4oopenAIApiVersion:默认2024-08-01-previewapiAppExists:默认false(是否复用已存在的容器应用)
部署由 azure.yaml 驱动——它将src/api作为containerapp类型的服务,使用仓库内 dockerfile 构建镜像(python:3.12-slim基础镜像,暴露端口 80,运行时环境变量HOST=0.0.0.0、PORT=80)。
提示:部署完成后,API 的 URL 会显示在 Azure Developer CLI 的
output部分,请复制保存,后续注册 Skill 与配置homeUrl都会用到。
第四步:配置 App Registration 的 homeUrl
将第二步创建的 App Registration 的homeUrl设置为已部署 API 的 URL。这是 Bot 能够响应 Copilot Studio 请求的必要条件——原文档强调"required for the bot to be able to respond to requests from Copilot Studio"。
第五步:在 Copilot Studio 中把 Bot 注册为 Skill
- 在 Microsoft 365 租户中打开 Copilot Studio;
- 新建一个 Agent 或复用已有 Agent;
- 在 Agent 页面右上角进入 "Settings";
- 进入 "Skills" 标签页,点击 "Add a skill";
- 输入
API_URL/manifest(API_URL即部署输出的 API 地址)作为 Skill URL; - 点击 "Next" 完成注册;
- 注册完成后,编辑或新建一个 Topic,在主题流中添加该 Skill 节点即可开始使用。
上图(image.png)展示的正是这一步的产物:左侧是 Copilot Studio 的主题工作流编辑器——Trigger 节点(描述为 "Jokes")通过蓝色箭头连接到 "Invoke Semantic Kernel skill" 的 Action 节点;右侧测试面板中用户提问 "Tell me a joke about developers",Agent 返回 "Why do developers prefer dark mode? Because light attracts bugs!",验证了端到端链路已打通。
源码级实现解析:Skill 后端是如何工作的
1. 配置层:环境变量即契约(config.py)
config.py 定义了全部运行时配置,是理解 Skill 行为的关键入口:
HOST = os.getenv("HOST", "localhost") PORT = int(os.getenv("PORT", 8080)) APP_ID = os.getenv("BOT_APP_ID") # Bot 应用注册 ID APP_PASSWORD = os.getenv("BOT_PASSWORD") # Bot 应用注册 client secret APP_TENANTID = os.getenv("BOT_TENANT_ID") # 租户 ID APP_TYPE = os.getenv("APP_TYPE", "singletenant") # 默认单租户 # Required for Copilot Skill ALLOWED_CALLERS = os.getenv("ALLOWED_CALLERS", ["*"]) # 允许调用本 Skill 的父 Bot ID 列表,或 "*" 放行全部 AZURE_OPENAI_CHAT_DEPLOYMENT_NAME = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME") AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT") AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION")validate()方法在模块加载时强制执行配置校验:HOST/PORT、APP_ID/APP_PASSWORD/APP_TENANTID、ALLOWED_CALLERS任一缺失都会直接抛异常,避免带着错误配置上线。
三个值得深挖的配置语义:
APP_TYPE(默认singletenant):声明 Bot 的身份验证模式,单租户模式下令牌校验范围被限定在APP_TENANTID指定的租户内,是多租户安全的第一道闸门;ALLOWED_CALLERS:声明允许调用本 Skill 的父 Bot(即 Copilot Studio 一侧的 Agent)App ID 白名单,默认["*"]表示放行所有 Agent。生产环境务必改为具体的 App ID 列表(见下方 auth.py 的校验逻辑);- Azure OpenAI 三件套:
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME、AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_VERSION会被AzureChatCompletion消费,由 azd 部署时注入。
2. 入口层:aiohttp 应用与 Manifest 动态生成(app.py)
app.py 实现两个端点:
/api/messages:读取请求 JSON 后直接交给bot.process(req)。原文档与代码注释都强调了一个 Skill 特有约束:在 Skill 上下文中,必须把响应作为请求的返回值返回给 Copilot Studio(而在 Teams 等其他渠道中,Activity 是由 Bot Framework 主动推送的);/manifest:读取copilot-studio.manifest.json模板,用容器应用的实际 FQDN 与 Bot App ID 做字符串替换后返回:
fqdn = f"https://{os.getenv('CONTAINER_APP_NAME')}.{os.getenv('CONTAINER_APP_ENV_DNS_SUFFIX')}/api/messages" manifest = manifest.replace("__botEndpoint", fqdn).replace("__botAppId", config.APP_ID)即 Manifest 中endpointUrl最终指向https://<容器应用FQDN>/api/messages,msAppId指向 Bot 的 App ID。
3. 清单层:Copilot Studio Skill Manifest(copilot-studio.manifest.json)
copilot-studio.manifest.json 是 Copilot Studio 识别 Skill 的"名片",核心字段包括:
$schema:Bot Framework Skill Manifest v2.2 的 JSON Schema;$id/name/version/description/publisherName:Skill 的标识与描述信息;endpoints:声明BotFrameworkV3协议的默认端点,endpointUrl与msAppId为__botEndpoint/__botAppId占位符,由/manifest端点动态替换;activities:声明本 Skill 支持message类型的 Activity("Invoke Semantic Kernel skill"),这是 Copilot Studio 与 Skill 交互的唯一活动类型。
4. 对话层:Teams Application + Semantic Kernel Agent(bot.py 与 sk_conversation_agent.py)
bot.py 使用teams-ai的Application[TurnState]构建 Bot 应用:
bot = ApplicationTurnState, adapter=AdapterWithErrorHandler(ConfigurationBotFrameworkAuthentication(config, auth_configuration=auth)), ) )注意代码注释中的关键提醒:adapter参数不能传 dict,必须传一个带APP_ID、APP_PASSWORD、APP_TENANTID属性的类实例——这里的config对象恰好满足这一契约。
对话逻辑通过两个事件装饰器挂载:
@bot.before_turn async def setup_chathistory(context, state): chat_history = state.conversation.get("chat_history") or ChatHistory() state.conversation["chat_history"] = chat_history return state @bot.activity("message") async def on_message(context, state): user_message = context.activity.text chat_history.add_user_message(user_message) sk_response = await agent.get_response(history=chat_history, user_input=user_message) state.conversation["chat_history"] = chat_history await context.send_activity(MessageFactory.text(sk_response, input_hint=InputHints.ignoring_input)) end = Activity.create_end_of_conversation_activity() end.code = EndOfConversationCodes.completed_successfully await context.send_activity(end) return True几个要点:
- 多轮记忆:利用
TurnState的 conversation 级存储,把 Semantic Kernel 的ChatHistory持久化在会话状态中,实现跨轮次的上下文连续; - SK 对话后端:调用
agent.get_response(history=chat_history, user_input=user_message)。代码注释标明该 API 需要semantic-kernel>=1.22.0(见 requirements.txt); - Skill 协议收尾:响应后必须发送
EndOfConversation活动(completed_successfully)告知 Copilot Studio 本轮对话结束。注释也提醒:真实 Skill 中应在用户完成目标任务后再发送,而非像示例这样每轮都立即结束。
sk_conversation_agent.py 则是最简的 Semantic Kernel Agent 构造:
from azure.identity import AzureCliCredential from semantic_kernel.agents import ChatCompletionAgent from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion agent = ChatCompletionAgent( service=AzureChatCompletion(credential=AzureCliCredential()), name="ChatAgent", instructions="You invent jokes to have a fun conversation with the user.", )- 使用
ChatCompletionAgent(Semantic Kernel 的对话补全 Agent 抽象); - 连接器为
AzureChatCompletion,凭证采用AzureCliCredential(在 Azure 环境内亦可替换为工作负载身份等托管身份凭证); instructions即系统提示词,是 SK Agent 的行为底座,可自由替换为你的业务指令。
5. 安全层:调用方校验与错误处理(auth.py 与 adapter.py)
调用方白名单校验(auth.py):auth.py 实现AllowedCallersClaimsValidator,其核心逻辑:
- 将
ALLOWED_CALLERS配置转为frozenset; - 在
claims_validator中,当白名单不含"*"且请求带有 Skill 声明(SkillValidation.is_skill_claim(claims))时,从令牌中提取appId,若不在白名单则抛出PermissionError; - 该 validator 通过
AuthenticationConfiguration注入 Bot 认证流程(见 bot.py 中auth = AuthenticationConfiguration(tenant_id=..., claims_validator=...))。
代码注释明确指出:不添加 claims validator 会导致 Skill 运行报错——这是 Skill 模式与普通 Bot 的关键差异之一。
错误处理适配器(adapter.py):adapter.py 的AdapterWithErrorHandler继承CloudAdapter,在 turn 异常时:
- 向用户发送友好错误消息("The skill encountered an error or bug.");
- 发送 trace activity 供 Bot Framework Emulator 排查;
- 向 Skill 调用方(父 Bot)发送
EndOfConversation活动,code 为SkillError,text 携带异常信息,让调用方决定后续处理。
这保证了 Skill 异常不会导致父 Agent 挂起,是生产化部署的必要加固。
关键注意事项与生产化建议
综合原文档与源码,以下坑点与建议值得重点记录:
- 框架选择受限:Python 的 Bot Framework SDK 目前仅支持 aiohttp,不要尝试用 FastAPI/Flask 直接替换;
- Skill 必须同步返回响应:
/api/messages的 HTTP 响应就是给 Copilot Studio 的回复,这与 Teams 等主动推送渠道的编程模型不同; - 必须实现 claims validator:
ALLOWED_CALLERS白名单校验是 Skill 安全的基础,生产环境不要保留"*"通配; - 记得发送 EndOfConversation:一轮对话结束要显式发送该活动(成功用
completed_successfully,异常用SkillError),否则调用方可能一直等待; homeUrl必须指向已部署 API:否则 Copilot Studio 无法回调你的 Skill;- 配置即契约:
BOT_APP_ID、BOT_PASSWORD、BOT_TENANT_ID等环境变量名是 Bot Framework 与 azd 参数映射的固定约定,config.py 中特意注释 "DO NOT CHANGE THIS KEYS!!"; - 模型参数可调:通过 azd 参数可指定 Azure OpenAI 模型(默认
gpt-4o)与 API 版本(默认2024-08-01-preview),需与你的 Azure OpenAI 资源实际部署保持一致。
总结
本示例展示了一条完整且可复用的"低代码 + Pro-Code"混合扩展路径:Copilot Studio 负责用户体验与流程编排,Semantic Kernel 负责 LLM 驱动的对话智能。你可以在此基础上将sk_conversation_agent.py中的简单笑话 Agent 替换为接入插件(Plugin)、函数调用(Function Calling)、向量检索(RAG)等能力的完整业务 Agent,并通过ALLOWED_CALLERS白名单、错误处理适配器与托管身份认证将 Skill 推向生产环境。示例全部源码位于 python/samples/demos/copilot_studio_skill,基础设施模板集中在 infra 目录,可作为你落地同类集成的起点。
【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考