基于ChatGPT函数调用来实现C#本地函数逻辑链式调用助力大模型落地
6 月 13 日 OpenAI 官网突然发布了重磅的 ChatGPT 更新,我相信大家都看到了 ,除了调用降本和增加更长的上下文版本外,开发者们最关心的应该还是新的函数调用能力。通过这项能力模型在需要的时候可以调用函数并生成对应的 JSON 对象作为输出。这使开发人员能更准确地从模型获取结构化数据,实现从自然语言到 API 调用或数据库查询的转换,也可以用于从文本中提取结构化数据。如果说之前的ChatGPT只能基于提示词结合类似的工具来实现调用链提示(比如大火的python LLM自动化库LangChain或者微软的Semantic Kernel),那么现在官方下场直接提供函数调用接口,无疑在稳定性(基于三方库的函数调用主要是依赖提示词实现,其稳定性和提示词质量高度相关)和易用性上都上了一大台阶。
今天.NET社区相关的SDK终于更新到了新的版本可以支持函数调用。今天我们就以一个具体的案例来讲一下什么是函数调用,基于函数调用我们可以实现哪些能力,从而将一个只能聊天的大语言模型落地到更加真实的业务场景中。相关代码demo已经更新到了github:https://github.com/sd797994/ChatgptFunctionCallDemo
现在我们假设一个业务场景,假设用户需要询问今天或者明天某个城市的天气情况,并且将相关的查询发送一封邮件到某个目标地址。在传统的开发中,我们一般会定义一个表单,让用户选择城市和日期,然后点击发送。系统会调用天气接口获取到天气,然后通过一段模板文本将占位符中的城市+日期+天气状况替换成查询的实际内容,然后发送给目标邮箱。整个流程大体如下:

在没有chatgpt之前,以上这个简单的操作是需要用户通过相对规范的表单操作来实现的,就算是基于传统的自然语言模型去处理这个任务,也需要大量的语意识别训练来识别用户的语意,然后根据语意去硬编码一些过程调用才能实现以上逻辑。无论从开发的难度和用户体验上来讲,都达不到商业化的预期的。但是现在基于大语言模型和函数调用,以上这些功能只需要单个开发者用极短的时间即可实现。因为基于大语言模型本身的逻辑思维,它可以选择调用哪些函数来实现功能,而我们要做的仅仅是告诉它有哪些功能而已。

