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. TS 基础及在 Vue 中的实践:TypeScript 都发布 5.0 版本啦,现在不学更待何时!

    大家好,我是 Kagol,OpenTiny 开源社区运营,TinyVue 跨端.跨框架组件库核心贡献者,专注于前端组件库建设和开源社区运营. 微软于3月16日发布了 TypeScript 5.0 版本 ...

  2. 端口转发、Http Tunnel、内网穿透

    原文链接:https://www.yuque.com/tec-nine/architecture/mgxc71 SSH 命令帮助 命令行选项有: -a 禁止转发认证代理的连接. -A 允许转发认证代理 ...

  3. 系统评价——层次分析法AHP的R语言实现(四)

    对一个事物的评价往往会涉及多个因素或者多个指标,评价是在多个因素相互作用下的一个综合判断.多指标综合评价方法具有以下的特点:包含若干个指标,分别说明被评价对象的不同方面,评价方法最终要对被评价对象作出 ...

  4. window远程桌面之通过修改端口链接

      windows开启及连接远程桌面 技术标签: 后端开发  windows         桌面 -> 此电脑 图标右键 -> 属性 远程设置 远程桌面 -> 修改为允许远程连接到 ...

  5. C# 通过一个控制台打开另一个控制台

    现有个需求是通过一个主程序获取配置的线程数和进程数打开连一个控制台程序,将线程数和系统编码作为参数传给控制台程序. 下面附上Demo. 1 private static void Main(strin ...

  6. $.set() 处理非响应式案例

    <template> <div id="app"> <ul> <li v-for="item in list" :ke ...

  7. 任务拆解,悠然自得,自动版本的ChatGPT,AutoGPT自动人工智能AI任务实践(Python3.10)

    当我们使用ChatGPT完成某些工作的时候,往往需要多轮对话,比如让ChatGPT分析.翻译.总结一篇网上的文章或者文档,再将总结的结果以文本的形式存储在本地.过程中免不了要和ChatGPT" ...

  8. MQTT-发布与订阅的报文

    MQTT发布订阅流程 在MQTT发布/订阅模式中,一个客户端既可以是发布者,也可以是订阅者,也可以同时具备这两个身份.当客户端发布一条消息时,它会被发送到代理,然后代理将消息路由到该主题的所有订阅者. ...

  9. 安装node并创建vue项目

    1.多版本管理工具 nvm https://github.com/coreybutler/nvm-windows/releases nvm-setup.zip 2. 打开nvm文件夹下的setting ...

  10. 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(12)- 从SD/eMMC启动

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MXRT1xxx系列MCU的SD/eMMC卡启动. 最近在恩智浦官方社区上支持了一个关于 i.MXRT 从 SD 卡启动的案例 ...