用 C# 扩展 Dynamics 365 Copilot:自定义插件与场景

Dynamics 365 Copilot 作为基于 AI 的智能助手,为企业用户提供了自动化流程、智能分析和自然语言交互的能力,但通用功能往往无法满足特定行业或企业的定制化需求。本文将详细介绍如何通过 C# 编写自定义插件,扩展 Dynamics 365 Copilot 的能力,并结合实际业务场景实现定制化 AI 交互。

一、核心基础:Dynamics 365 Copilot 扩展架构

Dynamics 365 Copilot 的扩展主要依赖于 Power Platform 插件框架Copilot Studio 的自定义连接器,核心技术栈包括:

  • C# (.NET Framework 4.8 或 .NET 6+):编写业务逻辑插件
  • Dynamics 365 SDK:与 Dataverse 数据交互
  • Azure OpenAI / 自定义 LLM 接口:扩展 AI 生成能力
  • Power Platform 注册工具:部署插件到 Dynamics 365 环境

扩展的核心逻辑是:通过 C# 插件拦截 Copilot 的交互请求,注入自定义业务规则,调用外部 AI 服务或 Dataverse 数据,最终返回定制化的 AI 响应。

二、环境准备

  1. 安装必备工具:
    • Visual Studio 2022(带 .NET 桌面开发和 Azure 开发工作负载)
    • Dynamics 365 SDK(最新版本)
    • Power Platform CLI(用于插件注册)
    • 有效的 Dynamics 365 环境(带 Copilot 启用权限)

创建 Dynamics 365 插件项目:

# 通过 Power Platform CLI 创建插件项目 pac plugin init --name CopilotCustomPlugin --template ClassLibrary 

三、核心示例:自定义客户服务 Copilot 插件

场景描述

某零售企业需要定制 Copilot 能力:当用户通过 Copilot 查询客户订单状态时,Copilot 能自动从 Dataverse 中读取该客户的订单数据,并结合 AI 生成结构化、个性化的回复(而非通用话术)。

核心源代码实现

