Loading

Semantic Kernel人工智能开发 - 第三章:Semantic Kernel插件系统详解——扩展AI能力的核心引擎

插件(Plugin)是Semantic Kernel框架的核心支柱,它充当着连接大语言模型的"智能"与传统业务逻辑的"确定性"之间的桥梁。本章将深入解析插件系统的设计原理、实现机制和实际应用,帮助您掌握扩展AI能力的关键技术。

1. 插件核心概念与设计哲学

1.1 什么是插件?

在Semantic Kernel中,插件是一组相关功能的集合,这些功能可以被大语言模型理解和调用。插件通过将自然语言语义与程序函数绑定,实现了从"不确定的AI推理"到"确定性的业务逻辑"的转化。

插件存在的根本原因在于大语言模型的固有局限性:

  • LLM的训练数据存在时间滞后性,无法获取实时信息

  • LLM不能直接调用API、操作数据库或执行具体业务逻辑

  • LLM生成的文本具有不确定性,而业务系统需要确定性操作

1.2 插件的设计哲学

Semantic Kernel的插件设计遵循三个核心原则:

  1. 确定性增强:通过预定义的函数和参数约束,将模糊的自然语言指令转化为精准的业务动作

  2. 模块化复用:每个插件可独立开发、测试和部署,支持跨项目复用

  3. AI可理解性:插件要提供详细的语义描述,包括函数输入、输出和副作用,以便AI正确理解和使用

2. 插件类型与实现机制

Semantic Kernel支持两种主要的插件类型,它们各有特点并适用于不同场景。

2.1 语义函数(Semantic Functions)

语义函数是通过自然语言模板(提示词)定义的函数,由AI模型执行。它们适用于需要创造性与灵活性的场景。

在文件系统中存储函数提示有几个优点,包括但不限于

模块化、可重复使用性与组织

  • 利益分离:将提示符分开文件,可以让你独立管理和修改它们,独立于代码库的其他部分。

  • 可重复使用的组件:存储在文件中的提示可以在多个项目中重复使用,减少冗余并确保一致性。

维护简便性:

  • 简化更新:在一个文件中更新提示时,无论该文件被导入到哪里,都会自动应用这些更改。这使得维护和更新提示更方便,无需搜索和编辑多个代码文件。

  • 版本控制:文件中的提示可以通过Git等工具进行版本控制,方便跟踪更改、恢复到之前的版本,并更有效地与他人协作。

合作:

  • 共享资源:将提示存储在文件中,方便与团队成员共享。它们可以存储在共享仓库中,促进协作并确保所有人使用相同的版本。

  • 标准化:使用统一的提示集有助于在应用的不同部分或多个应用间标准化响应和行为。

目录结构规范

语义插件需要遵循特定的文件结构约定:

Plugins/
├── Complaint/
│   ├── skprompt.txt      # 提示词模板
│   └── config.json        # 配置文件
└── WriterPlugins/
    ├── Joke/
    │   ├── skprompt.txt
    │   └── config.json
    └── ShortPoem/
        ├── skprompt.txt
        └── config.json

提示词模板示例(skprompt.txt)

BE FRIENDLY. BE POLITE. BE PROFESSIONAL.  
APOLOGIES FOR THE INCONVENIENCE.  
  
ENSURE THAT THE PERSON FEELS HEARD ABOUT THEIR EXPERIENCE AND THAT YOU ARE TAKING THEIR FEEDBACK SERIOUSLY.  
  
DEAR {{$customerName}},  
+++  
WE ARE SORRY TO HEAR ABOUT {{$request}}.  
WE WILL DO OUR BEST TO ENSURE THAT THIS DOES NOT HAPPEN AGAIN.  
THANK YOU FOR BRINGING THIS TO OUR ATTENTION.  
+++

