明志唯新

Semantic Kernel dotnet 1.0 beta7 发布

发表于

```最近 Semantic Kernel 1.0的测试版发布确实比较密集,1.0 beta7 版本带来了一些重要的变化和改进,使得 Semantic Kernel 具备更好的生产力支持:

## 重构 IAIServiceSelector

为了避免将来需要传递其他的信息时再做破坏性改变,对 `IAIServiceSelector` 做了一些重构。
重构后将具备允许应用通过一些额外的信息条件选择模型,例如下面这个通过 ModelId 来选择 AI Service 的情况:

``` csharp
public class ByModelIdAIServiceSelector : IAIServiceSelector
{
    private readonly string _openAIModelId;

    public ByModelIdAIServiceSelector(string openAIModelId)
    {
        this._openAIModelId = openAIModelId;
    }

    public (T?, AIRequestSettings?) SelectAIService<T>(SKContext context, ISKFunction skfunction) where T : IAIService
    {
        foreach (var model in skfunction.ModelSettings)
        {
            if (model is OpenAIRequestSettings openAIModel)
            {
                if (openAIModel.ModelId == this._openAIModelId)
                {
                    var service = context.ServiceProvider.GetService<T>(openAIModel.ServiceId);
                    if (service is not null)
                    {
                        return (service, model);
                    }
                }
            }
        }

        throw new SKException("Unable to find AI service to handled request.");
    }
}

另外,并在 IAIService 中还新增了一个只读属性

IReadOnlyDictionary<string, string> Attributes { get; }

用来标识 AI 服务的一些元数据信息,这样就可以通过 IAIServiceSelector 来实现复杂的 AI 服务选择,比如说选择满足需求中最便宜的或者具有某种特定能力的等。

更复杂的场景下,你可以利用 SK 的 pre-invocation hook 在应用向大模型正式发送提示前可以先计算 token 大小,以决定使用最佳那个的 AI Service 方法。

基于 OpenAI 函数调用构建的 Stepwise planner

一个使用 OpenAI 函数调用以逐步实现用户目标或问题的 Planner

增加 Handlebars Planner

基于 Handlebars 模板语言的新 Sequential Planner。使用 LLM 生成的更简单的脚本语言会产生更好的结果,并且您可以执行更复杂的事情(例如循环、复杂的对象支持等)。

变更内容:

  • 添加了 Handlebars planner、HandlebarsPlan class和HandlebarsTemplateEngineExtensions class,以支持使用 Handlebars 模板生成计划
  • 添加了 Handlebars 模板引擎扩展和系统助手,以实现更灵活、可定制化的提示和响应规划与呈现
  • 为添加了几个内置用于语义规划的 Handlebars 辅助类和函数,包括 greaterThan、lessThanOrEqual、greaterThanOrEqual、json、message、raw、doubleOpen doubleClose set 和 get 等
  • 为可用函数无法为给定目标创建计划的情况,在 HandlebarsPlanner 中实现了错误处理
  • 添加了几个新的 HandlebarsPlanner 示例和测试来演示用法

实验性新功能:集成 OpenAI assistants

在 OpenAI 首次开发者日活动中,OpenAI宣布了 GPTs 和 assistants API。这是在 chat completion 模型之上创建 Agent 的新方法。有了 assistants API ,建立代理所需的大部分繁重工作都被抹平了。其中的几个关键点如下:

  • 你将可以在 threads 中管理消息;
  • Memory 在后台自动化处理;
  • 一次可以调用多个函数,而不仅仅是一个函数。

借助于这些能力,基于 OpenAI 和 Semantic Kernel 构建 Agent 可以变得更快更轻松。作为 Semantic Kernel v1 提案中的一部分,SK 团队实验性的增加 OpenAI assistants 集成。在 kernel 中,SK 团队增加了一个抽象层,以便可以轻松的构建助理。

