-
-
Notifications
You must be signed in to change notification settings - Fork 529
Function Calling
Tolga Kayhan edited this page May 18, 2024
·
2 revisions
Code samples may appear a bit lengthy for function calls, but that does not necessarily mean they are complicated. We have two different samples. The first one demonstrates basic usage in a couple of different ways. The second example utilizes an experimental utility library, which is fairly easy to use, but as mentioned, it is still experimental.
public static async Task RunChatFunctionCallTest(IOpenAIService sdk)
{
ConsoleExtensions.WriteLine("Chat Tool Functions Call Testing is starting:", ConsoleColor.Cyan);
// example taken from:
// https://github.com/openai/openai-cookbook/blob/main/examples/How_to_call_functions_with_chat_models.ipynb
var fn1 = new FunctionDefinitionBuilder("get_current_weather", "Get the current weather")
.AddParameter("location", PropertyDefinition.DefineString("The city and state, e.g. San Francisco, CA"))
.AddParameter("format", PropertyDefinition.DefineEnum(new List<string> {"celsius", "fahrenheit"}, "The temperature unit to use. Infer this from the users location."))
.Validate()
.Build();
var fn2 = new FunctionDefinitionBuilder("get_n_day_weather_forecast", "Get an N-day weather forecast")
.AddParameter("location", new PropertyDefinition {Type = "string", Description = "The city and state, e.g. San Francisco, CA"})
.AddParameter("format", PropertyDefinition.DefineEnum(new List<string> {"celsius", "fahrenheit"}, "The temperature unit to use. Infer this from the users location."))
.AddParameter("num_days", PropertyDefinition.DefineInteger("The number of days to forecast"))
.Validate()
.Build();
var fn3 = new FunctionDefinitionBuilder("get_current_datetime", "Get the current date and time, e.g. 'Saturday, June 24, 2023 6:14:14 PM'")
.Build();
var fn4 = new FunctionDefinitionBuilder("identify_number_sequence", "Get a sequence of numbers present in the user message")
.AddParameter("values", PropertyDefinition.DefineArray(PropertyDefinition.DefineNumber("Sequence of numbers specified by the user")))
.Build();
try
{
ConsoleExtensions.WriteLine("Chat Function Call Test:", ConsoleColor.DarkCyan);
var request = new ChatCompletionCreateRequest
{
Messages = new List<ChatMessage>
{
ChatMessage.FromSystem("Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."),
ChatMessage.FromUser("Give me a weather report for Chicago, USA, for the next 5 days.")
},
Tools = new List<ToolDefinition> { ToolDefinition.DefineFunction(fn1), ToolDefinition.DefineFunction(fn2), ToolDefinition.DefineFunction(fn3), ToolDefinition.DefineFunction(fn4) },
// optionally, to force a specific function:
//ToolChoice = ToolChoice.FunctionChoice("get_current_weather"),
// or auto tool choice:
//ToolChoice = ToolChoice.Auto,
MaxTokens = 50,
Model = Models.Gpt_3_5_Turbo
};
var completionResult = await sdk.ChatCompletion.CreateCompletion(request);
/* expected output along the lines of:
Message:
Function call: get_n_day_weather_forecast
location: Chicago, USA
format: celsius
num_days: 5
*/
if (completionResult.Successful)
{
var choice = completionResult.Choices.First();
Console.WriteLine($"Message: {choice.Message.Content}");
var tools = choice.Message.ToolCalls;
if (tools != null)
{
request.Messages.Add(choice.Message);
Console.WriteLine($"Tools: {tools.Count}");
foreach (var toolCall in tools)
{
Console.WriteLine($" {toolCall.Id}: {toolCall.FunctionCall}");
var fn = toolCall.FunctionCall;
if (fn != null)
{
Console.WriteLine($" Function call: {fn.Name}");
foreach (var entry in fn.ParseArguments())
{
Console.WriteLine($" {entry.Key}: {entry.Value}");
}
if (fn.Name == "get_n_day_weather_forecast")
{
request.Messages.Add(ChatMessage.FromTool("10 Degrees", toolCall.Id!));
}
}
}
}
}
else
{
if (completionResult.Error == null)
{
throw new Exception("Unknown Error");
}
Console.WriteLine($"{completionResult.Error.Code}: {completionResult.Error.Message}");
}
var completionResultAfterTool = await sdk.ChatCompletion.CreateCompletion(request);
if (completionResultAfterTool.Successful)
{
Console.WriteLine(completionResultAfterTool.Choices.First().Message.Content);
}
else
{
if (completionResultAfterTool.Error == null)
{
throw new Exception("Unknown Error");
}
Console.WriteLine($"{completionResultAfterTool.Error.Code}: {completionResultAfterTool.Error.Message}");
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public static async Task RunChatFunctionCallTestAsStream(IOpenAIService sdk)
{
ConsoleExtensions.WriteLine("Chat Tool Functions Call Stream Testing is starting:", ConsoleColor.Cyan);
// example taken from:
// https://github.com/openai/openai-cookbook/blob/main/examples/How_to_call_functions_with_chat_models.ipynb
var fn1 = new FunctionDefinitionBuilder("get_current_weather", "Get the current weather")
.AddParameter("location", PropertyDefinition.DefineString("The city and state, e.g. San Francisco, CA"))
.AddParameter("format", PropertyDefinition.DefineEnum(new List<string> {"celsius", "fahrenheit"}, "The temperature unit to use. Infer this from the users location."))
.Validate()
.Build();
var fn2 = new FunctionDefinitionBuilder("get_n_day_weather_forecast", "Get an N-day weather forecast")
.AddParameter("location", new PropertyDefinition {Type = "string", Description = "The city and state, e.g. San Francisco, CA"})
.AddParameter("format", PropertyDefinition.DefineEnum(new List<string> {"celsius", "fahrenheit"}, "The temperature unit to use. Infer this from the users location."))
.AddParameter("num_days", PropertyDefinition.DefineInteger("The number of days to forecast"))
.Validate()
.Build();
var fn3 = new FunctionDefinitionBuilder("get_current_datetime", "Get the current date and time, e.g. 'Saturday, June 24, 2023 6:14:14 PM'")
.Build();
var fn4 = new FunctionDefinitionBuilder("identify_number_sequence", "Get a sequence of numbers present in the user message")
.AddParameter("values", PropertyDefinition.DefineArray(PropertyDefinition.DefineNumber("Sequence of numbers specified by the user")))
.Build();
var fn5 = new FunctionDefinitionBuilder("google_search", "Gets a result from Google Search")
.AddParameter("search_term", PropertyDefinition.DefineArray(PropertyDefinition.DefineNumber("Search Term")))
.Build();
var fn6 = new FunctionDefinitionBuilder("getURL", "Downloads the content of given website")
.AddParameter("URL", PropertyDefinition.DefineArray(PropertyDefinition.DefineNumber("Search Term")))
.Build();
try
{
ConsoleExtensions.WriteLine("Chat Function Call Test:", ConsoleColor.DarkCyan);
var request = new ChatCompletionCreateRequest
{
Messages = new List<ChatMessage>
{
ChatMessage.FromSystem("You are a bot that performs internet searches and also downloads content from websites."),
// to test weather forecast functions:
ChatMessage.FromUser("I need you to first search Google for \"Cat\" and, at the same time, download the contents from https://www.wired.com."),
//ChatMessage.FromUser("Give me a weather report for Chicago, USA, for the next 5 days and also current weather.")
// or to test array functions, use this instead:
// ChatMessage.FromUser("And also The combination is: One. Two. Three. Four. Five."),
},
Tools = new List<ToolDefinition> { ToolDefinition.DefineFunction(fn1), ToolDefinition.DefineFunction(fn2), ToolDefinition.DefineFunction(fn3), ToolDefinition.DefineFunction(fn4), ToolDefinition.DefineFunction(fn5), ToolDefinition.DefineFunction(fn6) },
// optionally, to force a specific function:
//ToolChoice = ToolChoice.FunctionChoice("get_current_weather"),
// or auto tool choice:
ToolChoice = ToolChoice.Auto,
//MaxTokens = 50,
Model = Models.Gpt_4_1106_preview
};
var completionResults = sdk.ChatCompletion.CreateCompletionAsStream(request);
/* when testing weather forecasts, expected output should be along the lines of:
Message:
Function call: get_n_day_weather_forecast
location: Chicago, USA
format: celsius
num_days: 5
*/
/* when testing array functions, expected output should be along the lines of:
Message:
Function call: identify_number_sequence
values: [1, 2, 3, 4, 5]
*/
var functionArguments = new Dictionary<int, string>();
await foreach (var completionResult in completionResults)
{
if (completionResult.Successful)
{
var choice = completionResult.Choices.First();
Console.WriteLine($"Message: {choice.Message.Content}");
var tools = choice.Message.ToolCalls;
if (tools != null)
{
request.Messages.Add(choice.Message);
Console.WriteLine($"Tools: {tools.Count}");
for (int i = 0; i < tools.Count; i++)
{
var toolCall = tools[i];
Console.WriteLine($" {toolCall.Id}: {toolCall.FunctionCall}");
var fn = toolCall.FunctionCall;
if (fn != null)
{
if (!string.IsNullOrEmpty(fn.Name))
{
Console.WriteLine($" Function call: {fn.Name}");
}
if (!string.IsNullOrEmpty(fn.Arguments))
{
if (functionArguments.TryGetValue(i, out var currentArguments))
{
currentArguments += fn.Arguments;
}
else
{
currentArguments = fn.Arguments;
}
functionArguments[i] = currentArguments;
fn.Arguments = currentArguments;
try
{
foreach (var entry in fn.ParseArguments())
{
Console.WriteLine($" {entry.Key}: {entry.Value}");
}
}
catch (Exception)
{
// ignore
}
}
if (fn.Name == "google_search")
{
request.Messages.Add(ChatMessage.FromTool("Tom", toolCall.Id!));
}
if (fn.Name == "getURL")
{
request.Messages.Add(ChatMessage.FromTool("News", toolCall.Id!));
}
}
}
}
}
else
{
if (completionResult.Error == null)
{
throw new Exception("Unknown Error");
}
Console.WriteLine($"{completionResult.Error.Code}: {completionResult.Error.Message}");
}
}
var completionResultsAfterTool = sdk.ChatCompletion.CreateCompletionAsStream(request);
await foreach (var completion in completionResultsAfterTool)
{
if (completion.Successful)
{
Console.Write(completion.Choices.First().Message.Content);
}
else
{
if (completion.Error == null)
{
throw new Exception("Unknown Error");
}
Console.WriteLine($"{completion.Error.Code}: {completion.Error.Message}");
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
For this example you also need to use Betalgo.OpenAI.Utilities
library
public static async Task ExerciseFunctionCalling(IOpenAIService openAIService)
{
var calculator = new Calculator();
var req = new ChatCompletionCreateRequest
{
//Functions = FunctionCallingHelper.GetFunctionDefinitions(calculator),
//Functions = FunctionCallingHelper.GetFunctionDefinitions(typeof(Calculator)),
Tools = FunctionCallingHelper.GetToolDefinitions<Calculator>(),
Messages = new List<ChatMessage>
{
ChatMessage.FromSystem("You are a helpful assistant."),
ChatMessage.FromUser("What is 2 + 2 * 6?") // GPT4 is needed for this
//ChatMessage.FromUser("What is 2 + 6?"), // GPT3.5 is enough for this
}
};
do
{
var reply = await openAIService.ChatCompletion.CreateCompletion(req, Models.Gpt_4_0613);
if (!reply.Successful)
{
Console.WriteLine(reply.Error?.Message);
break;
}
var response = reply.Choices.First().Message;
if (response.ToolCalls != null)
{
Console.WriteLine($"Invoking {response.ToolCalls.First().FunctionCall.Name} with params: {response.ToolCalls.First().FunctionCall.Arguments}");
}
else
{
Console.WriteLine(response.Content);
}
req.Messages.Add(response);
if (response.ToolCalls != null)
{
var functionCall = response.ToolCalls.First().FunctionCall;
var result = FunctionCallingHelper.CallFunction<float>(functionCall!, calculator);
response.Content = result.ToString(CultureInfo.CurrentCulture);
}
} while (req.Messages.Last().FunctionCall != null);
}
public class Calculator
{
public enum AdvancedOperators
{
Multiply,
Divide
}
[FunctionDescription("Adds two numbers.")]
public float Add(float a, float b)
{
return a + b;
}
[FunctionDescription("Subtracts two numbers.")]
public float Subtract(float a, float b)
{
return a - b;
}
[FunctionDescription("Performs advanced math operators on two numbers.")]
public float AdvancedMath(float a, float b, AdvancedOperators advancedOperator)
{
return advancedOperator switch
{
AdvancedOperators.Multiply => a * b,
AdvancedOperators.Divide => a / b,
_ => throw new ArgumentOutOfRangeException(nameof(advancedOperator), advancedOperator, null)
};
}
}