配置文件示例(config.json)

 {  
  "schema": 1,  
  "type": "completion",  
  "description": "Handle the customers complaint.",  
  "execution_settings": {  
    "default": {  
      "max_tokens": 1000,  
      "temperature": 0  
    }  
  },  
  "input_variables": [  
    {      
      "name": "$customerName",  
      "description": "The users name.",  
      "required": true  
    },  
    {      
	  "name": "$request",  
      "description": "The user's complaint.",  
      "required": true  
    }  
  ]}

2.2 本地函数(Native Functions)

本地函数使用传统编程语言(如C#、Python)编写,直接操作业务系统。它们适用于需要精确控制和确定性的场景。

C#本地函数示例

using System.ComponentModel;  
using Microsoft.SemanticKernel;  
  
namespace SemanticKernelPlugin;  
  
public class TimePlugin  
{  
    [KernelFunction, Description("获取当前时间")]  
    public string GetCurrentTime() => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");  
    
    [KernelFunction, Description("获取指定天数后的日期")]  
    public string GetDateAfterDays([Description("天数")] int days) => DateTime.Now.AddDays(days).ToString("yyyy-MM-dd");  
}

C#本地函数示例

builder.Plugins.AddFromType<TimePlugin>("time");

2.3 两种插件类型的对比

特性维度 语义函数 本地函数
执行引擎 AI模型 传统代码运行时
确定性 较低,依赖AI推理 高,完全可控
适用场景 创意生成、文本处理 业务操作、数据计算
开发复杂度 需设计提示词 需编写业务逻辑
性能特点 相对较慢,有token成本 快速,无额外成本

3. 插件的创建与注册

3.1 插件创建方式

Semantic Kernel提供了多种创建插件的方式,满足不同场景的需求:

从类实例创建插件

// 创建插件实例
var mathPlugin = new MathPlugin();
// 注册到内核
kernel.Plugins.AddFromObject(mathPlugin, "MathPlugin");

从目录创建语义插件

// 从目录结构自动加载插件
var plugin = kernel.ImportPluginFromPromptDirectory(
    pluginName: "WriterPlugin",
    pluginDirectory: "/path/to/plugins"
);

从OpenAPI规范创建插件[1]

await kernel.ImportPluginFromOpenApiAsync(
   pluginName: "lights",
   uri: new Uri("https://example.com/v1/swagger.json"),
   executionParameters: new OpenApiFunctionExecutionParameters()
   {
      // Determines whether payload parameter names are augmented with namespaces.
      // Namespaces prevent naming conflicts by adding the parent parameter name
      // as a prefix, separated by dots
      EnablePayloadNamespacing = true
   }
);

3.2 插件注册实战

以下是完整的插件注册示例代码:

using System.ClientModel;  
using System.Text;  
using Microsoft.Extensions.Configuration;  
using Microsoft.SemanticKernel;  
using Microsoft.SemanticKernel.ChatCompletion;  
using Microsoft.SemanticKernel.Connectors.OpenAI;  
using OpenAI;  
using SemanticKernelPlugin;  
  
var configuration = new ConfigurationBuilder()  
    .SetBasePath(Directory.GetCurrentDirectory())  
    .AddJsonFile("appsettings.json")  
    .Build();  
  
string apiKey = configuration.GetRequiredSection("api_key").Value ?? "";  
var openAIClientCredential = new ApiKeyCredential(apiKey);  var openAIClientOption = new OpenAIClientOptions  {    
Endpoint = new Uri("https://api.deepseek.com")  
};  var builder = Kernel.CreateBuilder()    
    .AddOpenAIChatCompletion(modelId: "deepseek-chat", openAIClient: new OpenAIClient(openAIClientCredential, openAIClientOption));  
  
builder.Plugins.AddFromPromptDirectory(Path.Combine(Directory.GetCurrentDirectory(), "Plugins"));  
builder.Plugins.AddFromType<TimePlugin>("time");  
// 创建插件实例  
var mathPlugin = new MathPlugin();  
// 注册到内核  
builder.Plugins.AddFromObject(mathPlugin, "MathPlugin");  
  
// 构建内核  
var kernel = builder.Build();  
  
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();  
ChatHistory chatHistory = new();  
  
  
while (true)  
{  
    Console.Write("User: ");  
    var content = Console.ReadLine();  
    if (content == "exit")  
    {        break;  
    }  
    if (string.IsNullOrWhiteSpace(content))  
    {        continue;  
    }  
    chatHistory.AddUserMessage(content);  
    // 配置函数调用行为  
    var settings = new OpenAIPromptExecutionSettings  
    {  
        FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()  
    };    // 创建聊天请求  
    var stream = chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel);  
    Console.Write("Assistant: ");  
    StringBuilder assistantMessage = new StringBuilder();  
    await foreach (var response in stream)  
    {        assistantMessage.Append(response.Content);  
        Console.Write(response.Content);  
    }    chatHistory.AddAssistantMessage(assistantMessage.ToString());  
    Console.WriteLine();  
}

