基于SemanticKernel开发一个业务智能体

背景

近期在做的一个项目,增加了一些AI相关的模块。在开发过程中,使用了微软最新的智能体开发框架,SemanticKernel Agent Framework(learn.microsoft.com/en-us/seman...

算是有一点不成熟的经验吧,想拿出来聊聊,首先SemanticKernel(以下简称SK)是微软开源的智能体开发框架,它的定位就是企业级的AI开发框架,致力于将智能业务和本地业务结合,开发出智能化的功能模块。

更多内容大家可以查看上面给出的官方文档,这里不再赘述。

对了,笔者曾经也写过基于SK的相关博客,欢迎点击查看:

mp.weixin.qq.com/s/FTXTZUWwU...

配置服务

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模块,但也要为这种即将到来的风暴变革做好准备。

相关推荐
JOEH6018 分钟前
Java 后端开发中的内存泄漏问题:90% 开发者都会踩的 5 个坑
后端
_野猪佩奇_牛马版18 分钟前
多智能体协作 - 使用 LangGraph 子图实现
后端
JOEH6019 分钟前
为什么你的数据库连接总超时?99% 的 Java 程序员都踩过这 5 个坑
后端
后端不背锅21 分钟前
对外接口设计完全指南:安全、高性能、可演进
后端
IT小崔36 分钟前
SqlSugar 使用教程
数据库·后端
Oneslide38 分钟前
Docker Compose 重启 RabbitMQ 数据丢失?
后端
架构师沉默39 分钟前
为什么国外程序员都写独立博客,而国内都在公众号?
java·后端·架构
开心就好202543 分钟前
Win11 抓包工具怎么选?网页请求与设备流量抓取
后端·ios
带刺的坐椅1 小时前
SolonCode v2026.4.1 发布(比 ClaudeCode 简约的编程智能体)
java·ai·llm·agent·solon-ai·claudecode·soloncode
爱丽_1 小时前
Spring 事务:传播行为、失效场景、回滚规则与最佳实践
java·后端·spring