Semantic Kernel (语义内核):创建人工智能应用程序的新方法


在本文中,我们将探索Semantic Kernel (语义内核),这是一种新的 Microsoft SDK,可简化将 AI 集成到传统应用程序中。

Semantic Kernel (语义内核)使开发人员能够轻松地将尖端人工智能与本机代码融合,为人工智能应用开辟新的可能性。

本文将继续讨论Semantic Kernel (语义内核)的特性和优点,以及一些当前使用的示例。

组件

在使用Semantic Kernel 开发解决方案时,我们可以使用一系列组件来为我们的应用程序提供更好的体验。

虽然建议熟悉它们,但并非所有组件都是必须使用的。符合Semantic Kernel 的组件如下所列:

  • Kernel 核心
  • Memories 记忆
  • Planner 规划器
  • Connectors 连接器
  • Plugins 插件(又称技能)
img

Semantic Kernel管道组件架构。来源:Semantic Kernel Github 存储库

Kernel 核心

它的名字已经向我们揭示了它在 SDK 中的重要性。

在内核中,我们将注册所有连接器和插件,并配置运行程序所需的内容。

此外,我们还可以添加对日志和遥测的支持,以检查程序的状态和性能,并在必要时协助调试。

Memories 记忆

现在我们来看看允许我们为用户问题提供上下文的组件。这意味着我们的插件可以回忆过去与用户的对话,为他们提出的问题提供背景。这些记忆可以通过三种不同的方式实现:

  • 键值对:像环境变量一样存储,并且按常规进行搜索,这意味着键和用户输入的文本之间必须存在一对一的关系。
  • 本地存储:保存在本地文件系统的文件中。当键值存储达到临界大小时,就该切换到这种类型的存储了。
  • 语义内存搜索:我们可以将信息表示为称为嵌入的数字向量。

Planner 规划器

规划器是一种接受用户提示并返回执行计划以执行请求的函数。我们可以从 SDK 附带的一组预定义规划器中进行选择:

  • SequentialPlanner:创建一个具有多个函数的计划,每个函数都与自己的输入和输出变量互连。
  • BasicPlanner:SequentialPlanner 的简化版本,将多个功能链接在一起。
  • ActionPlanner:创建具有单一功能的计划。
  • StepwisePlanner:逐步执行每个步骤,并在执行下一步之前观察结果。

Connectors 连接器

连接器在Semantic Kernel中起着非常重要的作用,因为它们充当不同组件之间的桥梁,实现它们之间的信息交换。

这些连接器可以开发与外部系统交互,例如与 HuggingFace 模型交互或使用 SQLite 数据库作为我们开发的内存。

Semantic Kernel SDK 附带一些预定义的连接器,可分为两个部分:

与 AI 模型的集成:

  • HuggingFace Microsoft.SemanticKernel.Connectors.AI.HuggingFace
  • Oobabooga Microsoft.SemanticKernel.Connectors.AI.Oobabooga
  • OpenAI Microsoft.SemanticKernel.Connectors.AO.OpenAI

支持现有的 RDBMS 和 NoSQL 内存:

  • Azure Cognitive Search
  • Chroma
  • DuckDB
  • Kusto
  • Milvus
  • Pinecone
  • Postgres
  • Qdrant
  • Redis
  • SQLite
  • Weaviate

Plugins 插件

插件可以描述为一组暴露给 AI 服务和应用程序的功能,无论是本机的还是语义的。

为这些函数编写代码是不够的;我们还必须能够提供有关其行为、输入和输出参数以及任何潜在副作用的语义细节。

此时,我们需要区分两种类型的函数:

  • 语义函数:这些功能会监听用户请求并使用自然语言提供响应。为了实现这一点,语义功能需要连接器来实现其目的。
  • 原生函数:这些函数用 C#、Python 和 Java(目前处于实验阶段)编写。它们处理 AI 模型不适合的操作,例如:
    • 数学计算。
    • 从内存中读取和保存数据。
    • 访问 REST API。

规划器可以使用本机函数代码中的注释来了解其操作,而不需要配置文件。

语义功能

仅用两个文件,我们就可以定义一个功能齐全的语义函数:一个用于配置函数,另一个用于定义提示。

让我们创建一个函数,将 JSON 文档转换为给定编程语言的数据结构。

该函数的配置,即 JSON 文件,如下所示:

 {
     "schema": 1,
     "type": "completion",
     "description": "Create a data structure from a JSON document.",
     "completion": {
          "max_tokens": 8000,
          "temperature": 0.1,
          "top_p": 0.0,
          "presence_penalty": 0.0,
          "frequency_penalty": 0.0
    },
     "input": {
          "parameters": [
              {
                    "name": "language",
                    "description": "The programming language in which the data model will be generated based on the JSON document",
                    "defaultValue": "Swift"
              },
              {
                    "name": "content",
                    "description": "The JSON document that is going to be converted",
                    "defaultValue": ""
              }
          ]
    }
 }