4. 插件调用机制

4.1 手动调用方式

手动调用适用于业务代码中主动触发插件功能的场景。

// 创建内核和插件
var kernel = Kernel.CreateBuilder()
    .AddAzureOpenAIChatCompletion("gpt-3.5-turbo", endpoint, apiKey)
    .Build();

kernel.ImportPluginFromType<TimePlugin>("TimePlugin");

// 手动调用插件函数
var arguments = new KernelArguments();
var result = await kernel.InvokeAsync("TimePlugin", "GetToday", arguments);

Console.WriteLine($"今天是: {result.GetValue<string>()}");

4.2 AI自动调用(Function Calling)

AI自动调用是Semantic Kernel最强大的特性之一,它允许LLM根据对话上下文自动选择并执行合适的插件。

配置自动函数调用

using Microsoft.SemanticKernel.Connectors.OpenAI;

// 配置函数调用行为
var settings = new OpenAIPromptExecutionSettings
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};

// 创建聊天历史
var history = new ChatHistory();
history.AddUserMessage("请问今天的日期是什么?");

// 自动触发函数调用
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var result = await chatCompletionService.GetChatMessageContentAsync(
    history,
    executionSettings: settings,
    kernel: kernel
);

Console.WriteLine(result.Content);

函数调用策略选项

策略 说明 使用场景
Auto 自动选择最合适的函数 通用场景,让AI自主决策
Required 必须调用指定函数 强制功能执行,精确控制
None 不调用任何函数 纯对话场景,避免意外调用

5. 高级插件开发技巧

5.1 插件元数据与发现

完善的元数据描述是确保AI正确理解和使用插件的关键。

public class AdvancedTaskPlugin
{
    [KernelFunction("complete_task")]
    [Description("根据任务ID完成任务。提供ID后,系统将标记任务为已完成状态")]
    [return: Description("返回更新后的任务对象,若找不到对应任务则返回null")]
    public TaskModel? CompleteTask(
        [Description("任务的唯一标识符,必须是正整数")] int id,
        [Description("完成备注信息,可选参数")] string? notes = null)
    {
        // 实现任务完成逻辑
        return updatedTask;
    }
}

5.2 错误处理与验证

健壮的插件需要包含完善的错误处理机制。

public class RobustInventoryPlugin
{
    private readonly Dictionary<string, int> _stock = new();
    
    [KernelFunction]
    [Description("从库存中扣除指定数量的商品")]
    public string DeductStock(string item, int quantity)
    {
        try
        {
            // 参数验证
            if (string.IsNullOrWhiteSpace(item))
                throw new ArgumentException("商品名称不能为空");
                
            if (quantity <= 0)
                throw new ArgumentException("扣除数量必须大于0");
            
            if (!_stock.ContainsKey(item))
                throw new KeyNotFoundException($"商品 '{item}' 不存在");
            
            if (_stock[item] < quantity)
                throw new InvalidOperationException(
                    $"库存不足:{item} 仅剩 {_stock[item]}件,尝试扣除 {quantity}件");
            
            // 执行库存操作
            _stock[item] -= quantity;
            
            return $"成功扣除 {quantity}件{item},剩余库存:{_stock[item]}件";
        }
        catch (Exception ex)
        {
            // 记录日志并抛出有意义的错误信息
            kernel.Logger.LogError(ex, "库存操作失败");
            throw new KernelFunctionException($"库存操作失败:{ex.Message}");
        }
    }
}