1. 插件核心类(CustomerOrderCopilotPlugin.cs)
using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; using System; using System.Linq; // 插件入口类,继承 IPlugin 接口 public class CustomerOrderCopilotPlugin : IPlugin { // 插件执行方法(核心入口) public void Execute(IServiceProvider serviceProvider) { // 1. 获取插件执行上下文 IPluginExecutionContext context = (IPluginExecutionContext) serviceProvider.GetService(typeof(IPluginExecutionContext)); // 2. 获取组织服务(用于操作 Dataverse 数据) IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory) serviceProvider.GetService(typeof(IOrganizationServiceFactory)); IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId); try { // 3. 从 Copilot 请求中提取参数(客户名称/ID、查询类型) string customerId = context.InputParameters["CustomerId"]?.ToString(); string queryType = context.InputParameters["QueryType"]?.ToString(); // 校验必填参数 if (string.IsNullOrEmpty(customerId) || queryType != "OrderStatus") { throw new InvalidPluginExecutionException("无效的查询参数:请提供客户ID并选择订单状态查询"); } // 4. 从 Dataverse 查询客户订单数据 var orderData = GetCustomerOrders(service, customerId); // 5. 构建定制化的 Copilot 响应内容 string copilotResponse = BuildCopilotResponse(orderData); // 6. 将响应写入输出参数,返回给 Copilot context.OutputParameters["CopilotResponse"] = copilotResponse; } catch (Exception ex) { // 插件异常处理(Dynamics 365 标准规范) throw new InvalidPluginExecutionException("客户订单查询插件执行失败:" + ex.Message, ex); } } /// <summary> /// 从 Dataverse 查询指定客户的订单数据 /// </summary> /// <param name="service">Dynamics 365 组织服务</param> /// <param name="customerId">客户GUID</param> /// <returns>订单数据集合</returns> private EntityCollection GetCustomerOrders(IOrganizationService service, string customerId) { // 构建查询表达式(查询 salesorder 实体) QueryExpression query = new QueryExpression("salesorder") { ColumnSet = new ColumnSet("name", "salesorderid", "statuscode", "totalamount", "createdon"), Criteria = new FilterExpression { Conditions = { new ConditionExpression("customerid", ConditionOperator.Equal, new Guid(customerId)) } }, // 按创建时间降序排列 Orders = { new OrderExpression("createdon", OrderType.Descending) } }; // 执行查询并返回结果 return service.RetrieveMultiple(query); } /// <summary> /// 构建 Copilot 定制化响应内容 /// </summary> /// <param name="orders">订单数据</param> /// <returns>结构化的 AI 回复文本</returns> private string BuildCopilotResponse(EntityCollection orders) { if (orders.Entities.Count == 0) { return "未查询到该客户的任何订单记录。"; } // 构建结构化回复(结合业务规则定制话术) var responseBuilder = new System.Text.StringBuilder(); responseBuilder.AppendLine($"为您查询到 {orders.Entities.Count} 条订单记录:\n"); foreach (var order in orders.Entities) { string orderName = order["name"].ToString(); string orderStatus = GetOrderStatusText(order.GetAttributeValue<OptionSetValue>("statuscode").Value); decimal totalAmount = order.GetAttributeValue<Money>("totalamount").Value; DateTime createDate = order.GetAttributeValue<DateTime>("createdon"); responseBuilder.AppendLine($"订单编号:{orderName}"); responseBuilder.AppendLine($"状态:{orderStatus}"); responseBuilder.AppendLine($"金额:{totalAmount:C}"); responseBuilder.AppendLine($"创建时间:{createDate:yyyy-MM-dd HH:mm}\n"); } responseBuilder.AppendLine("如需了解某笔订单的详细进度,可直接说出订单编号,我会为您进一步查询。"); return responseBuilder.ToString(); } /// <summary> /// 将订单状态码转换为易读文本(适配中文场景) /// </summary> /// <param name="statusCode">订单状态码</param> /// <returns>状态文本</returns> private string GetOrderStatusText(int statusCode) { return statusCode switch { 1 => "草稿", 2 => "已提交", 3 => "已批准", 4 => "已发货", 5 => "已完成", 6 => "已取消", _ => "未知状态" }; } } 
2. 插件注册配置(PluginRegistration.cs)
using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Client; using System; using System.ServiceModel; // 插件注册辅助类(用于Power Platform CLI部署) public class PluginRegistrationHelper { /// <summary> /// 注册插件到 Dynamics 365 环境 /// </summary> /// <param name="serviceUri">Dynamics 365 服务地址</param> /// <param name="username">管理员账号</param> /// <param name="password">密码</param> /// <param name="domain">域名(如适用)</param> public static void RegisterPlugin(Uri serviceUri, string username, string password, string domain) { // 构建认证凭据 ClientCredentials credentials = new ClientCredentials(); credentials.UserName.UserName = username; credentials.UserName.Password = password; // 创建组织服务代理 using (OrganizationServiceProxy serviceProxy = new OrganizationServiceProxy( serviceUri, null, credentials, null)) { serviceProxy.EnableProxyTypes(); // 注册插件步骤(绑定到 Copilot 自定义消息) CreatePluginStep(serviceProxy, "new_CopilotCustomerOrderQuery", "CustomerOrderCopilotPlugin", "Microsoft.Crm.Sdk.Messages.ExecutePluginRequest"); } } private static void CreatePluginStep(IOrganizationService service, string messageName, string pluginTypeName, string requestType) { // 插件步骤创建逻辑(简化版,实际部署建议使用Power Platform CLI) Entity pluginStep = new Entity("sdkmessageprocessingstep") { ["name"] = $"Copilot_{messageName}_Step", ["sdkmessageid"] = new EntityReference("sdkmessage", new Guid("替换为实际消息ID")), ["pluginassemblyid"] = new EntityReference("pluginassembly", new Guid("替换为程序集ID")), ["plugintypeid"] = new EntityReference("plugintype", new Guid("替换为插件类型ID")), ["stage"] = 40, // 操作后执行 ["mode"] = 0, // 同步模式 ["rank"] = 1 }; service.Create(pluginStep); } } 

四、插件部署与集成到 Copilot

1. 插件编译与签名

  • 在 Visual Studio 中编译项目,生成强名称签名的 DLL(右键项目 → 属性 → 签名 → 选择强名称密钥文件)。

使用 Power Platform CLI 将 DLL 注册到 Dynamics 365 环境:

# 登录 Dynamics 365 环境 pac auth create --url https://your-org.crm.dynamics.com # 注册插件程序集 pac plugin register --assembly "bin\Debug\CopilotCustomPlugin.dll" --serviceprincipalid "你的SPN ID" --serviceprincipalsecret "你的SPN密钥" 

2. Copilot Studio 集成

  1. 打开 Copilot Studio,创建新的自定义主题 “客户订单查询”;
  2. 添加 “调用自定义插件” 操作,绑定上述 C# 插件;
  3. 配置输入参数(客户 ID)和输出参数(Copilot 响应);
  4. 发布自定义主题,关联到 Dynamics 365 应用。

五、进阶场景:集成外部 LLM 增强 Copilot 能力

如果需要进一步增强 AI 生成能力,可以在插件中调用 Azure OpenAI API,结合业务数据生成更自然的回复:

// 新增方法:调用 Azure OpenAI 生成定制化回复 private string GenerateAiResponseWithOpenAI(string orderData) { var openAIClient = new Azure.AI.OpenAI.OpenAIClient( new Uri("https://your-openai-resource.openai.azure.com/"), new Azure.AzureKeyCredential("你的 OpenAI API 密钥")); var chatCompletionsOptions = new Azure.AI.OpenAI.ChatCompletionsOptions() { DeploymentName = "gpt-35-turbo", Messages = { new Azure.AI.OpenAI.ChatMessage(Azure.AI.OpenAI.ChatRole.System, "你是零售企业的客户服务助手,需基于提供的订单数据生成友好、简洁的中文回复,避免技术术语。"), new Azure.AI.OpenAI.ChatMessage(Azure.AI.OpenAI.ChatRole.User, $"基于以下订单数据生成回复:{orderData}") }, Temperature = 0.7f, MaxTokens = 500 }; var response = openAIClient.GetChatCompletions(chatCompletionsOptions); return response.Value.Choices[0].Message.Content; } 

六、关键注意事项

  1. 性能优化:插件执行超时时间默认 2 分钟,需优化 Dataverse 查询(使用分页、索引);
  2. 权限控制:插件以当前用户身份执行,需为用户分配 salesorder 实体的读取权限;
  3. 异常处理:必须捕获并抛出 InvalidPluginExecutionException,确保错误信息能返回给 Copilot;
  4. 版本管理:插件更新需通过 Power Platform CLI 重新注册,建议保留历史版本。

总结

  1. Dynamics 365 Copilot 可通过 C# 插件扩展,核心是实现 IPlugin 接口并拦截 Copilot 交互请求,结合 Dataverse 数据定制 AI 响应;
  2. 自定义插件的核心流程为:提取请求参数 → 查询业务数据 → 构建定制化响应 → 返回给 Copilot;
  3. 进阶场景可集成 Azure OpenAI 等外部 LLM,进一步增强 Copilot 的自然语言生成能力,同时需注意插件的性能、权限和异常处理。

Read more

城市热岛效应研究:GLM-4.6V-Flash-WEB分析红外遥感数据

城市热岛效应研究:GLM-4.6V-Flash-WEB分析红外遥感数据 在一座超大城市凌晨三点的卫星图上,灯火通明的中心商务区像一块持续发烫的烙铁,而郊区的公园与农田则呈现出深蓝的冷色调——这不是简单的灯光分布差异,而是真实存在的“城市热岛”脉搏。随着全球近60%的人口聚集于城市,这种由人类活动重塑地表能量平衡的现象,已不再只是气候学论文中的术语,而是直接影响居民健康、能源消耗与极端天气应对能力的现实挑战。 传统的气象站网络虽然能提供精确的气温记录,但其站点间距动辄数公里,在复杂的城市肌理中如同盲人摸象。我们真正需要的,是一双既能看清每一栋楼宇热辐射细节,又能理解“为什么这里更热”的智能之眼。近年来,多光谱遥感技术的发展让高分辨率地表温度反演成为可能,但面对每天从太空传回的TB级图像数据,人工解译早已不堪重负。即便是自动化算法,也常困于“看得见温度,读不懂城市”的窘境:它能圈出高温斑块,却无法判断那是数据中心散热、交通拥堵积热,还是缺乏绿化的老旧社区。 正是在这种背景下,GLM-4.6V-Flash-WEB 的出现显得尤为及时。这款由智谱AI推出的轻量级多模态模型,并非简单地将大

