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#本地函数逻辑链式调用助力大模型落地的更多相关文章

  1. python 函数的链式调用(一个函数调用使用两个括号)

    # python 函数的链式调用 def funcA(a): def funcB(b): for a_each in a: x = funcB(a_each) return x return func ...

  2. WebView中Js与Android本地函数的相互调用

    介绍 随着Html5的普及,html在表现力上不一定比原生应用差,并且有很强的扩展兼容性,所以越来越多的应用是采用Html与Android原生混合开发模式实现. 既然要实现混合开发,那么Js与Andr ...

  3. Android使用JNI(从java调用本地函数)

    当编写一个混合有本地C代码和Java的应用程序时,需要使用Java本地接口(JNI)作为连接桥梁.JNI作为一个软件层和API,允许使用本地代码调用Java对象的方法,同时也允许在Java方法中调用本 ...

  4. Scala学习笔记(六):本地函数、头等函数、占位符和部分应用函数

    本地函数 可以在方法内定义方法,这种方法叫本地函数,本地函数可以直接访问父函数的参数 def parent(x: Int, y: Int): Unit ={ def child(y:Int) = y ...

  5. Arcgis GDB文件地理数据库、shapefile、coverage 和其他基于文件的数据源所支持的函数的完整列表

    函数 以下是文件地理数据库.shapefile.coverage 和其他基于文件的数据源所支持的函数的完整列表.个人地理数据库和 ArcSDE 地理数据库也支持这些函数,但这些数据源可能使用不同的语法 ...

  6. C# 中的本地函数

    今天我们来聊一聊 C# 中的本地函数.本地函数是从 C# 7.0 开始引入,并在 C# 8.0 和 C# 9.0 中加以完善的. 引入本地函数的原因 我们来看一下微软 C# 语言首席设计师 Mads ...

  7. Hadoop详解(04-1) - 基于hadoop3.1.3配置Windows10本地开发运行环境

    Hadoop详解(04-1) - 基于hadoop3.1.3配置Windows10本地开发运行环境 环境准备 安装jdk环境 安装idea 配置maven 搭建好的hadoop集群 配置hadoop ...

  8. 基于ChatGPT的API的C#接入研究

    今年开年,最火的莫过于ChatGPT的相关讨论,这个提供了非常强大的AI处理,并且整个平台也提供了很多对应的API进行接入的处理,使得我们可以在各种程序上无缝接入AI的后端处理,从而实现智能AI的各种 ...

  9. OpenTranslator:一款基于ChatGPT API的翻译神器

    这是一款使用 ChatGPT API 进行划词翻译和文本润色的浏览器插件.借助了 ChatGPT 强大的翻译能力,它将帮助您更流畅地阅读外语和编辑外语. 它能干啥 一. 可翻译 二. 可润色 三. 可 ...

  10. DeepSpeed Chat: 一键式RLHF训练,让你的类ChatGPT千亿大模型提速省钱15倍

    DeepSpeed Chat: 一键式RLHF训练,让你的类ChatGPT千亿大模型提速省钱15倍 1. 概述 近日来,ChatGPT及类似模型引发了人工智能(AI)领域的一场风潮. 这场风潮对数字世 ...

随机推荐

  1. Nmap基本使用【速查版】

    列举远程机器开放的端口 nmap [域名] 列举远程机器开放的端口和服务 nmap --dns-servers 8.8.8.8 [域名] nmap进行探测之前要把域名通过DNS服务器解析为IP地址,我 ...

  2. 内网搭建DNS服务器

    DNS:Domain Name Service,域名解析服务 监听端口:udp/53,tcp/53 应用程序:bind 根域:. 一级域: 组织域:.com, .org, .net, .mil, .e ...

  3. [Java EE]辨析: POJO(PO / DTO / VO) | BO/DO | DAO

    概念不清,会很影响开发中的逻辑性和条理性,进而影响接口设计,代码编写的质量. 网络上大家对这些个概念的探究很多,但终究没有一个统一的说法. 不论哪家解释,我觉得最重要的是: 1)词汇之间的解释统一: ...

  4. ModelAndView方法的返回值类型

    一.ModelAndView @RequestMapping("/selectById") public ModelAndView queryById(Integer id){ M ...

  5. Vulnhub Development靶场 Walkthrough

    Recon 首先使用netdiscover进行二层Arp扫描. ┌──(kali㉿kali)-[~] └─$ sudo netdiscover -r 192.168.80.0/24 Currently ...

  6. day68:Vue:类值操作/style样式操作&v-for&filer/computed/watch&生命周期钩子函数&axios

    目录 1.类值操作 :class 2.style操作样式 :style 3:示例:选项卡 @click+:class 4.v-for示例:循环商品显示 5.过滤器:filter 6.计算属性:comp ...

  7. 记录 跨境猴 shopee 虾皮 货代 贴单 仓储 打包系统 介绍

    shopee虾皮|lazada|shopify代打包贴单仓储系统(简称:跨境猴)基于ThinkPHP6.0+AdminLTE-2.3.11+JQuery+PHP7.0+Swoole+Mysql5.5+ ...

  8. GIL和池的概念

    1.GIL概念 1. 什么是GIL(为Cpython解释器) GIL本身就是一把互斥锁. 原理都一样. 都是让并发的线程同一时间只能执行一个 所以有了GIL的存在. 同一进程下的多个线程同一时刻只能有 ...

  9. React 组件进入和退出动画实现

    在实现一个React中的弹框组件时,想给组件加个进入和退出动画,但发现React没有Vue3那样现成的api,因此需要自己设计. 主要思路为给组件添加一个state来选择className,不同的cl ...

  10. 记一次 .NET某医疗器械清洗系统 卡死分析

    一:背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧. 二:WinDbg 分析 1. 为什么会卡死 因为是窗体程 ...