Skip to content

Commit

Permalink
OpenAI-DotNet 7.7.0 (#241)
Browse files Browse the repository at this point in the history
- Added `Tool` call and `Function` call Utilities and helper methods
- Added `FunctionAttribute` to decorate methods to be identified and used in function calling
- `Chat.Message.ToolCalls` can be directly invoked using `Function.Invoke()` or `Function.InvokeAsync(CancellationToken)`
- Assistant tool call outputs can be easily generated using `assistnat.GetToolOutputAsync(run.RequiredAction.SubmitToolOutputs.ToolCalls)`
  - Check updated docs for more details and examples
- Fixed `ChatRequest` seed parameter not being set correctly when using tools
  • Loading branch information
StephenHodgson authored Feb 22, 2024
1 parent 8e99b07 commit 192c765
Show file tree
Hide file tree
Showing 43 changed files with 706 additions and 368 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/Publish-Nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ on:
permissions:
contents: read
pages: write
checks: write
id-token: write
pull-requests: write

concurrency:
group: ${{ github.ref }}
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet-Proxy/Proxy/OpenAIProxyStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class OpenAIProxyStartup
private IAuthenticationFilter authenticationFilter;

// Copied from https://github.com/microsoft/reverse-proxy/blob/51d797986b1fea03500a1ad173d13a1176fb5552/src/ReverseProxy/Forwarder/RequestUtilities.cs#L61-L83
private static readonly HashSet<string> ExcludedHeaders = new HashSet<string>
private static readonly HashSet<string> ExcludedHeaders = new()
{
HeaderNames.Connection,
HeaderNames.TransferEncoding,
Expand Down
2 changes: 2 additions & 0 deletions OpenAI-DotNet-Tests-Proxy/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using OpenAI.Proxy;
Expand Down
2 changes: 2 additions & 0 deletions OpenAI-DotNet-Tests/TestFixture_00_00_Authentication.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using System;
using System.IO;
Expand Down
4 changes: 3 additions & 1 deletion OpenAI-DotNet-Tests/TestFixture_00_01_Proxy.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using System;
using System.Net;
using System.Net.Http;
Expand Down
40 changes: 40 additions & 0 deletions OpenAI-DotNet-Tests/TestFixture_00_02_Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using System;

namespace OpenAI.Tests
{
internal class TestFixture_00_02_Extensions
{
[Test]
public void Test_01_Tools()
{
var tools = Tool.GetAllAvailableTools();

for (var i = 0; i < tools.Count; i++)
{
var tool = tools[i];

if (tool.Type != "function")
{
Console.Write($" \"{tool.Type}\"");
}
else
{
Console.Write($" \"{tool.Function.Name}\"");
}

if (tool.Function?.Parameters != null)
{
Console.Write($": {tool.Function.Parameters}");
}

if (i < tools.Count - 1)
{
Console.Write(",\n");
}
}
}
}
}
4 changes: 3 additions & 1 deletion OpenAI-DotNet-Tests/TestFixture_01_Models.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using System;
using System.Linq;
using System.Threading.Tasks;
Expand Down
206 changes: 55 additions & 151 deletions OpenAI-DotNet-Tests/TestFixture_03_Chat.cs

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion OpenAI-DotNet-Tests/TestFixture_05_Images.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using OpenAI.Images;
using OpenAI.Models;
using System;
Expand Down
6 changes: 4 additions & 2 deletions OpenAI-DotNet-Tests/TestFixture_06_Embeddings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using System.Threading.Tasks;
using OpenAI.Models;

Expand All @@ -14,7 +16,7 @@ public async Task Test_1_CreateEmbedding()
Assert.IsNotNull(embedding);
Assert.IsNotEmpty(embedding.Data);
}

[Test]
public async Task Test_2_CreateEmbeddingWithDimensions()
{
Expand Down
4 changes: 3 additions & 1 deletion OpenAI-DotNet-Tests/TestFixture_07_Audio.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using OpenAI.Audio;
using System;
using System.IO;
Expand Down
6 changes: 4 additions & 2 deletions OpenAI-DotNet-Tests/TestFixture_08_Files.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using OpenAI.Chat;
using System;
using System.Collections.Generic;
Expand All @@ -13,7 +15,7 @@ internal class TestFixture_08_Files : AbstractTestFixture
public async Task Test_01_UploadFile()
{
Assert.IsNotNull(OpenAIClient.FilesEndpoint);
var testData = new Conversation(new List<Message> { new Message(Role.Assistant, "I'm a learning language model") });
var testData = new Conversation(new List<Message> { new(Role.Assistant, "I'm a learning language model") });
await File.WriteAllTextAsync("test.jsonl", testData);
Assert.IsTrue(File.Exists("test.jsonl"));
var result = await OpenAIClient.FilesEndpoint.UploadFileAsync("test.jsonl", "fine-tune");
Expand Down
84 changes: 43 additions & 41 deletions OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using OpenAI.Chat;
using OpenAI.Files;
using OpenAI.FineTuning;
Expand All @@ -17,65 +19,65 @@ private async Task<FileResponse> CreateTestTrainingDataAsync()
{
var conversations = new List<Conversation>
{
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "What's the capital of France?"),
new Message(Role.Assistant, "Paris, as if everyone doesn't know that already.")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "What's the capital of France?"),
new(Role.Assistant, "Paris, as if everyone doesn't know that already.")
}),
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "Who wrote 'Romeo and Juliet'?"),
new Message(Role.Assistant, "Oh, just some guy named William Shakespeare. Ever heard of him?")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "Who wrote 'Romeo and Juliet'?"),
new(Role.Assistant, "Oh, just some guy named William Shakespeare. Ever heard of him?")
}),
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "How far is the Moon from Earth?"),
new Message(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "How far is the Moon from Earth?"),
new(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.")
}),
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "What's the capital of France?"),
new Message(Role.Assistant, "Paris, as if everyone doesn't know that already.")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "What's the capital of France?"),
new(Role.Assistant, "Paris, as if everyone doesn't know that already.")
}),
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "Who wrote 'Romeo and Juliet'?"),
new Message(Role.Assistant, "Oh, just some guy named William Shakespeare. Ever heard of him?")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "Who wrote 'Romeo and Juliet'?"),
new(Role.Assistant, "Oh, just some guy named William Shakespeare. Ever heard of him?")
}),
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "How far is the Moon from Earth?"),
new Message(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "How far is the Moon from Earth?"),
new(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.")
}),
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "What's the capital of France?"),
new Message(Role.Assistant, "Paris, as if everyone doesn't know that already.")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "What's the capital of France?"),
new(Role.Assistant, "Paris, as if everyone doesn't know that already.")
}),
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "Who wrote 'Romeo and Juliet'?"),
new Message(Role.Assistant, "Oh, just some guy named William Shakespeare. Ever heard of him?")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "Who wrote 'Romeo and Juliet'?"),
new(Role.Assistant, "Oh, just some guy named William Shakespeare. Ever heard of him?")
}),
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "How far is the Moon from Earth?"),
new Message(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "How far is the Moon from Earth?"),
new(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.")
}),
new Conversation(new List<Message>
new(new List<Message>
{
new Message(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new Message(Role.User, "How far is the Moon from Earth?"),
new Message(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.")
new(Role.System, "Marv is a factual chatbot that is also sarcastic."),
new(Role.User, "How far is the Moon from Earth?"),
new(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.")
})
};
const string localTrainingDataPath = "fineTunesTestTrainingData.jsonl";
Expand Down
4 changes: 3 additions & 1 deletion OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using OpenAI.Moderations;
using System;
using System.Threading.Tasks;
Expand Down
12 changes: 7 additions & 5 deletions OpenAI-DotNet-Tests/TestFixture_11_Assistants.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using OpenAI.Assistants;
using System;
using System.Collections.Generic;
Expand All @@ -21,7 +23,7 @@ public async Task Test_01_CreateAssistant()
var file = await OpenAIClient.FilesEndpoint.UploadFileAsync(testFilePath, "assistants");
File.Delete(testFilePath);
Assert.IsFalse(File.Exists(testFilePath));
var request = new CreateAssistantRequest("gpt-3.5-turbo-1106",
var request = new CreateAssistantRequest("gpt-3.5-turbo",
name: "test-assistant",
description: "Used for unit testing.",
instructions: "You are test assistant",
Expand All @@ -40,7 +42,7 @@ public async Task Test_01_CreateAssistant()
Assert.AreEqual("test-assistant", assistant.Name);
Assert.AreEqual("Used for unit testing.", assistant.Description);
Assert.AreEqual("You are test assistant", assistant.Instructions);
Assert.AreEqual("gpt-3.5-turbo-1106", assistant.Model);
Assert.AreEqual("gpt-3.5-turbo", assistant.Model);
Assert.IsNotEmpty(assistant.Metadata);
testAssistant = assistant;
Console.WriteLine($"{assistant} -> {assistant.Metadata["test"]}");
Expand Down Expand Up @@ -68,7 +70,7 @@ public async Task Test_03_ModifyAssistants()
Assert.IsNotNull(testAssistant);
Assert.IsNotNull(OpenAIClient.AssistantsEndpoint);
var request = new CreateAssistantRequest(
model: "gpt-4-1106-preview",
model: "gpt-4-turbo-preview",
name: "Test modified",
description: "Modified description",
instructions: "You are modified test assistant");
Expand All @@ -77,7 +79,7 @@ public async Task Test_03_ModifyAssistants()
Assert.AreEqual("Test modified", assistant.Name);
Assert.AreEqual("Modified description", assistant.Description);
Assert.AreEqual("You are modified test assistant", assistant.Instructions);
Assert.AreEqual("gpt-4-1106-preview", assistant.Model);
Assert.AreEqual("gpt-4-turbo-preview", assistant.Model);
Assert.IsTrue(assistant.Metadata.ContainsKey("test"));
Console.WriteLine($"{assistant.Id} -> modified");
}
Expand Down
52 changes: 20 additions & 32 deletions OpenAI-DotNet-Tests/TestFixture_12_Threads.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using OpenAI.Assistants;
using OpenAI.Files;
using OpenAI.Tests.Weather;
Expand All @@ -7,8 +9,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

namespace OpenAI.Tests
Expand Down Expand Up @@ -216,7 +216,7 @@ public async Task Test_06_01_CreateRun()
new CreateAssistantRequest(
name: "Math Tutor",
instructions: "You are a personal math tutor. Answer questions briefly, in a sentence or less.",
model: "gpt-4-1106-preview"));
model: "gpt-4-turbo-preview"));
Assert.NotNull(assistant);
testAssistant = assistant;
var thread = await OpenAIClient.ThreadsEndpoint.CreateThreadAsync();
Expand Down Expand Up @@ -344,29 +344,13 @@ public async Task Test_06_06_TestCleanup()
[Test]
public async Task Test_07_01_SubmitToolOutput()
{
var function = new Function(
nameof(WeatherService.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" }
});
testAssistant = await OpenAIClient.AssistantsEndpoint.CreateAssistantAsync(new CreateAssistantRequest(tools: new Tool[] { function }));
var run = await testAssistant.CreateThreadAndRunAsync("I'm in Kuala-Lumpur, please tell me what's the temperature in celsius now?");
var tools = new List<Tool>
{
Tool.GetOrCreateTool(typeof(WeatherService), nameof(WeatherService.GetCurrentWeatherAsync))
};
var assistantRequest = new CreateAssistantRequest(tools: tools, instructions: "You are a helpful weather assistant. Use the appropriate unit based on geographical location.");
testAssistant = await OpenAIClient.AssistantsEndpoint.CreateAssistantAsync(assistantRequest);
var run = await testAssistant.CreateThreadAndRunAsync("I'm in Kuala-Lumpur, please tell me what's the temperature now?");
testThread = await run.GetThreadAsync();
// waiting while run is Queued and InProgress
run = await run.WaitForStatusChangeAsync();
Expand Down Expand Up @@ -394,13 +378,17 @@ public async Task Test_07_01_SubmitToolOutput()
var toolCall = run.RequiredAction.SubmitToolOutputs.ToolCalls[0];
Assert.AreEqual("function", toolCall.Type);
Assert.IsNotNull(toolCall.FunctionCall);
Assert.AreEqual(nameof(WeatherService.GetCurrentWeather), toolCall.FunctionCall.Name);
Assert.IsTrue(toolCall.FunctionCall.Name.Contains(nameof(WeatherService.GetCurrentWeatherAsync)));
Assert.IsNotNull(toolCall.FunctionCall.Arguments);
Console.WriteLine($"tool call arguments: {toolCall.FunctionCall.Arguments}");
var functionArgs = JsonSerializer.Deserialize<WeatherArgs>(toolCall.FunctionCall.Arguments);
var functionResult = WeatherService.GetCurrentWeather(functionArgs);
var toolOutput = new ToolOutput(toolCall.Id, functionResult);
run = await run.SubmitToolOutputsAsync(toolOutput);
var toolOutputs = await testAssistant.GetToolOutputsAsync(run.RequiredAction.SubmitToolOutputs.ToolCalls);

foreach (var toolOutput in toolOutputs)
{
Console.WriteLine($"tool call output: {toolOutput.Output}");
}

run = await run.SubmitToolOutputsAsync(toolOutputs);
// waiting while run in Queued and InProgress
run = await run.WaitForStatusChangeAsync();
Assert.AreEqual(RunStatus.Completed, run.Status);
Expand Down
Loading

0 comments on commit 192c765

Please sign in to comment.