我们可以看到,配置中有三个主要部分。第一部分是定义函数的名称和描述,这很重要,因为内核稍后将使用它们来确定何时使用我们的函数。

接下来是完成部分,在这里我们指定 LLM 模型的操作,例如token的数量(类似于字数)或温度,这决定了我们希望模型在生成输出时有多富有想象力。

最后,我们有输入部分,在这里我们指定函数运行所需的参数。

现在到了我们定义函数提示的部分,我们在名为 的文本文件中执行此操作skprompt.txt

 Act as a senior {{$language}} developer. Convert this JSON structure in a {{$language}} data model.
 ​
 --- Begin ---
 {{$content}}
 --- End ---

我们可以看到,我们使用字符串插值来包含我们在配置文件中定义的输入参数,使用“name”属性中定义的变量名。

为了防止即时注入技术,建议对我们希望模型运行的内容进行限制,以避免出现意外结果。

本机函数Native functions

正如我们之前提到的,本机函数是执行更基本任务的函数,就像本例中的函数一样,它仅计算源代码文件中的行数。

 using System.IO;
 using System.ComponentModel;
 using Microsoft.SemanticKernel.Orchestration;
 using Microsoft.SemanticKernel.SkillDefinition;
 ​
 namespace Globant.Plugins;
 ​
 public sealed class Statistics
 {
    [SKFunction, Description("Genenate statistics for a source code file in a given path")]
     public string GetStatistics([Description("Path to the source code file to read")] string path)
    {
         var lineCount = File.ReadLines(path).Count();
         return $"Statistics: {lineCount} lines";
    }
 }

为了向内核公开这个本机函数,我们在函数中使用装饰器:

  • SKFunction:用于提供功能描述。
  • SKParameter:给出函数每个参数的用法的概念(如果有多个的话)。
  • Description:类似,SKParameter但只有一个参数。

让一切正常运转

现在我们已经创建了语义函数和本机函数,是时候让所有这些工作了。因此,我们在 Mac 上打开我们最喜欢的 C# 代码编辑器并创建一个名为的文件Program.cs,其内容可能如下:

 using System.IO;
 using Microsoft.SemanticKernel;
 using Microsoft.SemanticKernel.Skills.Core;
 using Microsoft.SemanticKernel.Planning;
 using Microsoft.SemanticKernel.Connectors.AI.HuggingFace;
 using Microsoft.Extensions.Logging;
 ​
 using Globant.Plugins;
 ​
 var builder = new KernelBuilder();
 ​
 // OpenAI
 builder.WithOpenAIChatCompletionService(
     "gpt-3.5-turbo", // OpenAI Model name
     "sk-cJ...d5"     // OpenAI API Key
 );
 ​
 // Huggingface
 builder.WithHuggingFaceTextCompletionService(
     "meta-llama/Llama-2-70b-chat-hf" // model
 );
 ​
 // Azure OpenIA Services
 builder.WithAzureChatCompletionService(
  "gpt-35-turbo",        // Model
  "/endpoint/azure/...", // Endpoint
  "azure...key"          // API Key
 );
 ​
 using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
 {
     builder
        .SetMinimumLevel(0)
        .AddDebug()
        .AddConsole();
 });
 ​
 builder.WithLoggerFactory(loggerFactory);
 ​
 var kernel = builder.Build();
 ​
 // Import predefined functions
 var filePlugin = kernel.ImportSkill(new FileIOSkill(), "file");
 ​
 // Import our semantic functions
 var pluginsDirectory = Directory.GetCurrentDirectory();
 kernel.ImportSemanticSkillFromDirectory(pluginsDirectory, "Plugins");
 // Import our native functions
 var codePlugin = kernel.ImportSkill(new Statistics(), "codeStatisticsPlugin");

我们做的第一件事就是导入一堆必要的命名空间以使一切正常工作(第 1 行到第 8 行)。

然后,我们创建一个构建器的实例(通过模式,而不是因为它是一个构造函数😜),这将有助于塑造我们的内核。

 var builder = KernelBuilder();

我们是否想使用微软在 Azure 和 OpenAI 中的 AI 模型,以及从 Huggingface 中的众多模型中使用我们喜欢的模型?

没问题,我们可以将它们包含在我们的内核中,正如我们在第 13 行(OpenAI)、第 19 行(Huggingface)和第 24 行(Azure AI)中看到的那样。

重要提示:我们需要一个用户和一个 API 密钥来使用这些服务,请务必检查调用这些服务的价格。

你需要知道每个时刻发生了什么吗?当然!让我们在内核中添加一个日志。你可以在第 30 行看到它是如何完成的。

不要重新发明轮子;请记住,微软为我们提供了一系列可以在内核中使用的插件,正如您在第 43 行看到的那样。

最后,是时候导入我们辛苦开发的插件了(说实话,不是真的;这是一个非常简单的插件🤪)。

为此,首先,我们在第 46 行和第 47 行导入语义函数。我们所做的是传递包含所有函数的文件夹,内核负责处理它们并了解它们每个的作用。

这样,我们的内核和插件就可以使用了。

好的,现在让我告诉你如何使用它

假设我们希望模型告诉我们代码文件中的行数。

好吧,事实证明我们有一个本机函数可以完成所有这些工作,因此我们告诉内核我们想要使用它。请记住,该函数需要文件路径作为参数,这是我们在函数中指定的第一件事RunAsync。然后,我们告诉内核哪个函数负责运行它。

 var sourceCodeFilePath = "/Users/adolfo/Documents/Proyectos/Labs/SemanticKernel/Program.cs";
 var result = await kernel.RunAsync(sourceCodeFilePath, codePlugin["GetStatistics"]);
 ​
 Console.WriteLine(result);

输出将会像这样:

img

如果我们想测试其中一个语义函数,我们也可以这样做,尽管代码略有不同。我们将使用语义函数进行编程语言转换。

 ...
 using Microsoft.SemanticKernel.Orchestration;
 ...
 ​
 // Import the semantic functions
 var pluginsDirectory = Directory.GetCurrentDirectory();
 var orquestationPlugin = kernel.ImportSemanticSkillFromDirectory(pluginsDirectory, "Plugins");
 ​
 var variables = new ContextVariables
 {
    ["input_language"] = "C#",
    ["output_language"] = "Swift",
    ["code"] = @"using Globant.Plugins;
     public sealed class Statistics
     {
         public int GetStatistics(string path)
         {
             var lineCount = File.ReadLines(path).Count();
             return lineCount;
         }
     }
     "
 };
 ​
 var convertResult = (await kernel.RunAsync(variables, orquestationPlugin["CodeConverter"])).Result;
 Console.WriteLine(convertResult);

为了将参数传递给语义函数,我们使用一种ContextVariables类型来向函数提供数据。在我们的例子中,这包括源语言、我们要翻译的代码和目标语言。

规划器:让我们的插件更智能

您说得对;到目前为止,我们已经了解了如何直接使用这些函数,但我们插件的用户不需要或不应该知道我们的函数的名称或其参数。这就是规划器发挥作用的地方。

 // Create planner
 var planner = new SequentialPlanner(kernel);
 ​
 // Create a plan for the ask
 var ask = "Get statistics for the file located at /Users/adolfo/Documents/Proyectos/Labs/SemanticKernel/Program.cs and convert the code from C# to Swift";
 var plan = await planner.CreatePlanAsync(ask);
 ​
 // Execute the plan
 var plannerResult = await kernel.RunAsync(plan);
 ​
 Console.WriteLine($"Plan results:\n{plannerResult.Result}");

如您所见,传递给规划器的是一个提示,它很可能是用户在我们的应用程序中提供的文本。

规划器负责了解用户的需求并在我们的插件中找到执行任务所需的功能。

使用 Prompt Flow 测试规划器

我们的规划器使用名为Prompt Flow的 VS Code 扩展进行测试。

img

它是一个可视化编辑器,我们可以在其中定义函数及其输入和输出参数,还可以监控它们的性能。

Packages 包

Microsoft.SemanticKernel 中包含一些包:

  1. Microsoft.SemanticKernel.Abstractions:包含核心和其他 SK 组件使用的通用接口和类。
  2. Microsoft.SemanticKernel.Core:包含SK的核心逻辑,例如提示工程、语义记忆以及语义函数定义和编排。
  3. Microsoft.SemanticKernel.Connectors.AI.OpenAI:连接到 OpenAI 和 Azure OpenAI,允许使用 GPT3、GPT3.5、GPT4、DALL-E2 运行语义功能、聊天和图像生成。

nuget.org 上提供的其他软件包:

  1. Microsoft.SemanticKernel.Connectors.Memory.Qdrant:用于插件和语义内存的 Qdrant 连接器。
  2. Microsoft.SemanticKernel.Connectors.Memory.Sqlite:用于插件和语义内存的 SQLite 连接器
  3. Microsoft.SemanticKernel.Plugins.Document:文档插件:文字处理、OpenXML 等。
  4. Microsoft.SemanticKernel.Plugins.MsGraph:Microsoft Graph 插件:访问您的租户数据、安排会议、发送电子邮件等。
  5. Microsoft.SemanticKernel.Plugins.OpenAPI:OpenAPI 插件。
  6. Microsoft.SemanticKernel.Plugins.Web:Web 插件:搜索网络、下载文件等。
  7. Microsoft.SemanticKernel.Reliability.Polly:http 弹性的扩展。

结论

通过这个新框架,微软为未来将人工智能融入我们的开发中奠定了基础,例如在桌面上使用 .NET MAUI 的跨平台应用程序,以及在服务器上使用 ASP.NET Core。

也许最重要的是,组织可以使用其公司数据将新的 AI 服务集成到其应用程序中,并始终确保该数据的隐私合规性。