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 响应。
二、环境准备
-
安装必备工具:
- Visual Studio 2022(带 .NET 桌面开发和 Azure 开发工作负载)
- Dynamics 365 SDK(最新版本)
- Power Platform CLI(用于插件注册)
- 有效的 Dynamics 365 环境(带 Copilot 启用权限)
-
创建 Dynamics 365 插件项目:
bash# 通过 Power Platform CLI 创建插件项目 pac plugin init --name CopilotCustomPlugin --template ClassLibrary
三、核心示例:自定义客户服务 Copilot 插件
场景描述
某零售企业需要定制 Copilot 能力:当用户通过 Copilot 查询客户订单状态时,Copilot 能自动从 Dataverse 中读取该客户的订单数据,并结合 AI 生成结构化、个性化的回复(而非通用话术)。
核心源代码实现
1. 插件核心类(CustomerOrderCopilotPlugin.cs)
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)
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 环境:
bash# 登录 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 集成
- 打开 Copilot Studio,创建新的自定义主题 "客户订单查询";
- 添加 "调用自定义插件" 操作,绑定上述 C# 插件;
- 配置输入参数(客户 ID)和输出参数(Copilot 响应);
- 发布自定义主题,关联到 Dynamics 365 应用。
五、进阶场景:集成外部 LLM 增强 Copilot 能力
如果需要进一步增强 AI 生成能力,可以在插件中调用 Azure OpenAI API,结合业务数据生成更自然的回复:
cs
// 新增方法:调用 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;
}
六、关键注意事项
- 性能优化:插件执行超时时间默认 2 分钟,需优化 Dataverse 查询(使用分页、索引);
- 权限控制:插件以当前用户身份执行,需为用户分配 salesorder 实体的读取权限;
- 异常处理 :必须捕获并抛出
InvalidPluginExecutionException,确保错误信息能返回给 Copilot; - 版本管理:插件更新需通过 Power Platform CLI 重新注册,建议保留历史版本。
总结
- Dynamics 365 Copilot 可通过 C# 插件扩展,核心是实现
IPlugin接口并拦截 Copilot 交互请求,结合 Dataverse 数据定制 AI 响应; - 自定义插件的核心流程为:提取请求参数 → 查询业务数据 → 构建定制化响应 → 返回给 Copilot;
- 进阶场景可集成 Azure OpenAI 等外部 LLM,进一步增强 Copilot 的自然语言生成能力,同时需注意插件的性能、权限和异常处理。