From 7087c2f49f519a1b70ce7305628a773cfdae1162 Mon Sep 17 00:00:00 2001 From: Zeeshan Mustafa Date: Fri, 31 May 2024 16:49:29 +0500 Subject: [PATCH] Step 4 - updated readme.md explaining usage of image based messages. Step 3: added test project to cover all possible config and methods. Improved logic by adding content item validation, removed magic strings by moving to catalogs etc. Support for image_url content added for chat completion endpoint Step-1: encapsulate "Content" property in ChatGptMessage in order to support complex type of content for image support. --- README.md | 38 ++++ src/ChatGPT.Net.sln | 19 +- src/ChatGPT.Net/ChatGPT.cs | 104 +++++++--- src/ChatGPT.Net/DTO/ChatGPT/ChatGptMessage.cs | 134 ++++++++++++- .../DTO/ChatGPT/ChatGptMessageRoles.cs | 11 ++ src/ChatGPT.Net/DTO/ChatGPT/ChatGptModels.cs | 9 + src/ChatGPT.Net/DTO/ChatGPT/ChatGptOptions.cs | 2 +- src/ChatGPT.Net/DTO/ChatGPT/ChatGptRequest.cs | 2 +- .../ChatGpt.Net.Tests.csproj | 35 ++++ src/ChatGpt.Net.Tests/GlobalUsings.cs | 1 + .../Official/ChatCompletionTests.cs | 185 ++++++++++++++++++ src/ChatGpt.Net.Tests/chocolate.png | Bin 0 -> 38152 bytes 12 files changed, 511 insertions(+), 29 deletions(-) create mode 100644 src/ChatGPT.Net/DTO/ChatGPT/ChatGptMessageRoles.cs create mode 100644 src/ChatGPT.Net/DTO/ChatGPT/ChatGptModels.cs create mode 100644 src/ChatGpt.Net.Tests/ChatGpt.Net.Tests.csproj create mode 100644 src/ChatGpt.Net.Tests/GlobalUsings.cs create mode 100644 src/ChatGpt.Net.Tests/Official/ChatCompletionTests.cs create mode 100644 src/ChatGpt.Net.Tests/chocolate.png diff --git a/README.md b/README.md index 210ab25..963dc57 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,43 @@ Console.WriteLine(response); await bot.AskStream(response => { Console.WriteLine(response); }, "What is the weather like today?", "conversation name"); + +// Set a system message +bot.SetConversationSystemMessage("conversation name", "You are a helpful assistant that provides clear and concise answers to questions."); +``` +Some models can understand both text and images. To use this feature, you need to pass a list of content items that includes both text and images. + +```csharp +// To stream responses with both image and text input, create a list of content items +var contentItems = new List(); + +// Each content item can be either "Text" type or "Image" type. Let's add text to inquire about the image +contentItems.Add(new ChatGptMessageContentItem() +{ + Type = ChatGptMessageContentType.TEXT, + Text = "what is this image about?" +}); + +// Now, create another content item of "Image" type +var contentItemWithImage = new ChatGptMessageContentItem() +{ + Type = ChatGptMessageContentType.IMAGE +}; +// Set image by path +contentItemWithImage.SetImageFromFile(""); +// Or set image by Url +contentItemWithImage.SetImageFromUrl("https://path-to-image.com/image.png"); +// Or set base64 image url +contentItemWithImage.SetImageFromUrl("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA....."); + +contentItems.Add(contentItemWithImage); + +// Only specific models understand image input. Therefore, if you are using 'gpt-3.5-turbo' or similar model, switch or override the model before calling 'AskStream'. More details about `ChatGptOptions` (config) are covered in the next section. +config.Model = ChatGptModels.GPT_4_Vision_Preview; + +var response = await bot.AskStream(response => { + Console.WriteLine(response); +}, contentItems, "conversation name"); ``` ### ChatGPT Unofficial API @@ -120,6 +157,7 @@ Console.WriteLine(response); await bot.AskStream(response => { Console.WriteLine(response); }, "What is the weather like today?", "conversation name"); + ``` ## Configuration options diff --git a/src/ChatGPT.Net.sln b/src/ChatGPT.Net.sln index 29d4eba..ad2e693 100644 --- a/src/ChatGPT.Net.sln +++ b/src/ChatGPT.Net.sln @@ -1,8 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatGPT.Net", "ChatGPT.Net\ChatGPT.Net.csproj", "{EFFFF53D-219F-48E1-87D2-4ABA130CC37F}" +# Visual Studio Version 17 +VisualStudioVersion = 17.10.34916.146 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatGPT.Net", "ChatGPT.Net\ChatGPT.Net.csproj", "{EFFFF53D-219F-48E1-87D2-4ABA130CC37F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatGPT.Console", "ChatGPT.Console\ChatGPT.Console.csproj", "{677A2D4D-2EDA-4C84-8456-3CDEE6BFB4E7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatGPT.Console", "ChatGPT.Console\ChatGPT.Console.csproj", "{677A2D4D-2EDA-4C84-8456-3CDEE6BFB4E7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatGpt.Net.Tests", "ChatGpt.Net.Tests\ChatGpt.Net.Tests.csproj", "{570FD4CD-519A-4AFB-BB12-CB3041F4E601}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -18,5 +23,15 @@ Global {677A2D4D-2EDA-4C84-8456-3CDEE6BFB4E7}.Debug|Any CPU.Build.0 = Debug|Any CPU {677A2D4D-2EDA-4C84-8456-3CDEE6BFB4E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {677A2D4D-2EDA-4C84-8456-3CDEE6BFB4E7}.Release|Any CPU.Build.0 = Release|Any CPU + {570FD4CD-519A-4AFB-BB12-CB3041F4E601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {570FD4CD-519A-4AFB-BB12-CB3041F4E601}.Debug|Any CPU.Build.0 = Debug|Any CPU + {570FD4CD-519A-4AFB-BB12-CB3041F4E601}.Release|Any CPU.ActiveCfg = Release|Any CPU + {570FD4CD-519A-4AFB-BB12-CB3041F4E601}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F8BC8082-4E36-4863-9783-6C98E093FBCB} EndGlobalSection EndGlobal diff --git a/src/ChatGPT.Net/ChatGPT.cs b/src/ChatGPT.Net/ChatGPT.cs index f29d747..8478d5c 100644 --- a/src/ChatGPT.Net/ChatGPT.cs +++ b/src/ChatGPT.Net/ChatGPT.cs @@ -39,10 +39,9 @@ private async IAsyncEnumerable StreamCompletion(Stream stream) public void SetConversationSystemMessage(string conversationId, string message) { var conversation = GetConversation(conversationId); - conversation.Messages.Add(new ChatGptMessage + conversation.Messages.Add(new ChatGptMessage(message) { - Role = "system", - Content = message + Role = ChatGptMessageRoles.SYSTEM, }); } @@ -50,10 +49,9 @@ public void ReplaceConversationSystemMessage(string conversationId, string messa { var conversation = GetConversation(conversationId); conversation.Messages = conversation.Messages.Where(x => x.Role != "system").ToList(); - conversation.Messages.Add(new ChatGptMessage + conversation.Messages.Add(new ChatGptMessage(message) { - Role = "system", - Content = message + Role = ChatGptMessageRoles.SYSTEM, }); } @@ -133,10 +131,9 @@ public async Task Ask(string prompt, string? conversationId = null) { var conversation = GetConversation(conversationId); - conversation.Messages.Add(new ChatGptMessage + conversation.Messages.Add(new ChatGptMessage(prompt) { - Role = "user", - Content = prompt + Role = ChatGptMessageRoles.USER, }); var reply = await SendMessage(new ChatGptRequest @@ -154,12 +151,11 @@ public async Task Ask(string prompt, string? conversationId = null) conversation.Updated = DateTime.Now; - var response = reply.Choices.FirstOrDefault()?.Message.Content ?? ""; + var response = reply.Choices.FirstOrDefault()?.Message.GetContent ?? ""; - conversation.Messages.Add(new ChatGptMessage + conversation.Messages.Add(new ChatGptMessage(response) { - Role = "assistant", - Content = response + Role = ChatGptMessageRoles.ASSISTANT, }); return response; @@ -169,10 +165,9 @@ public async Task AskStream(Action callback, string prompt, stri { var conversation = GetConversation(conversationId); - conversation.Messages.Add(new ChatGptMessage + conversation.Messages.Add(new ChatGptMessage(prompt) { - Role = "user", - Content = prompt + Role = ChatGptMessageRoles.USER, }); var reply = await SendMessage(new ChatGptRequest @@ -195,7 +190,42 @@ public async Task AskStream(Action callback, string prompt, stri conversation.Updated = DateTime.Now; - return reply.Choices.FirstOrDefault()?.Message.Content ?? ""; + return reply.Choices.FirstOrDefault()?.Message.GetContent ?? ""; + } + + public async Task AskStream(Action callback, List promptContentItems, string? conversationId = null) + { + await ValidateContentItems(promptContentItems); + + var conversation = GetConversation(conversationId); + var msg = new ChatGptMessage() + { + Role = ChatGptMessageRoles.USER, + }; + msg.ContentItems.AddRange(promptContentItems); + conversation.Messages.Add(msg); + + var reply = await SendMessage(new ChatGptRequest + { + Messages = conversation.Messages, + Model = Config.Model, + Stream = true, + Temperature = Config.Temperature, + TopP = Config.TopP, + FrequencyPenalty = Config.FrequencyPenalty, + PresencePenalty = Config.PresencePenalty, + Stop = Config.Stop, + MaxTokens = Config.MaxTokens, + }, response => + { + var content = response.Choices.FirstOrDefault()?.Delta.Content; + if (content is null) return; + if (!string.IsNullOrWhiteSpace(content)) callback(content); + }); + + conversation.Updated = DateTime.Now; + + return reply.Choices.FirstOrDefault()?.Message.GetContent ?? ""; } public async Task SendMessage(ChatGptRequest requestBody, Action? callback = null) @@ -209,7 +239,7 @@ public async Task SendMessage(ChatGptRequest requestBody, Actio { {"Authorization", $"Bearer {APIKey}" } }, - Content = new StringContent(JsonConvert.SerializeObject(requestBody)) + Content = new StringContent(JsonConvert.SerializeObject(requestBody, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore })) { Headers = { @@ -249,16 +279,13 @@ public async Task SendMessage(ChatGptRequest requestBody, Actio return new ChatGptResponse { Id = reply?.Id ?? Guid.NewGuid().ToString(), - Model = reply?.Model ?? "gpt-3.5-turbo", + Model = reply?.Model ?? ChatGptModels.GPT_3_5_Turbo, Created = reply?.Created ?? 0, Choices = new List { new() { - Message = new ChatGptMessage - { - Content = concatMessages - } + Message = new ChatGptMessage(concatMessages) } } }; @@ -269,4 +296,35 @@ public async Task SendMessage(ChatGptRequest requestBody, Actio if(content.Error is not null) throw new Exception(content.Error.Message); return content; } + + /// + /// Validates ContentItems array for text and image type of chat completion request + /// + /// + /// + /// Thrown when invalid configuration is detected + /// Thrown when invalid content item is detected + private async Task ValidateContentItems(List promptContentItems) + { + if (promptContentItems is null || promptContentItems.Count == 0) + throw new ArgumentException("Invalid request. No message to send."); + + if (promptContentItems.Any(x => x.Type == ChatGptMessageContentType.IMAGE) + && Config.Model != ChatGptModels.GPT_4_Vision_Preview) + throw new ArgumentException("Invalid model. This model cannot understand images.", "ChatGptOptions.Model"); + + //Validate 'text' type of content items + var textTypeItems = promptContentItems.Where(x => x.Type == ChatGptMessageContentType.TEXT).ToList(); + if (textTypeItems.Any(x => x.GetImageUrl?.Length > 0)) + throw new InvalidOperationException("Invalid request. One of the content item has content type set as 'text' but image URL is provided. Only one should be set in single content item."); + if (textTypeItems.Any(x => string.IsNullOrWhiteSpace(x.Text))) + throw new InvalidOperationException("Invalid request. One of the content item has content type set as 'text' but 'text' value is not set for this content item."); + + //validate 'image_url' type of content items. + var imageTypeItems = promptContentItems.Where(x => x.Type == ChatGptMessageContentType.IMAGE).ToList(); + if (imageTypeItems.Any(x => x.Text is not null && x.Text.Length > 0)) + throw new InvalidOperationException("Invalid operation. One of the content item has message content type set as 'image' but text value is provided. Only one should be set in single content item."); + if (imageTypeItems.Any(x => string.IsNullOrWhiteSpace(x.GetImageUrl))) + throw new InvalidOperationException("Invalid operation. One of the content item has content type set as 'image' but 'imageUrl' value is not set for this content item."); + } } diff --git a/src/ChatGPT.Net/DTO/ChatGPT/ChatGptMessage.cs b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptMessage.cs index 32a3dee..e819e87 100644 --- a/src/ChatGPT.Net/DTO/ChatGPT/ChatGptMessage.cs +++ b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptMessage.cs @@ -1,12 +1,142 @@ using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace ChatGPT.Net.DTO.ChatGPT; +[JsonConverter(typeof(ChatGptMessageConverter))] public class ChatGptMessage { [JsonProperty("role")] - public string Role { get; set; } = "user"; + public string Role { get; set; } = ChatGptMessageRoles.USER; [JsonProperty("content")] - public string Content { get; set; } + private string Content { get; set; } + + public List ContentItems { get; set; } = new(); + + public ChatGptMessage(string message) + { + Content = message; + if(ContentItems is null) + ContentItems = new(); + } + public ChatGptMessage() + { + ContentItems = new(); + + } + + public string GetContent + => Content ?? string.Empty; + + +} + + + +public class ChatGptMessageContentItem +{ + /// + /// Possible options: + /// + [JsonProperty("type")] + public string Type { get; set; } + + /// + /// Null/Empty when is provided. Only once be provided at a time + /// + [JsonProperty("text")] + public string Text { get; set; } + + /// + /// Null/Empty when is provided. Only once be provided at a time + /// + [JsonProperty("image_url")] + private string ImageUrl { get; set; } + + /// + /// Sets ImageUrl from file path + /// + /// Full path to file. eg, C:/MyFiles/image1.png + public void SetImageFromFile(string filePath) + { + var fileContent = File.ReadAllBytes(filePath); + ImageUrl = "data:image/jpeg;base64," + Convert.ToBase64String(fileContent); + } + + /// + /// Sets ImageUrl from URL + /// + /// Image URL + public void SetImageFromUrl(string url) + { + ImageUrl = url; + } + + [JsonIgnore] + public string GetImageUrl + => ImageUrl; +} + +public class ChatGptMessageContentType +{ + public const string TEXT = "text"; + public const string IMAGE = "image_url"; +} + +public class ChatGptMessageConverter : JsonConverter +{ + public override bool CanConvert(Type objectType) + { + return (objectType == typeof(ChatGptMessage)); + } + + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + if (value is null) + { + writer.WriteNull(); + return; + } + var chatMessage = (ChatGptMessage)value; + var jsonObject = new JObject(); + + jsonObject["role"] = chatMessage.Role; + + if (!string.IsNullOrEmpty(chatMessage.GetContent)) + { + jsonObject["content"] = chatMessage.GetContent; + } + else + { + jsonObject["content"] = JToken.FromObject(chatMessage.ContentItems, serializer); + } + + jsonObject.WriteTo(writer); + } + + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + var jsonObject = JObject.Load(reader); + + var chatMessage = new ChatGptMessage(); + if (jsonObject["content"].Type == JTokenType.String) + { + chatMessage = new(jsonObject["content"].ToString()); + + } + else if(jsonObject["content"].Type == JTokenType.Array) + { + chatMessage.ContentItems = jsonObject["content"].ToObject>(serializer); + } + else + { + chatMessage.ContentItems = new List(); //nothing in cotnentitems and nothing in content + } + + chatMessage.Role = jsonObject["role"].ToString(); + return chatMessage; + } + + } \ No newline at end of file diff --git a/src/ChatGPT.Net/DTO/ChatGPT/ChatGptMessageRoles.cs b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptMessageRoles.cs new file mode 100644 index 0000000..ff90f6f --- /dev/null +++ b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptMessageRoles.cs @@ -0,0 +1,11 @@ + + +namespace ChatGPT.Net.DTO.ChatGPT; + +public class ChatGptMessageRoles +{ + public const string SYSTEM = "system"; + public const string USER = "user"; + public const string ASSISTANT = "assistant"; + public const string TOOL = "tool"; +} diff --git a/src/ChatGPT.Net/DTO/ChatGPT/ChatGptModels.cs b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptModels.cs new file mode 100644 index 0000000..c5793ee --- /dev/null +++ b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptModels.cs @@ -0,0 +1,9 @@ +namespace ChatGPT.Net.DTO.ChatGPT; + +public class ChatGptModels +{ + public const string GPT_3_5_Turbo = "gpt-3.5-turbo"; + public const string GPT_4_Vision_Preview = "gpt-4-vision-preview"; + public const string GPT_4_turbo = "gpt-4-turbo"; + public const string GPT_4o = "gpt-4o"; +} diff --git a/src/ChatGPT.Net/DTO/ChatGPT/ChatGptOptions.cs b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptOptions.cs index 34bd3f5..668a658 100644 --- a/src/ChatGPT.Net/DTO/ChatGPT/ChatGptOptions.cs +++ b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptOptions.cs @@ -3,7 +3,7 @@ public class ChatGptOptions : ChatGptConfig { public string BaseUrl { get; set; } = "https://api.openai.com"; - public string Model { get; set; } = "gpt-3.5-turbo"; + public string Model { get; set; } = ChatGptModels.GPT_3_5_Turbo; public double Temperature { get; set; } = 0.7; public double TopP { get; set; } = 0.9; public long MaxTokens { get; set; } = 256; diff --git a/src/ChatGPT.Net/DTO/ChatGPT/ChatGptRequest.cs b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptRequest.cs index 7efb45a..5e0f682 100644 --- a/src/ChatGPT.Net/DTO/ChatGPT/ChatGptRequest.cs +++ b/src/ChatGPT.Net/DTO/ChatGPT/ChatGptRequest.cs @@ -5,7 +5,7 @@ namespace ChatGPT.Net.DTO.ChatGPT; public partial class ChatGptRequest { [JsonProperty("model")] - public string Model { get; set; } = "gpt-3.5-turbo"; + public string Model { get; set; } = ChatGptModels.GPT_3_5_Turbo; [JsonProperty("temperature")] public double Temperature { get; set; } = 0.7; diff --git a/src/ChatGpt.Net.Tests/ChatGpt.Net.Tests.csproj b/src/ChatGpt.Net.Tests/ChatGpt.Net.Tests.csproj new file mode 100644 index 0000000..bb98c34 --- /dev/null +++ b/src/ChatGpt.Net.Tests/ChatGpt.Net.Tests.csproj @@ -0,0 +1,35 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + Always + + + + diff --git a/src/ChatGpt.Net.Tests/GlobalUsings.cs b/src/ChatGpt.Net.Tests/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/src/ChatGpt.Net.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/src/ChatGpt.Net.Tests/Official/ChatCompletionTests.cs b/src/ChatGpt.Net.Tests/Official/ChatCompletionTests.cs new file mode 100644 index 0000000..0ec6a8b --- /dev/null +++ b/src/ChatGpt.Net.Tests/Official/ChatCompletionTests.cs @@ -0,0 +1,185 @@ +using ChatGPT.Net.DTO.ChatGPT; + +namespace ChatGpt.Net.Tests.Official +{ + public class ChatCompletionTests + { + public ChatGptOptions options; + public string ApiKey; + public ChatCompletionTests() + { + options = new() + { + BaseUrl = "https://api.openai.com", //The base URL for the OpenAI API + Model = ChatGptModels.GPT_3_5_Turbo, // The specific model to use + Temperature = 0.7, // Controls randomness in the response (0-1) + TopP = 0.9, // Controls diversity in the response (0-1) + MaxTokens = 3500, // The maximum number of tokens in the response + Stop = null, // Sequence of tokens that will stop generation + PresencePenalty = 0.0, // Penalizes new tokens based on their existing presence in the context + FrequencyPenalty = 0.0 // Penalizes new tokens based on their frequency in the context + }; + ApiKey = Environment.GetEnvironmentVariable("API_KEY"); + + } + + [Fact] + public async Task ChatCompletion_ValidConfig_IsSuccessResponse() + { + var bot = new ChatGPT.Net.ChatGpt(ApiKey, options); + + Assert.NotNull(bot); + + var response = await bot.AskStream(Console.Write, "Who are you?", "default"); + Assert.NotNull(response); + + } + + [Fact] + public async Task ChatCompletion_ConversationContext_IsMaintained() + { + var bot = new ChatGPT.Net.ChatGpt(ApiKey, options); + + var myFirstQuestion = "Who are you?"; + var response = await bot.AskStream(Console.Write, myFirstQuestion, "default"); + Assert.NotNull(response); + + response = await bot.AskStream(Console.Write, "Where are you from?", "default"); + Assert.NotNull(response); + + response = await bot.AskStream(Console.Write, "Can you remind me what was my first question to you? Just respond witht the question text, do not add anything else.", "default"); + Assert.Equal(myFirstQuestion.ToLower(), response.ToLower().Trim(' ', '\\', '"')); + + } + + [Fact] + public async Task ChatCompletion_SystemMessage_IsSet() + { + var bot = new ChatGPT.Net.ChatGpt(ApiKey, options); + bot.SetConversationSystemMessage("default", "You are a helpful assistant. When I ask you 'who am I?', you simply respond with a word 'cool-dev'. Do not add anything else"); + + var response = await bot.AskStream(Console.Write, "Who am I?", "default"); + Assert.NotNull(response); + Assert.Equal("cool-dev", response); + } + + [Fact] + public async Task ChatCompletion_WithImage_IsWorking() + { + options.Model = ChatGptModels.GPT_4_Vision_Preview; + var bot = new ChatGPT.Net.ChatGpt(ApiKey, options); + + var contentItems = new List(); + contentItems.Add(new ChatGptMessageContentItem() + { + Type = ChatGptMessageContentType.TEXT, + Text = "what is this image about?" + }); + + var contentItemWithImage = new ChatGptMessageContentItem() + { + Type = ChatGptMessageContentType.IMAGE + }; + var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "chocolate.png"); + contentItemWithImage.SetImageFromFile(filePath); + contentItems.Add(contentItemWithImage); + + var response = await bot.AskStream(Console.Write, contentItems, "default"); + Assert.NotNull(response); + Assert.True(response.ToLower().Contains("kit kat")); + } + + [Fact] + public async Task ConversationContext_WithSystemAndUserAndImageMixedContent_IsWorking() + { + options.Model = ChatGptModels.GPT_4_Vision_Preview; + var bot = new ChatGPT.Net.ChatGpt(ApiKey, options); + var conversationId = "default"; + bot.SetConversationSystemMessage(conversationId, "You are a helpful assistant"); + + var contentItems = new List(); + contentItems.Add(new ChatGptMessageContentItem() + { + Type = ChatGptMessageContentType.TEXT, + Text = "what is this image about?" + }); + + var contentItemWithImage = new ChatGptMessageContentItem() + { + Type = ChatGptMessageContentType.IMAGE + }; + var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "chocolate.png"); + contentItemWithImage.SetImageFromFile(filePath); + contentItems.Add(contentItemWithImage); + + //Calling overload expecting "ContentItems" for image based inputs etc. on same conversation thread. + var response = await bot.AskStream(Console.Write, contentItems, conversationId); + Assert.NotNull(response); + Assert.True(response.ToLower().Contains("kit kat")); + + //Calling overload expecting simple string prompt inputs on same conversation thread. + response = await bot.AskStream(Console.Write, "Say again, what is the name of this chocolate?", conversationId); + Assert.NotNull(response); + Assert.True(response.ToLower().Contains("kit kat")); + + } + + [Fact] + public async Task ChatCompletionWithImage_UsingUnsupportedModel_ThrowsException() + { + options.Model = ChatGptModels.GPT_4o; + var bot = new ChatGPT.Net.ChatGpt(ApiKey, options); + + var contentItems = new List(); + contentItems.Add(new ChatGptMessageContentItem() + { + Type = ChatGptMessageContentType.TEXT, + Text = "what is this image about?" + }); + + var contentItemWithImage = new ChatGptMessageContentItem() + { + Type = ChatGptMessageContentType.IMAGE + }; + contentItemWithImage.SetImageFromUrl("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII"); + contentItems.Add(contentItemWithImage); + + var exception = await Assert.ThrowsAsync(async () => + await bot.AskStream(Console.Write, contentItems, "default")); + Assert.True(exception.Message.ToLower().Contains("invalid model")); + } + + [Fact] + public async Task ChatCompletionWithImage_UsingInvalidContentItems_ThrowsException() + { + options.Model = ChatGptModels.GPT_4_Vision_Preview; + var bot = new ChatGPT.Net.ChatGpt(ApiKey, options); + + var contentItems = new List(); + contentItems.Add(new ChatGptMessageContentItem() + { + Type = ChatGptMessageContentType.TEXT, + Text = "what is this image about?" + }); + + var contentItemWithImage = new ChatGptMessageContentItem() + { + Type = ChatGptMessageContentType.IMAGE + }; + contentItemWithImage.SetImageFromUrl("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII"); + + //Intentionally setting "text" property of "image_url" type of content item. Invalid operation exception should be thrown + contentItemWithImage.Text = "What is this image?"; + + contentItems.Add(contentItemWithImage); + + + + var exception = await Assert.ThrowsAsync(async () => + await bot.AskStream(Console.Write, contentItems, "default")); + Assert.True(exception.Message.ToLower().Contains("invalid operation")); + } + + + } +} \ No newline at end of file diff --git a/src/ChatGpt.Net.Tests/chocolate.png b/src/ChatGpt.Net.Tests/chocolate.png new file mode 100644 index 0000000000000000000000000000000000000000..a8e720a95dc50dbb768bfce9383c78fddc719f79 GIT binary patch literal 38152 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