Qwen3-32B开源部署新范式:Clawdbot提供CLI命令行工具+Web UI双操作入口

Qwen3-32B开源部署新范式:Clawdbot提供CLI命令行工具+Web UI双操作入口 1. 为什么你需要一个“更轻、更稳、更顺手”的Qwen3-32B用法? 你是不是也遇到过这些情况? 下载完Qwen3-32B模型,光是装Ollama、拉镜像、配环境变量就折腾掉一整个下午;好不容易跑起来,发现每次调用都要写curl命令或改Python脚本;想给同事演示,还得临时搭个前端页面——结果UI丑、响应慢、连历史对话都存不住。 Clawdbot不是又一个“封装一层API”的工具。它把Qwen3-32B真正变成了你电脑里一个开箱即用的本地AI伙伴: * 不用碰Docker Compose文件,不用记端口映射规则,一条命令就能启动; * 命令行里直接聊天、批量提问、导出记录,像用ls、cat一样自然; * Web界面干净清爽,支持多轮对话、上下文记忆、自定义系统提示,打开浏览器就能用; * 所有交互都走本地,模型不上传、数据不出设备、请求不经过第三方服务器。 这不是“能跑就行”的部署,而是为真实使用场景打磨出来的双入口工作流——CLI适合开发者快速验证和集成,Web

从零上手PaddleOCR-VL-WEB:打造高精度多语言OCR应用

从零上手PaddleOCR-VL-WEB:打造高精度多语言OCR应用 1. 引言:为什么你需要一个强大的OCR工具? 你有没有遇到过这样的情况:手头有一堆PDF合同、扫描的教材、带表格的财报,想从中提取信息,却只能手动复制粘贴?更别提那些包含公式、图表、多栏排版的复杂文档了——传统OCR工具要么识别错乱,要么干脆“视而不见”。 今天要介绍的 PaddleOCR-VL-WEB,正是为解决这些问题而生。它不是普通的OCR工具,而是百度开源的一款高精度、多语言、支持复杂文档结构识别的大模型级OCR系统。无论是中文报告、英文论文,还是日文说明书、阿拉伯语文件,它都能精准识别文本、表格、公式、图片等元素,并保持原始布局逻辑。 更重要的是,这个镜像已经为你预装好了所有依赖和可视化界面,无需配置环境、不用写复杂代码,一键启动就能用。无论你是开发者、数据分析师,还是企业用户,都能快速搭建属于自己的智能文档处理系统。 本文将带你: * 快速部署 PaddleOCR-VL-WEB 镜像 * 理解其核心能力与适用场景 * 实际体验网页端的OCR识别效果 * 掌握如何将其集成到实际业务中

Telegram bot & Mini-App开发实践---Telegram简单介绍与初始化小程序获取window.Telegram.WebApp对象并解析

Telegram bot & Mini-App开发实践---Telegram简单介绍与初始化小程序获取window.Telegram.WebApp对象并解析

➡️【好看的灵魂千篇一律,有趣的鲲志一百六七!】- 欢迎认识我~~作者:鲲志说(公众号、B站同名,视频号:鲲志说996)科技博主:极星会 星辉大使后端研发:java、go、python、TS,前电商、现web3主理人:COC杭州开发者社区主理人 、周周黑客松杭州主理人、AI爱好者: AI电影共创社杭州核心成员、阿里蚂蚁校友会技术AI分会副秘书长博客专家:阿里云专家博主;ZEEKLOG博客专家、后端领域新星创作者、内容合伙人 今天是2024年10月24日,又是一年1024程序员节。和往常一样,平淡的度过了一天,又和往常不一样,收到了人生第一束花花🌹值得纪念。就像两年前毅然决然的从电商行业进入一个零基础零认知的web3世界一样,都有第一次的刻骨铭心,选择了就勇敢的做下去,开花结果是期待,但过程也十分重要。也像2016年下半年第一次注册ZEEKLOG去检索问题的解决方案,经过多番查阅实践,终于解决;更像2017年9月27日我的第一篇ZEEKLOG博客文章潦草问世,当初不追求得到什么,只把ZEEKLOG文章当作是学习笔记,知识总结,一路写写停停,不知不觉间也悄然过去了7个年头,断然想不到博