5.3 插件组合与协作

多个插件可以协同工作,形成复杂的功能链。

// 创建协同工作的插件组合
var mathPlugin = new MathPlugin();
var timePlugin = new TimePlugin();
var textPlugin = new TextPlugin();

// 注册所有插件
kernel.Plugins.AddFromObject(mathPlugin, "Math");
kernel.Plugins.AddFromObject(timePlugin, "Time");
kernel.Plugins.AddFromObject(textPlugin, "Text");

// 复杂任务:结合多个插件功能
string complexPrompt = """
    当前时间:{{Time.Now}}
    用户请求:{{$input}}
    
    请执行以下操作:
    1. 对输入文本进行清理:{{Text.Trim $input}}
    2. 计算文本长度:{{Text.Length $input}}
    3. 基于当前时间提供建议
    """;

6. 实战案例:智能库存管理系统

让我们通过一个完整的实战案例来巩固所学知识。

6.1 定义库存管理插件

using System.ComponentModel;
using Microsoft.SemanticKernel;

public class InventoryManagementPlugin
{
    private readonly Dictionary<string, int> _inventory = new();
    
    [KernelFunction]
    [Description("添加商品到库存系统")]
    public string AddItem(string itemName, int initialQuantity)
    {
        _inventory[itemName] = initialQuantity;
        return $"商品 '{itemName}' 已添加到库存,初始数量:{initialQuantity}";
    }
    
    [KernelFunction]
    [Description("查询商品库存数量")]
    public string CheckStock(string itemName)
    {
        if (_inventory.TryGetValue(itemName, out int quantity))
            return $"商品 '{itemName}' 当前库存:{quantity}件";
        else
            return $"商品 '{itemName}' 不存在于库存中";
    }
    
    [KernelFunction]
    [Description("获取库存概览报告")]
    public string GetInventoryReport()
    {
        if (_inventory.Count == 0)
            return "库存系统为空";
            
        var report = "库存概览报告:\n";
        foreach (var item in _inventory)
        {
            report += $"- {item.Key}: {item.Value}件\n";
        }
        return report;
    }
}

6.2 集成与测试

using System.ClientModel;  
using System.Text;  
using Microsoft.Extensions.Configuration;  
using Microsoft.SemanticKernel;  
using Microsoft.SemanticKernel.ChatCompletion;  
using Microsoft.SemanticKernel.Connectors.OpenAI;  
using OpenAI;  
using SemanticKernelPlugin;  
  
var configuration = new ConfigurationBuilder()  
    .SetBasePath(Directory.GetCurrentDirectory())  
    .AddJsonFile("appsettings.json")  
    .Build();  
  
string apiKey = configuration.GetRequiredSection("api_key").Value ?? "";  
var openAIClientCredential = new ApiKeyCredential(apiKey);  var openAIClientOption = new OpenAIClientOptions  {    
Endpoint = new Uri("https://api.deepseek.com")  
};  var builder = Kernel.CreateBuilder()    
    .AddOpenAIChatCompletion(modelId: "deepseek-chat", openAIClient: new OpenAIClient(openAIClientCredential, openAIClientOption));  
  
builder.Plugins.AddFromPromptDirectory(Path.Combine(Directory.GetCurrentDirectory(), "Plugins"));  
builder.Plugins.AddFromType<TimePlugin>("Time");  
builder.Plugins.AddFromType<InventoryManagementPlugin>("InventoryManagementPlugin");  
// 创建插件实例  
var mathPlugin = new MathPlugin();  
// 注册到内核  
builder.Plugins.AddFromObject(mathPlugin, "MathPlugin");  
  
