背景
前些天使用直接对接大模型接口的方式,在我们自己的系统里对接了腾讯混元和DeepSeek,并且封装了一个内部的函数,可以很方便的增加其他OpenAPI风格的模型到系统里来,而不用做很大的调整(长这样👇,也写过一篇文章)。
但事实上,已经有很多大模型的开发框架已经帮我们完成了集成多种模型的功能,大多数场景下,我们没必要自己再去造一个轮子。
更重要的原因是,单纯的自己去对接各个厂家的api,也就意味着,我们想利用大模型来完成的一些真正落地到项目本身来提高效率或者的功能,都需要我们自己写代码完成,我的意思是尽管都是openapi兼容的风格,但实际开发起来,工作量还是有的。比如我们想要使用function call的功能来实现调用本地的方法,这里面就涉及到一些复杂的tool参数传递,尤其是当你的本地方法相对复杂的时候,在通过硬编码的方式去执行相关操作,会非常痛苦,很考验耐力!
因此,我们需要一个框架来帮我们完成这些工作。
sk框架
关于Semantic Kernel(以下简称sk)的介绍,以及基本的使用方法,强烈推荐直接看官网内容:learn.microsoft.com/en-us/seman...
我这里简单介绍一下,sk就是微软推出的轻量级,开源,的开发套件,主要目的是针对构建AI Agent的场景,只需要简单的配置,我们就可以集成多加大模型作为底座支撑,方便的把本地方法构建为插件,集成到sk当中,来实现类似"小爱同学,把灯打开"之类的操作,使AI真正和我们本地的系统相结合,完成一些以前只能靠人工完成的重复性工作,比如审核材料,查找信息等。
sk不单单支持微软的C#语言,还提供Python和Java语言的支持,非常适合企业开发,来增强现有的应用服务能力。
类似的开发框架还有LangChain(python),CrewAI,AutoGen等(这些咱都没用过哈,也是查了一下才知道,这有一篇介绍性的文章,或者大家自己gpt一下哈)
集成步骤
⚠️__说明,因为我也是前两天才开始接触sk并尝试着将其集成到现有的项目中,小有所成便兴奋的想发文记录,所以给出的流程,代码等仅供参考,后续挖掘更多sk的能力后,会再次发文。
添加引用
使用习惯的方式,把sk的nuget包安装到项目里
xml
<ItemGroup>
<!--其他包-->
<PackageReference Include="Microsoft.SemanticKernel" Version="1.33.0" />
</ItemGroup>
基本上,在需要使用sk能力的地方,需要引入下面这几个命名空间
csharp
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
注入服务
官网的quick start引导页里给出的例子是使用的控制台程序,而通过依赖注入的方式将sk注入到项目中的例子也给了,只有阅读官方文档就可以找到。
此外,官方的仓库里还给出了一些代码案例,比如👉:github.com/microsoft/s...
还有这个👉:github.com/microsoft/s...
强烈推荐先看一下官方的介绍。
我这里就是使用依赖注入的方式来注入sk服务
csharp
private static IServiceCollection AddSemanticKernel3(this IServiceCollection services)
{
string deployment = "{模型名字,如gpt-3.5-turbo}";
var apikey = "{申请的apikey}";
services.AddKernel();
Uri uri = new Uri( "{端点地址,自己部署网关的话就改成自己的网关地址,如https://... .openai.azure.com/或者本地的网关}");
#pragma warning disable SKEXP0010 // 类型仅用于评估,在将来的更新中可能会被更改或删除。取消此诊断以继续。
services.AddOpenAIChatCompletion(deployment, uri, apikey);
#pragma warning restore SKEXP0010 // 类型仅用于评估,在将来的更新中可能会被更改或删除。取消此诊断以继续。
return services;
}
准备一个本地服务
这里我们可以直接使用默认的日志服务,这也是官网教程里给出的例子,或者自己手搓一个,也可以直接集成系统之前写好的服务。
我这里是直接集成的原来的接口服务
csharp
public interface IUserAnswerRecordRepo : IExaminationRepository<UserAnswerRecord>
{
//各种接口服务
//...
}
增加插件
插件这个环节非常重要,算是sk框架最大的特色之一了,它提供了很多集成插件的方式,我这里只介绍使用本地函数封装成插件的方式
csharp
public class RecordSearchPlugin(IServiceProvider serviceProvider)
{
private IServiceProvider _serviceProvider = serviceProvider;
[KernelFunction("get_userrecord_by_id")]
[Description("Get the record by id")]
[return: Description("The record of the user; will return null if the user dose not exist")]
public async Task<DbServices.Dtos.Exam.UserAnswerRecord.UserAnswerSubmitRecordDto?> GetUserRecordByIdAsync(long id)
{
var _recordRepo = _serviceProvider.GetRequiredService<IRecordBase>();
if (!await _recordRepo.getAnyAsync(u => u.Id == id))
{
return null;
}
return (await _recordRepo.getOneAsync(u => u.Id == id))
.Adapt<DbServices.Dtos.Exam.UserAnswerRecord.UserAnswerSubmitRecordDto>();
}
}
这个插件的功能跟简单就是提供一个id,然后找到相应的记录。注意,插件的封装要保证单一原则,如果我们需要复杂的交互,可以封装多个插件,但最大不要超过20个,官方对此给出的解释如下。
OpenAI也给出过类似的规定:platform.openai.com/docs/guides...
还有就是,我这里面给插件的提示词用的都是英文,因为我还是按照文档的介绍来改造的,所以就先用英文进行的测试,实际上使用中文应该也是ok的。
调用
事实上,在调用之前,我们还可以编排一下插件的执行逻辑,这里我还没有试到那一步,所以先跳过了,官方的介绍在这里👉:learn.microsoft.com/en-us/seman...
csharp
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly Kernel _kernel;
private readonly IServiceProvider _serviceProvider;
public HomeController(ILogger<HomeController> logger,Kernel kernel, IServiceProvider serviceProvider)
{
_logger = logger;
_kernel = kernel.Clone();
_serviceProvider = serviceProvider;
}
public async Task<IActionResult> Test()
{
_kernel.Plugins.AddFromType<RecordSearchPlugin>("RecordSearch", _serviceProvider);
// 获取聊天完成服务
var chatCompletionService = _kernel.GetRequiredService<IChatCompletionService>();
// 启用自动函数调用
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
};
ChatHistory chatHistory = [];
string? input = null;
chatHistory.AddUserMessage("Please get the record which id is 617235768115590");
var chatResult = await chatCompletionService.GetChatMessageContentAsync(
chatHistory,
openAIPromptExecutionSettings,
_kernel);
Console.Write($"\nAssistant : {chatResult}\n");
return Content("查看控制台输出内容");
}
}
执行之后,看到控制台输出的内容
完成✌️
总结
浅尝基本就是这样啦,后续的工作就是一些优化方面的了,可以发挥无限的想象力去给我们的线上系统插上翅膀了,我感觉这就是本地化的"小爱同学",比如以前审查资料,需要人一个个的去找,去查用户提交的信息,有了AI加持之后,我们可以像使唤家里的"小爱同学"一样,让它帮我们去完成这类重复性的工作。
比如给他一个命令"帮我把今天考试所有考90分的同学导出来",或者更复杂一点,"帮我把今天考试所有得90分的同学导出来,并给他们发送邮件"等等。
当然了,基础的功能还是要我们自己来开发,AI作为代理角色,完成的是以前系统操作人完成的工作,而不是开发者,或者不完全是开发者。
好了,先聊这么多,下次继续!