首页 > 编程语言 >基于ChatGPT函数调用来实现C#本地函数逻辑链式调用助力大模型落地

基于ChatGPT函数调用来实现C#本地函数逻辑链式调用助力大模型落地

时间:2023-06-19 16:36:07浏览次数:43  
标签:调用 C# 函数调用 new gpt input ChatGPT public

  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 = "[email protected]";
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平替(比如基于函数调用+低代码)。。。

 

标签:调用,C#,函数调用,new,gpt,input,ChatGPT,public
From: https://www.cnblogs.com/gmmy/p/17491444.html

相关文章

  • 使用NamedParameterJdbcTemplate指定命名参数
    在本文中,我们将介绍如何在连接到后端Postgres数据库的Spring启动应用程序中使用NamedParameterJdbcTemplate。我们将使用NamedParameterJdbcTemplate从PostgresDB插入,更新和删除员工。为了保持设计的合理性,我将dao,service和controller分开了。服务只是本文的一个转折点。概观Named......
  • nginx配置多个配置文件,nginx配置多个conf的方式 播报文章
    可以通过在nginx.conf文件中使用include关键字来引入多个子配置文件,从而实现对Nginx的多配置管理。下面是简单的操作步骤:  1.进入Nginx的conf目录(通常是/etc/nginx或者/usr/local/nginx/conf),创建一个名为conf.d的目录,用于存放多个子配置文件:  mkdir......
  • Docker Compose 引用环境变量
    ComposeCLI与环境变量ComposeCLI(composecommand-line即docker-compose程序)能够识别名称为COMPOSE_PROJECT_NAME和COMPOSE_FILE等环境变量(具体支持的环境变量请参考这里)。比如我们可以通过这两个环境变量为docker-compose指定project的名称和配置文件:$exportC......
  • fcntl文件枷锁模块
    fcntl模块本模块基于文件描述符来进行文件控制和I/O控制。它是Unix系统调用fcntl()和ioctl()的接口。关于这些调用的完整描述,请参阅Unix手册的fcntl(2)和ioctl(2)页面。flock介绍fcntl.flock(f,operation)f:文件描述符operation:操作fcntl.LOCK_UN......
  • 数据库信息速递 阿里巴巴的分布式数据库OceanBase旨在进军中国以外的市场 (翻译)...
    该分布式SQL兼容数据库支持混合事务分析处理(HTAP),可以在低规格的机器上运行,例如树莓派,该公司表示。阿里巴巴及其金融服务子公司蚂蚁金服已经为中国以外的市场推出了OceanBase分布式关系数据库的新版本。该版本名为OceanBase4.0,于上周发布,可以在单个低规格机器上运行,例如树莓派,并且......
  • Spring Batch:将数据从Web服务处理到MongoDB
    概观在这篇文章中,我们将介绍如何创建一个使用Web服务数据并将其插入MongoDB数据库的SpringBatch应用程序。要求阅读本文的开发人员必须熟悉SpringBatch(示例)和MongoDB。环境Mongo数据库部署在MLab中。请按照本快速入门中的步骤操作。批处理应用程序部署在Heroku PaaS中。详情  ......
  • ChatGPT:语言模型的进化与应用前景
    当代技术的进步为我们带来了各种令人兴奋的创新,其中ChatGPT(ChatbotGPT)是人工智能领域的一项重要成果。ChatGPT是基于GPT(GenerativePre-trainedTransformer)模型的聊天机器人,它利用自然语言处理和深度学习技术,使得与机器进行对话成为可能。ChatGPT的工作原理ChatGPT的核心是......
  • Java_Jdbc_连接池的testQuery/validationQuery设置
     JDBC连接池的testQuery/validationQuery设置 在《Tomcat中使用Connector/J连接MySQL的超时问题》帖子中想要增加对连接池中连接的测试/验证,防止数据库认为连接已死而Web应用服务器认为连接还有效的问题,Mysql文档中提到Tomcat文档中的例子中用的是validationQuery,但是网......
  • MariaDB_installing,starting and stoping,configuring,logging in
    InstallingMariaDBBinaryTarballsvia: https://mariadb.com/kb/en/installing-mariadb-binary-tarballs/ MariaDBBinarytarballsarenamedfollowingthepattern:mariadb-VERSION-OS.tar.gz.Besureto downloadthecorrectversionforyourmachine.Note: Someb......
  • useEffect useCallback 和 useMemo
    useEffectuseEffect可以帮助我们在DOM更新完成后执行某些副作用操作,如数据获取,设置订阅以及手动更改React组件中的DOM等有了useEffect,我们可以在函数组件中实现像类组件中的生命周期那样某个阶段做某件事情,具有:componentDidMountcomponentDidUpdatecomponentWillUnmou......