接下来我们就基于实际的操作看看AI是如何实现的,首先我们更新到最新官方推荐的社区SDK版本
<PackageReference Include="Betalgo.OpenAI" Version="7.1.0-beta" />
接下来我们需要定义一个函数调用库,这个调用库主要的作用就是将我们的函数以表达式编译的方式生成匿名委托缓存,同时使用反射生成ChatGpt可识别的函数命名规范,具体的调用库实现这里不再赘述,有兴趣的可以具体看看项目下的ChatGptFunctionCallProcessor相关实现,重点是讲讲如何调用openai的接口实现业务功能的:
首先定义一个日期函数,用于将用户口语化的日期转化成真实的日期,比如“今天”,“明天”转化成实际的日期来供天气函数查询。接着我们定义一个天气查询函数,用于查询对应城市的某日的天气情况,最后我们定义一个发邮件的函数,让gpt可以通过它来发送邮件,完整的类函数定义如下:
public class FunctionCallCentner
{
[Description("查询用户希望的日期对应的真实日期")]
public async Task<CommonOutput> GetDate(GetDayInput input)
{
await Task.CompletedTask;
Console.WriteLine($"system:GetDate函数调用触发,参数:city={input.DateType}");
return new CommonOutput() { data = new GetDayOutput { Date = DateTime.Now.AddDays(input.DateType == DateType.Yesterday ? -1 : input.DateType == DateType.Tomorrow ? 1 : input.DateType == DateType.DayAfterTomorrow ? 2 : 0).ToShortDateString(), }, Success = true };
}
[Description("根据城市和真实日期获取天气信息")]
public async Task<CommonOutput> GetWeather(GetWeatherInput input)
{
if (!DateTime.TryParse(input.Date, out _))
return new CommonOutput() { Success = false, message = "日期格式错误" };
await Task.CompletedTask;
Console.WriteLine($"system:GetWeather函数调用触发,参数:city={input.City},date={input.Date}");
return new CommonOutput() { data = new GetWeatherOutput { City = input.City, Date = input.Date, Weather = "overcast to cloudy", TemperatureRange = "22˚C-28˚C" }, Success = true };
}
[Description("向目标邮箱发送电子邮件")]
public async Task<CommonOutput> SendEmail(SendEmailInput input)
{
await Task.CompletedTask;
Console.WriteLine($"system:SendEmail函数调用触发,参数:targetemail={input.TargetEmail},content={input.Content}");
return new CommonOutput() { Success = true };
}
}
这里面的我就不做具体的实现了,只是打印了log而已。接着我们需要对这些入参和出参进行定义,如下:
public class GetDayInput
{
[Description("日期枚举")]
public DateType DateType { get; set; }
}
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum DateType
{
Yesterday,
Today,
Tomorrow,
DayAfterTomorrow
}
public class GetDayOutput
{
public string Date { get; set; }
}
public class GetWeatherInput
{
[Description("城市名称")]
public string City { get; set; }
[Description("真实日期,格式:yyyy/mm/dd")]
public string Date { get; set; }
}
public class GetWeatherOutput: GetWeatherInput
{
public string Weather { get; set; }
public string TemperatureRange { get; set; }
}
public class SendEmailInput
{
[Description("目标邮件地址")]
public string TargetEmail { get; set; }
[Description("邮件完整内容")]
public string Content { get; set; }
}
public class CommonOutput
{
public string message { get; set; }
public object data { get; set; }
public bool Success { get; set; }
}
可以看到无论是函数还是入参都需要编写Description特性,这是gpt理解这个函数的方法用途以及入参定义的关键,一定不能缺少。另外官方的demo中并没有涉及出参的描述,所以这里我也没有添加。猜测可能gpt会自动基于出参的内容自动化的提取结果。
接着我们编写具体的业务代码,这里的关键是当gpt返回结果时,我们需要根据gpt返回的操作(直接输出内容/函数调用)来判断,如果gpt要求函数调用,则我们需要调用本地函数后再组装成新的chatmessage[]再次调用gpt,也就是说其实本质上是多轮递归式的调用来实现的逻辑链,比如当我问“天气+邮件”时,gpt首先会告诉我调用天气,并给我对应的参数。我返回天气,gpt在组装邮件的内容并告诉我调用邮件,给我参数。我再调用发送邮件并返回操作成功。gpt最后判断任务结束,输出内容。核心业务如下:
var key = "sk-Ab...jW";
var openAiService = new OpenAIService(new OpenAiOptions()
{
ApiKey = key
});
var email = "testmyemail@goolg.com";
var userprompt = $"我想分别获取成都市今天和西安市明天的天气情况,并发送到{email}这个邮箱";
Console.WriteLine($"user:{userprompt}");
var center = new FunctionCallCentner();
var messages = new List<ChatMessage>
{
ChatMessage.FromSystem("You are a helpful assistant."),
ChatMessage.FromUser(userprompt)
};
await SessionExecute(messages);
async Task SessionExecute(List<ChatMessage> messages)
{
var completionResult = await openAiService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest
{
Messages = messages,
Model = Models.Gpt_3_5_Turbo_0613,
Functions = center.GetDefinition().ToList()
});
if (completionResult.Successful)
{
if (completionResult.Choices.First().Message.FunctionCall != null)
{
completionResult.Choices.First().Message.Content = "";
messages.Add(completionResult.Choices.First().Message);
messages.Add(await center.CallFunction(completionResult.Choices.First().Message.FunctionCall.Name, completionResult.Choices.First().Message.FunctionCall.ParseArguments()));
await SessionExecute(messages);
}
else
{
Console.WriteLine("assistant:" + completionResult.Choices.First().Message.Content);
}
}
}
接下来我们看看gpt实际的运行情况:
可以看到gpt很聪明的将我们的任务进行了拆解,并且正确的调用了对应的函数(比如很聪明的基于用户模糊的问题“今天”“明天”去调用日期函数并且传递正确的枚举值),获取到每一轮函数返回的内容后,执行了正确的发邮件这个动作。并且最后贴心的告诉用户它已经执行完毕任务,让用户及时检查自己的邮箱。
如果说半年前chatgpt的横空出世还仅仅是让人觉得它仅仅是一个大号的聊天plus的话,那么现在基于函数调用让我们见识到了其恐怖的任务拆解,调度执行能力。通过对零散的API进行组装来实现用户复杂需求的实现,这在以往的开发中是根本无法想象的存在,说实话这东西将会颠覆现有的IT软件开发/交互,甚至很多IT岗位将面临被GPT平替(比如基于函数调用+低代码)。。。
基于ChatGPT函数调用来实现C#本地函数逻辑链式调用助力大模型落地的更多相关文章
- python 函数的链式调用(一个函数调用使用两个括号)
# python 函数的链式调用 def funcA(a): def funcB(b): for a_each in a: x = funcB(a_each) return x return func ...
- WebView中Js与Android本地函数的相互调用
介绍 随着Html5的普及,html在表现力上不一定比原生应用差,并且有很强的扩展兼容性,所以越来越多的应用是采用Html与Android原生混合开发模式实现. 既然要实现混合开发,那么Js与Andr ...
- Android使用JNI(从java调用本地函数)
当编写一个混合有本地C代码和Java的应用程序时,需要使用Java本地接口(JNI)作为连接桥梁.JNI作为一个软件层和API,允许使用本地代码调用Java对象的方法,同时也允许在Java方法中调用本 ...
- Scala学习笔记(六):本地函数、头等函数、占位符和部分应用函数
本地函数 可以在方法内定义方法,这种方法叫本地函数,本地函数可以直接访问父函数的参数 def parent(x: Int, y: Int): Unit ={ def child(y:Int) = y ...
- Arcgis GDB文件地理数据库、shapefile、coverage 和其他基于文件的数据源所支持的函数的完整列表
函数 以下是文件地理数据库.shapefile.coverage 和其他基于文件的数据源所支持的函数的完整列表.个人地理数据库和 ArcSDE 地理数据库也支持这些函数,但这些数据源可能使用不同的语法 ...
- C# 中的本地函数
今天我们来聊一聊 C# 中的本地函数.本地函数是从 C# 7.0 开始引入,并在 C# 8.0 和 C# 9.0 中加以完善的. 引入本地函数的原因 我们来看一下微软 C# 语言首席设计师 Mads ...
- Hadoop详解(04-1) - 基于hadoop3.1.3配置Windows10本地开发运行环境
Hadoop详解(04-1) - 基于hadoop3.1.3配置Windows10本地开发运行环境 环境准备 安装jdk环境 安装idea 配置maven 搭建好的hadoop集群 配置hadoop ...
- 基于ChatGPT的API的C#接入研究
今年开年,最火的莫过于ChatGPT的相关讨论,这个提供了非常强大的AI处理,并且整个平台也提供了很多对应的API进行接入的处理,使得我们可以在各种程序上无缝接入AI的后端处理,从而实现智能AI的各种 ...
- OpenTranslator:一款基于ChatGPT API的翻译神器
这是一款使用 ChatGPT API 进行划词翻译和文本润色的浏览器插件.借助了 ChatGPT 强大的翻译能力,它将帮助您更流畅地阅读外语和编辑外语. 它能干啥 一. 可翻译 二. 可润色 三. 可 ...
- DeepSpeed Chat: 一键式RLHF训练,让你的类ChatGPT千亿大模型提速省钱15倍
DeepSpeed Chat: 一键式RLHF训练,让你的类ChatGPT千亿大模型提速省钱15倍 1. 概述 近日来,ChatGPT及类似模型引发了人工智能(AI)领域的一场风潮. 这场风潮对数字世 ...
随机推荐
- 监听watch踏坑之旅!!!vuex中如果数组发生变换但是用watch你监听不到
vuex: SET_INFO(state,info) { console.log('info',info) state.info.unshift(info) state.info.pop() cons ...
- 一遍博客带你上手Servlet
概念 Servlet其实就是Java提供的一门动态web资源开发技术.本质就是一个接口. 快速入门 创建web项目,导入servlet依赖坐标(注意依赖范围scope,是provided,只在编译和测 ...
- CTF-NEFU校赛-题解
Write by NEFUNSI: ghosin 0ERROR 签到 signin 下载 signin.txt 打开得到一串 base64,解码得到 flag{we1come_t0_NEFUCTF!} ...
- odoo 开发入门教程系列-准备一些操作(Action)?
准备一些操作(Action)? 到目前为止,我们主要通过声明字段和视图来构建模块.在任何真实的业务场景中,我们都希望将一些业务逻辑链接到操作按钮.在我们的房地产示例中,我们希望能够: 取消或将房产设置 ...
- 二进制安装Kubernetes(k8s) v1.24.1 IPv4/IPv6双栈
二进制安装Kubernetes(k8s) v1.24.1 IPv4/IPv6双栈 Kubernetes 开源不易,帮忙点个star,谢谢了 介绍 kubernetes二进制安装 后续尽可能第一时间更新 ...
- Docker入门实践笔记-Dockerfile
镜像是一个打包文件,其中包含了应用程序及其运行所依赖的环境,例如文件系统.环境变量.配置参数等等 联合文件系统 容器镜像内部并不是一个平坦的结构,而是由许多的镜像层组成,每层都是只读不可修改修改的一组 ...
- C# 如何设计一个好用的日志库?【架构篇】
〇.前言 相信你在实际工作期间经常遇到或听到这样的说法: "我现在加一下日志,等会儿你再操作下." "只有在程序出问题以后才会知道打一个好的日志有多么重要.&qu ...
- ArcGIS切片服务获取切片方案xml文件(conf.xml)
在使用ArcGIS进行影像.地形等切片时,往往需要保持一致的切片方案才能够更好的加载地图服务. 本文介绍如何获取已经发布好的ArcGIS服务的切片方案xml文件. 当然切片xml文件还可以通过工具Ge ...
- Qt 加载 libjpeg 库出现“长跳转已经运行”错误
继上篇 Qt5.15.0 升级至 Qt5.15.9 遇到的一些错误 篇幅有点长,先说解决方法,在编译静态库时加上 -qt-libjpeg,编译出 libjpeg 库后,在项目中使用 #pragma c ...
- UE中根据场景模型,导出缩略图
在实际使用中,我们有了很多模型,但是有时候我们需要这些模型对应的缩略图,比如我有很多物品,我想弄个仓库,有2种方式,要么,弄个仓库场景,一个物体一个格子摆放第二种,就是为每个物体制作一个缩略图 如果一 ...