背景
近期在做的一个项目,增加了一些AI相关的模块。在开发过程中,使用了微软最新的智能体开发框架,SemanticKernel Agent Framework(learn.microsoft.com/en-us/seman...)
算是有一点不成熟的经验吧,想拿出来聊聊,首先SemanticKernel(以下简称SK)是微软开源的智能体开发框架,它的定位就是企业级的AI开发框架,致力于将智能业务和本地业务结合,开发出智能化的功能模块。
更多内容大家可以查看上面给出的官方文档,这里不再赘述。
对了,笔者曾经也写过基于SK的相关博客,欢迎点击查看:
配置服务
SK框架支持兼容OpenAI接口风格的模型,所以大部分国内的模型都是可以使用的,我这里封装了3个,分别是Kimi,Qwen和DeepSeek,其他看情况修改即可。
基本代码如下,这里返回值是一个Kernel,所以注意编写的时候引入合适的命名空间,博客篇幅有限我就不灌太多代码了。
csharp
public static Kernel CreateKernel(IConfiguration configuration)
{
var builder = Kernel.CreateBuilder();
var provider = configuration["AI:Provider"]!;
var (model, key, endpoint) = provider.ToLower() switch
{
"qwen" => (
configuration["AI:Qwen:Model"]!,
configuration["AI:Qwen:ApiKey"]!,
configuration["AI:Qwen:Endpoint"]!),
"kimi" => (
configuration["AI:Kimi:Model"]!,
configuration["AI:Kimi:ApiKey"]!,
configuration["AI:Kimi:Endpoint"]!),
"deepseek" => (
configuration["AI:DeepSeek:Model"]!,
configuration["AI:DeepSeek:ApiKey"]!,
configuration["AI:DeepSeek:Endpoint"]!),
_ => throw new ArgumentException($"暂时还不支持的AI提供商: {provider}")
};
ConsoleHelper.WriteLine($"正在使用模型:{model},{DateTime.Now}", ConsoleColor.Cyan);
// 添加 OpenAI 兼容的模型服务
builder.AddOpenAIChatCompletion(
modelId: model,
apiKey: key,
endpoint: new Uri(endpoint));
return builder.Build();
}
即便我们在国内使用GPT,Gemini,Claude等国际模型有困难,但国内的AI行业发展的也不容小觑,因此完全没必要为此苦恼,我们可以使用国内的大模型来做我们的业务底座,性能,效果甚至可能更好。
编写插件
配置完成后,我们可以先编写服务插件,当然这个步骤不是绝对的,看个人习惯和项目情况。代码实际上比较简单,按照你项目的架构风格编写业务代码即可,需要注意的是,插件方法的头部要增加一些标记,让SK框架可以认出这些插件,看一下案例代码
csharp
[KernelFunction("query_by_project_id")] // 👈--注意这个要使用蛇形命名法
[Description("通过项目ID查询项目详细信息")] // 👈--这个说明也很重要
public async Task<string> QueryByProjectIdAsync(
[Description("项目ID,如 720540936868229")] long projectId)
{
ConsoleHelper.WriteLine($"=== 智能体命中插件,通过项目ID查询项目信息{DateTime.Now} ===");
ConsoleHelper.WriteLine($"项目ID: {projectId}");
try
{
if (_decProjectProvider != null)
{
var result = await _decProjectProvider.GetDecProjectDetail(projectId);
if (!result.IsSuccess || result.Value == null)
{
return $"未找到项目ID为 {projectId} 的项目信息。";
}
return FormatProjectInfo(result.Value);
}
return "记录不存在";
}
catch (Exception ex)
{
ConsoleHelper.WriteLine($"项目ID查询失败: {ex.Message}");
return $"查询失败: {ex.Message}";
}
}
上面代码比较简单,就是一次简单的业务信息查询,但需要说明的是,方法体头部的KernelFunction标记,建议使用 snake_case(蛇形命名法)命名函数,以提高与主流大模型工具调用协议的兼容性。还一个要注意的是需要编写方法说明和参数说明,这个在模型执行工具调用的时候也很重要,算是明确的提示词吧,这些就当个规范记住就好。
定义智能体
这里智能体的定义,也是根据实际情况,看如何操作更加方便,我这里是定义了一个基类,在基类中先定好了智能体要定义的属性,方法等,然后所有派生类都要集成这个基类,并实现独特的智能体角色。
因此我这里定义专属智能体的代码就非常简单
csharp
public class MyBusinessAgent : ModernAgentBase
{
public override string Name => "MyBusinessAgent";
public override string Description => "业务智能体";
protected override string Instructions => "你是专业的业务助手...";
public MyBusinessAgent(Kernel kernel) : base(kernel) { }
}
比如刚刚的查询插件,我这里在这个基类的先定下,可以这样定义(部分代码)
csharp
public class ModernProjectQueryAgent : ModernAgentBase
{
private readonly ProjectQueryPlugin _projectQueryPlugin;
public override string Name => "ModernProjectQueryAgent";
public override string Description => "现代化项目信息查询智能体,支持通过项目ID查询项目详情";
protected override string Instructions => "你是专业的项目信息查询助手,能够根据用户提供的项目ID查询项目信息。你拥有 query_by_project_id 查询工具,支持6位以上数字的项目ID查询。请始终以专业、高效的方式为用户提供准确的项目查询服务。";
// 注意这里的几个参数,对智能体的调用结果十分重要,大家可以自行了解一下
protected override KernelArguments DefaultArguments => CreateStandardArguments(
temperature: 0.3,
topP: 0.8,
maxTokens: 1500,
enableFunctionCalling: true
);
public ModernProjectQueryAgent(Kernel kernel, IServiceProvider serviceProvider) : base(kernel)
{
_projectQueryPlugin = new ProjectQueryPlugin(serviceProvider);
}
/// <summary>
/// 配置智能体插件
/// </summary>
protected override void ConfigurePlugins(ChatCompletionAgent agent)
{
AddPlugin(_projectQueryPlugin, "ProjectQuery");
ConsoleHelper.WriteLine($"=== {Name} 插件配置完成 ===",ConsoleColor.Green);
ConsoleHelper.WriteLine($"已加载插件数量: {_kernel.Plugins.Count}",ConsoleColor.Green);
foreach (var plugin in _kernel.Plugins)
{
ConsoleHelper.WriteLine($"插件: {plugin.Name}", ConsoleColor.Green);
foreach (var function in plugin)
{
ConsoleHelper.WriteLine($" - 函数: {function.Name} - {function.Description}", ConsoleColor.Green);
}
}
}
/// <summary>
/// 智能查询项目信息
/// </summary>
public async Task<string> SmartQueryAsync(string userInput, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(userInput))
{
return GetUsageHelp();
}
try
{
var chatHistory = new ChatHistory();
var queryPrompt = $"请分析以下用户输入并执行相应的项目查询:\n{userInput}";
var response = await GetResponseAsync(queryPrompt, chatHistory, cancellationToken);
var result = response.Content ?? "查询失败,未获取到有效响应";
return result;
}
catch (Exception ex)
{
return FormatErrorResponse(userInput, ex.Message);
}
}
/// <summary>
/// 执行智能体对话(获取单一响应)
/// </summary>
public virtual async Task<ChatMessageContent> GetResponseAsync(
string userMessage,
ChatHistory? chatHistory = null,
CancellationToken cancellationToken = default)
{
// 如果没有提供历史记录,创建新的
chatHistory ??= new ChatHistory();
// 添加用户消息
chatHistory.AddUserMessage(userMessage);
// 获取响应
ChatMessageContent? lastResponse = null;
await foreach (var response in InvokeAsync(chatHistory, cancellationToken))
{
lastResponse = response;
// 将助手响应添加到历史记录
chatHistory.Add(response);
}
return lastResponse ?? throw new InvalidOperationException("智能体未返回有效响应");
}
}
// 关于基类的实现,这里也简单列一下
public abstract class ModernAgentBase
{
protected readonly Kernel _kernel;
private ChatCompletionAgent? _agent;
public abstract string Name { get; }
public abstract string Description { get; }
protected abstract string Instructions { get; }
protected virtual KernelArguments DefaultArguments => new();
protected ModernAgentBase(Kernel kernel)
{
_kernel = kernel;
InitializeAgent();
}
private void InitializeAgent()
{
var agentBuilder = new ChatCompletionAgentBuilder()
.WithKernel(_kernel)
.WithName(Name)
.WithDescription(Description)
.WithInstructions(Instructions)
.WithArguments(DefaultArguments);
// 允许子类扩展插件
ConfigurePlugins(agentBuilder);
_agent = agentBuilder.Build();
}
/// <summary>
/// 子类重写此方法以注册插件(工具)
/// </summary>
protected virtual void ConfigurePlugins(ChatCompletionAgentBuilder builder)
{
// 默认不注册插件,由子类实现
}
/// <summary>
/// 向 Kernel 注册插件(供子类调用)
/// </summary>
protected void AddPlugin(object pluginInstance, string pluginName)
{
_kernel.Plugins.AddFromObject(pluginInstance, pluginName);
}
/// <summary>
/// 执行对话并返回最终响应(支持多轮工具调用)
/// </summary>
public async Task<ChatMessageContent> GetResponseAsync(
string userMessage,
ChatHistory? chatHistory = null,
CancellationToken cancellationToken = default)
{
chatHistory ??= new ChatHistory();
chatHistory.AddUserMessage(userMessage);
ChatMessageContent? lastMessage = null;
await foreach (var message in _agent!.InvokeAsync(chatHistory, cancellationToken))
{
lastMessage = message;
chatHistory.Add(message);
}
return lastMessage ?? throw new InvalidOperationException("出现异常");
}
}
注意啊,受篇幅限制,我不能把全部的定义都放上来,整个智能体的定义流程基本就是,定义基本属性-->注入插件-->调用服务.
上面这个例子主要实在ConfigurePlugins这个重载方法里,注入了需要的插件。实际上这么看智能体的定义也有想想我们传统的分层业务,定义仓储,开放接口,然后在宿主项目中引入,最后在开发成webapi或者页面服务,开发思路都是一样的,也是要保持单一职责,所以如果我们的智能体模块要好用,就需要多个智能体,多个插件,互相不干扰,也要保持智能体角色的单一性。
创建智能服务
前面的工作完成后,可以创建一个智能服务,来统一的接受宿主层发过来的请求,比如我们就把场景限定在一个对话框,但这个对话框不仅仅是可以聊天,还能智能识别你的聊天意图,自动的去调用不同角色的智能体来实现本地化的服务
csharp
public async Task<string> SendMessageAsync(
string userInput,
string? sessionId = null,
CancellationToken ct = default)
{
try
{
// 前置业务省略
// ...
// 智能选择智能体
var selectedAgent = SelectAppropriateAgent(userInput);
ConsoleHelper.WriteLine($"选择的智能体: {selectedAgent}");
string response;
if (selectedAgent == AgentType.ProjectQuery)
{
// 使用项目查询智能体
response = await _projectQueryAgent.SmartQueryAsync(finalUserInput, ct);
}
else
{
// 使用客服智能体,提供历史上下文
var contextInfo = ExtractContextInfo(history, referencedContent);
response = await _customerSupportAgent.GetSmartResponseAsync(
finalUserInput,
history,
contextInfo,
ct);
}
// 6. 只有生成了有效回复才继续存储到Redis
if (!string.IsNullOrWhiteSpace(response))
{
// 保存聊天历史业务,略
}
return response;
}
catch (Exception ex)
{
ConsoleHelper.WriteLine($"处理消息失败: {ex.Message}");
return "抱歉,系统暂时无法响应,请稍后再试。";
}
}
注入容器
前面的工作完成后,就可以在宿主系统里注入服务了,这部分代码很
csharp
var kernel = ModernAgentConfiguration.CreateKernel(configuration);
services.AddSingleton(kernel);
// 注册智能体
services.AddSingleton<ModernProjectQueryAgent>();
services.AddSingleton<ModernCustomerSupportAgent>();
// 注册统一服务
services.AddSingleton<ICompatibleAgentService, ModernAgentService>();
测试效果
宿主系统完成服务注入后,就可以看一下效果怎么样了,我这边是在BlazorServer的项目里创建了一个对话框,代码就不贴了,看下执行效果吧

可以看到,智能体和常见的聊天机器人还是有区别,我们可以在定义的时候限制它的聊天能力,让它不要回答与角色无关的内容。然后问到业务相关的事情时,又能准确的识别我们的意图,给出正确的回复
这里,我打印了一些日志,仅供参考,如下

好了,至此,一个智能体就顺利集成到我们原有的项目里啦,收工!
结束语
我觉得现阶段开发的大部分的传统项目,都应该尽可能的去集成一个AI模块,即便你确实没有明确的需求也要去尝试一下。
记得前阵子看过微软CEO的一个演讲,他提到一个观点,大概是说,我们开发的软件,由于最终使用者都是人,所以需要投入大量精力在界面,交互等工作上面,即便是服务端的开发有时候也需要顺应这种交互,而在AI的时代,仅仅通过自然语言,AI就可以帮我们更高效的完成很多复杂的任务,而在将任务交给AI的时候,是不需要太多交互和界面设计的,我们只需要等一个结果就好。
当然这是一个愿景,但从AI的发展速度来看,距离这个愿景的实现可能越来越近了,作为开发者,我们也应该积极的顺应这个时代,从认知到实践,都应该积极的做出改变。可能你的项目真的不需要一个AI模块,但也要为这种即将到来的风暴变革做好准备。