// 构建内核  
var kernel = builder.Build();  
  
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();  
ChatHistory chatHistory = new();  
  
  
while (true)  
{  
    Console.Write("User: ");  
    var content = Console.ReadLine();  
    if (content == "exit")  
    {        break;  
    }  
    if (string.IsNullOrWhiteSpace(content))  
    {        continue;  
    }  
    chatHistory.AddUserMessage(content);  
    // 配置函数调用行为  
    var settings = new OpenAIPromptExecutionSettings  
    {  
        FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()  
    };    // 创建聊天请求  
    var stream = chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel);  
    Console.Write("Assistant: ");  
    StringBuilder assistantMessage = new StringBuilder();  
    await foreach (var response in stream)  
    {        assistantMessage.Append(response.Content);  
        Console.Write(response.Content);  
    }    chatHistory.AddAssistantMessage(assistantMessage.ToString());  
    Console.WriteLine();  
}

6.3 运行结果示例

User: 请帮我检查笔记本电脑的库存情况,并告诉我今天日期
Assistant: 我来帮您检查笔记本电脑的库存情况,并获取今天的日期。根据查询结果:

1. **库存情况**:目前库存系统中没有"笔记本电脑"这个商品记录。

2. **今天日期**:今天是2025年12月25日,当前时间是21:51:28。

如果您需要将笔记本电脑添加到库存系统中,我可以帮您添加并设置初始库存数量。或者如果您想查看其他商品的库存情况,请告诉我具体的商品名称。
User: 添加笔记本电脑到库存系统,入库50台笔记本
Assistant: 我来帮您将笔记本电脑添加到库存系统,并设置初始库存为50台。太好了!笔记本电脑已经成功添加到库存系统中,初始库存数量为50台。

现在您可以:
1. 随时查询笔记本电脑的库存数量
2. 查看整个库存系统的概览报告
3. 添加其他商品到库存系统

需要我帮您做其他库存管理操作吗?
User: 请帮我检查笔记本电脑的库存情况,并告诉我今天日期
Assistant: 我来帮您检查笔记本电脑的库存情况,并获取今天的日期。根据查询结果:

1. **笔记本电脑库存情况**:当前库存为50件

2. **今天日期**:今天是2025年12月25日,当前时间是21:53:33

笔记本电脑库存状态良好,有50台可供销售或使用。

7. 插件开发最佳实践

7.1 设计原则

  1. 单一职责:每个插件应聚焦特定的业务领域

  2. 清晰命名:使用描述性的函数名,避免缩写

  3. 参数简化:尽量使用基本类型,减少复杂对象

  4. 详细描述:为每个函数和参数提供完整的自然语言说明

7.2 错误处理

  1. 友好错误:提供有意义的错误信息,便于AI理解并回复用户

  2. 异常处理:妥善处理异常,避免插件崩溃影响整体系统

  3. 输入验证:在函数开始处验证参数合法性

7.3 性能优化

  1. 异步操作:对于I/O密集型操作,使用异步方法

  2. 资源管理:及时释放非托管资源

  3. 缓存策略:对频繁访问的数据实施合适的缓存机制

总结

本章深入探讨了Semantic Kernel插件系统的各个方面。插件作为连接AI智能与传统业务逻辑的桥梁,是构建真正"可执行"AI应用的核心技术。

通过本章学习,您应该掌握了:

  • 插件的基本概念和设计哲学

  • 两种插件类型(语义函数和本地函数)的特性和应用场景

  • 插件的创建、注册和调用机制

  • 内置插件库的功能和使用方法

  • 高级开发技巧和最佳实践

在下一章中,我们将探讨Semantic Kernel的另一个核心组件:记忆(Memory)系统,学习如何为AI应用添加长期记忆和上下文感知能力。

本文源码:https://github.com/huangmingji/semantic-kernel-plugin


  1. https://learn.microsoft.com/zh-cn/semantic-kernel/concepts/plugins/adding-openapi-plugins?pivots=programming-language-csharp ↩︎

posted @ 2026-01-21 15:18  黄明基  阅读(2)  评论(0)    收藏  举报