Skip to content

Commit

Permalink
OpenAI-DotNet 7.7.7 (#282)
Browse files Browse the repository at this point in the history
- Updated static models list
  - Added gpt-4-turbo
  - Marked some models as deprecated since they are no longer available
- Added temperature to CreateRunRequest and CreateThreadAndRunRequest
- Fixed temperature to string conversion to be invariant culture for
audio requests
- Fixed type checking built in function tool calls

---------

Co-authored-by: EssentialNRG <[email protected]>
Co-authored-by: Dennis van den Berg <[email protected]>
  • Loading branch information
3 people authored Apr 21, 2024
1 parent 9124a33 commit 417a7fd
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 68 deletions.
2 changes: 2 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ assignees: ''
<!-- A clear and concise description of what the bug is. -->

## To Reproduce

Steps to reproduce the behavior:

1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
Expand Down
31 changes: 28 additions & 3 deletions OpenAI-DotNet-Tests/TestFixture_00_02_Tools.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using NUnit.Framework;
using OpenAI.Images;
using OpenAI.Tests.Weather;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using NUnit.Framework;
using OpenAI.Images;
using OpenAI.Tests.Weather;

namespace OpenAI.Tests
{
Expand Down Expand Up @@ -74,5 +74,30 @@ private string FunctionWithArgs(string arg1, string arg2)
{
return $"{arg1} {arg2}";
}

[Test]
public void Test_03_Tool_works_when_called_concurrently()
{
Assert.Multiple(async () =>
{
await Task.WhenAll(
Test(1),
Test(2),
Test(3),
Test(4)
);
});

async Task Test(int id)
{
var tool = Tool.FromFunc($"myFunc_{id}", () => id, "This func allows reading the local variable id");

// Delay a little bit to simulate calling OpenAi API:
await Task.Delay(50);

var result = tool.InvokeFunction<int>();
Assert.AreEqual(id, result);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

using NUnit.Framework;
using OpenAI.Assistants;
using OpenAI.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace OpenAI.Tests
{
internal class TestFixture_11_Assistants : AbstractTestFixture
internal class TestFixture_02_Assistants : AbstractTestFixture
{
private AssistantResponse testAssistant;

Expand All @@ -23,7 +24,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",
var request = new CreateAssistantRequest(Model.GPT3_5_Turbo,
name: "test-assistant",
description: "Used for unit testing.",
instructions: "You are test assistant",
Expand All @@ -42,7 +43,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", assistant.Model);
Assert.AreEqual(Model.GPT3_5_Turbo.ToString(), assistant.Model);
Assert.IsNotEmpty(assistant.Metadata);
testAssistant = assistant;
Console.WriteLine($"{assistant} -> {assistant.Metadata["test"]}");
Expand Down Expand Up @@ -70,7 +71,7 @@ public async Task Test_03_ModifyAssistants()
Assert.IsNotNull(testAssistant);
Assert.IsNotNull(OpenAIClient.AssistantsEndpoint);
var request = new CreateAssistantRequest(
model: "gpt-4-turbo-preview",
model: Model.GPT4_Turbo,
name: "Test modified",
description: "Modified description",
instructions: "You are modified test assistant");
Expand All @@ -79,7 +80,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-turbo-preview", assistant.Model);
Assert.AreEqual(Model.GPT4_Turbo.ToString(), assistant.Model);
Assert.IsTrue(assistant.Metadata.ContainsKey("test"));
Console.WriteLine($"{assistant.Id} -> modified");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NUnit.Framework;
using OpenAI.Assistants;
using OpenAI.Files;
using OpenAI.Models;
using OpenAI.Tests.Weather;
using OpenAI.Threads;
using System;
Expand All @@ -16,7 +17,7 @@ namespace OpenAI.Tests
/// <summary>
/// https://github.com/openai/openai-cookbook/blob/main/examples/Assistants_API_overview_python.ipynb
/// </summary>
internal class TestFixture_12_Threads : AbstractTestFixture
internal class TestFixture_03_Threads : AbstractTestFixture
{
private static RunResponse testRun;
private static ThreadResponse testThread;
Expand Down Expand Up @@ -216,7 +217,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-turbo-preview"));
model: Model.GPT4_Turbo));
Assert.NotNull(assistant);
testAssistant = assistant;
var thread = await OpenAIClient.ThreadsEndpoint.CreateThreadAsync();
Expand Down Expand Up @@ -346,6 +347,7 @@ public async Task Test_07_01_SubmitToolOutput()
{
var tools = new List<Tool>
{
Tool.CodeInterpreter,
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.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace OpenAI.Tests
{
internal class TestFixture_03_Chat : AbstractTestFixture
internal class TestFixture_04_Chat : AbstractTestFixture
{
[Test]
public async Task Test_01_01_GetChatCompletion()
Expand All @@ -24,7 +24,7 @@ public async Task Test_01_01_GetChatCompletion()
new(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new(Role.User, "Where was it played?"),
};
var chatRequest = new ChatRequest(messages, Model.GPT4);
var chatRequest = new ChatRequest(messages, Model.GPT4_Turbo);
var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
Assert.IsNotNull(response);
Assert.IsNotNull(response.Choices);
Expand Down Expand Up @@ -84,7 +84,7 @@ public async Task Test_01_03_JsonMode()
new(Role.System, "You are a helpful assistant designed to output JSON."),
new(Role.User, "Who won the world series in 2020?"),
};
var chatRequest = new ChatRequest(messages, "gpt-4-turbo-preview", responseFormat: ChatResponseFormat.Json);
var chatRequest = new ChatRequest(messages, Model.GPT4_Turbo, responseFormat: ChatResponseFormat.Json);
var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
Assert.IsNotNull(response);
Assert.IsNotNull(response.Choices);
Expand Down Expand Up @@ -289,7 +289,7 @@ public async Task Test_02_03_ChatCompletion_Multiple_Tools_Streaming()
};

var tools = Tool.GetAllAvailableTools(false, forceUpdate: true, clearCache: true);
var chatRequest = new ChatRequest(messages, model: "gpt-4-turbo-preview", tools: tools, toolChoice: "auto");
var chatRequest = new ChatRequest(messages, model: Model.GPT4_Turbo, tools: tools, toolChoice: "auto");
var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
Expand All @@ -311,7 +311,7 @@ public async Task Test_02_03_ChatCompletion_Multiple_Tools_Streaming()
messages.Add(new Message(toolCall, output));
}

chatRequest = new ChatRequest(messages, model: "gpt-4-turbo-preview", tools: tools, toolChoice: "none");
chatRequest = new ChatRequest(messages, model: Model.GPT4_Turbo, tools: tools, toolChoice: "none");
response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);

Assert.IsNotNull(response);
Expand Down Expand Up @@ -383,7 +383,7 @@ public async Task Test_03_01_GetChatVision()
new ImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", ImageDetail.Low)
})
};
var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview");
var chatRequest = new ChatRequest(messages, model: Model.GPT4_Turbo);
var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
Assert.IsNotNull(response);
Assert.IsNotNull(response.Choices);
Expand All @@ -404,7 +404,7 @@ public async Task Test_03_02_GetChatVisionStreaming()
new ImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", ImageDetail.Low)
})
};
var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview");
var chatRequest = new ChatRequest(messages, model: Model.GPT4_Turbo);
var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
Expand Down
14 changes: 12 additions & 2 deletions OpenAI-DotNet/Assistants/AssistantExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,12 @@ public static async Task<bool> DeleteFileAsync(this AssistantResponse assistant,
/// <returns>Tool output result as <see cref="string"/></returns>
public static string InvokeToolCall(this AssistantResponse assistant, ToolCall toolCall)
{
var tool = assistant.Tools.FirstOrDefault(tool => toolCall.Type == "function" && tool.Function.Name == toolCall.FunctionCall.Name) ??
if (toolCall.Type != "function")
{
throw new InvalidOperationException($"Cannot invoke built in tool {toolCall.Type}");
}

var tool = assistant.Tools.FirstOrDefault(tool => tool.Type == "function" && tool.Function.Name == toolCall.FunctionCall.Name) ??
throw new InvalidOperationException($"Failed to find a valid tool for [{toolCall.Id}] {toolCall.Type}");
tool.Function.Arguments = toolCall.FunctionCall.Arguments;
return tool.InvokeFunction();
Expand All @@ -202,7 +207,12 @@ public static string InvokeToolCall(this AssistantResponse assistant, ToolCall t
/// <returns>Tool output result as <see cref="string"/></returns>
public static async Task<string> InvokeToolCallAsync(this AssistantResponse assistant, ToolCall toolCall, CancellationToken cancellationToken = default)
{
var tool = assistant.Tools.FirstOrDefault(tool => toolCall.Type == "function" && tool.Function.Name == toolCall.FunctionCall.Name) ??
if (toolCall.Type != "function")
{
throw new InvalidOperationException($"Cannot invoke built in tool {toolCall.Type}");
}

var tool = assistant.Tools.FirstOrDefault(tool => tool.Type == "function" && tool.Function.Name == toolCall.FunctionCall.Name) ??
throw new InvalidOperationException($"Failed to find a valid tool for [{toolCall.Id}] {toolCall.Type}");
tool.Function.Arguments = toolCall.FunctionCall.Arguments;
return await tool.InvokeFunctionAsync(cancellationToken).ConfigureAwait(false);
Expand Down
5 changes: 3 additions & 2 deletions OpenAI-DotNet/Audio/AudioEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using OpenAI.Extensions;
using System;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Text.Json;
Expand Down Expand Up @@ -121,7 +122,7 @@ private async Task<string> Internal_CreateTranscriptionAsync(AudioTranscriptionR

if (request.Temperature.HasValue)
{
content.Add(new StringContent(request.Temperature.ToString()), "temperature");
content.Add(new StringContent(request.Temperature.Value.ToString(CultureInfo.InvariantCulture)), "temperature");
}

switch (request.TimestampGranularities)
Expand Down Expand Up @@ -192,7 +193,7 @@ private async Task<string> Internal_CreateTranslationAsync(AudioTranslationReque

if (request.Temperature.HasValue)
{
content.Add(new StringContent(request.Temperature.ToString()), "temperature");
content.Add(new StringContent(request.Temperature.Value.ToString(CultureInfo.InvariantCulture)), "temperature");
}

request.Dispose();
Expand Down
24 changes: 23 additions & 1 deletion OpenAI-DotNet/Common/Function.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public Function(string name, string description = null, JsonNode parameters = nu
throw new ArgumentException($"The name of the function does not conform to naming standards: {NameRegex}");
}

if (functionCache.ContainsKey(name))
{
throw new ArgumentException($"The function \"{name}\" is already registered.");
}

Name = name;
Description = description;
Parameters = parameters;
Expand All @@ -68,28 +73,43 @@ public Function(string name, string description, string parameters)
throw new ArgumentException($"The name of the function does not conform to naming standards: {NameRegex}");
}

if (functionCache.ContainsKey(name))
{
throw new ArgumentException($"The function \"{name}\" is already registered.");
}

Name = name;
Description = description;
Parameters = JsonNode.Parse(parameters);
functionCache[Name] = this;
}


internal Function(string name, string description, MethodInfo method, object instance = null)
{
if (!System.Text.RegularExpressions.Regex.IsMatch(name, NameRegex))
{
throw new ArgumentException($"The name of the function does not conform to naming standards: {NameRegex}");
}

if (functionCache.ContainsKey(name))
{
throw new ArgumentException($"The function \"{name}\" is already registered.");
}

Name = name;
Description = description;
MethodInfo = method;
Parameters = method.GenerateJsonSchema();
Instance = instance;

functionCache[Name] = this;
}

internal static Function GetOrCreateFunction(string name, string description, MethodInfo method, object instance = null)
=> functionCache.TryGetValue(name, out var function)
? function
: new Function(name, description, method, instance);

#region Func<,> Overloads

public static Function FromFunc<TResult>(string name, Func<TResult> function, string description = null)
Expand Down Expand Up @@ -236,6 +256,8 @@ internal void CopyFrom(Function other)

private static readonly ConcurrentDictionary<string, Function> functionCache = new();

internal static void ClearFunctionCache() => functionCache.Clear();

/// <summary>
/// Invokes the function and returns the result as json.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion OpenAI-DotNet/Common/Tool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public async Task<T> InvokeFunctionAsync<T>(CancellationToken cancellationToken
public static void ClearRegisteredTools()
{
toolCache.Clear();
Function.ClearFunctionCache();
toolCache.Add(CodeInterpreter);
toolCache.Add(Retrieval);
}
Expand Down Expand Up @@ -244,7 +245,7 @@ public static Tool GetOrCreateTool(Type type, string methodName, string descript
return tool;
}

tool = new Tool(new Function(functionName, description ?? string.Empty, method));
tool = new Tool(Function.GetOrCreateFunction(functionName, description ?? string.Empty, method));
toolCache.Add(tool);
return tool;
}
Expand Down
Loading

0 comments on commit 417a7fd

Please sign in to comment.