```最近 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 来扩展助手功能,使其更加强大,并优化性能和成本。总结如下:
- 简化的函数调用 - 为了让您的助手更加有用,您可以为它们提供要执行的操作。我们将通过利用已经在内核中注册的插件来简化此过程。当您与助手交流时,我们将向其提供您添加的函数,并在从模型获取响应时自动运行这些函数。
- 复杂的多步骤计划 - 通过 Agent,OpenAI 可以一次性调用多个函数,但它仍然无法创建具有条件逻辑、循环和变量传递的复杂计划。使用 Semantic Kernel 的 Planner,您可以做到这一点。这不仅节省了 token,还可以让您生成完整的计划,并在这些计划执行之前可以由人工审查。
- 多模型支持 - 当前的 Agent 使用 GPT-3.5-turbo、GPT-4,以及即将推出的 GPT-4-turbo 来完成所有聊天任务。然而,作为开发者,您可能希望更加精确地选择使用不同的模型。您可以在一些简单语义功能上使用 GPT-3.5-turbo,并在最终回复中使用 GPT-4-turbo。通过 Semantic Kernel,您可以进行这些优化操作。甚至可以结合 OpenAI Agent 与非 OpenAI 模型一起使用。
- 更好地控制 Memory – 如果要使用高级 Memory 体系结构来更好地控制保存和检索 Memory 的方式(如: Kernel Memory 或 Llama index),则可以将这些服务添加为插件,以便为 Agent 提供更好的上下文。
- 更高级别的可见性和监控 - 通过 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。如下图示例:
重命名 SKFunction 工厂方法
之前的版本中 SKFunction
有两种方法:FromNativeMethod
和 FromNativeFunction
。这里的 “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# 入门用户更容易上手,修正了一些教程示例