大模型应用开发初探 : 通用函数调用Planner
大家好,我是Edison。
上一篇,我们了解了什么是AI Agent以及如何用Semantic Kernel手搓一个AI Agent。有朋友留言说,自动函数调用对大模型有较高的要求,比如Azure OpenAI、智谱AI等这些收费的大模型产品就能很好地规划和处理函数调用,而像是一些开源的小参数量的模型例如qwen2-7b-instruct这种可能效果就不太好。刚好,之前在网上看到一位大佬的开源通用函数调用方案,于是重构了一下上一篇的Agent应用。
UniversalFunctionCaller
这个项目是一个封装了大模型对话的入口,有点类似我们在ASP.NET中写的Filter,在处理某个真正的请求时,给其设置一些横切面,例如PreHandle,PostHandle之类的方法供用户做自定义处理,最终完成所谓的AoP(面向横切面编程)的效果。这个项目做的事儿其实也就是封装了横切面,在真正将prompt发给LLM前,它会读取一些自定义的优点类似于预训练的prompt来对用户的prompt进行“增强“。例如,下面这个方法 GetAskFromHistory 就会来 设定一个函数调用的背景 以及 给出一些预置的训练提示词供大模型理解,妥妥的一个手动增强版提示词工程:
public class UniversalFunctionCaller
{
...... public async Task<string> RunAsync(ChatHistory askHistory)
{
var ask = await GetAskFromHistory(askHistory);
return await RunAsync(ask);
} private async Task<string> GetAskFromHistory(ChatHistory askHistory)
{
var sb = new StringBuilder();
var userAndAssistantMessages = askHistory.Where(h => h.Role == AuthorRole.Assistant || h.Role == AuthorRole.User);
foreach (var message in userAndAssistantMessages)
sb.AppendLine($"{message.Role.ToString()}: {message.Content}"); var extractAskFromHistoryPrompt = $@"阅读这段用户与助手之间的对话。
总结用户在最后一句话中希望助手做什么
##对话开始##
{sb.ToString()}
##对话结束##"; var extractAskResult = await _chatCompletion.GetChatMessageContentAsync(extractAskFromHistoryPrompt);
var ask = extractAskResult.Content;
return ask;
}
......
}
然后,它会初始化一个ChatHistory,提供一些示范性的对话,让大模型知道是否该进行函数调用 以及 如何调用:
private ChatHistory InitializeChatHistory(string ask)
{
var history = new ChatHistory();
history.Add(new ChatMessageContent(AuthorRole.User, "New task: 启动飞船"));
history.Add(new ChatMessageContent(AuthorRole.Assistant, "GetMySpaceshipName()"));
history.Add(new ChatMessageContent(AuthorRole.User, "长征七号"));
history.Add(new ChatMessageContent(AuthorRole.Assistant, "StartSpaceship(ship_name: \"长征七号\")"));
history.Add(new ChatMessageContent(AuthorRole.User, "飞船启动"));
history.Add(new ChatMessageContent(AuthorRole.Assistant, "Finished(finalmessage: \"'长征七号'飞船启动 \")"));return history;
}
而示范用的函数则将其封装到了一个预置的Plugin,我们暂且叫它 PreTrainingPlugin,它是一个internal访问的class,只用于对prompt进行增强即给出示例:
internal class PreTrainingPlugin
{
[KernelFunction, Description("当工作流程完成,没有更多的函数需要调用时,调用这个函数")]
public string Finished([Description("总结已完成的工作和结果,尽量简洁明了。")] string finalmessage)
{
return string.Empty;
//no actual implementation, for internal routing only
} [KernelFunction, Description("获取用户飞船的名称")]
public string GetMySpaceshipName()
{
return "长征七号";
} [KernelFunction, Description("启动飞船")]
public void StartSpaceship([Description("启动的飞船的名字")] string ship_name)
{
//no actual implementation, for internal routing only
}
}
同时,它会将你定义的Functions总结为一个string列表,然后作为可用的Function list 放到prompt中告诉大模型:
然后,就开始根据用户的prompt进行函数调用了,直到它认为不会再需要函数调用时就结束,这个方法的全部代码如下所示:
public async Task<string> RunAsync(string task)
{
// Initialize plugins
var plugins = _kernel.Plugins;
var internalPlugin = _kernel.Plugins.AddFromType<PreTrainingPlugin>(); // Convert plugins to text
var pluginsAsText = GetTemplatesAsTextPrompt3000(plugins); // Initialize function call and chat history
var nextFunctionCall = new FunctionCall { Name = ConfigConstants.FunctionCallStatus.Start };
var chatHistory = InitializeChatHistory(task); // Add new task to chat history
chatHistory.Add(new ChatMessageContent(AuthorRole.User, $"New task: {task}")); // Process function calls
for (int iteration = 0; iteration < 10 && nextFunctionCall.Name != ConfigConstants.FunctionCallStatus.Finished; iteration++)
{
nextFunctionCall = await GetNextFunctionCallAsync(chatHistory, pluginsAsText);
if (nextFunctionCall == null)
throw new Exception("The LLM is not compatible with this approach!"); // Add function call to chat history
var nextFunctionCallText = GetCallAsTextPrompt3000(nextFunctionCall);
chatHistory.AddAssistantMessage(nextFunctionCallText); // Invoke plugin and add response to chat history
var pluginResponse = await InvokePluginAsync(nextFunctionCall);
chatHistory.AddUserMessage(pluginResponse);
} // Remove internal plugin
_kernel.Plugins.Remove(internalPlugin); // Check if task was completed successfully
if (nextFunctionCall.Name == ConfigConstants.FunctionCallStatus.Finished)
{
var finalMessage = nextFunctionCall.Parameters[0].Value.ToString();
return finalMessage;
} throw new Exception("LLM could not finish workflow within 10 steps. Please consider increasing the number of steps!");
}
需要特别注意的是,不建议在一个prompt中涉及超过10次函数调用,这样效果不太好,处理速度也慢,验证也不太方便。
此外,在方法内部进行函数调用的分析时,自动加了一个如下所示的SystemMessage,用于设定一些通用的规则给到大模型进行理解:
private string GetLoopSystemMessage(string pluginsAsTextPrompt3000)
{
var systemPrompt = $@"你是一个计算机系统。
你只能使用TextPrompt3000指令,让用户调用对应的函数,而用户将作为另一个回答这些函数的计算机系统。
以下是您所需实现的目标,以及用户可以使用的函数列表。
您需要找出用户到达目标的下一步,并推荐一个TextPrompt3000函数调用。
您还会得到一个TextPrompt3000 Schema格式的函数列表。
TextPrompt3000格式的定义如下所示:
{GetTextPrompt300Explanation()}
##可用函数列表开始##
{pluginsAsTextPrompt3000}
##可用函数列表结束## 以下规则非常重要:
1) 你只能推荐一个函数及其参数,而不是多个函数
2) 你可以推荐的函数只存在于可用函数列表中
3) 你需要为该函数提供所有参数。不要在函数名或参数名中转义特殊字符,直接使用(如只写aaa_bbb,不要写成aaa\_bbb)
4) 你推荐的历史记录与函数需要对更接近目标有重要作用
5) 不要将函数相互嵌套。 遵循列表中的函数,这不是一个数学问题。 不要使用占位符。
我们只需要一个函数,下一个所需的函数。举个例子, 如果 function A() 需要在 function B()中当参数使用, 不要使用 B(A())。 而是,
如果A还没有被调用, 先调用 A()。返回的结果将在下一次迭代中在B中使用。
6) 不要推荐一个最近已经调用过的函数。 使用输出代替。 不要将占位符或函数作为其他函数的参数使用。
7) 只写出一个函数调用,不解释原因,不提供理由。您只能写出一个函数调用!
8) 当所有必需的函数都被调用,且计算机系统呈现了结果,调用Finished函数并展示结果。
9) 请使用中文回答。 如果你违反了任何这些规定,那么会有一只小猫死去。
";
return systemPrompt;
}
综上所示,这就是提示词工程的魔力所在!
更新后的AI Agent效果
这里我们快速对原来的WorkOrder Agent重构了一下,增加了 Use Function Planner 的 checkbox选项,如果你勾选了它,就会使用上面介绍的 UniversalFunctionCaller 进行prompt的包裹和预处理,然后再发给大模型 以及 进行函数调用。
这里我修改了使用的模型和平台信息,这里我们基于SiliconCloud来使用一个通义千问的小参数文本生成模型Qwen2-7B-Instruct来试试:
{
"LLM_API_PROVIDER": "QwenAI",
"LLM_API_MODEL": "Qwen/Qwen2-7B-Instruct",
"LLM_API_BASE_URL": "https://api.siliconflow.cn",
"LLM_API_KEY": "sk-**************" // Update this value to yours
}
具体效果如下图所示:
(1)没有使用Function Planner的效果
(2)使用了Function Planner的效果
可以看到,我的需求其实包含3个步骤:第一步是更新工单的Quantity,第二步是更新工单的状态,第三步是查询更新后的工单信息。而这几个步骤我们假设其实都是需要去调用MES WorkOrderService API才能获得的,这里我们的Agent理解到了要点,并分别调用了两个function实现了任务。
这个示例代码的结构如下所示:
我这里将UniversalFunctionCaller放到了解决方案中的Shared类库中了,源码来自Jenscaasen大佬的开源项目,中文翻译的prompt来自国内的mingupupu大佬的介绍。
小结
本文简单介绍了一种面向小参数量模型的通用函数调用方案,基于这个方案,我们可以在这类大模型上进行准确的函数调用,以便实现更可靠的AI Agent。
参考内容
国外的Jenscaasen大佬开源的这个项目 : https://github.com/Jenscaasen/UniversalLLMFunctionCaller
国内的mingupupu大佬的介绍和翻译:https://www.cnblogs.com/mingupupu/p/18385798
示例源码
GitHub:https://github.com/Coder-EdisonZhou/EDT.Agent.Demos
推荐学习
Microsoft Learn, 《Semantic Kernel 学习之路》
大模型应用开发初探 : 通用函数调用Planner的更多相关文章
- Unity3D游戏开发初探—2.初步了解3D模型基础
一.什么是3D模型? 1.1 3D模型概述 简而言之,3D模型就是三维的.立体的模型,D是英文Dimensions的缩写. 3D模型也可以说是用3Ds MAX建造的立体模型,包括各种建筑.人物.植被. ...
- 无插件的大模型浏览器Autodesk Viewer开发培训-武汉-2014年8月28日 9:00 – 12:00
武汉附近的同学们有福了,这是全球第一次关于Autodesk viewer的教室培训. :) 你可能已经在各种场合听过或看过Autodesk最新推出的大模型浏览器,这是无需插件的浏览器模型,支持几十种数 ...
- 基于Unity的AR开发初探:第一个AR应用程序
记得2014年曾经写过一个Unity3D的游戏开发初探系列,收获了很多好评和鼓励,不过自那之后再也没有用过Unity,因为没有相关的需求让我能用到.目前公司有一个App开发的需求,想要融合一下AR到A ...
- Web开发初探(系统理解Web知识点)
一.Web开发介绍 我们看到的网页通过代码来实现的 ,这些代码由浏览器解释并渲染成你看到的丰富多彩的页面效果. 这个浏览器就相当于Python的解释器,专门负责解释和执行(渲染)网页代码. 写网页的代 ...
- 华为高级研究员谢凌曦:下一代AI将走向何方?盘古大模型探路之旅
摘要:为了更深入理解千亿参数的盘古大模型,华为云社区采访到了华为云EI盘古团队高级研究员谢凌曦.谢博士以非常通俗的方式为我们娓娓道来了盘古大模型研发的"前世今生",以及它背后的艰难 ...
- DeepSpeed Chat: 一键式RLHF训练,让你的类ChatGPT千亿大模型提速省钱15倍
DeepSpeed Chat: 一键式RLHF训练,让你的类ChatGPT千亿大模型提速省钱15倍 1. 概述 近日来,ChatGPT及类似模型引发了人工智能(AI)领域的一场风潮. 这场风潮对数字世 ...
- winform快速开发平台 -> 通用权限管理之动态菜单
这几个月一直忙APP的项目,没来得及更新项目,想想该抽出时间整理一下开发思路,跟大家分享,同时也希望得到宝贵的建议. 先说一下我们的权限管理的的设计思路,首先一个企业信息化管理系统一定会用到权限管理, ...
- [web建站] 极客WEB大前端专家级开发工程师培训视频教程
极客WEB大前端专家级开发工程师培训视频教程 教程下载地址: http://www.fu83.cn/thread-355-1-1.html 课程目录:1.走进前端工程师的世界HTML51.HTML5 ...
- 【原创】开发Kafka通用数据平台中间件
开发Kafka通用数据平台中间件 (含本次项目全部代码及资源) 目录: 一. Kafka概述 二. Kafka启动命令 三.我们为什么使用Kafka 四. Kafka数据平台中间件设计及代码解析 五. ...
- 编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则)
编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则) 目录 建议1: 不要在常量和变量中出现易混淆的字母 建议2: 莫让常量蜕变成变量 建议3: 三元操作符的类型务 ...
随机推荐
- 【译】使 Visual Studio 更加可视化
任何 Web.桌面或移动开发人员都经常使用图像.你可以从 C#.HTML.XAML.CSS.C++.VB.TypeScript 甚至代码注释中引用它们.有些图像是本地的,有些存在于线上或网络共享中,而 ...
- CF1204A 题解
洛谷链接&CF 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 给定一个二进制字符串 \(S\),求这个二进制数包含 \(4 ^ k\) 的个数. 特殊的:若 ...
- 玄机-第二章日志分析-mysql应急响应
目录 前言 简介 应急开始 准备工作 日志分析 步骤 1 步骤 2 步骤 3 步骤 4 总结 补充mysql中的/var/log/mysql/erro.log 记录上传文件信息的原因 前言 这里应急需 ...
- Kubernetes 部署Dashboard UI
实践环境 CentOS-7-x86_64-DVD-1810 Docker 19.03.9 Kubernetes version: v1.20.5 发布Dashboard 可以通过运行以下命令部署Das ...
- Ubuntu-20.04.6-server安装MySQL实现远程连接
Ubuntu-20.04.6-server安装MySQL,修改密码 安装MySQL 一.查看是否安装数据库 mysql --version 二.更新系统中的所有软件包和存储库 sudo apt upd ...
- ubuntu18.04 环境下如何为 vscode 安装kite
传说中的Python环境下最好用的智能补全工具 kite =============================================================== 1. ...
- 记录一次实验室linux系统的GPU服务器死机排查过程——某显卡满负荷导致内核进程超时导致系统死机
在自己没有管理多台高负荷的ubuntu显卡服务器之前,我是万万想不到linux服务器居然也是如此容易死机的. 什么每个版本的TensorFlow调用显卡驱动时和内核不兼容,什么系统自动升级导致的显卡驱 ...
- 2024年Apache DolphinScheduler RoadMap:引领开源调度系统的未来
非常欢迎大家来到Apache DolphinScheduler社区!随着开源技术在全球范围内的快速发展,社区的贡献者 "同仁" 一直致力于构建一个强大而活跃的开源调度系统社区,为用 ...
- 一款.NET开发的AI无损放大工具
前言 今天大姚给大家分享一款由.NET开源(GPL-3.0 license).基于腾讯ARC Lab提供的Real-ESRGAN模型开发的AI无损放大工具:AI-Lossless-Zoomer. Re ...
- 高考志愿填报指南:使用AI阅读工具ChatDOC搭建专业、好用、免费的AI高考志愿填报系统
高考志愿填报指南:使用 ChatDOC 搭建专业.好用.免费的 AI 高考志愿填报系统 不说废话,直接上干货.针对高考志愿填报,这篇文章能为你提供以下内容:高考志愿填报专业数据.高考志愿填报分析思路. ...