写点什么

再尝 Semantic Kernel

作者:为自己带盐
  • 2025-02-21
    河北
  • 本文字数:2717 字

    阅读完需:约 9 分钟

背景

书接上回《浅尝semantic kernel》


上次只试了试 SK 框架的插件特性,这次试了下编排的特性,也就是把多个本地任务按顺序按逻辑执行,没想到效果也是出奇的好!

关键步骤

架构层面的代码本篇不在赘述,这里只做一些使用 planning 的效果展示。


还是以上篇提到的审查材料这个工作为目标。

编排任务

我这里,编排的任务逻辑如下图



这个逻辑,其实就是人作为审查人员的工作流程,审查时,先一个个找到每一份申报材料,然后再打开他们的材料,看看填写是否有误,有误的呢,就退回去,没有的,就初审通过。


流程虽然简单,但是相对繁琐,而且,审查的过程容易出错,那这个工作能不能交给 AI 来执行。


我利用 SK 框架来测试了一番,发现效果还不错。

创建插件

查询

上述任务流的第一个步骤是查询,先找到关键信息,我们需要准备这样一个方法。



[KernelFunction("get_apply_by_number")] [Description("通过赛队编号获取赛队信息")] [return: Description("如果编号存在,返回编号所属赛队详情,反之返回空")] public ApplyModel? GetApplyInfoByNumber(string number) { Assistant.Logger.Debug("找人插件正确执行"); ///具体业务代码不赘述 }
复制代码


需要多说一句的是,这一步里返回的值里包含了一个附件,也就是用户提交的文档材料,下一个步骤需要用到它,实际上每个人的场景都不一样,我这里只是刚好遇到了这种上一步的输出,是下一步的输入的情况。

识别

获取到文档材料之后,要先对材料进行识别


[KernelFunction("get_document_info")][Description("识别文档内容并返回识别后的信息")][return: Description("当文件路径并存在或者识别失败时,返回空,反之则返回识别后的信息")]public async Task<OcrResponseDto?> GetDocumentInfo(string pdfpath){    Assistant.Logger.Warning("文档识别插件正确执行");   ///识别文档的业务代码}
复制代码


识别完成后,转入第下一步

比对信息

这一步,完全是交给大模型来完成的,我们只需要在提示词中告知它怎么做这个工作就好了!


后面提示词部分会具体展示

发送通知

我这里是创建了一个发送邮件的插件,将审核的结果,邮件通知到指定的用户


[KernelFunction("send_notice_by_email")][Description("发送通知到指定邮箱")][return: Description("如果发送任务执行成功直接返回true,反之返回false")]public async Task<bool> SendNoticeByEmail(string email, string content){    Assistant.Logger.Warning("通知插件正确执行");   ///邮件发送的业务代码}
复制代码

编排工作流

有了 SK 框架的帮助,我们可以方便的完成插件的创建,再结合大模型提供的推理能力,只需要通过合适的提示词,就可以把我们创建的多个工作流串联起来,完成复杂的工作,代码如下👇


[HttpPost,ValidateAntiForgeryToken]public async Task<IActionResult> CheckingChat(ChatForChecking chatModel){    if (string.IsNullOrWhiteSpace(chatModel.prompt))        return Json(_resp.error("无输入"));    _kernel.Plugins.AddFromType<GetReportInfo>(nameof(GetReportInfo), _serviceProvider);    _kernel.Plugins.AddFromType<Notice>(nameof(Notice), _serviceProvider);    // 获取聊天完成服务    var chatCompletionService = _kernel.GetRequiredService<IChatCompletionService>();
// 启用自动函数调用 OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
ChatHistory history = []; history.AddSystemMessage($"你是一个材料审查员,需要用到2个参数,一个从数据库读取的,完整的,结构化的申报信息参数,包括赛队编号,赛项,成员,学校,指导教师,邮箱等,另一个是通过文档识别接口识别出来的材料信息,里面逐行输出了提取的材料信息和置信度," + $"这个材料是用户提交的附件信息,里面包含了全部或者部分第一个参数里的信息。" + $"你的工作如下:" + $"1.从对话信息中提取赛队编号信息,并调用本地插件进行检索,注意,赛队编号格式是2个连续大写字母加8为数字,如:AA24091234,其中第一位大写字母只能是A,B,C,D,前两位数字代表年份,如24代表2024年,23代表2023年;如格式错误则直接返回异常输入,后续工作不必再继续执行;" + $"2.如果步骤1返回了结果,那么整个结果将作为第一个输入参数,而其中有一个FileName属性,表示了文档材料的保存路径,使用该属性值再次调用合适的本地插件,识别文档信息,作为第二个输入参数;" + $"3.如果步骤2执行正常,将2个参数进行比对,如果你认为第二个参数归属于第一个参数的概率很高,也就是该材料提交的信息是正确的,那么通过第一个参数中的Email属性,调用合适的本地插件发送通知邮件,告知他材料审核通过;反之,那就输出“我无法确认该材料真实性,请联系人工审查员处理”" + $"注意,每个指令只执行1次,1次不成功就终止,不要重试;" ); if (chatModel.messages!=null && chatModel.messages.Length != 0) { foreach(var item in chatModel.messages) { AuthorRole role = AuthorRole.User; if (item.role.Equals("assistant",StringComparison.OrdinalIgnoreCase)) role = AuthorRole.Assistant; else if (item.role.Equals("tool", StringComparison.OrdinalIgnoreCase)) role = AuthorRole.Tool; history.AddMessage(role, item.message); } } history.AddUserMessage(chatModel.prompt); var chatResult = await chatCompletionService.GetChatMessageContentAsync( history, executionSettings: openAIPromptExecutionSettings, _kernel);
Console.Write($"\nAssistant : {chatResult}\n");
return Json(chatResult);}
复制代码

效果

输入

响应

为了能看到每个插件的执行步骤,我在代码里打了日志

第一步,找人

第二步,识别文档

第三步,比对(大模型自动执行)

这一步控制台没输出,因为我的提示词里让他自行判断,如果确定材料正确就执行通知那一步去了,给一个我之前单独测试这一步的截图


第四步,通知

到此,核心工作结束,发送邮件通知




⚠️:注意哈,这一顿操作下来,token 的消耗少不了,我这一次了一下消耗了 15000 左右个 token,有正好也做到这的小伙伴,留意一下哈。

总结

对 AI 的探索和落地,真的越来越上头了!


这也是农历 2024 年最后一篇啦,祝大家新年快乐!


发布于: 刚刚阅读数: 5
用户头像

学着写代码 2019-04-11 加入

是一枚,热爱技术,天赋不高,又有点轴,的猿。。

评论

发布
暂无评论
再尝Semantic Kernel_智能体_为自己带盐_InfoQ写作社区