From 6635d9a1d4eae9dd324956310062888a050b0ee9 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 14 Jun 2023 13:45:41 -0400 Subject: [PATCH 01/11] OpenAI-DotNet 6.7.0 - Added function calling to chat models - Added chunk size parameter to embedding request --- OpenAI-DotNet/OpenAI-DotNet.csproj | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/OpenAI-DotNet/OpenAI-DotNet.csproj b/OpenAI-DotNet/OpenAI-DotNet.csproj index 268dd919..5ec424f3 100644 --- a/OpenAI-DotNet/OpenAI-DotNet.csproj +++ b/OpenAI-DotNet/OpenAI-DotNet.csproj @@ -17,8 +17,11 @@ More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet- https://github.com/RageAgainstThePixel/OpenAI-DotNet OpenAI, AI, ML, API, gpt-4, gpt-3.5-tubo, gpt-3, chatGPT, chat-gpt, gpt-2, gpt OpenAI API - 6.8.7 - Version 6.8.7 + 6.7.0 + Version 6.7.0 +- Added function calling to chat models +- Added chunk size parameter to embedding request +Version 6.8.7 - Added ToString and string operator to Moderation Scores Version 6.8.6 - Populated finish reason in streaming chat final message content From 144aeaaab16e14c4040123d0972611e3cd848ce7 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 14 Jun 2023 13:49:41 -0400 Subject: [PATCH 02/11] added function role --- OpenAI-DotNet/Chat/Role.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenAI-DotNet/Chat/Role.cs b/OpenAI-DotNet/Chat/Role.cs index 5a903871..6d543c4c 100644 --- a/OpenAI-DotNet/Chat/Role.cs +++ b/OpenAI-DotNet/Chat/Role.cs @@ -10,5 +10,7 @@ public enum Role Assistant = 2, [EnumMember(Value = "user")] User = 3, + [EnumMember(Value = "function")] + Function = 4, } } \ No newline at end of file From 90ecab4a3d95cb31b4559b180cec29911813749c Mon Sep 17 00:00:00 2001 From: damiant3 <131276826+damiant3@users.noreply.github.com> Date: Thu, 15 Jun 2023 09:51:43 -0700 Subject: [PATCH 03/11] Initial Changes for Functions (#117) Co-authored-by: Stephen Hodgson --- OpenAI-DotNet-Tests/TestFixture_03_Chat.cs | 97 ++++++++++++++++++++++ OpenAI-DotNet/Chat/ChatRequest.cs | 38 ++++++++- OpenAI-DotNet/Chat/Choice.cs | 2 + OpenAI-DotNet/Chat/Function.cs | 79 ++++++++++++++++++ OpenAI-DotNet/Chat/Message.cs | 13 ++- OpenAI-DotNet/Models/Model.cs | 11 ++- 6 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 OpenAI-DotNet/Chat/Function.cs diff --git a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs index fe2021b6..303ef9c7 100644 --- a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs +++ b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using System.Threading.Tasks; namespace OpenAI.Tests @@ -95,5 +98,99 @@ public async Task Test_3_GetChatStreamingCompletionEnumerableAsync() } } } + + [Test] + public async Task Test_4_GetChatFunctionCompletion() + { + Assert.IsNotNull(OpenAIClient.ChatEndpoint); + var messages = new List + { + new Message(Role.System, "You are a helpful weather assistant."), + new Message(Role.User, "What's the weather like today?"), + }; + var functions = new List + { + new Function( + "GetCurrentWeather", + "Get the current weather in a given location", + new JsonObject + { + ["type"] = "object", + ["properties"] = new JsonObject + { + ["location"] = new JsonObject + { + ["type"] = "string", + ["description"] = "The city and state, e.g. San Francisco, CA" + }, + ["unit"] = new JsonObject + { + ["type"] = "string", + ["enum"] = new JsonArray {"celsius", "fahrenheit"} + } + }, + ["required"] = new JsonArray { "location", "unit" } + }) + }; + + var chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613"); + var result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + Assert.IsNotNull(result); + Assert.IsNotNull(result.Choices); + Assert.IsTrue(result.Choices.Count == 1); + messages.Add(result.FirstChoice.Message); + + Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + + messages.Add(new Message(Role.User, "I'm in Glasgow, Scotland")); + chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613"); + result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + + Assert.IsNotNull(result); + Assert.IsNotNull(result.Choices); + Assert.IsTrue(result.Choices.Count == 1); + messages.Add(result.FirstChoice.Message); + + if (!string.IsNullOrWhiteSpace(result.FirstChoice.Message.Content)) + { + Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + + messages.Add(new Message(Role.User, "celsius")); + chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613"); + result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + Assert.IsNotNull(result); + Assert.IsNotNull(result.Choices); + Assert.IsTrue(result.Choices.Count == 1); + } + + Assert.IsTrue(result.FirstChoice.FinishReason == "function_call"); + Assert.IsTrue(result.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather)); + Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); + Console.WriteLine($"{result.FirstChoice.Message.Function.Arguments}"); + var functionArgs = JsonSerializer.Deserialize(result.FirstChoice.Message.Function.Arguments.ToString()); + var functionResult = WeatherService.GetCurrentWeather(functionArgs); + Assert.IsNotNull(functionResult); + Console.WriteLine(functionResult); + messages.Add(new Message(Role.Function, functionResult)); + } + + internal class WeatherService + { + public static string GetCurrentWeather(WeatherArgs args) + { + return $"The current weather in {args.Location} is 20 {args.Unit}"; + } + } + + internal class WeatherArgs + { + [JsonPropertyName("location")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Location { get; set; } + + [JsonPropertyName("unit")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Unit { get; set; } + } } } \ No newline at end of file diff --git a/OpenAI-DotNet/Chat/ChatRequest.cs b/OpenAI-DotNet/Chat/ChatRequest.cs index 043e958f..e662830c 100644 --- a/OpenAI-DotNet/Chat/ChatRequest.cs +++ b/OpenAI-DotNet/Chat/ChatRequest.cs @@ -14,8 +14,8 @@ public sealed class ChatRequest /// The list of messages for the current chat session. /// /// - /// ID of the model to use.
- /// Currently, only gpt-4, gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported. + /// Id of the model to use.
+ /// Currently, only gpt-4 and gpt-3.5-turbo and their variants are supported. /// /// /// What sampling temperature to use, between 0 and 2. @@ -66,6 +66,12 @@ public sealed class ChatRequest /// /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. /// + /// + /// If functions is not null or empty, this is required. Pass "auto" to let the API decide, "none" if none are to be called, or {"name": "function-name"} + /// + /// + /// An optional list of functions to get arguments for. + /// public ChatRequest( IEnumerable messages, string model = null, @@ -76,8 +82,10 @@ public ChatRequest( int? maxTokens = null, double? presencePenalty = null, double? frequencyPenalty = null, - Dictionary logitBias = null, - string user = null) + IReadOnlyDictionary logitBias = null, + string user = null, + string functionCall = null, + IEnumerable functions = null) { Model = string.IsNullOrWhiteSpace(model) ? Models.Model.GPT3_5_Turbo : model; @@ -103,6 +111,14 @@ public ChatRequest( FrequencyPenalty = frequencyPenalty; LogitBias = logitBias; User = user; + + if (string.IsNullOrEmpty(functionCall) && Functions is { Count: > 0 }) + { + throw new ArgumentException("If functions are provided, please also provide a function_call specifier e.g. (auto, none, or {\"name\": \"\"})"); + } + + FunctionCall = functionCall; + Functions = functions?.ToList(); } /// @@ -200,5 +216,19 @@ public ChatRequest( /// [JsonPropertyName("user")] public string User { get; } + + /// + /// If functions is not null or empty, this is required. Pass "auto" to let the API decide, "none" if none are to be called, or {"name": "function-name"} + /// + [JsonPropertyName("function_call")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string FunctionCall { get; } + + /// + /// An optional list of functions to get arguments for. + /// + [JsonPropertyName("functions")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public IReadOnlyList Functions { get; } } } diff --git a/OpenAI-DotNet/Chat/Choice.cs b/OpenAI-DotNet/Chat/Choice.cs index 840e8f17..38e99bc8 100644 --- a/OpenAI-DotNet/Chat/Choice.cs +++ b/OpenAI-DotNet/Chat/Choice.cs @@ -21,10 +21,12 @@ public Choice( [JsonInclude] [JsonPropertyName("message")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Message Message { get; private set; } [JsonInclude] [JsonPropertyName("delta")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Delta Delta { get; private set; } [JsonInclude] diff --git a/OpenAI-DotNet/Chat/Function.cs b/OpenAI-DotNet/Chat/Function.cs new file mode 100644 index 00000000..fd9db8d6 --- /dev/null +++ b/OpenAI-DotNet/Chat/Function.cs @@ -0,0 +1,79 @@ +using System; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace OpenAI.Chat +{ + public class Function + { + /// + /// Creates a new function description to insert into a chat conversation. + /// + /// + /// Required. The name of the function to generate arguments for based on the context in a message.
+ /// May contain a-z, A-Z, 0-9, underscores and dashes, with a maximum length of 64 characters. Recommended to not begin with a number or a dash. + /// + /// + /// An optional description of the function, used by the API to determine if it is useful to include in the response. + /// + /// + /// An optional JSON object describing the parameters of the function that the model should generate in JSON schema format (json-schema.org). + /// + /// + /// // TODO + /// + public Function(string name, string description = null, JsonNode parameters = null, JsonNode arguments = null) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException($"{nameof(name)} cannot be null or whitespace.", nameof(name)); + } + + if (name.Length > 64) + { + throw new ArgumentException($"{nameof(name)} cannot be longer than 64 characters.", nameof(name)); + } + + Name = name; + Description = description; + Parameters = parameters; + Arguments = arguments; + } + + /// + /// The name of the function to generate arguments for.
+ /// May contain a-z, A-Z, 0-9, and underscores and dashes, with a maximum length of 64 characters. + /// Recommended to not begin with a number or a dash. + ///
+ [JsonInclude] + [JsonPropertyName("name")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Name { get; private set; } + + /// + /// The optional description of the function. + /// + [JsonInclude] + [JsonPropertyName("description")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Description { get; private set; } + + /// + /// The optional parameters of the function. + /// Describe the parameters that the model should generate in JSON schema format (json-schema.org). + /// + [JsonInclude] + [JsonPropertyName("parameters")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public JsonNode Parameters { get; private set; } + + [JsonInclude] + [JsonPropertyName("arguments")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public JsonNode Arguments { get; private set; } + + public override string ToString() => $"{Name}: {Description}"; + + public static implicit operator string(Function function) => function.ToString(); + } +} diff --git a/OpenAI-DotNet/Chat/Message.cs b/OpenAI-DotNet/Chat/Message.cs index 87210aed..bf3e3e05 100644 --- a/OpenAI-DotNet/Chat/Message.cs +++ b/OpenAI-DotNet/Chat/Message.cs @@ -17,11 +17,15 @@ public sealed class Message /// Optional, The name of the author of this message.
/// May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters. /// - public Message(Role role, string content, string name = null) + /// + /// //TODO + /// + public Message(Role role, string content, string name = null, Function function = null) { Role = role; Content = content; Name = name; + Function = function; } /// @@ -36,14 +40,21 @@ public Message(Role role, string content, string name = null) /// [JsonInclude] [JsonPropertyName("content")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string Content { get; private set; } + [JsonInclude] + [JsonPropertyName("function_call")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Function Function { get; private set; } + /// /// Optional, The name of the author of this message.
/// May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters. ///
[JsonInclude] [JsonPropertyName("name")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string Name { get; private set; } public override string ToString() => Content ?? string.Empty; diff --git a/OpenAI-DotNet/Models/Model.cs b/OpenAI-DotNet/Models/Model.cs index 43b3a2e8..0b127d4f 100644 --- a/OpenAI-DotNet/Models/Model.cs +++ b/OpenAI-DotNet/Models/Model.cs @@ -77,13 +77,18 @@ public Model(string id, string ownedBy = null) public static Model GPT4 { get; } = new("gpt-4", "openai"); /// - /// Same capabilities as the base gpt-4 mode but with 4x the context length. Will be updated with our latest model iteration. + /// Same capabilities as the base gpt-4 mode but with 4x the context length. Will be updated with our latest model iteration. Tokens are 2x the price of gpt-4. /// public static Model GPT4_32K { get; } = new("gpt-4-32k", "openai"); /// - /// Because gpt-3.5-turbo performs at a similar capability to text-davinci-003 but at 10% - /// the price per token, we recommend gpt-3.5-turbo for most use cases. + /// Same capabilities as the base gpt-3.5-turbo mode but with 4x the context length. Tokens are 2x the price of gpt-3.5-turbo. Will be updated with our latest model iteration. + /// + public static Model GPT3_5_Turbo_16K { get; } = new("gpt-3.5-turbo-16k", "openai"); + + /// + /// Because gpt-3.5-turbo performs at a similar capability to text-davinci-003 but at 5% the price per input token and 10% the price per output token of davinci, + /// we recommend gpt-3.5-turbo for most use cases. /// public static Model GPT3_5_Turbo { get; } = new("gpt-3.5-turbo", "openai"); From d6a74d0b904429efb0ae94d6cdaac2b6c0be7e77 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Thu, 15 Jun 2023 13:50:06 -0400 Subject: [PATCH 04/11] renamed param --- OpenAI-DotNet-Tests/TestFixture_03_Chat.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs index 303ef9c7..609738fe 100644 --- a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs +++ b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs @@ -176,9 +176,9 @@ public async Task Test_4_GetChatFunctionCompletion() internal class WeatherService { - public static string GetCurrentWeather(WeatherArgs args) + public static string GetCurrentWeather(WeatherArgs weatherArgs) { - return $"The current weather in {args.Location} is 20 {args.Unit}"; + return $"The current weather in {weatherArgs.Location} is 20 {weatherArgs.Unit}"; } } From d9ce70a31c195c2fde53922a553afef4fd8ac835 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Thu, 15 Jun 2023 13:58:42 -0400 Subject: [PATCH 05/11] Move WeatherService into TestServices folder --- OpenAI-DotNet-Tests/TestFixture_03_Chat.cs | 21 +---------------- .../TestServices/WeatherService.cs | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 20 deletions(-) create mode 100644 OpenAI-DotNet-Tests/TestServices/WeatherService.cs diff --git a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs index 609738fe..d1ec35a5 100644 --- a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs +++ b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs @@ -1,11 +1,11 @@ using NUnit.Framework; using OpenAI.Chat; +using OpenAI.Tests.Weather; using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; -using System.Text.Json.Serialization; using System.Threading.Tasks; namespace OpenAI.Tests @@ -173,24 +173,5 @@ public async Task Test_4_GetChatFunctionCompletion() Console.WriteLine(functionResult); messages.Add(new Message(Role.Function, functionResult)); } - - internal class WeatherService - { - public static string GetCurrentWeather(WeatherArgs weatherArgs) - { - return $"The current weather in {weatherArgs.Location} is 20 {weatherArgs.Unit}"; - } - } - - internal class WeatherArgs - { - [JsonPropertyName("location")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public string Location { get; set; } - - [JsonPropertyName("unit")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public string Unit { get; set; } - } } } \ No newline at end of file diff --git a/OpenAI-DotNet-Tests/TestServices/WeatherService.cs b/OpenAI-DotNet-Tests/TestServices/WeatherService.cs new file mode 100644 index 00000000..4158e4cd --- /dev/null +++ b/OpenAI-DotNet-Tests/TestServices/WeatherService.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace OpenAI.Tests.Weather +{ + internal class WeatherService + { + public static string GetCurrentWeather(WeatherArgs weatherArgs) + { + return $"The current weather in {weatherArgs.Location} is 20 {weatherArgs.Unit}"; + } + } + + internal class WeatherArgs + { + [JsonPropertyName("location")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Location { get; set; } + + [JsonPropertyName("unit")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Unit { get; set; } + } +} \ No newline at end of file From 1b10fcf95716646de5daf9912f744fb96866058a Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 17 Jun 2023 03:08:23 -0400 Subject: [PATCH 06/11] some tweaks to output to make it look nicer --- OpenAI-DotNet-Tests/TestFixture_03_Chat.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs index d1ec35a5..48ce07b5 100644 --- a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs +++ b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs @@ -108,10 +108,16 @@ public async Task Test_4_GetChatFunctionCompletion() new Message(Role.System, "You are a helpful weather assistant."), new Message(Role.User, "What's the weather like today?"), }; + + foreach (var message in messages) + { + Console.WriteLine($"{message.Role}: {message.Content}"); + } + var functions = new List { new Function( - "GetCurrentWeather", + nameof(WeatherService.GetCurrentWeather), "Get the current weather in a given location", new JsonObject { @@ -170,8 +176,8 @@ public async Task Test_4_GetChatFunctionCompletion() var functionArgs = JsonSerializer.Deserialize(result.FirstChoice.Message.Function.Arguments.ToString()); var functionResult = WeatherService.GetCurrentWeather(functionArgs); Assert.IsNotNull(functionResult); - Console.WriteLine(functionResult); messages.Add(new Message(Role.Function, functionResult)); + Console.WriteLine($"{Role.Function}: {functionResult}"); } } } \ No newline at end of file From 2037ca30bb14212d5a5bb2741e9e44b2349f1c94 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 17 Jun 2023 03:10:30 -0400 Subject: [PATCH 07/11] removed to string override and implicit op --- OpenAI-DotNet/Chat/Function.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/OpenAI-DotNet/Chat/Function.cs b/OpenAI-DotNet/Chat/Function.cs index fd9db8d6..7439bce3 100644 --- a/OpenAI-DotNet/Chat/Function.cs +++ b/OpenAI-DotNet/Chat/Function.cs @@ -71,9 +71,5 @@ public Function(string name, string description = null, JsonNode parameters = nu [JsonPropertyName("arguments")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public JsonNode Arguments { get; private set; } - - public override string ToString() => $"{Name}: {Description}"; - - public static implicit operator string(Function function) => function.ToString(); } } From ce8e55fcee9895d92120c9e0df959527c94eea10 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 17 Jun 2023 03:21:44 -0400 Subject: [PATCH 08/11] some cleanup and polish --- OpenAI-DotNet-Tests/TestFixture_03_Chat.cs | 8 ++++++-- OpenAI-DotNet/Chat/Function.cs | 8 +++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs index 48ce07b5..d588763a 100644 --- a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs +++ b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs @@ -148,7 +148,9 @@ public async Task Test_4_GetChatFunctionCompletion() Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); - messages.Add(new Message(Role.User, "I'm in Glasgow, Scotland")); + var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); + messages.Add(locationMessage); + Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}"); chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613"); result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); @@ -161,7 +163,9 @@ public async Task Test_4_GetChatFunctionCompletion() { Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); - messages.Add(new Message(Role.User, "celsius")); + var unitMessage = new Message(Role.User, "celsius"); + messages.Add(unitMessage); + Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}"); chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613"); result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); Assert.IsNotNull(result); diff --git a/OpenAI-DotNet/Chat/Function.cs b/OpenAI-DotNet/Chat/Function.cs index 7439bce3..d79b4fe1 100644 --- a/OpenAI-DotNet/Chat/Function.cs +++ b/OpenAI-DotNet/Chat/Function.cs @@ -4,6 +4,9 @@ namespace OpenAI.Chat { + /// + /// + /// public class Function { /// @@ -20,7 +23,7 @@ public class Function /// An optional JSON object describing the parameters of the function that the model should generate in JSON schema format (json-schema.org). /// /// - /// // TODO + /// The arguments to use when calling the function. /// public Function(string name, string description = null, JsonNode parameters = null, JsonNode arguments = null) { @@ -67,6 +70,9 @@ public Function(string name, string description = null, JsonNode parameters = nu [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public JsonNode Parameters { get; private set; } + /// + /// The arguments to use when calling the function. + /// [JsonInclude] [JsonPropertyName("arguments")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] From 6edd8d09fd298315c6213dab8af34361a2b2cf24 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 17 Jun 2023 03:43:48 -0400 Subject: [PATCH 09/11] some more tweaks and final changes --- OpenAI-DotNet/Chat/Message.cs | 5 ++++- OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs | 2 +- OpenAI-DotNet/Models/Model.cs | 17 ++++++++++------- OpenAI-DotNet/OpenAI-DotNet.csproj | 1 - 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/OpenAI-DotNet/Chat/Message.cs b/OpenAI-DotNet/Chat/Message.cs index bf3e3e05..8a224d01 100644 --- a/OpenAI-DotNet/Chat/Message.cs +++ b/OpenAI-DotNet/Chat/Message.cs @@ -18,7 +18,7 @@ public sealed class Message /// May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters. /// /// - /// //TODO + /// The function that should be called, as generated by the model. /// public Message(Role role, string content, string name = null, Function function = null) { @@ -43,6 +43,9 @@ public Message(Role role, string content, string name = null, Function function [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string Content { get; private set; } + /// + /// The function that should be called, as generated by the model. + /// [JsonInclude] [JsonPropertyName("function_call")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] diff --git a/OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs b/OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs index eb75fc0d..b916fa2a 100644 --- a/OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs +++ b/OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs @@ -17,7 +17,7 @@ public sealed class EmbeddingsRequest /// Each input must not exceed 8192 tokens in length. /// /// - /// ID of the model to use.
+ /// ID of the model to use.
/// Defaults to: /// /// diff --git a/OpenAI-DotNet/Models/Model.cs b/OpenAI-DotNet/Models/Model.cs index 0b127d4f..20f6300e 100644 --- a/OpenAI-DotNet/Models/Model.cs +++ b/OpenAI-DotNet/Models/Model.cs @@ -72,25 +72,28 @@ public Model(string id, string ownedBy = null) public string Parent { get; private set; } /// - /// More capable than any GPT-3.5 model, able to do more complex tasks, and optimized for chat. Will be updated with our latest model iteration. + /// More capable than any GPT-3.5 model, able to do more complex tasks, and optimized for chat. + /// Will be updated with our latest model iteration. /// public static Model GPT4 { get; } = new("gpt-4", "openai"); /// - /// Same capabilities as the base gpt-4 mode but with 4x the context length. Will be updated with our latest model iteration. Tokens are 2x the price of gpt-4. + /// Same capabilities as the base gpt-4 mode but with 4x the context length. + /// Will be updated with our latest model iteration. Tokens are 2x the price of gpt-4. /// public static Model GPT4_32K { get; } = new("gpt-4-32k", "openai"); /// - /// Same capabilities as the base gpt-3.5-turbo mode but with 4x the context length. Tokens are 2x the price of gpt-3.5-turbo. Will be updated with our latest model iteration. + /// Because gpt-3.5-turbo performs at a similar capability to text-davinci-003 but at 10% + /// the price per token, we recommend gpt-3.5-turbo for most use cases. /// - public static Model GPT3_5_Turbo_16K { get; } = new("gpt-3.5-turbo-16k", "openai"); + public static Model GPT3_5_Turbo { get; } = new("gpt-3.5-turbo", "openai"); /// - /// Because gpt-3.5-turbo performs at a similar capability to text-davinci-003 but at 5% the price per input token and 10% the price per output token of davinci, - /// we recommend gpt-3.5-turbo for most use cases. + /// Same capabilities as the base gpt-3.5-turbo mode but with 4x the context length. + /// Tokens are 2x the price of gpt-3.5-turbo. Will be updated with our latest model iteration. /// - public static Model GPT3_5_Turbo { get; } = new("gpt-3.5-turbo", "openai"); + public static Model GPT3_5_Turbo_16K { get; } = new("gpt-3.5-turbo-16k", "openai"); /// /// The most powerful, largest engine available, although the speed is quite slow. diff --git a/OpenAI-DotNet/OpenAI-DotNet.csproj b/OpenAI-DotNet/OpenAI-DotNet.csproj index 5ec424f3..c6ce8d5d 100644 --- a/OpenAI-DotNet/OpenAI-DotNet.csproj +++ b/OpenAI-DotNet/OpenAI-DotNet.csproj @@ -20,7 +20,6 @@ More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet- 6.7.0 Version 6.7.0 - Added function calling to chat models -- Added chunk size parameter to embedding request Version 6.8.7 - Added ToString and string operator to Moderation Scores Version 6.8.6 From 682e38cdc3eb633d7cfa0489a48e443978bcc9f4 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 17 Jun 2023 04:11:50 -0400 Subject: [PATCH 10/11] updated docs --- README.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8845d4b4..0a42787f 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ Install-Package OpenAI-DotNet - [Authentication](#authentication) - [Azure OpenAI](#azure-openai) - - [Azure Active Directory Authentication](#azure-active-directory-authentication) :new: -- [OpenAI API Proxy](#openai-api-proxy) :new: + - [Azure Active Directory Authentication](#azure-active-directory-authentication) +- [OpenAI API Proxy](#openai-api-proxy) - [Models](#models) - [List Models](#list-models) - [Retrieve Models](#retrieve-model) @@ -50,6 +50,7 @@ Install-Package OpenAI-DotNet - [Chat](#chat) - [Chat Completions](#chat-completions) - [Streaming](#chat-streaming) + - [Functions](#chat-functions) :new: - [Edits](#edits) - [Create Edit](#create-edit) - [Embeddings](#embeddings) @@ -424,6 +425,90 @@ await foreach (var result in api.ChatEndpoint.StreamCompletionEnumerableAsync(ch } ``` +##### [Chat Functions](https://platform.openai.com/docs/api-reference/chat/create#chat/create-functions) + +> Only available with the latest 0613 model series! + +```csharp +var api = new OpenAIClient(); +var messages = new List +{ + new Message(Role.System, "You are a helpful weather assistant."), + new Message(Role.User, "What's the weather like today?"), +}; + +foreach (var message in messages) +{ + Console.WriteLine($"{message.Role}: {message.Content}"); +} + +// Define the functions that the assistant is able to use: +var functions = new List +{ + new Function( + nameof(WeatherService.GetCurrentWeather), + "Get the current weather in a given location", + new JObject + { + ["type"] = "object", + ["properties"] = new JObject + { + ["location"] = new JObject + { + ["type"] = "string", + ["description"] = "The city and state, e.g. San Francisco, CA" + }, + ["unit"] = new JObject + { + ["type"] = "string", + ["enum"] = new JArray {"celsius", "fahrenheit"} + } + }, + ["required"] = new JArray { "location", "unit" } + }) +}; + +var chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613"); +var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +messages.Add(result.FirstChoice.Message); +Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); +var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); +messages.Add(locationMessage); +Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}"); +chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613"); +result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +messages.Add(result.FirstChoice.Message); + +if (!string.IsNullOrWhiteSpace(result.FirstChoice.Message.Content)) +{ + // It's possible that the assistant will also ask you which units you want the temperature in. + Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + + var unitMessage = new Message(Role.User, "celsius"); + messages.Add(unitMessage); + Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}"); + chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613"); + result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +} + +Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); +Console.WriteLine($"{result.FirstChoice.Message.Function.Arguments}"); +var functionArgs = JsonConvert.DeserializeObject(result.FirstChoice.Message.Function.Arguments.ToString()); +var functionResult = WeatherService.GetCurrentWeather(functionArgs); +messages.Add(new Message(Role.Function, functionResult)); +Console.WriteLine($"{Role.Function}: {functionResult}"); +// System: You are a helpful weather assistant. +// User: What's the weather like today? +// Assistant: Sure, may I know your current location? | Finish Reason: stop +// User: I'm in Glasgow, Scotland +// Assistant: GetCurrentWeather | Finish Reason: function_call +// { +// "location": "Glasgow, Scotland", +// "unit": "celsius" +// } +// Function: The current weather in Glasgow, Scotland is 20 celsius +``` + ### [Edits](https://platform.openai.com/docs/api-reference/edits) Given a prompt and an instruction, the model will return an edited version of the prompt. From 3dc9a7b7af4b136e82c1f1df29566082bbcdeefa Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 17 Jun 2023 04:19:26 -0400 Subject: [PATCH 11/11] OpenAI-DotNet 7.0.0 --- OpenAI-DotNet/OpenAI-DotNet.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenAI-DotNet/OpenAI-DotNet.csproj b/OpenAI-DotNet/OpenAI-DotNet.csproj index c6ce8d5d..6dc8837b 100644 --- a/OpenAI-DotNet/OpenAI-DotNet.csproj +++ b/OpenAI-DotNet/OpenAI-DotNet.csproj @@ -17,8 +17,8 @@ More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet- https://github.com/RageAgainstThePixel/OpenAI-DotNet OpenAI, AI, ML, API, gpt-4, gpt-3.5-tubo, gpt-3, chatGPT, chat-gpt, gpt-2, gpt OpenAI API - 6.7.0 - Version 6.7.0 + 7.0.0 + Version 7.0.0 - Added function calling to chat models Version 6.8.7 - Added ToString and string operator to Moderation Scores