在今天的 kernel 中,您只能定义可用的函数、模型和提示模板引擎。这有助于创建一个运行时环境,使您的语义和本地函数能够相互通信,但许多其他部分仍需要开发人员来实现。例如,要构建一个完整的带有 Semantic Kernel 的Agent 程序,您必须自行管理整个聊天记录。当使用 OpenAI 函数调用时,这会特别麻烦。对于外行来说,这可能会令人困惑和具有挑战性,因此 SK 团队希望让它变得更好。

为了简化起见,SK 团队将在内核中进行更新,以便它可以在幕后使用 assistants API。有了这个变化,就意味着 SK 团队可以从中更新已经简化的 v1 提案 ...

原本这样的代码:

// Create a new kernel
IKernel kernel = new Kernel(
    aiServices: new () { },
    plugins: new () { intentPlugin, mathPlugin }
);

// Start the chat
ChatHistory chatHistory = gpt35Turbo.CreateNewChat();
while(true)
{
    Console.Write("User > ");
    chatHistory.AddUserMessage(Console.ReadLine()!);

    // Run the simple chat
    var result = await kernel.RunAsync(
        chatFunction,
        variables: new() {{ "messages", chatHistory }},
        streaming: true
    );

    Console.Write("Agent > ");
    await foreach(var message in result.GetStreamingValue<string>()!)
    {
        Console.Write(message);
    }

    Console.WriteLine();
    chatHistory.AddAgentMessage(await result.GetValueAsync<string>()!);
}

简化后将变成这样:

// Create a new kernel
AssistantKernel kernel = new AssistantKernel(
    aiServices: new () { gpt35Turbo, gpt4Agent },
    plugins: new () { intentPlugin, mathPlugin }
);

// Start the chat
kernel.StartChat(chatFunction);
while(true)
{
    Console.Write("User > ");

    // Run the simple chat
    var result = await kernel.SendUserMessage(
        Console.ReadLine()!,
        streaming: true
    );

    Console.Write("Agent > ");
    await foreach(var message in result.GetStreamingValue<string>()!)
    {
        Console.Write(message);
    }
    Console.WriteLine();
}

使用这种方式,您不再需要自己管理聊天记录。此外,“运行”内核将变得更加容易,因为您只需要传入用户的最后一个输入即可。

在幕后,每当您使用 SendUserMessage 方法时,我们将会:

  • 1)调用必要的 OpenAI API 来发送用户的消息,
  • 2)从 LLM 中获取响应,
  • 3)最后将结果返回给您。

尽管 OpenAI 的新 assistant APIs 功能已很强大了,但它们并不能做所有事情。这就是 Semantic Kernel 发挥作用的地方。通过支持插件、规划器和多模型支持,您可以使用 Semantic Kernel 来扩展助手功能,使其更加强大,并优化性能和成本。总结如下:

  1. 简化的函数调用 - 为了让您的助手更加有用,您可以为它们提供要执行的操作。我们将通过利用已经在内核中注册的插件来简化此过程。当您与助手交流时,我们将向其提供您添加的函数,并在从模型获取响应时自动运行这些函数。
  2. 复杂的多步骤计划 - 通过 Agent,OpenAI 可以一次性调用多个函数,但它仍然无法创建具有条件逻辑、循环和变量传递的复杂计划。使用 Semantic Kernel 的 Planner,您可以做到这一点。这不仅节省了 token,还可以让您生成完整的计划,并在这些计划执行之前可以由人工审查。
  3. 多模型支持 - 当前的 Agent 使用 GPT-3.5-turbo、GPT-4,以及即将推出的 GPT-4-turbo 来完成所有聊天任务。然而,作为开发者,您可能希望更加精确地选择使用不同的模型。您可以在一些简单语义功能上使用 GPT-3.5-turbo,并在最终回复中使用 GPT-4-turbo。通过 Semantic Kernel,您可以进行这些优化操作。甚至可以结合 OpenAI Agent 与非 OpenAI 模型一起使用。
  4. 更好地控制 Memory – 如果要使用高级 Memory 体系结构来更好地控制保存和检索 Memory 的方式(如: Kernel Memory 或 Llama index),则可以将这些服务添加为插件,以便为 Agent 提供更好的上下文。
  5. 更高级别的可见性和监控 - 通过 Semantic Kernel 的 pre/post hooks,您可以轻松地将遥测数据添加到 Kernel 中,以便轻松获得对所有本机函数和语义函数中的 token 使用情况、呈现提示词等。

