-
Notifications
You must be signed in to change notification settings - Fork 350
Add ChatTools and ResponseTools helper classes #422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
a0ec8a3
04b7069
294ed2d
d909b21
1dfadee
12e8a87
47f3e2d
93f94c6
8ab51f1
4309965
dcc655c
24f1821
a63b571
7bc969c
d8da69a
567ffa0
fe67356
385dd35
78e3e5f
f46fd40
4009c2b
5703fc0
76b907a
5ce1d02
c984bc8
de89187
869a455
6079911
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| using Moq; | ||
| using NUnit.Framework; | ||
| using OpenAI.Chat; | ||
| using OpenAI.Embeddings; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.ClientModel; | ||
| using System.ClientModel.Primitives; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Net.Http; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace OpenAI.Tests.Utility; | ||
|
|
||
| [TestFixture] | ||
| [Category("Utility")] | ||
| public class ChatToolsTests | ||
|
||
| { | ||
| private class TestTools | ||
| { | ||
| public static string Echo(string message) => message; | ||
| public static int Add(int a, int b) => a + b; | ||
| } | ||
|
|
||
| private Mock<EmbeddingClient> mockEmbeddingClient; | ||
|
|
||
| [SetUp] | ||
| public void Setup() | ||
| { | ||
| mockEmbeddingClient = new Mock<EmbeddingClient>("text-embedding-ada-002", new ApiKeyCredential("test-key")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void CanAddLocalTools() | ||
| { | ||
| var tools = new ChatTools(); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| Assert.That(tools.Tools, Has.Count.EqualTo(2)); | ||
| Assert.That(tools.Tools.Any(t => t.FunctionName == "Echo")); | ||
| Assert.That(tools.Tools.Any(t => t.FunctionName == "Add")); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CanCallToolsAsync() | ||
| { | ||
| var tools = new ChatTools(); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| var toolCalls = new[] | ||
| { | ||
| ChatToolCall.CreateFunctionToolCall("call1", "Echo", BinaryData.FromString(@"{""message"": ""Hello""}")), | ||
| ChatToolCall.CreateFunctionToolCall("call2", "Add", BinaryData.FromString(@"{""a"": 2, ""b"": 3}")) | ||
| }; | ||
|
|
||
| var results = await tools.CallAsync(toolCalls); | ||
| var resultsList = results.ToList(); | ||
|
|
||
| Assert.That(resultsList, Has.Count.EqualTo(2)); | ||
| Assert.That(resultsList[0].ToolCallId, Is.EqualTo("call1")); | ||
| Assert.That(resultsList[0].Content[0].Text, Is.EqualTo("Hello")); | ||
| Assert.That(resultsList[1].ToolCallId, Is.EqualTo("call2")); | ||
| Assert.That(resultsList[1].Content[0].Text, Is.EqualTo("5")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void CreatesCompletionOptionsWithTools() | ||
| { | ||
| var tools = new ChatTools(); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| var options = tools.CreateCompletionOptions(); | ||
|
|
||
| Assert.That(options.Tools, Has.Count.EqualTo(2)); | ||
| Assert.That(options.Tools.Any(t => t.FunctionName == "Echo")); | ||
| Assert.That(options.Tools.Any(t => t.FunctionName == "Add")); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CanFilterToolsByRelevance() | ||
| { | ||
| // Setup mock embedding client to return a mock response | ||
| var embedding = OpenAIEmbeddingsModelFactory.OpenAIEmbedding(vector: new[] { 0.5f, 0.5f }); | ||
| var embeddingCollection = OpenAIEmbeddingsModelFactory.OpenAIEmbeddingCollection( | ||
| items: new[] { embedding }, | ||
| model: "text-embedding-ada-002", | ||
| usage: OpenAIEmbeddingsModelFactory.EmbeddingTokenUsage(10, 10)); | ||
| var mockResponse = new MockPipelineResponse(200); | ||
|
|
||
| mockEmbeddingClient | ||
| .Setup(c => c.GenerateEmbeddingAsync( | ||
| It.IsAny<string>(), | ||
| It.IsAny<EmbeddingGenerationOptions>(), | ||
| It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(ClientResult.FromValue(embedding, mockResponse)); | ||
|
|
||
| var tools = new ChatTools(mockEmbeddingClient.Object); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| var options = await Task.Run(() => tools.CreateCompletionOptions("Need to add two numbers", 1, 0.5f)); | ||
|
|
||
| Assert.That(options.Tools, Has.Count.LessThanOrEqualTo(1)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void ImplicitConversionToCompletionOptions() | ||
| { | ||
| var tools = new ChatTools(); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| ChatCompletionOptions options = tools; | ||
|
|
||
| Assert.That(options.Tools, Has.Count.EqualTo(2)); | ||
| Assert.That(options.Tools.Any(t => t.FunctionName == "Echo")); | ||
| Assert.That(options.Tools.Any(t => t.FunctionName == "Add")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void ThrowsWhenCallingNonExistentTool() | ||
| { | ||
| var tools = new ChatTools(); | ||
| var toolCalls = new[] | ||
| { | ||
| ChatToolCall.CreateFunctionToolCall("call1", "NonExistentTool", BinaryData.FromString("{}")) | ||
| }; | ||
|
|
||
| Assert.ThrowsAsync<InvalidOperationException>(() => tools.CallAsync(toolCalls)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| using Moq; | ||
| using NUnit.Framework; | ||
| using OpenAI.Embeddings; | ||
| using OpenAI.Responses; | ||
| using System; | ||
| using System.ClientModel; | ||
| using System.ClientModel.Primitives; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace OpenAI.Tests.Utility; | ||
|
|
||
| [TestFixture] | ||
| [Category("Utility")] | ||
| public class ResponseToolsTests | ||
| { | ||
| private class TestTools | ||
| { | ||
| public static string Echo(string message) => message; | ||
| public static int Add(int a, int b) => a + b; | ||
| } | ||
|
|
||
| private Mock<EmbeddingClient> mockEmbeddingClient; | ||
|
|
||
| [SetUp] | ||
| public void Setup() | ||
| { | ||
| mockEmbeddingClient = new Mock<EmbeddingClient>("text-embedding-ada-002", new ApiKeyCredential("test-key")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void CanAddLocalTools() | ||
| { | ||
| var tools = new ResponseTools(); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| Assert.That(tools.Tools, Has.Count.EqualTo(2)); | ||
| Assert.That(tools.Tools.Any(t => ((string)t.GetType().GetProperty("Name").GetValue(t)).Contains("Echo"))); | ||
| Assert.That(tools.Tools.Any(t => ((string)t.GetType().GetProperty("Name").GetValue(t)).Contains("Add"))); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CanCallToolAsync() | ||
| { | ||
| var tools = new ResponseTools(); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| var toolCall = new FunctionCallResponseItem("call1", "Echo", BinaryData.FromString(@"{""message"": ""Hello""}")); | ||
| var result = await tools.CallAsync(toolCall); | ||
|
|
||
| Assert.That(result.CallId, Is.EqualTo("call1")); | ||
| Assert.That(result.ToString(), Is.EqualTo("Hello")); | ||
|
|
||
| var addCall = new FunctionCallResponseItem("call2", "Add", BinaryData.FromString(@"{""a"": 2, ""b"": 3}")); | ||
| result = await tools.CallAsync(addCall); | ||
|
|
||
| Assert.That(result.CallId, Is.EqualTo("call2")); | ||
| Assert.That(result.ToString(), Is.EqualTo("5")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void CreatesResponseOptionsWithTools() | ||
| { | ||
| var tools = new ResponseTools(); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| var options = tools.CreateResponseOptions(); | ||
|
|
||
| Assert.That(options.Tools, Has.Count.EqualTo(2)); | ||
| Assert.That(options.Tools.Any(t => t.ToString().Contains("Echo"))); | ||
| Assert.That(options.Tools.Any(t => t.ToString().Contains("Add"))); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CanFilterToolsByRelevance() | ||
| { | ||
| // Setup mock embedding client to return a mock response | ||
| var embedding = OpenAIEmbeddingsModelFactory.OpenAIEmbedding(vector: new[] { 0.5f, 0.5f }); | ||
| var embeddingCollection = OpenAIEmbeddingsModelFactory.OpenAIEmbeddingCollection( | ||
| items: new[] { embedding }, | ||
| model: "text-embedding-ada-002", | ||
| usage: OpenAIEmbeddingsModelFactory.EmbeddingTokenUsage(10, 10)); | ||
| var mockResponse = new MockPipelineResponse(200); | ||
|
|
||
| mockEmbeddingClient | ||
| .Setup(c => c.GenerateEmbeddingsAsync( | ||
| It.IsAny<BinaryContent>(), | ||
| It.IsAny<RequestOptions>())) | ||
| .ReturnsAsync(ClientResult.FromValue(embeddingCollection, mockResponse)); | ||
|
|
||
| var tools = new ResponseTools(mockEmbeddingClient.Object); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| var options = await Task.Run(() => tools.CreateResponseOptions("Need to add two numbers", 1, 0.5f)); | ||
|
|
||
| Assert.That(options.Tools, Has.Count.LessThanOrEqualTo(1)); | ||
| mockEmbeddingClient.Verify( | ||
| c => c.GenerateEmbeddingsAsync( | ||
| It.IsAny<BinaryContent>(), | ||
| It.IsAny<RequestOptions>()), | ||
| Times.Once); | ||
| } | ||
|
|
||
| [Test] | ||
| public void ImplicitConversionToResponseOptions() | ||
| { | ||
| var tools = new ResponseTools(); | ||
| tools.AddLocalTools(typeof(TestTools)); | ||
|
|
||
| ResponseCreationOptions options = tools; | ||
|
|
||
| Assert.That(options.Tools, Has.Count.EqualTo(2)); | ||
| Assert.That(options.Tools.Any(t => t.ToString().Contains("Echo"))); | ||
| Assert.That(options.Tools.Any(t => t.ToString().Contains("Add"))); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task ReturnsErrorForNonExistentTool() | ||
| { | ||
| var tools = new ResponseTools(); | ||
| var toolCall = new FunctionCallResponseItem("call1", "NonExistentTool", BinaryData.FromString("{}")); | ||
|
|
||
| var result = await tools.CallAsync(toolCall); | ||
| Assert.That(result.ToString(), Does.StartWith("I don't have a tool called")); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.