diff --git a/README.md b/README.md index a5cc89e..571c5c6 100644 --- a/README.md +++ b/README.md @@ -512,7 +512,7 @@ var functionResponse = await GetWeatherAsync(functionCall.Arguments); await chatGptClient.AddToolResponseAsync(conversationId, tool, functionResponse); ``` -Check out the [Function calling sample](https://github.com/marcominerva/ChatGptNet/blob/master/samples/ChatGptFunctionCallingConsole/Application.cs#L18) for a complete implementation of this workflow. +Finally, you need to resend the original message to the chat completion API, so that the model can continue the conversation taking into account the function call response. Check out the [Function calling sample](https://github.com/marcominerva/ChatGptNet/blob/master/samples/ChatGptFunctionCallingConsole/Application.cs#L18) for a complete implementation of this workflow. ## Content filtering diff --git a/docs/ChatGptNet.Models/ChatGptToolParameters.md b/docs/ChatGptNet.Models/ChatGptToolParameters.md index 38d32e1..e74e9fd 100644 --- a/docs/ChatGptNet.Models/ChatGptToolParameters.md +++ b/docs/ChatGptNet.Models/ChatGptToolParameters.md @@ -1,6 +1,6 @@ # ChatGptToolParameters class -Contains parameters about the tools that are available for ChatGPT. +Contains parameters about the tool calls that are available for ChatGPT. ```csharp public class ChatGptToolParameters diff --git a/docs/ChatGptNet/IChatGptClient.md b/docs/ChatGptNet/IChatGptClient.md index 70ee002..640ed2b 100644 --- a/docs/ChatGptNet/IChatGptClient.md +++ b/docs/ChatGptNet/IChatGptClient.md @@ -10,7 +10,7 @@ public interface IChatGptClient | name | description | | --- | --- | -| [AddInteractionAsync](IChatGptClient/AddInteractionAsync.md)(…) | Explicitly adds a new interaction (a question and the corresponding answer) to the conversation history. | +| [AddInteractionAsync](IChatGptClient/AddInteractionAsync.md)(…) | Explicitly adds a new interaction (a question and the corresponding answer) to an existing conversation history. | | [AddToolResponseAsync](IChatGptClient/AddToolResponseAsync.md)(…) | Adds a function response to the conversation history. (4 methods) | | [AskAsync](IChatGptClient/AskAsync.md)(…) | Requests a new chat interaction. (4 methods) | | [AskStreamAsync](IChatGptClient/AskStreamAsync.md)(…) | Requests a new chat interaction with streaming response, like in ChatGPT. (2 methods) | diff --git a/docs/ChatGptNet/IChatGptClient/AddInteractionAsync.md b/docs/ChatGptNet/IChatGptClient/AddInteractionAsync.md index 7b4fabb..001e40d 100644 --- a/docs/ChatGptNet/IChatGptClient/AddInteractionAsync.md +++ b/docs/ChatGptNet/IChatGptClient/AddInteractionAsync.md @@ -1,6 +1,6 @@ # IChatGptClient.AddInteractionAsync method -Explicitly adds a new interaction (a question and the corresponding answer) to the conversation history. +Explicitly adds a new interaction (a question and the corresponding answer) to an existing conversation history. ```csharp public Task AddInteractionAsync(Guid conversationId, string question, string answer, @@ -22,6 +22,7 @@ The Task corresponding to the asynchronous operation. | exception | condition | | --- | --- | +| ArgumentException | *conversationId* is Empty. | | ArgumentNullException | *question* or *answer* are `null`. | ## See Also diff --git a/docs/ChatGptNet/IChatGptClient/AddToolResponseAsync.md b/docs/ChatGptNet/IChatGptClient/AddToolResponseAsync.md index a007aff..e03a038 100644 --- a/docs/ChatGptNet/IChatGptClient/AddToolResponseAsync.md +++ b/docs/ChatGptNet/IChatGptClient/AddToolResponseAsync.md @@ -22,6 +22,7 @@ The Task corresponding to the asynchronous operation. | exception | condition | | --- | --- | +| ArgumentException | *conversationId* is Empty. | | ArgumentNullException | [`Name`](../../ChatGptNet.Models/ChatGptFunction/Name.md) or *content* are `null`. | | InvalidOperationException | The conversation history is empty. | diff --git a/docs/README.md b/docs/README.md index 0f1a2ad..8b26f5e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -55,7 +55,7 @@ | class [ChatGptTool](./ChatGptNet.Models/ChatGptTool.md) | Represents a tool that the model may call. | | class [ChatGptToolCall](./ChatGptNet.Models/ChatGptToolCall.md) | A tool call generated by the model, such as a function call. | | static class [ChatGptToolChoices](./ChatGptNet.Models/ChatGptToolChoices.md) | Contains constants for ChatGPT function call types. | -| class [ChatGptToolParameters](./ChatGptNet.Models/ChatGptToolParameters.md) | Contains parameters about the function calls that are available for ChatGPT. | +| class [ChatGptToolParameters](./ChatGptNet.Models/ChatGptToolParameters.md) | Contains parameters about the tools calls that are available for ChatGPT. | | static class [ChatGptToolTypes](./ChatGptNet.Models/ChatGptToolTypes.md) | Contains constants for ChatGPT tool types. | | class [ChatGptUsage](./ChatGptNet.Models/ChatGptUsage.md) | Contains information about the API usage. | | static class [OpenAIChatGptModels](./ChatGptNet.Models/OpenAIChatGptModels.md) | Contains all the chat completion models that are currently supported by OpenAI. | diff --git a/samples/ChatGptBlazor.Wasm/ChatGptBlazor.Wasm.csproj b/samples/ChatGptBlazor.Wasm/ChatGptBlazor.Wasm.csproj index f51b787..ca361a7 100644 --- a/samples/ChatGptBlazor.Wasm/ChatGptBlazor.Wasm.csproj +++ b/samples/ChatGptBlazor.Wasm/ChatGptBlazor.Wasm.csproj @@ -8,7 +8,7 @@ - + diff --git a/samples/ChatGptFunctionCallingConsole/Application.cs b/samples/ChatGptFunctionCallingConsole/Application.cs index 5db0b0f..d1b751b 100644 --- a/samples/ChatGptFunctionCallingConsole/Application.cs +++ b/samples/ChatGptFunctionCallingConsole/Application.cs @@ -5,15 +5,8 @@ namespace ChatGptConsole; -internal class Application +internal class Application(IChatGptClient chatGptClient) { - private readonly IChatGptClient chatGptClient; - - public Application(IChatGptClient chatGptClient) - { - this.chatGptClient = chatGptClient; - } - public async Task ExecuteAsync() { Console.WriteLine("Welcome! You can ask me whatever you want, but if you ask me something about the weather, I will probably suggest you to call a function."); @@ -107,15 +100,12 @@ public async Task ExecuteAsync() var functionCall = response.GetFunctionCall()!; Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(functionCall.Name); Console.WriteLine(functionCall.Arguments); - Console.ResetColor(); - // Simulate the calling to the function. + // Simulates the call to the function. var functionResponse = await GetWeatherAsync(functionCall.GetArgumentsAsJson()); - Console.WriteLine(functionResponse); // After the function has been called, it is necessary to add the response to the conversation. @@ -127,6 +117,17 @@ public async Task ExecuteAsync() // for example the gpt-4 1106-preview model, you need the following code: //var tool = response.GetToolCalls()!.First(); //await chatGptClient.AddToolResponseAsync(conversationId, tool, functionResponse); + + Console.WriteLine("The function gives the following response:"); + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(functionResponse); + Console.ResetColor(); + + // Finally, it sends the original message back to the model, to obtain a response that takes into account the function call. + response = await chatGptClient.AskAsync(conversationId, message, toolParameters); + + Console.WriteLine(response.GetContent()); } else { @@ -150,14 +151,30 @@ public async Task ExecuteAsync() private static Task GetWeatherAsync(JsonDocument? arguments) { - var summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; + string[] summaries = + [ + "Freezing", + "Bracing", + "Chilly", + "Cool", + "Mild", + "Warm", + "Balmy", + "Hot", + "Sweltering", + "Scorching" + ]; var location = arguments?.RootElement.GetProperty("location").GetString(); - var response = $"It is {summaries[Random.Shared.Next(summaries.Length)]} in {location}, with {Random.Shared.Next(-20, 35)}° degrees"; + var response = $$""" + { + "location": "{{location}}", + "temperature": {{Random.Shared.Next(-5, 35)}}, + "description": "{{summaries[Random.Shared.Next(summaries.Length)]}}" + } + """; + return Task.FromResult(response); } } diff --git a/src/ChatGptNet/ChatGptClient.cs b/src/ChatGptNet/ChatGptClient.cs index c5c4ee6..591505d 100644 --- a/src/ChatGptNet/ChatGptClient.cs +++ b/src/ChatGptNet/ChatGptClient.cs @@ -246,12 +246,12 @@ public async Task LoadConversationAsync(Guid conversationId, IEnumerable(); - messages = messages.Union(new ChatGptMessage[] - { + messages = messages.Union([ new() { Role = ChatGptRoles.User, @@ -262,20 +262,21 @@ public async Task AddInteractionAsync(Guid conversationId, string question, stri Role = ChatGptRoles.Assistant, Content = answer } - }); + ]); await UpdateCacheAsync(conversationId, messages, cancellationToken); } public async Task AddToolResponseAsync(Guid conversationId, string? toolId, string name, string content, CancellationToken cancellationToken = default) { + ThrowIfEmptyConversationId(conversationId, nameof(conversationId)); ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(content); var messages = await cache.GetAsync(conversationId, cancellationToken); if (!messages?.Any() ?? true) { - throw new InvalidOperationException("Cannot add a function response message if the conversation history is empty"); + throw new InvalidOperationException("Cannot add a tool/function response message if the conversation history is empty"); } messages = messages!.Append(new() @@ -338,7 +339,8 @@ private ChatGptRequest CreateChatGptRequest(IEnumerable messages { } => JsonDocument.Parse($$"""{ "type": "{{ChatGptToolTypes.Function}}", "{{ChatGptToolTypes.Function}}": { "name": "{{toolParameters.ToolChoice}}" } }"""), _ => null }, - // If the tool paramters uses the legacy function properties. + + // If the tool parameters uses the legacy function properties. Functions = toolParameters?.Functions, FunctionCall = toolParameters?.FunctionCall switch { @@ -426,4 +428,12 @@ private static void NormalizeResponse(HttpResponseMessage httpResponse, Response response.Error.StatusCode = (int)httpResponse.StatusCode; } } + + private static void ThrowIfEmptyConversationId(Guid guid, string parameterName) + { + if (guid == Guid.Empty) + { + throw new ArgumentException($"The value {guid} is invalid", parameterName); + } + } } diff --git a/src/ChatGptNet/IChatGptClient.cs b/src/ChatGptNet/IChatGptClient.cs index 45caf2f..fb5f710 100644 --- a/src/ChatGptNet/IChatGptClient.cs +++ b/src/ChatGptNet/IChatGptClient.cs @@ -155,13 +155,14 @@ IAsyncEnumerable AskStreamAsync(string message, ChatGptParamete IAsyncEnumerable AskStreamAsync(Guid conversationId, string message, ChatGptParameters? parameters = null, string? model = null, bool addToConversationHistory = true, CancellationToken cancellationToken = default); /// - /// Explicitly adds a new interaction (a question and the corresponding answer) to the conversation history. + /// Explicitly adds a new interaction (a question and the corresponding answer) to an existing conversation history. /// /// The unique identifier of the conversation. /// The question. /// The answer. /// The token to monitor for cancellation requests. /// The corresponding to the asynchronous operation. + /// is . /// or are . Task AddInteractionAsync(Guid conversationId, string question, string answer, CancellationToken cancellationToken = default); @@ -230,6 +231,7 @@ Task LoadConversationAsync(IEnumerable messages, Cancellat /// The content of the function response. /// The token to monitor for cancellation requests. /// The corresponding to the asynchronous operation. + /// is . /// or are . /// The conversation history is empty. /// diff --git a/src/ChatGptNet/Models/ChatGptToolParameters.cs b/src/ChatGptNet/Models/ChatGptToolParameters.cs index e4bba16..2835f8b 100644 --- a/src/ChatGptNet/Models/ChatGptToolParameters.cs +++ b/src/ChatGptNet/Models/ChatGptToolParameters.cs @@ -1,7 +1,7 @@ namespace ChatGptNet.Models; /// -/// Contains parameters about the tools calls that are available for ChatGPT. +/// Contains parameters about the tool calls that are available for ChatGPT. /// public class ChatGptToolParameters {