将 Type 和 Schema 添加到 ParameterViews 中

为了给 Planner 提供更多关于如何使用插件函数的信息,我们需要从原生函数和远程 OpenAPI 规范中保留更多信息。

允许在导入和 OpenAPI swagger 时排除操作

导入 OpenAPI 插件时,可能会出现 SK 不支持的操作,从而触发异常。可以在 OpenApiFunctionExecutionParameters 添加新选项 OperationsToExclude 来排除某些操作(例如,那些否则会导致异常的操作)。

例如:SK 目前仅支持 “application/json” 和 “text/plain” 媒体类型,当 kernel.ImportOpenApiPluginFunctionsAsync 遇到使用 “multipart/form-data” 的操作时会抛出异常(SKException($"Neither of the media types of {operationId} is supported."))。

OpenAPI 函数参数移除 server-url 参数

当 OpenAPI 函数注册到 kernel 时,kernel 会人为地在 OpenAPI 函数的参数视图中添加一个参数 serverl-url。可选 server-url 参数允许 Planner 动态提供备用服务器 URL,以覆盖向插件注册的 URL。该参数所提供的灵活性有时可能会 Planner 困惑,从而创建无法执行的计划。

本地插件和 OpenAPI 插件对 Planner 应该是无关的。SK 团队移除了这个人为创建的参数,以提高规划器在创建有效函数调用时的性能,而不考虑函数类型如何。

当然了我们知道有一些 OpenAPI 插件需要 server-url 替代,例如 Jira 插件和 Azure Key Vault 插件。对于这些插件,仍然可以在导入/注册时通过可选 OpenAIFunctionExecutionParameters 参数提供服务器 URL。如下图示例:

image

重命名 SKFunction 工厂方法

之前的版本中 SKFunction 有两种方法:FromNativeMethodFromNativeFunction。这里的 “Native” 部分在 C# 的上下文中令人困惑,因为 “native” 通常与 “managed” 形成对比。这两个方法之间的唯一区别应该是方法信息的提供方式,无论是单个委托还是 MethodInfo 和目标对象的组合,所以本次将这两个方法重命名为同一 Create 方法的重载,接受除指定方法外的相同参数。

本机函数添加 ReturnParameterView

增加有关本机函数(native functions)输出的更具体信息,以便为 Planner 提供更好的支持。提供了一种使用属性描述本机函数输出的方法,以及一种通过 FunctionView 查看此说明以及输出返回类型的方法。

其他变更:

  • 为适配 .NET 8 ,修正了一些 NRT(nullable reference type) 警告
  • Microsoft.SemanticKernel.Experimental.Orchestration.Flow 程序集上使用 .NET 8 新带来的 ExperimentalAttribute,以便强制标识该特性为实验性的
  • 修复了 Plugins.Memory 代码签名的一个 bug
  • 修复了 ChatHistoryExtensions 的一些问题
  • 将 GetFunctionsAsync 和 GetAvailableFunctionsAsync 方法的返回类型从 IOrderedEnumerable<FunctionView>`` 改为 IEnumerable`
  • 通过缓存而不是每次创建一个新的 JsonSerializerOptions,来降低 Serialize/Deserialize 时的成本消耗,提升性能
  • 将 .Net notebooks 示例升级到 Microsoft.SemanticKernel, 1.0.0-beta6
  • 修复了一个不必要的 string.Join
  • 清理代码,默认加载 BasicTemplateEngine
  • 从 planner 扩展中删除 ConcurrentBag 的使用,改为 List
  • 为使 C# 入门用户更容易上手,修正了一些教程示例

参考源: