Function calling是ChatGPT的新功能,它允许我们在API调用中描述特定函数的特性,模型会根据我们的描述,智能地决定是否生成一个包含函数参数的JSON对象作为输出。这样我们就可以用ChatGPT和其他的工具或API进行交互,实现更多的功能。
例如:
1.将自然语言转换为数据库查询和API调用。我们可以使用这个特性来将普通语言转换为内部API调用,来回答问题或提供更好的选项。//文档地址 https://platform.openai.com/docs/guides/gpt/function-calling //官网示例 https://github.com/openai/openai-cookbook/blob/main/examples/How_to_call_functions_with_chat_models.ipynb例如我们想要模型调用一个名为get_current_weather的函数,来获取某个地点的当前天气情况,可以这样设置functions参数:
{ "get_current_weather": { "type": "object", "properties": { "location": { "type": "string", "description": "地点,比如:北京、上海" }, "unit": { "type": "string", "enum": ["摄氏度", "华氏度"] } }, "required": ["location", "unit"] } }这段JSON Schema描述的意思是:
{ "name": "get_current_weather" }这样,当向模型发送一个类似于“今天北京的天气如何?”的输入时,模型就会尝试生成一个符合get_current_weather函数签名的JSON对象作为输出,比如:
{ "location": "北京", "unit": "摄氏度" }当然,如果我们不添加function_call参数也可以,模型会根据我们的输入和函数描述,自动判断是否需要调用函数,以及调用哪个函数。但是这样可能会降低准确性和可靠性,所以建议尽量明确地告诉模型你想要它做什么。
public class RegistrationStatus { /// <summary> /// 挂号状态 /// </summary> public bool Status { get; set; } /// <summary> /// 状态说明 /// </summary> public string Message { get; set; } /// <summary> /// 挂号信息 /// </summary> public RegistrationInfo RegistrationInfo { get; set; } } public class RegistrationInfo { /// <summary> /// 序号 /// </summary> public int QueueNumber { get; set; } /// <summary> /// 手机号码 /// </summary> public string PhoneNumber { get; set; } /// <summary> /// 挂号时间 /// </summary> public DateOnly RegistrationTime { get; set; } /// <summary> /// 提交时间 /// </summary> public DateTime SubmissionTime { get; set; } }1、Register(挂号)
// 堆代码 duidaima.com //挂号 public static string Register(string phoneNumber, DateOnly registrationTime) { if (_registrationInfos.Any(m => m.PhoneNumber == phoneNumber && m.RegistrationTime == registrationTime)) return JsonSerializer.Serialize(new RegistrationStatus { Status = false, Message = "请勿重复挂号", }); var registrationInfo = new RegistrationInfo { QueueNumber = ++_queueNumber, PhoneNumber = phoneNumber, RegistrationTime = registrationTime, SubmissionTime = DateTime.Now }; _registrationInfos.Add(registrationInfo); // 获取给定位置的当前天气信息 return JsonSerializer.Serialize(new RegistrationStatus { Status = true, Message = "挂号成功", RegistrationInfo = registrationInfo }); }
//查询 public static string Query(string phoneNumber, DateOnly registrationTime) { var registrationInfo = _registrationInfos.FirstOrDefault(m => m.PhoneNumber == phoneNumber && m.RegistrationTime == registrationTime); var registrationStatus = new RegistrationStatus { Status = registrationInfo != null, Message = registrationInfo != null ? "查询到挂号信息" : "未查询到挂号信息", RegistrationInfo = registrationInfo }; return JsonSerializer.Serialize(registrationStatus); }为了方便调用,我们定义一个 AvailableFunctions 变量来存储函数名和函数委托。
Install-Package Betalgo.OpenAI -Version 7.1.2-beta
new FunctionDefinition { Name = "gpt_register", Description = "通过指定的手机号码和日期进行挂号", Parameters = new FunctionParameters { Type = "object", Properties = new Dictionary<string, FunctionParameterPropertyValue> { { "phoneNumber",new FunctionParameterPropertyValue { Type = "string", Description = "挂号使用的手机号码" } }, { "registrationTime" ,new FunctionParameterPropertyValue { Type = "string", Description = "挂号的日期,比如:明天", Enum = new[] { "今天","明天","后天" } } } }, Required = new[] { "phoneNumber", "registrationTime" } }, }我们定义了一个名为 gpt_register 的函数,它的功能是通过指定的手机号码和日期进行挂号。phoneNumber 参数的类型是字符串,它表示挂号使用的手机号码。registrationTime 参数的类型也是字符串,它表示挂号的日期,并且它只能取三个值:今天、明天或后天。这两个参数都是必须提供的,否则函数无法执行。
new FunctionDefinition { Name = "gpt_query", Description = "根据手机号码查询挂号信息", Parameters = new FunctionParameters { Type = "object", Properties = new Dictionary<string, FunctionParameterPropertyValue> { { "phoneNumber",new FunctionParameterPropertyValue { Type = "string", Description = "要查询的手机号码" } }, { "registrationTime" ,new FunctionParameterPropertyValue { Type = "string", Description = "挂号的日期,比如:明天", Enum = new[] { "今天","明天","后天" } } } }, Required = new[] { "phoneNumber", "registrationTime" } } }我们定义了一个名为 gpt_query 的函数,它的功能是根据手机号码及日期查询挂号信息。phoneNumber 是一个字符串类型,表示要查询的手机号码。registrationTime 也是一个字符串类型,表示挂号的日期。这个参数只能取三个值:今天,明天,或后天。这两个参数都是必须提供的,否则函数无法执行。
await foreach (var completion in completionResult) { if (cancellationToken.IsCancellationRequested) break; // 堆代码 duidaima.com if (completion.Successful) { var responseMessage = completion.Choices.First().Message; if (responseMessage.FunctionCall != null) { var functionName = responseMessage.FunctionCall.Name; var functionArgs = JsonSerializer.Deserialize<Dictionary<string, string>>(responseMessage.FunctionCall.Arguments); var functionToCall = ChatGPTFunctionCalling.AvailableFunctions[functionName]; var registrationTime = functionArgs.GetValueOrDefault("registrationTime") switch { "后天" => DateOnly.FromDateTime(DateTime.Now.AddDays(2)), "明天" => DateOnly.FromDateTime(DateTime.Now.AddDays(1)), _ => DateOnly.FromDateTime(DateTime.Now.Date), }; var functionResponse = functionToCall(functionArgs.GetValueOrDefault("phoneNumber"), registrationTime); chatCompletionCreateRequest.Messages.Add(ChatMessage.FromFunction(functionResponse, functionName)); var completionTwo = await _openAiService.ChatCompletion.CreateCompletion( new ChatCompletionCreateRequest { Messages = chatCompletionCreateRequest.Messages, Model = OpenAI.ObjectModels.Models.Gpt_3_5_Turbo_0613 }, cancellationToken: cancellationToken); if (completionTwo.Successful) { await Response.WriteAsync(completionTwo.Choices.First().Message.Content ?? "", cancellationToken); await Response.Body.FlushAsync(cancellationToken); } else { if (completionTwo.Error == null) throw new Exception("Unknown Error"); await Response.WriteAsync($"{completionTwo.Error.Code}: {completionTwo.Error.Message}"); await Response.Body.FlushAsync(); } } else { await Response.WriteAsync(responseMessage.Content ?? ""); await Response.Body.FlushAsync(); } } else { if (completion.Error == null) throw new Exception("Unknown Error"); await Response.WriteAsync($"{completion.Error.Code}: {completion.Error.Message}"); await Response.Body.FlushAsync(); } }整个调用过程可以分为以下几个步骤: