diff --git a/.github/workflows/Publish-Nuget.yml b/.github/workflows/Publish-Nuget.yml
index 859d27b4..3c8ba366 100644
--- a/.github/workflows/Publish-Nuget.yml
+++ b/.github/workflows/Publish-Nuget.yml
@@ -31,32 +31,55 @@ env:
jobs:
build:
if: ${{ !github.event_name == 'pull_request' || !github.event.pull_request.draft }}
- runs-on: windows-latest
+ runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
+
- uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- - uses: microsoft/setup-msbuild@v1
+ - run: dotnet restore
+
+ - run: dotnet build --configuration Release --no-restore
+
- name: Test Packages
- run: dotnet test --configuration Release
if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' }}
+ run: dotnet test --configuration Release --collect:"XPlat Code Coverage" --logger:trx --no-build --no-restore --results-directory ./test-results
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORGANIZATION_ID: ${{ secrets.OPENAI_ORGANIZATION_ID }}
- - name: Build Pack and Publish NuGet Package
+ - name: Publish Test Results
+ if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }}
+ uses: EnricoMi/publish-unit-test-result-action@v2
+ with:
+ files: test-results/**/*.trx
+ comment_mode: off
+ report_individual_runs: true
+ compare_to_earlier_commit: false
+
+ - name: Code Coverage Summary Report
+ if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }}
+ uses: irongut/CodeCoverageSummary@v1.3.0
+ with:
+ filename: test-results/**/coverage.cobertura.xml
+ badge: true
+ format: 'markdown'
+ output: 'both'
+
+ - name: Write Coverage Job Summary
+ if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }}
+ run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY
+
+ - name: Pack and Publish NuGet Package
run: |
$projectPath = "${{ github.workspace }}\OpenAI-DotNet"
$proxyProjectPath = "${{ github.workspace }}\OpenAI-DotNet-Proxy"
- # build all
- dotnet build --configuration Release
-
# pack OpenAI-DotNet
dotnet pack $projectPath --configuration Release --include-symbols
$out = "$projectPath\bin\Release"
diff --git a/OpenAI-DotNet-Tests/AbstractTestFixture.cs b/OpenAI-DotNet-Tests/AbstractTestFixture.cs
index ec492660..1facbe32 100644
--- a/OpenAI-DotNet-Tests/AbstractTestFixture.cs
+++ b/OpenAI-DotNet-Tests/AbstractTestFixture.cs
@@ -1,6 +1,6 @@
-using System;
-using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
+using System;
using System.Net.Http;
namespace OpenAI.Tests
diff --git a/OpenAI-DotNet-Tests/OpenAI-DotNet-Tests.csproj b/OpenAI-DotNet-Tests/OpenAI-DotNet-Tests.csproj
index 2565cf58..0ae86c67 100644
--- a/OpenAI-DotNet-Tests/OpenAI-DotNet-Tests.csproj
+++ b/OpenAI-DotNet-Tests/OpenAI-DotNet-Tests.csproj
@@ -11,6 +11,7 @@
+
diff --git a/OpenAI-DotNet-Tests/TestFixture_00_Authentication.cs b/OpenAI-DotNet-Tests/TestFixture_00_00_Authentication.cs
similarity index 99%
rename from OpenAI-DotNet-Tests/TestFixture_00_Authentication.cs
rename to OpenAI-DotNet-Tests/TestFixture_00_00_Authentication.cs
index e29afba9..8c589730 100644
--- a/OpenAI-DotNet-Tests/TestFixture_00_Authentication.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_00_00_Authentication.cs
@@ -6,7 +6,7 @@
namespace OpenAI.Tests
{
- internal class TestFixture_00_Authentication
+ internal class TestFixture_00_00_Authentication
{
[SetUp]
public void Setup()
diff --git a/OpenAI-DotNet-Tests/TextFixture_11_Proxy.cs b/OpenAI-DotNet-Tests/TestFixture_00_01_Proxy.cs
similarity index 96%
rename from OpenAI-DotNet-Tests/TextFixture_11_Proxy.cs
rename to OpenAI-DotNet-Tests/TestFixture_00_01_Proxy.cs
index d4f386bf..f962fcde 100644
--- a/OpenAI-DotNet-Tests/TextFixture_11_Proxy.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_00_01_Proxy.cs
@@ -6,7 +6,7 @@
namespace OpenAI.Tests
{
- internal class TextFixture_11_Proxy : AbstractTestFixture
+ internal class TestFixture_00_01_Proxy : AbstractTestFixture
{
[Test]
public async Task Test_01_Health()
diff --git a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs
index df71e694..9ce073d7 100644
--- a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs
@@ -1,5 +1,6 @@
using NUnit.Framework;
using OpenAI.Chat;
+using OpenAI.Models;
using OpenAI.Tests.Weather;
using System;
using System.Collections.Generic;
@@ -23,32 +24,24 @@ public async Task Test_01_GetChatCompletion()
new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new Message(Role.User, "Where was it played?"),
};
- var chatRequest = new ChatRequest(messages, number: 2);
- var result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 2);
+ var chatRequest = new ChatRequest(messages, Model.GPT4);
+ var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsNotEmpty(response.Choices);
- foreach (var choice in result.Choices)
+ foreach (var choice in response.Choices)
{
Console.WriteLine($"[{choice.Index}] {choice.Message.Role}: {choice.Message.Content} | Finish Reason: {choice.FinishReason}");
}
- result.GetUsage();
-
- Console.WriteLine(result.LimitRequests);
- Console.WriteLine(result.RemainingRequests);
- Console.WriteLine(result.ResetRequests);
- Console.WriteLine(result.LimitTokens);
- Console.WriteLine(result.RemainingTokens);
- Console.WriteLine(result.ResetTokens);
+ response.GetUsage();
}
[Test]
public async Task Test_02_GetChatStreamingCompletion()
{
Assert.IsNotNull(OpenAIClient.ChatEndpoint);
- const int choiceCount = 2;
var messages = new List
{
new Message(Role.System, "You are a helpful assistant."),
@@ -56,14 +49,8 @@ public async Task Test_02_GetChatStreamingCompletion()
new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new Message(Role.User, "Where was it played?"),
};
- var chatRequest = new ChatRequest(messages, number: choiceCount);
- var cumulativeDelta = new List();
-
- for (var i = 0; i < choiceCount; i++)
- {
- cumulativeDelta.Add(string.Empty);
- }
-
+ var chatRequest = new ChatRequest(messages);
+ var cumulativeDelta = string.Empty;
var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
@@ -72,24 +59,17 @@ public async Task Test_02_GetChatStreamingCompletion()
foreach (var choice in partialResponse.Choices.Where(choice => choice.Delta?.Content != null))
{
- cumulativeDelta[choice.Index] += choice.Delta.Content;
+ cumulativeDelta += choice.Delta.Content;
}
});
-
Assert.IsNotNull(response);
Assert.IsNotNull(response.Choices);
- Assert.IsTrue(response.Choices.Count == choiceCount);
-
- for (var i = 0; i < choiceCount; i++)
- {
- var choice = response.Choices[i];
- Assert.IsFalse(string.IsNullOrEmpty(choice?.Message?.Content));
- Console.WriteLine($"[{choice.Index}] {choice.Message.Role}: {choice.Message.Content} | Finish Reason: {choice.FinishReason}");
- Assert.IsTrue(choice.Message.Role == Role.Assistant);
- var deltaContent = cumulativeDelta[i];
- Assert.IsTrue(choice.Message.Content.Equals(deltaContent));
- }
-
+ var choice = response.FirstChoice;
+ Assert.IsFalse(string.IsNullOrEmpty(choice?.Message?.Content));
+ Console.WriteLine($"[{choice!.Index}] {choice.Message!.Role}: {choice.Message.Content} | Finish Reason: {choice.FinishReason}");
+ Assert.IsTrue(choice.Message.Role == Role.Assistant);
+ Assert.IsTrue(choice.Message.Content!.Equals(cumulativeDelta));
+ Console.WriteLine(response.ToString());
response.GetUsage();
}
@@ -104,16 +84,24 @@ public async Task Test_03_GetChatStreamingCompletionEnumerableAsync()
new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new Message(Role.User, "Where was it played?"),
};
- var chatRequest = new ChatRequest(messages, number: 2);
- await foreach (var result in OpenAIClient.ChatEndpoint.StreamCompletionEnumerableAsync(chatRequest))
+ var cumulativeDelta = string.Empty;
+ var chatRequest = new ChatRequest(messages);
+ await foreach (var partialResponse in OpenAIClient.ChatEndpoint.StreamCompletionEnumerableAsync(chatRequest))
{
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.NotZero(result.Choices.Count);
+ Assert.IsNotNull(partialResponse);
+ Assert.NotNull(partialResponse.Choices);
+ Assert.NotZero(partialResponse.Choices.Count);
+
+ foreach (var choice in partialResponse.Choices.Where(choice => choice.Delta?.Content != null))
+ {
+ cumulativeDelta += choice.Delta.Content;
+ }
}
+
+ Console.WriteLine(cumulativeDelta);
}
- [Test]
+ //[Test]
[Obsolete]
public async Task Test_04_GetChatFunctionCompletion()
{
@@ -155,54 +143,54 @@ public async Task Test_04_GetChatFunctionCompletion()
};
var chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto");
- var result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland");
messages.Add(locationMessage);
Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}");
chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto");
- result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
- if (!string.IsNullOrEmpty(result.FirstChoice.Message.Content))
+ if (!string.IsNullOrEmpty(response.FirstChoice.Message.Content))
{
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var unitMessage = new Message(Role.User, "celsius");
messages.Add(unitMessage);
Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}");
chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto");
- result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
+ response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
}
- Assert.IsTrue(result.FirstChoice.FinishReason == "function_call");
- Assert.IsTrue(result.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather));
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}");
- Console.WriteLine($"{result.FirstChoice.Message.Function.Arguments}");
- var functionArgs = JsonSerializer.Deserialize(result.FirstChoice.Message.Function.Arguments.ToString());
+ Assert.IsTrue(response.FirstChoice.FinishReason == "function_call");
+ Assert.IsTrue(response.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather));
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Function.Arguments}");
+ var functionArgs = JsonSerializer.Deserialize(response.FirstChoice.Message.Function.Arguments.ToString());
var functionResult = WeatherService.GetCurrentWeather(functionArgs);
Assert.IsNotNull(functionResult);
messages.Add(new Message(Role.Function, functionResult, nameof(WeatherService.GetCurrentWeather)));
Console.WriteLine($"{Role.Function}: {functionResult}");
chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto");
- result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Console.WriteLine(result);
+ response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Console.WriteLine(response);
}
- [Test]
+ //[Test]
[Obsolete]
public async Task Test_05_GetChatFunctionCompletion_Streaming()
{
@@ -243,64 +231,64 @@ public async Task Test_05_GetChatFunctionCompletion_Streaming()
};
var chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto");
- var result = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
+ var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
Assert.NotNull(partialResponse.Choices);
Assert.NotZero(partialResponse.Choices.Count);
});
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland");
messages.Add(locationMessage);
Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}");
chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto");
- result = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
+ response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
Assert.NotNull(partialResponse.Choices);
Assert.NotZero(partialResponse.Choices.Count);
});
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
- if (!string.IsNullOrEmpty(result.FirstChoice.Message.Content))
+ if (!string.IsNullOrEmpty(response.FirstChoice.Message.Content))
{
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var unitMessage = new Message(Role.User, "celsius");
messages.Add(unitMessage);
Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}");
chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto");
- result = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
+ response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
Assert.NotNull(partialResponse.Choices);
Assert.NotZero(partialResponse.Choices.Count);
});
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
}
- Assert.IsTrue(result.FirstChoice.FinishReason == "function_call");
- Assert.IsTrue(result.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather));
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}");
- Console.WriteLine($"{result.FirstChoice.Message.Function.Arguments}");
+ Assert.IsTrue(response.FirstChoice.FinishReason == "function_call");
+ Assert.IsTrue(response.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather));
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Function.Arguments}");
- var functionArgs = JsonSerializer.Deserialize(result.FirstChoice.Message.Function.Arguments.ToString());
+ var functionArgs = JsonSerializer.Deserialize(response.FirstChoice.Message.Function.Arguments.ToString());
var functionResult = WeatherService.GetCurrentWeather(functionArgs);
Assert.IsNotNull(functionResult);
messages.Add(new Message(Role.Function, functionResult, nameof(WeatherService.GetCurrentWeather)));
Console.WriteLine($"{Role.Function}: {functionResult}");
}
- [Test]
+ //[Test]
[Obsolete]
public async Task Test_06_GetChatFunctionForceCompletion()
{
@@ -342,13 +330,13 @@ public async Task Test_06_GetChatFunctionForceCompletion()
};
var chatRequest = new ChatRequest(messages, functions: functions);
- var result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland");
messages.Add(locationMessage);
@@ -358,18 +346,18 @@ public async Task Test_06_GetChatFunctionForceCompletion()
functions: functions,
functionCall: nameof(WeatherService.GetCurrentWeather),
model: "gpt-3.5-turbo-0613");
- result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
-
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
-
- Assert.IsTrue(result.FirstChoice.FinishReason == "stop");
- Assert.IsTrue(result.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather));
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}");
- Console.WriteLine($"{result.FirstChoice.Message.Function.Arguments}");
- var functionArgs = JsonSerializer.Deserialize(result.FirstChoice.Message.Function.Arguments.ToString());
+ response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
+
+ Assert.IsTrue(response.FirstChoice.FinishReason == "stop");
+ Assert.IsTrue(response.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather));
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Function.Arguments}");
+ var functionArgs = JsonSerializer.Deserialize(response.FirstChoice.Message.Function.Arguments.ToString());
var functionResult = WeatherService.GetCurrentWeather(functionArgs);
Assert.IsNotNull(functionResult);
messages.Add(new Message(Role.Function, functionResult, nameof(WeatherService.GetCurrentWeather)));
@@ -416,45 +404,45 @@ public async Task Test_07_GetChatToolCompletion()
["required"] = new JsonArray { "location", "unit" }
})
};
-
var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
- var result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland");
messages.Add(locationMessage);
Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}");
chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
- result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
- if (!string.IsNullOrEmpty(result.FirstChoice.Message.Content))
+ if (!string.IsNullOrEmpty(response.FirstChoice.Message.Content))
{
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var unitMessage = new Message(Role.User, "celsius");
messages.Add(unitMessage);
Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}");
chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
- result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
+ response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
}
- var usedTool = result.FirstChoice.Message.ToolCalls[0];
- Assert.IsTrue(result.FirstChoice.FinishReason == "tool_calls");
+ Assert.IsTrue(response.FirstChoice.FinishReason == "tool_calls");
+ var usedTool = response.FirstChoice.Message.ToolCalls[0];
+ Assert.IsNotNull(usedTool);
Assert.IsTrue(usedTool.Function.Name == nameof(WeatherService.GetCurrentWeather));
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}");
Console.WriteLine($"{usedTool.Function.Arguments}");
var functionArgs = JsonSerializer.Deserialize(usedTool.Function.Arguments.ToString());
var functionResult = WeatherService.GetCurrentWeather(functionArgs);
@@ -462,8 +450,8 @@ public async Task Test_07_GetChatToolCompletion()
messages.Add(new Message(usedTool, functionResult));
Console.WriteLine($"{Role.Tool}: {functionResult}");
chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
- result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Console.WriteLine(result);
+ response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Console.WriteLine(response);
}
[Test]
@@ -504,57 +492,57 @@ public async Task Test_08_GetChatToolCompletion_Streaming()
["required"] = new JsonArray { "location", "unit" }
})
};
-
var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
- var result = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
+ var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
Assert.NotNull(partialResponse.Choices);
Assert.NotZero(partialResponse.Choices.Count);
});
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland");
messages.Add(locationMessage);
Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}");
chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
- result = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
+ response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
Assert.NotNull(partialResponse.Choices);
Assert.NotZero(partialResponse.Choices.Count);
});
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
- if (!string.IsNullOrEmpty(result.FirstChoice.Message.Content))
+ if (!string.IsNullOrEmpty(response.FirstChoice.Message.Content))
{
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var unitMessage = new Message(Role.User, "celsius");
messages.Add(unitMessage);
Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}");
chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
- result = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
+ response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
Assert.NotNull(partialResponse.Choices);
Assert.NotZero(partialResponse.Choices.Count);
});
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
}
- Assert.IsTrue(result.FirstChoice.FinishReason == "tool_calls");
- var usedTool = result.FirstChoice.Message.ToolCalls[0];
+ Assert.IsTrue(response.FirstChoice.FinishReason == "tool_calls");
+ var usedTool = response.FirstChoice.Message.ToolCalls[0];
+ Assert.IsNotNull(usedTool);
Assert.IsTrue(usedTool.Function.Name == nameof(WeatherService.GetCurrentWeather));
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}");
Console.WriteLine($"{usedTool.Function.Arguments}");
var functionArgs = JsonSerializer.Deserialize(usedTool.Function.Arguments.ToString());
@@ -603,15 +591,14 @@ public async Task Test_09_GetChatToolForceCompletion()
["required"] = new JsonArray { "location", "unit" }
})
};
-
var chatRequest = new ChatRequest(messages, tools: tools);
- var result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland");
messages.Add(locationMessage);
@@ -620,19 +607,20 @@ public async Task Test_09_GetChatToolForceCompletion()
messages,
tools: tools,
toolChoice: nameof(WeatherService.GetCurrentWeather));
- result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Assert.IsTrue(result.Choices.Count == 1);
- messages.Add(result.FirstChoice.Message);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Assert.IsTrue(response.Choices.Count == 1);
+ messages.Add(response.FirstChoice.Message);
- var usedTool = result.FirstChoice.Message.ToolCalls[0];
- Assert.IsTrue(result.FirstChoice.FinishReason == "stop");
+ Assert.IsTrue(response.FirstChoice.FinishReason == "stop");
+ var usedTool = response.FirstChoice.Message.ToolCalls[0];
+ Assert.IsNotNull(usedTool);
Assert.IsTrue(usedTool.Function.Name == nameof(WeatherService.GetCurrentWeather));
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}");
Console.WriteLine($"{usedTool.Function.Arguments}");
- var functionArgs = JsonSerializer.Deserialize(result.FirstChoice.Message.ToolCalls[0].Function.Arguments.ToString());
+ var functionArgs = JsonSerializer.Deserialize(usedTool.Function.Arguments.ToString());
var functionResult = WeatherService.GetCurrentWeather(functionArgs);
Assert.IsNotNull(functionResult);
messages.Add(new Message(usedTool, functionResult));
@@ -653,11 +641,11 @@ public async Task Test_10_GetChatVision()
})
};
var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview");
- var result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}");
- result.GetUsage();
+ var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishDetails}");
+ response.GetUsage();
}
[Test]
@@ -674,16 +662,16 @@ public async Task Test_11_GetChatVisionStreaming()
})
};
var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview");
- var result = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
+ var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
Assert.IsNotNull(partialResponse);
Assert.NotNull(partialResponse.Choices);
Assert.NotZero(partialResponse.Choices.Count);
});
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.Choices);
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}");
- result.GetUsage();
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.Choices);
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishDetails}");
+ response.GetUsage();
}
}
}
\ No newline at end of file
diff --git a/OpenAI-DotNet-Tests/TestFixture_04_Edits.cs b/OpenAI-DotNet-Tests/TestFixture_04_Edits.cs
index 9f343db3..b49200bb 100644
--- a/OpenAI-DotNet-Tests/TestFixture_04_Edits.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_04_Edits.cs
@@ -8,7 +8,6 @@ namespace OpenAI.Tests
[Obsolete]
internal class TestFixture_04_Edits : AbstractTestFixture
{
- [Test]
public async Task Test_1_GetBasicEdit()
{
Assert.IsNotNull(OpenAIClient.EditsEndpoint);
@@ -18,7 +17,7 @@ public async Task Test_1_GetBasicEdit()
Assert.NotNull(result.Choices);
Assert.NotZero(result.Choices.Count);
Console.WriteLine(result);
- Assert.IsTrue(result.ToString().Contains("week"));
+ Assert.IsTrue(result.ToString()?.Contains("week"));
}
}
}
diff --git a/OpenAI-DotNet-Tests/TestFixture_05_Images.cs b/OpenAI-DotNet-Tests/TestFixture_05_Images.cs
index 49100810..09b9a931 100644
--- a/OpenAI-DotNet-Tests/TestFixture_05_Images.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_05_Images.cs
@@ -1,5 +1,6 @@
using NUnit.Framework;
using OpenAI.Images;
+using OpenAI.Models;
using System;
using System.IO;
using System.Threading.Tasks;
@@ -13,16 +14,16 @@ public async Task Test_1_GenerateImages()
{
Assert.IsNotNull(OpenAIClient.ImagesEndPoint);
- var request = new ImageGenerationRequest("A house riding a velociraptor", Models.Model.DallE_2);
- var results = await OpenAIClient.ImagesEndPoint.GenerateImageAsync(request);
+ var request = new ImageGenerationRequest("A house riding a velociraptor", Model.DallE_2);
+ var imageResults = await OpenAIClient.ImagesEndPoint.GenerateImageAsync(request);
- Assert.IsNotNull(results);
- Assert.NotZero(results.Count);
- Assert.IsNotEmpty(results[0]);
+ Assert.IsNotNull(imageResults);
+ Assert.NotZero(imageResults.Count);
- foreach (var result in results)
+ foreach (var image in imageResults)
{
- Console.WriteLine(result);
+ Assert.IsNotNull(image);
+ Console.WriteLine(image);
}
}
@@ -31,16 +32,16 @@ public async Task Test_2_GenerateImages_B64_Json()
{
Assert.IsNotNull(OpenAIClient.ImagesEndPoint);
- var request = new ImageGenerationRequest("A house riding a velociraptor", Models.Model.DallE_2, responseFormat: ResponseFormat.B64_Json);
- var results = await OpenAIClient.ImagesEndPoint.GenerateImageAsync(request);
+ var request = new ImageGenerationRequest("A house riding a velociraptor", Model.DallE_2, responseFormat: ResponseFormat.B64_Json);
+ var imageResults = await OpenAIClient.ImagesEndPoint.GenerateImageAsync(request);
- Assert.IsNotNull(results);
- Assert.NotZero(results.Count);
- Assert.IsNotEmpty(results[0]);
+ Assert.IsNotNull(imageResults);
+ Assert.NotZero(imageResults.Count);
- foreach (var result in results)
+ foreach (var image in imageResults)
{
- Console.WriteLine(result);
+ Assert.IsNotNull(image);
+ Console.WriteLine(image);
}
}
@@ -49,18 +50,19 @@ public async Task Test_3_GenerateImageEdits()
{
Assert.IsNotNull(OpenAIClient.ImagesEndPoint);
- var imageAssetPath = Path.GetFullPath(@"..\..\..\Assets\image_edit_original.png");
- var maskAssetPath = Path.GetFullPath(@"..\..\..\Assets\image_edit_mask.png");
+ var imageAssetPath = Path.GetFullPath("../../../Assets/image_edit_original.png");
+ var maskAssetPath = Path.GetFullPath("../../../Assets/image_edit_mask.png");
var request = new ImageEditRequest(imageAssetPath, maskAssetPath, "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small);
- var results = await OpenAIClient.ImagesEndPoint.CreateImageEditAsync(request);
+ var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageEditAsync(request);
- Assert.IsNotNull(results);
- Assert.NotZero(results.Count);
+ Assert.IsNotNull(imageResults);
+ Assert.NotZero(imageResults.Count);
- foreach (var result in results)
+ foreach (var image in imageResults)
{
- Console.WriteLine(result);
+ Assert.IsNotNull(image);
+ Console.WriteLine(image);
}
}
@@ -69,18 +71,19 @@ public async Task Test_4_GenerateImageEdits_B64_Json()
{
Assert.IsNotNull(OpenAIClient.ImagesEndPoint);
- var imageAssetPath = Path.GetFullPath(@"..\..\..\Assets\image_edit_original.png");
- var maskAssetPath = Path.GetFullPath(@"..\..\..\Assets\image_edit_mask.png");
+ var imageAssetPath = Path.GetFullPath("../../../Assets/image_edit_original.png");
+ var maskAssetPath = Path.GetFullPath("../../../Assets/image_edit_mask.png");
var request = new ImageEditRequest(imageAssetPath, maskAssetPath, "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small, responseFormat: ResponseFormat.B64_Json);
- var results = await OpenAIClient.ImagesEndPoint.CreateImageEditAsync(request);
+ var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageEditAsync(request);
- Assert.IsNotNull(results);
- Assert.NotZero(results.Count);
+ Assert.IsNotNull(imageResults);
+ Assert.NotZero(imageResults.Count);
- foreach (var result in results)
+ foreach (var image in imageResults)
{
- Console.WriteLine(result);
+ Assert.IsNotNull(image);
+ Console.WriteLine(image);
}
}
@@ -89,16 +92,17 @@ public async Task Test_5_GenerateImageVariations()
{
Assert.IsNotNull(OpenAIClient.ImagesEndPoint);
- var imageAssetPath = Path.GetFullPath(@"..\..\..\Assets\image_edit_original.png");
+ var imageAssetPath = Path.GetFullPath("../../../Assets/image_edit_original.png");
var request = new ImageVariationRequest(imageAssetPath, size: ImageSize.Small);
- var results = await OpenAIClient.ImagesEndPoint.CreateImageVariationAsync(request);
+ var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageVariationAsync(request);
- Assert.IsNotNull(results);
- Assert.NotZero(results.Count);
+ Assert.IsNotNull(imageResults);
+ Assert.NotZero(imageResults.Count);
- foreach (var result in results)
+ foreach (var image in imageResults)
{
- Console.WriteLine(result);
+ Assert.IsNotNull(image);
+ Console.WriteLine(image);
}
}
@@ -107,16 +111,17 @@ public async Task Test_6_GenerateImageVariations_B64_Json()
{
Assert.IsNotNull(OpenAIClient.ImagesEndPoint);
- var imageAssetPath = Path.GetFullPath(@"..\..\..\Assets\image_edit_original.png");
+ var imageAssetPath = Path.GetFullPath("../../../Assets/image_edit_original.png");
var request = new ImageVariationRequest(imageAssetPath, size: ImageSize.Small, responseFormat: ResponseFormat.B64_Json);
- var results = await OpenAIClient.ImagesEndPoint.CreateImageVariationAsync(request);
+ var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageVariationAsync(request);
- Assert.IsNotNull(results);
- Assert.NotZero(results.Count);
+ Assert.IsNotNull(imageResults);
+ Assert.NotZero(imageResults.Count);
- foreach (var result in results)
+ foreach (var image in imageResults)
{
- Console.WriteLine(result);
+ Assert.IsNotNull(image);
+ Console.WriteLine(image);
}
}
}
diff --git a/OpenAI-DotNet-Tests/TestFixture_06_Embeddings.cs b/OpenAI-DotNet-Tests/TestFixture_06_Embeddings.cs
index 73d0fc7a..0247b1da 100644
--- a/OpenAI-DotNet-Tests/TestFixture_06_Embeddings.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_06_Embeddings.cs
@@ -9,9 +9,9 @@ internal class TestFixture_06_Embeddings : AbstractTestFixture
public async Task Test_1_CreateEmbedding()
{
Assert.IsNotNull(OpenAIClient.EmbeddingsEndpoint);
- var result = await OpenAIClient.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter...");
- Assert.IsNotNull(result);
- Assert.IsNotEmpty(result.Data);
+ var embedding = await OpenAIClient.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter...");
+ Assert.IsNotNull(embedding);
+ Assert.IsNotEmpty(embedding.Data);
}
[Test]
@@ -23,9 +23,9 @@ public async Task Test_2_CreateEmbeddingsWithMultipleInputs()
"The food was delicious and the waiter...",
"The food was terrible and the waiter..."
};
- var result = await OpenAIClient.EmbeddingsEndpoint.CreateEmbeddingAsync(embeddings);
- Assert.IsNotNull(result);
- Assert.AreEqual(result.Data.Count, 2);
+ var embedding = await OpenAIClient.EmbeddingsEndpoint.CreateEmbeddingAsync(embeddings);
+ Assert.IsNotNull(embedding);
+ Assert.AreEqual(embedding.Data.Count, 2);
}
}
}
diff --git a/OpenAI-DotNet-Tests/TestFixture_07_Audio.cs b/OpenAI-DotNet-Tests/TestFixture_07_Audio.cs
index cfde4fd8..d5f92029 100644
--- a/OpenAI-DotNet-Tests/TestFixture_07_Audio.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_07_Audio.cs
@@ -12,7 +12,7 @@ internal class TestFixture_07_Audio : AbstractTestFixture
public async Task Test_1_Transcription()
{
Assert.IsNotNull(OpenAIClient.AudioEndpoint);
- var transcriptionAudio = Path.GetFullPath(@"..\..\..\Assets\T3mt39YrlyLoq8laHSdf.mp3");
+ var transcriptionAudio = Path.GetFullPath("../../../Assets/T3mt39YrlyLoq8laHSdf.mp3");
using var request = new AudioTranscriptionRequest(transcriptionAudio, temperature: 0.1f, language: "en");
var result = await OpenAIClient.AudioEndpoint.CreateTranscriptionAsync(request);
Assert.IsNotNull(result);
@@ -23,7 +23,7 @@ public async Task Test_1_Transcription()
public async Task Test_2_Translation()
{
Assert.IsNotNull(OpenAIClient.AudioEndpoint);
- var translationAudio = Path.GetFullPath(@"..\..\..\Assets\Ja-botchan_1-1_1-2.mp3");
+ var translationAudio = Path.GetFullPath("../../../Assets/Ja-botchan_1-1_1-2.mp3");
using var request = new AudioTranslationRequest(Path.GetFullPath(translationAudio));
var result = await OpenAIClient.AudioEndpoint.CreateTranslationAsync(request);
Assert.IsNotNull(result);
@@ -43,7 +43,7 @@ async Task ChunkCallback(ReadOnlyMemory chunkCallback)
var result = await OpenAIClient.AudioEndpoint.CreateSpeechAsync(request, ChunkCallback);
Assert.IsFalse(result.IsEmpty);
- await File.WriteAllBytesAsync(@"..\..\..\Assets\HelloWorld.mp3", result.ToArray());
+ await File.WriteAllBytesAsync("../../../Assets/HelloWorld.mp3", result.ToArray());
}
}
-}
\ No newline at end of file
+}
diff --git a/OpenAI-DotNet-Tests/TestFixture_08_Files.cs b/OpenAI-DotNet-Tests/TestFixture_08_Files.cs
index 2474af12..44d3ef14 100644
--- a/OpenAI-DotNet-Tests/TestFixture_08_Files.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_08_Files.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Text.Json;
using System.Threading.Tasks;
namespace OpenAI.Tests
@@ -14,14 +15,12 @@ public async Task Test_01_UploadFile()
{
Assert.IsNotNull(OpenAIClient.FilesEndpoint);
var testData = new Conversation(new List { new Message(Role.Assistant, "I'm a learning language model") });
- await File.WriteAllTextAsync("test.jsonl", testData);
+ await File.WriteAllTextAsync("test.jsonl", JsonSerializer.Serialize(testData, OpenAIClient.DefaultJsonSerializerOptions));
Assert.IsTrue(File.Exists("test.jsonl"));
var result = await OpenAIClient.FilesEndpoint.UploadFileAsync("test.jsonl", "fine-tune");
-
Assert.IsNotNull(result);
Assert.IsTrue(result.FileName == "test.jsonl");
Console.WriteLine($"{result.Id} -> {result.Object}");
-
File.Delete("test.jsonl");
Assert.IsFalse(File.Exists("test.jsonl"));
}
@@ -30,12 +29,12 @@ public async Task Test_01_UploadFile()
public async Task Test_02_ListFiles()
{
Assert.IsNotNull(OpenAIClient.FilesEndpoint);
- var result = await OpenAIClient.FilesEndpoint.ListFilesAsync();
+ var fileList = await OpenAIClient.FilesEndpoint.ListFilesAsync();
- Assert.IsNotNull(result);
- Assert.IsNotEmpty(result);
+ Assert.IsNotNull(fileList);
+ Assert.IsNotEmpty(fileList);
- foreach (var file in result)
+ foreach (var file in fileList)
{
var fileInfo = await OpenAIClient.FilesEndpoint.GetFileInfoAsync(file);
Assert.IsNotNull(fileInfo);
@@ -47,12 +46,12 @@ public async Task Test_02_ListFiles()
public async Task Test_03_DownloadFile()
{
Assert.IsNotNull(OpenAIClient.FilesEndpoint);
- var files = await OpenAIClient.FilesEndpoint.ListFilesAsync();
+ var fileList = await OpenAIClient.FilesEndpoint.ListFilesAsync();
- Assert.IsNotNull(files);
- Assert.IsNotEmpty(files);
+ Assert.IsNotNull(fileList);
+ Assert.IsNotEmpty(fileList);
- var testFileData = files[0];
+ var testFileData = fileList[0];
var result = await OpenAIClient.FilesEndpoint.DownloadFileAsync(testFileData, Directory.GetCurrentDirectory());
Assert.IsNotNull(result);
@@ -67,20 +66,20 @@ public async Task Test_03_DownloadFile()
public async Task Test_04_DeleteFiles()
{
Assert.IsNotNull(OpenAIClient.FilesEndpoint);
- var files = await OpenAIClient.FilesEndpoint.ListFilesAsync();
- Assert.IsNotNull(files);
- Assert.IsNotEmpty(files);
+ var fileList = await OpenAIClient.FilesEndpoint.ListFilesAsync();
+ Assert.IsNotNull(fileList);
+ Assert.IsNotEmpty(fileList);
- foreach (var file in files)
+ foreach (var file in fileList)
{
var result = await OpenAIClient.FilesEndpoint.DeleteFileAsync(file);
Assert.IsTrue(result);
Console.WriteLine($"{file.Id} -> deleted");
}
- files = await OpenAIClient.FilesEndpoint.ListFilesAsync();
- Assert.IsNotNull(files);
- Assert.IsEmpty(files);
+ fileList = await OpenAIClient.FilesEndpoint.ListFilesAsync();
+ Assert.IsNotNull(fileList);
+ Assert.IsEmpty(fileList);
}
}
}
diff --git a/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs b/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs
index 91b61870..5944879c 100644
--- a/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs
@@ -7,15 +7,16 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text.Json;
using System.Threading.Tasks;
namespace OpenAI.Tests
{
internal class TestFixture_09_FineTuning : AbstractTestFixture
{
- private async Task CreateTestTrainingDataAsync()
+ private async Task CreateTestTrainingDataAsync()
{
- var conersations = new List
+ var conversations = new List
{
new Conversation(new List
{
@@ -78,10 +79,8 @@ private async Task CreateTestTrainingDataAsync()
new Message(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.")
})
};
-
const string localTrainingDataPath = "fineTunesTestTrainingData.jsonl";
- await File.WriteAllLinesAsync(localTrainingDataPath, conersations.Select(conversation => conversation.ToString()));
-
+ await File.WriteAllLinesAsync(localTrainingDataPath, conversations.Select(conversation => JsonSerializer.Serialize(conversation, OpenAIClient.DefaultJsonSerializerOptions)));
var fileData = await OpenAIClient.FilesEndpoint.UploadFileAsync(localTrainingDataPath, "fine-tune");
File.Delete(localTrainingDataPath);
Assert.IsFalse(File.Exists(localTrainingDataPath));
@@ -106,10 +105,12 @@ public async Task Test_02_ListFineTuneJobs()
Assert.IsNotNull(OpenAIClient.FineTuningEndpoint);
var list = await OpenAIClient.FineTuningEndpoint.ListJobsAsync();
Assert.IsNotNull(list);
- Assert.IsNotEmpty(list.Jobs);
+ Assert.IsNotEmpty(list.Items);
- foreach (var job in list.Jobs.OrderByDescending(job => job.CreatedAt))
+ foreach (var job in list.Items.OrderByDescending(job => job.CreatedAt))
{
+ Assert.IsNotNull(job);
+ Assert.IsNotNull(job.Client);
Console.WriteLine($"{job.Id} -> {job.CreatedAt} | {job.Status}");
}
}
@@ -118,15 +119,15 @@ public async Task Test_02_ListFineTuneJobs()
public async Task Test_03_RetrieveFineTuneJobInfo()
{
Assert.IsNotNull(OpenAIClient.FineTuningEndpoint);
- var list = await OpenAIClient.FineTuningEndpoint.ListJobsAsync();
- Assert.IsNotNull(list);
- Assert.IsNotEmpty(list.Jobs);
+ var jobList = await OpenAIClient.FineTuningEndpoint.ListJobsAsync();
+ Assert.IsNotNull(jobList);
+ Assert.IsNotEmpty(jobList.Items);
- foreach (var job in list.Jobs.OrderByDescending(job => job.CreatedAt))
+ foreach (var job in jobList.Items.OrderByDescending(job => job.CreatedAt))
{
- var request = await OpenAIClient.FineTuningEndpoint.GetJobInfoAsync(job);
- Assert.IsNotNull(request);
- Console.WriteLine($"{request.Id} -> {request.Status}");
+ var response = await OpenAIClient.FineTuningEndpoint.GetJobInfoAsync(job);
+ Assert.IsNotNull(response);
+ Console.WriteLine($"{job.Id} -> {job.CreatedAt} | {job.Status}");
}
}
@@ -136,23 +137,21 @@ public async Task Test_04_ListFineTuneEvents()
Assert.IsNotNull(OpenAIClient.FineTuningEndpoint);
var list = await OpenAIClient.FineTuningEndpoint.ListJobsAsync();
Assert.IsNotNull(list);
- Assert.IsNotEmpty(list.Jobs);
+ Assert.IsNotEmpty(list.Items);
- foreach (var job in list.Jobs)
+ foreach (var job in list.Items)
{
- if (job.Status == JobStatus.Cancelled)
- {
- continue;
- }
+ if (job.Status == JobStatus.Cancelled) { continue; }
var eventList = await OpenAIClient.FineTuningEndpoint.ListJobEventsAsync(job);
Assert.IsNotNull(eventList);
- Assert.IsNotEmpty(eventList.Events);
-
- Console.WriteLine($"{job.Id} -> status: {job.Status} | event count: {eventList.Events.Count}");
+ Assert.IsNotEmpty(eventList.Items);
+ Console.WriteLine($"{job.Id} -> status: {job.Status} | event count: {eventList.Items.Count}");
- foreach (var @event in eventList.Events.OrderByDescending(@event => @event.CreatedAt))
+ foreach (var @event in eventList.Items.OrderByDescending(@event => @event.CreatedAt))
{
+ Assert.IsNotNull(@event);
+ Assert.IsNotNull(@event.Client);
Console.WriteLine($" {@event.CreatedAt} [{@event.Level}] {@event.Message}");
}
@@ -166,9 +165,9 @@ public async Task Test_05_CancelFineTuneJob()
Assert.IsNotNull(OpenAIClient.FineTuningEndpoint);
var list = await OpenAIClient.FineTuningEndpoint.ListJobsAsync();
Assert.IsNotNull(list);
- Assert.IsNotEmpty(list.Jobs);
+ Assert.IsNotEmpty(list.Items);
- foreach (var job in list.Jobs)
+ foreach (var job in list.Items)
{
if (job.Status is > JobStatus.NotStarted and < JobStatus.Succeeded)
{
diff --git a/OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs b/OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs
index fd3465c3..421a0952 100644
--- a/OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs
+++ b/OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs
@@ -12,8 +12,8 @@ public async Task Test_1_Moderate()
{
Assert.IsNotNull(OpenAIClient.ModerationsEndpoint);
- var violationResponse = await OpenAIClient.ModerationsEndpoint.GetModerationAsync("I want to kill them.");
- Assert.IsTrue(violationResponse);
+ var isViolation = await OpenAIClient.ModerationsEndpoint.GetModerationAsync("I want to kill them.");
+ Assert.IsTrue(isViolation);
var response = await OpenAIClient.ModerationsEndpoint.CreateModerationAsync(new ModerationsRequest("I love you"));
Assert.IsNotNull(response);
diff --git a/OpenAI-DotNet-Tests/TestFixture_11_Assistants.cs b/OpenAI-DotNet-Tests/TestFixture_11_Assistants.cs
new file mode 100644
index 00000000..273d68f7
--- /dev/null
+++ b/OpenAI-DotNet-Tests/TestFixture_11_Assistants.cs
@@ -0,0 +1,154 @@
+using NUnit.Framework;
+using OpenAI.Assistants;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace OpenAI.Tests
+{
+ internal class TestFixture_11_Assistants : AbstractTestFixture
+ {
+ private static AssistantResponse testAssistant;
+
+ [Test]
+ public async Task Test_01_CreateAssistant()
+ {
+ Assert.IsNotNull(OpenAIClient.AssistantsEndpoint);
+ const string testFilePath = "assistant_test_1.txt";
+ await File.WriteAllTextAsync(testFilePath, "Knowledge is power!");
+ Assert.IsTrue(File.Exists(testFilePath));
+ 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",
+ name: "test-assistant",
+ description: "Used for unit testing.",
+ instructions: "You are test assistant",
+ metadata: new Dictionary
+ {
+ ["int"] = "1",
+ ["test"] = Guid.NewGuid().ToString()
+ },
+ tools: new[]
+ {
+ Tool.Retrieval
+ },
+ files: new[] { file.Id });
+ var assistant = await OpenAIClient.AssistantsEndpoint.CreateAssistantAsync(request);
+ Assert.IsNotNull(assistant);
+ 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.IsNotEmpty(assistant.Metadata);
+ testAssistant = assistant;
+ Console.WriteLine($"{assistant} -> {assistant.Metadata["test"]}");
+ }
+
+ [Test]
+ public async Task Test_02_ListAssistants()
+ {
+ Assert.IsNotNull(OpenAIClient.AssistantsEndpoint);
+ var assistantsList = await OpenAIClient.AssistantsEndpoint.ListAssistantsAsync();
+ Assert.IsNotNull(assistantsList);
+ Assert.IsNotEmpty(assistantsList.Items);
+
+ foreach (var assistant in assistantsList.Items)
+ {
+ var retrieved = OpenAIClient.AssistantsEndpoint.RetrieveAssistantAsync(assistant);
+ Assert.IsNotNull(retrieved);
+ Console.WriteLine($"{assistant} -> {assistant.CreatedAt}");
+ }
+ }
+
+ [Test]
+ public async Task Test_03_ModifyAssistants()
+ {
+ Assert.IsNotNull(testAssistant);
+ Assert.IsNotNull(OpenAIClient.AssistantsEndpoint);
+ var request = new CreateAssistantRequest(
+ model: "gpt-4-1106-preview",
+ name: "Test modified",
+ description: "Modified description",
+ instructions: "You are modified test assistant");
+ var assistant = await testAssistant.ModifyAsync(request);
+ Assert.IsNotNull(assistant);
+ 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.IsTrue(assistant.Metadata.ContainsKey("test"));
+ Console.WriteLine($"{assistant.Id} -> modified");
+ }
+
+ [Test]
+ public async Task Test_04_01_UploadAssistantFile()
+ {
+ Assert.IsNotNull(testAssistant);
+ Assert.IsNotNull(OpenAIClient.AssistantsEndpoint);
+ const string testFilePath = "assistant_test_2.txt";
+ await File.WriteAllTextAsync(testFilePath, "Knowledge is power!");
+ Assert.IsTrue(File.Exists(testFilePath));
+ var file = testAssistant.UploadFileAsync(testFilePath);
+ Assert.IsNotNull(file);
+ }
+
+ [Test]
+ public async Task Test_04_02_ListAssistantFiles()
+ {
+ Assert.IsNotNull(testAssistant);
+ Assert.IsNotNull(OpenAIClient.AssistantsEndpoint);
+ var filesList = await testAssistant.ListFilesAsync();
+ Assert.IsNotNull(filesList);
+ Assert.IsNotEmpty(filesList.Items);
+
+ foreach (var file in filesList.Items)
+ {
+ Assert.IsNotNull(file);
+ var retrieved = await testAssistant.RetrieveFileAsync(file);
+ Assert.IsNotNull(retrieved);
+ Assert.IsTrue(retrieved.Id == file.Id);
+ Console.WriteLine($"{retrieved.AssistantId}'s file -> {retrieved.Id}");
+ // TODO 400 Bad Request error when attempting to download assistant files. Likely OpenAI bug.
+ //var downloadPath = await retrieved.DownloadFileAsync(Directory.GetCurrentDirectory(), true);
+ //Console.WriteLine($"downloaded {retrieved} -> {downloadPath}");
+ //Assert.IsTrue(File.Exists(downloadPath));
+ //File.Delete(downloadPath);
+ //Assert.IsFalse(File.Exists(downloadPath));
+ }
+ }
+
+ [Test]
+ public async Task Test_04_03_DeleteAssistantFiles()
+ {
+ Assert.IsNotNull(testAssistant);
+ Assert.IsNotNull(OpenAIClient.AssistantsEndpoint);
+ var filesList = await testAssistant.ListFilesAsync();
+ Assert.IsNotNull(filesList);
+ Assert.IsNotEmpty(filesList.Items);
+
+ foreach (var file in filesList.Items)
+ {
+ Assert.IsNotNull(file);
+ var isDeleted = await testAssistant.DeleteFileAsync(file);
+ Assert.IsTrue(isDeleted);
+ Console.WriteLine($"Deleted {file.Id}");
+ }
+
+ filesList = await testAssistant.ListFilesAsync();
+ Assert.IsNotNull(filesList);
+ Assert.IsEmpty(filesList.Items);
+ }
+
+ [Test]
+ public async Task Test_05_DeleteAssistant()
+ {
+ Assert.IsNotNull(testAssistant);
+ Assert.IsNotNull(OpenAIClient.AssistantsEndpoint);
+ var result = await testAssistant.DeleteAsync();
+ Assert.IsTrue(result);
+ Console.WriteLine($"{testAssistant.Id} -> deleted");
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet-Tests/TestFixture_12_Threads.cs b/OpenAI-DotNet-Tests/TestFixture_12_Threads.cs
new file mode 100644
index 00000000..e7823a47
--- /dev/null
+++ b/OpenAI-DotNet-Tests/TestFixture_12_Threads.cs
@@ -0,0 +1,448 @@
+using NUnit.Framework;
+using OpenAI.Assistants;
+using OpenAI.Files;
+using OpenAI.Tests.Weather;
+using OpenAI.Threads;
+using System;
+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
+{
+ ///
+ /// https://github.com/openai/openai-cookbook/blob/main/examples/Assistants_API_overview_python.ipynb
+ ///
+ internal class TestFixture_12_Threads : AbstractTestFixture
+ {
+ private static RunResponse testRun;
+ private static ThreadResponse testThread;
+ private static MessageResponse testMessage;
+ private static AssistantResponse testAssistant;
+
+ [Test]
+ public async Task Test_01_CreateThread()
+ {
+ Assert.IsNotNull(OpenAIClient.ThreadsEndpoint);
+ var thread = await OpenAIClient.ThreadsEndpoint.CreateThreadAsync(new CreateThreadRequest(
+ new List
+ {
+ "Test message"
+ },
+ new Dictionary
+ {
+ ["test"] = nameof(Test_01_CreateThread)
+ }));
+ Assert.IsNotNull(thread);
+ Assert.IsNotNull(thread.Metadata);
+ Assert.IsNotEmpty(thread.Metadata);
+ testThread = thread;
+ Console.WriteLine($"Create thread {thread.Id} -> {thread.CreatedAt}");
+ }
+
+ [Test]
+ public async Task Test_02_RetrieveThread()
+ {
+ Assert.IsNotNull(testThread);
+ Assert.IsNotNull(OpenAIClient.ThreadsEndpoint);
+ var thread = await testThread.UpdateAsync();
+ Assert.IsNotNull(thread);
+ Assert.AreEqual(testThread.Id, thread.Id);
+ Assert.IsNotNull(thread.Metadata);
+ Console.WriteLine($"Retrieve thread {thread.Id} -> {thread.CreatedAt}");
+ }
+
+ [Test]
+ public async Task Test_03_ModifyThread()
+ {
+ Assert.IsNotNull(testThread);
+ Assert.IsNotNull(OpenAIClient.ThreadsEndpoint);
+ var newMetadata = new Dictionary
+ {
+ ["test"] = nameof(Test_03_ModifyThread)
+ };
+ var thread = await testThread.ModifyAsync(newMetadata);
+ Assert.IsNotNull(thread);
+ Assert.AreEqual(testThread.Id, thread.Id);
+ Assert.IsNotNull(thread.Metadata);
+ Console.WriteLine($"Modify thread {thread.Id} -> {thread.Metadata["test"]}");
+ }
+
+ [Test]
+ public async Task Test_04_01_CreateMessage()
+ {
+ Assert.IsNotNull(testThread);
+ Assert.IsNotNull(OpenAIClient.ThreadsEndpoint);
+ const string testFilePath = "assistant_test_1.txt";
+ await File.WriteAllTextAsync(testFilePath, "Knowledge is power!");
+ Assert.IsTrue(File.Exists(testFilePath));
+ var file = await OpenAIClient.FilesEndpoint.UploadFileAsync(testFilePath, "assistants");
+ Assert.NotNull(file);
+ File.Delete(testFilePath);
+ Assert.IsFalse(File.Exists(testFilePath));
+ await testThread.CreateMessageAsync("hello world!");
+ var request = new CreateMessageRequest("Test create message",
+ new[] { file.Id },
+ new Dictionary
+ {
+ ["test"] = nameof(Test_04_01_CreateMessage)
+ });
+ MessageResponse message;
+ try
+ {
+ message = await testThread.CreateMessageAsync(request);
+ }
+ finally
+ {
+ await CleanupFileAsync(file);
+ }
+
+ Assert.IsNotNull(message);
+ Assert.AreEqual(testThread.Id, message.ThreadId);
+ testMessage = message;
+ }
+
+ [Test]
+ public async Task Test_04_02_ListMessages()
+ {
+ Assert.IsNotNull(testThread);
+ Assert.IsNotNull(OpenAIClient.ThreadsEndpoint);
+ var message1 = await testThread.CreateMessageAsync("Test message 1");
+ Assert.IsNotNull(message1);
+ var message2 = await testThread.CreateMessageAsync("Test message 2");
+ Assert.IsNotNull(message2);
+ var list = await testThread.ListMessagesAsync();
+ Assert.IsNotNull(list);
+ Assert.IsNotEmpty(list.Items);
+
+ foreach (var message in list.Items)
+ {
+ Assert.NotNull(message);
+ var threadMessage = await testThread.RetrieveMessageAsync(message);
+ Assert.NotNull(threadMessage);
+ Console.WriteLine($"[{threadMessage.Id}] {threadMessage.Role}: {threadMessage.PrintContent()}");
+ var updated = await message.UpdateAsync();
+ Assert.IsNotNull(updated);
+ }
+ }
+
+ [Test]
+ public async Task Test_04_03_ModifyMessage()
+ {
+ Assert.IsNotNull(testThread);
+ Assert.IsNotNull(OpenAIClient.ThreadsEndpoint);
+ var metadata = new Dictionary
+ {
+ ["test"] = nameof(Test_04_03_ModifyMessage)
+ };
+ var modified = await testMessage.ModifyAsync(metadata);
+ Assert.IsNotNull(modified);
+ Assert.IsNotNull(modified.Metadata);
+ Assert.IsTrue(modified.Metadata["test"].Equals(nameof(Test_04_03_ModifyMessage)));
+ Console.WriteLine($"Modify message metadata: {modified.Id} -> {modified.Metadata["test"]}");
+ metadata.Add("test2", nameof(Test_04_03_ModifyMessage));
+ var modifiedThreadMessage = await testThread.ModifyMessageAsync(modified, metadata);
+ Assert.IsNotNull(modifiedThreadMessage);
+ Assert.IsNotNull(modifiedThreadMessage.Metadata);
+ Console.WriteLine($"Modify message metadata: {modifiedThreadMessage.Id} -> {string.Join("\n", modifiedThreadMessage.Metadata.Select(meta => $"[{meta.Key}] {meta.Value}"))}");
+ }
+
+ [Test]
+ public async Task Test_04_04_UploadAndDownloadMessageFiles()
+ {
+ Assert.IsNotNull(testThread);
+ Assert.IsNotNull(OpenAIClient.ThreadsEndpoint);
+ var file1 = await CreateTestFileAsync("test_1.txt");
+ var file2 = await CreateTestFileAsync("test_2.txt");
+ try
+ {
+ var createRequest = new CreateMessageRequest("Test content with files", new[] { file1.Id, file2.Id });
+ var message = await testThread.CreateMessageAsync(createRequest);
+ var fileList = await message.ListFilesAsync();
+ Assert.IsNotNull(fileList);
+ Assert.AreEqual(2, fileList.Items.Count);
+
+ foreach (var file in fileList.Items)
+ {
+ var retrieved = await message.RetrieveFileAsync(file);
+ Assert.IsNotNull(retrieved);
+ Console.WriteLine(file.Id);
+ // TODO 400 bad request errors. Likely OpenAI bug downloading message file content.
+ //var filePath = await message.DownloadFileContentAsync(file, Directory.GetCurrentDirectory(), true);
+ //Assert.IsFalse(string.IsNullOrWhiteSpace(filePath));
+ //Assert.IsTrue(File.Exists(filePath));
+ //File.Delete(filePath);
+ }
+
+ var threadList = await testThread.ListFilesAsync(message);
+ Assert.IsNotNull(threadList);
+ Assert.IsNotEmpty(threadList.Items);
+
+ //foreach (var file in threadList.Items)
+ //{
+ // // TODO 400 bad request errors. Likely OpenAI bug downloading message file content.
+ // var filePath = await file.DownloadContentAsync(Directory.GetCurrentDirectory(), true);
+ // Assert.IsFalse(string.IsNullOrWhiteSpace(filePath));
+ // Assert.IsTrue(File.Exists(filePath));
+ // File.Delete(filePath);
+ //}
+ }
+ finally
+ {
+ await CleanupFileAsync(file1);
+ await CleanupFileAsync(file2);
+ }
+ }
+
+ [Test]
+ public async Task Test_05_DeleteThread()
+ {
+ Assert.IsNotNull(testThread);
+ Assert.IsNotNull(OpenAIClient.ThreadsEndpoint);
+ var isDeleted = await testThread.DeleteAsync();
+ Assert.IsTrue(isDeleted);
+ Console.WriteLine($"Deleted thread {testThread.Id}");
+ }
+
+
+
+ [Test]
+ public async Task Test_06_01_CreateRun()
+ {
+ Assert.NotNull(OpenAIClient.ThreadsEndpoint);
+ var assistant = await OpenAIClient.AssistantsEndpoint.CreateAssistantAsync(
+ 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"));
+ Assert.NotNull(assistant);
+ testAssistant = assistant;
+ var thread = await OpenAIClient.ThreadsEndpoint.CreateThreadAsync();
+ Assert.NotNull(thread);
+
+ try
+ {
+ var message = thread.CreateMessageAsync("I need to solve the equation `3x + 11 = 14`. Can you help me?");
+ Assert.NotNull(message);
+ var run = await thread.CreateRunAsync(assistant);
+ Assert.IsNotNull(run);
+ var threadRun = thread.CreateRunAsync();
+ Assert.NotNull(threadRun);
+ }
+ finally
+ {
+ await thread.DeleteAsync();
+ }
+ }
+
+ [Test]
+ public async Task Test_06_02_CreateThreadAndRun()
+ {
+ Assert.NotNull(testAssistant);
+ Assert.NotNull(OpenAIClient.ThreadsEndpoint);
+ var messages = new List { "I need to solve the equation `3x + 11 = 14`. Can you help me?" };
+ var threadRequest = new CreateThreadRequest(messages);
+ var run = await testAssistant.CreateThreadAndRunAsync(threadRequest);
+ Assert.IsNotNull(run);
+ Console.WriteLine($"Created thread and run: {run.ThreadId} -> {run.Id} -> {run.CreatedAt}");
+ testRun = run;
+ var thread = await run.GetThreadAsync();
+ Assert.NotNull(thread);
+ testThread = thread;
+ }
+
+ [Test]
+ public async Task Test_06_03_ListRunsAndSteps()
+ {
+ Assert.NotNull(testThread);
+ Assert.NotNull(OpenAIClient.ThreadsEndpoint);
+ var runList = await testThread.ListRunsAsync();
+ Assert.IsNotNull(runList);
+ Assert.IsNotEmpty(runList.Items);
+
+ foreach (var run in runList.Items)
+ {
+ Assert.IsNotNull(run);
+ Assert.IsNotNull(run.Client);
+ var retrievedRun = await run.UpdateAsync();
+ Assert.IsNotNull(retrievedRun);
+ Console.WriteLine($"[{retrievedRun.Id}] {retrievedRun.Status} | {retrievedRun.CreatedAt}");
+ }
+ }
+
+ [Test]
+ public async Task Test_06_04_ModifyRun()
+ {
+ Assert.NotNull(testRun);
+ Assert.NotNull(OpenAIClient.ThreadsEndpoint);
+ // run in Queued and InProgress can't be modified
+ var run = await testRun.WaitForStatusChangeAsync();
+ Assert.IsNotNull(run);
+ Assert.IsTrue(run.Status == RunStatus.Completed);
+ var metadata = new Dictionary
+ {
+ ["test"] = nameof(Test_06_04_ModifyRun)
+ };
+ var modified = await run.ModifyAsync(metadata);
+ Assert.IsNotNull(modified);
+ Assert.AreEqual(run.Id, modified.Id);
+ Assert.IsNotNull(modified.Metadata);
+ Assert.Contains("test", modified.Metadata.Keys.ToList());
+ Assert.AreEqual(nameof(Test_06_04_ModifyRun), modified.Metadata["test"]);
+ }
+
+ [Test]
+ public async Task Test_06_05_CancelRun()
+ {
+ Assert.IsNotNull(testThread);
+ Assert.IsNotNull(testAssistant);
+ Assert.NotNull(OpenAIClient.ThreadsEndpoint);
+ var run = await testThread.CreateRunAsync(testAssistant);
+ Assert.IsNotNull(run);
+ Assert.IsTrue(run.Status == RunStatus.Queued);
+ run = await run.CancelAsync();
+ Assert.IsNotNull(run);
+ Assert.IsTrue(run.Status == RunStatus.Cancelling);
+
+ try
+ {
+ // waiting while run is cancelling
+ run = await run.WaitForStatusChangeAsync();
+ }
+ catch (Exception e)
+ {
+ // Sometimes runs will get stuck in Cancelling state,
+ // for now we just log when it happens.
+ Console.WriteLine(e);
+ }
+
+ Assert.IsTrue(run.Status is RunStatus.Cancelled or RunStatus.Cancelling);
+ }
+
+ [Test]
+ public async Task Test_06_06_TestCleanup()
+ {
+ if (testAssistant != null)
+ {
+ var isDeleted = await testAssistant.DeleteAsync();
+ Assert.IsTrue(isDeleted);
+ }
+
+ if (testThread != null)
+ {
+ var isDeleted = await testThread.DeleteAsync();
+ Assert.IsTrue(isDeleted);
+ }
+ }
+
+ [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?");
+ testThread = await run.GetThreadAsync();
+ // waiting while run is Queued and InProgress
+ run = await run.WaitForStatusChangeAsync();
+ Assert.IsNotNull(run);
+ Assert.AreEqual(RunStatus.RequiresAction, run.Status);
+ Assert.IsNotNull(run.RequiredAction);
+ Assert.IsNotNull(run.RequiredAction.SubmitToolOutputs);
+ Assert.IsNotEmpty(run.RequiredAction.SubmitToolOutputs.ToolCalls);
+
+ var runStepList = await run.ListRunStepsAsync();
+ Assert.IsNotNull(runStepList);
+ Assert.IsNotEmpty(runStepList.Items);
+
+ foreach (var runStep in runStepList.Items)
+ {
+ Assert.IsNotNull(runStep);
+ Assert.IsNotNull(runStep.Client);
+ var retrievedRunStep = await runStep.UpdateAsync();
+ Assert.IsNotNull(retrievedRunStep);
+ Console.WriteLine($"[{runStep.Id}] {runStep.Status} {runStep.CreatedAt} -> {runStep.ExpiresAt}");
+ var retrieveStepRunStep = await run.RetrieveRunStepAsync(runStep.Id);
+ Assert.IsNotNull(retrieveStepRunStep);
+ }
+
+ 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.IsNotNull(toolCall.FunctionCall.Arguments);
+ Console.WriteLine($"tool call arguments: {toolCall.FunctionCall.Arguments}");
+ var functionArgs = JsonSerializer.Deserialize(toolCall.FunctionCall.Arguments);
+ var functionResult = WeatherService.GetCurrentWeather(functionArgs);
+ var toolOutput = new ToolOutput(toolCall.Id, functionResult);
+ run = await run.SubmitToolOutputsAsync(toolOutput);
+ // waiting while run in Queued and InProgress
+ run = await run.WaitForStatusChangeAsync();
+ Assert.AreEqual(RunStatus.Completed, run.Status);
+ var messages = await run.ListMessagesAsync();
+ Assert.IsNotNull(messages);
+ Assert.IsNotEmpty(messages.Items);
+
+ foreach (var message in messages.Items.OrderBy(response => response.CreatedAt))
+ {
+ Assert.IsNotNull(message);
+ Assert.IsNotEmpty(message.Content);
+ Console.WriteLine($"{message.Role}: {message.PrintContent()}");
+ }
+ }
+
+ [Test]
+ public async Task Test_07_02_TestCleanup()
+ {
+ if (testAssistant != null)
+ {
+ var isDeleted = await testAssistant.DeleteAsync();
+ Assert.IsTrue(isDeleted);
+ }
+
+ if (testThread != null)
+ {
+ var isDeleted = await testThread.DeleteAsync();
+ Assert.IsTrue(isDeleted);
+ }
+ }
+
+ private async Task CreateTestFileAsync(string filePath)
+ {
+ await File.WriteAllTextAsync(filePath, "Knowledge is power!");
+ Assert.IsTrue(File.Exists(filePath));
+ var file = await OpenAIClient.FilesEndpoint.UploadFileAsync(filePath, "assistants");
+ File.Delete(filePath);
+ Assert.IsFalse(File.Exists(filePath));
+ return file;
+ }
+
+ private async Task CleanupFileAsync(FileResponse file)
+ {
+ var isDeleted = await OpenAIClient.FilesEndpoint.DeleteFileAsync(file);
+ Assert.IsTrue(isDeleted);
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet-Tests/TestServices/WeatherArgs.cs b/OpenAI-DotNet-Tests/TestServices/WeatherArgs.cs
new file mode 100644
index 00000000..94d53cf6
--- /dev/null
+++ b/OpenAI-DotNet-Tests/TestServices/WeatherArgs.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Tests.Weather
+{
+ internal class WeatherArgs
+ {
+ [JsonPropertyName("location")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string Location { get; set; }
+
+ [JsonPropertyName("unit")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string Unit { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet-Tests/TestServices/WeatherService.cs b/OpenAI-DotNet-Tests/TestServices/WeatherService.cs
index 4158e4cd..eb8c962d 100644
--- a/OpenAI-DotNet-Tests/TestServices/WeatherService.cs
+++ b/OpenAI-DotNet-Tests/TestServices/WeatherService.cs
@@ -1,23 +1,8 @@
-using System.Text.Json.Serialization;
-
-namespace OpenAI.Tests.Weather
+namespace OpenAI.Tests.Weather
{
internal class WeatherService
{
public static string GetCurrentWeather(WeatherArgs weatherArgs)
- {
- return $"The current weather in {weatherArgs.Location} is 20 {weatherArgs.Unit}";
- }
- }
-
- internal class WeatherArgs
- {
- [JsonPropertyName("location")]
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
- public string Location { get; set; }
-
- [JsonPropertyName("unit")]
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
- public string Unit { get; set; }
+ => $"The current weather in {weatherArgs.Location} is 20 {weatherArgs.Unit}";
}
}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Assistants/AssistantExtensions.cs b/OpenAI-DotNet/Assistants/AssistantExtensions.cs
new file mode 100644
index 00000000..cfcd84cc
--- /dev/null
+++ b/OpenAI-DotNet/Assistants/AssistantExtensions.cs
@@ -0,0 +1,158 @@
+using OpenAI.Files;
+using OpenAI.Threads;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OpenAI.Assistants
+{
+ public static class AssistantExtensions
+ {
+ ///
+ /// Modify the assistant.
+ ///
+ /// .
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task ModifyAsync(this AssistantResponse assistant, CreateAssistantRequest request, CancellationToken cancellationToken = default)
+ {
+ request = new CreateAssistantRequest(assistant: assistant, model: request.Model, name: request.Name, description: request.Description, instructions: request.Instructions, tools: request.Tools, files: request.FileIds, metadata: request.Metadata);
+ return await assistant.Client.AssistantsEndpoint.ModifyAssistantAsync(assistantId: assistant.Id, request: request, cancellationToken: cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
+ }
+
+ ///
+ /// Delete the assistant.
+ ///
+ /// .
+ /// Optional, .
+ /// True, if the was successfully deleted.
+ public static async Task DeleteAsync(this AssistantResponse assistant, CancellationToken cancellationToken = default)
+ => await assistant.Client.AssistantsEndpoint.DeleteAssistantAsync(assistant.Id, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Create a thread and run it.
+ ///
+ /// .
+ /// Optional, .
+ /// Optional, .
+ /// .
+ public static async Task CreateThreadAndRunAsync(this AssistantResponse assistant, CreateThreadRequest request = null, CancellationToken cancellationToken = default)
+ => await assistant.Client.ThreadsEndpoint.CreateThreadAndRunAsync(new CreateThreadAndRunRequest(assistant.Id, createThreadRequest: request), cancellationToken).ConfigureAwait(false);
+
+ #region Files
+
+ ///
+ /// Returns a list of assistant files.
+ ///
+ /// .
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task> ListFilesAsync(this AssistantResponse assistant, ListQuery query = null, CancellationToken cancellationToken = default)
+ => await assistant.Client.AssistantsEndpoint.ListFilesAsync(assistant.Id, query, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Attach a file to the .
+ ///
+ /// .
+ ///
+ /// A (with purpose="assistants") that the assistant should use.
+ /// Useful for tools like retrieval and code_interpreter that can access files.
+ ///
+ /// Optional, .
+ /// .
+ public static async Task AttachFileAsync(this AssistantResponse assistant, FileResponse file, CancellationToken cancellationToken = default)
+ => await assistant.Client.AssistantsEndpoint.AttachFileAsync(assistant.Id, file, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Uploads a new file at the specified and attaches it to the .
+ ///
+ /// .
+ /// The local file path to upload.
+ /// Optional, .
+ /// .
+ public static async Task UploadFileAsync(this AssistantResponse assistant, string filePath, CancellationToken cancellationToken = default)
+ {
+ var file = await assistant.Client.FilesEndpoint.UploadFileAsync(new FileUploadRequest(filePath, "assistant"), cancellationToken).ConfigureAwait(false);
+ return await assistant.AttachFileAsync(file, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Retrieves the .
+ ///
+ /// .
+ /// The ID of the file we're getting.
+ /// Optional, .
+ /// .
+ public static async Task RetrieveFileAsync(this AssistantResponse assistant, string fileId, CancellationToken cancellationToken = default)
+ => await assistant.Client.AssistantsEndpoint.RetrieveFileAsync(assistant.Id, fileId, cancellationToken).ConfigureAwait(false);
+
+ // TODO 400 bad request errors. Likely OpenAI bug downloading assistant file content.
+ /////
+ ///// Downloads the to the specified .
+ /////
+ ///// .
+ ///// The directory to download the file into.
+ ///// Optional, delete the cached file. Defaults to false.
+ ///// Optional, .
+ ///// The full path of the downloaded file.
+ //public static async Task DownloadFileAsync(this AssistantFileResponse assistantFile, string directory, bool deleteCachedFile = false, CancellationToken cancellationToken = default)
+ // => await assistantFile.Client.FilesEndpoint.DownloadFileAsync(assistantFile.Id, directory, deleteCachedFile, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Remove AssistantFile.
+ ///
+ ///
+ /// Note that removing an AssistantFile does not delete the original File object,
+ /// it simply removes the association between that File and the Assistant.
+ /// To delete a File, use .
+ ///
+ /// .
+ /// Optional, .
+ /// True, if file was removed.
+ public static async Task RemoveFileAsync(this AssistantFileResponse file, CancellationToken cancellationToken = default)
+ => await file.Client.AssistantsEndpoint.RemoveFileAsync(file.AssistantId, file.Id, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Remove AssistantFile.
+ ///
+ ///
+ /// Note that removing an AssistantFile does not delete the original File object,
+ /// it simply removes the association between that File and the Assistant.
+ /// To delete a File, use .
+ ///
+ /// .
+ /// The ID of the file to remove.
+ /// Optional, .
+ /// True, if file was removed.
+ public static async Task RemoveFileAsync(this AssistantResponse assistant, string fileId, CancellationToken cancellationToken = default)
+ => await assistant.Client.AssistantsEndpoint.RemoveFileAsync(assistant.Id, fileId, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Removes and Deletes a file from the assistant.
+ ///
+ /// .
+ /// Optional, .
+ /// True, if the file was successfully removed from the assistant and deleted.
+ public static async Task DeleteFileAsync(this AssistantFileResponse file, CancellationToken cancellationToken = default)
+ {
+ var isRemoved = await file.RemoveFileAsync(cancellationToken).ConfigureAwait(false);
+ return isRemoved && await file.Client.FilesEndpoint.DeleteFileAsync(file.Id, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Removes and Deletes a file from the .
+ ///
+ /// .
+ /// The ID of the file to delete.
+ /// Optional, .
+ /// True, if the file was successfully removed from the assistant and deleted.
+ public static async Task DeleteFileAsync(this AssistantResponse assistant, string fileId, CancellationToken cancellationToken = default)
+ {
+ var isRemoved = await assistant.Client.AssistantsEndpoint.RemoveFileAsync(assistant.Id, fileId, cancellationToken).ConfigureAwait(false);
+ return isRemoved && await assistant.Client.FilesEndpoint.DeleteFileAsync(fileId, cancellationToken).ConfigureAwait(false);
+ }
+
+ #endregion Files
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Assistants/AssistantFileResponse.cs b/OpenAI-DotNet/Assistants/AssistantFileResponse.cs
new file mode 100644
index 00000000..b6dc132a
--- /dev/null
+++ b/OpenAI-DotNet/Assistants/AssistantFileResponse.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Assistants
+{
+ ///
+ /// File attached to an assistant.
+ ///
+ public sealed class AssistantFileResponse : BaseResponse
+ {
+ ///
+ /// The identifier, which can be referenced in API endpoints.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ ///
+ /// The object type, which is always assistant.file.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ ///
+ /// The Unix timestamp (in seconds) for when the assistant file was created.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
+
+ ///
+ /// The assistant ID that the file is attached to.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("assistant_id")]
+ public string AssistantId { get; private set; }
+
+ public static implicit operator string(AssistantFileResponse file) => file?.ToString();
+
+ public override string ToString() => Id;
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Assistants/AssistantResponse.cs b/OpenAI-DotNet/Assistants/AssistantResponse.cs
new file mode 100644
index 00000000..9d6e0b65
--- /dev/null
+++ b/OpenAI-DotNet/Assistants/AssistantResponse.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Assistants
+{
+ ///
+ /// Purpose-built AI that uses OpenAI�s models and calls tools.
+ ///
+ public sealed class AssistantResponse : BaseResponse
+ {
+ ///
+ /// The identifier, which can be referenced in API endpoints.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ ///
+ /// The object type, which is always assistant.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ ///
+ /// The Unix timestamp (in seconds) for when the assistant was created.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
+
+ ///
+ /// The name of the assistant.
+ /// The maximum length is 256 characters.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("name")]
+ public string Name { get; private set; }
+
+ ///
+ /// The description of the assistant.
+ /// The maximum length is 512 characters.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("description")]
+ public string Description { get; private set; }
+
+ ///
+ /// ID of the model to use.
+ /// You can use the List models API to see all of your available models,
+ /// or see our Model overview for descriptions of them.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("model")]
+ public string Model { get; private set; }
+
+ ///
+ /// The system instructions that the assistant uses.
+ /// The maximum length is 32768 characters.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("instructions")]
+ public string Instructions { get; private set; }
+
+ ///
+ /// A list of tool enabled on the assistant.
+ /// There can be a maximum of 128 tools per assistant.
+ /// Tools can be of types 'code_interpreter', 'retrieval', or 'function'.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("tools")]
+ public IReadOnlyList Tools { get; private set; }
+
+ ///
+ /// A list of file IDs attached to this assistant.
+ /// There can be a maximum of 20 files attached to the assistant.
+ /// Files are ordered by their creation date in ascending order.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("file_ids")]
+ public IReadOnlyList FileIds { get; private set; }
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; private set; }
+
+ public static implicit operator string(AssistantResponse assistant) => assistant?.Id;
+
+ public override string ToString() => Id;
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Assistants/AssistantsEndpoint.cs b/OpenAI-DotNet/Assistants/AssistantsEndpoint.cs
new file mode 100644
index 00000000..230368b9
--- /dev/null
+++ b/OpenAI-DotNet/Assistants/AssistantsEndpoint.cs
@@ -0,0 +1,159 @@
+using OpenAI.Extensions;
+using OpenAI.Files;
+using System;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OpenAI.Assistants
+{
+ public sealed class AssistantsEndpoint : BaseEndPoint
+ {
+ internal AssistantsEndpoint(OpenAIClient api) : base(api) { }
+
+ protected override string Root => "assistants";
+
+ ///
+ /// Get list of assistants.
+ ///
+ /// .
+ /// Optional, .
+ ///
+ public async Task> ListAssistantsAsync(ListQuery query = null, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl(queryParameters: query), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize>(responseAsString, Api);
+ }
+
+ ///
+ /// Create an assistant.
+ ///
+ /// .
+ /// Optional, .
+ /// .
+ public async Task CreateAssistantAsync(CreateAssistantRequest request = null, CancellationToken cancellationToken = default)
+ {
+ request ??= new CreateAssistantRequest();
+ var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Retrieves an assistant.
+ ///
+ /// The ID of the assistant to retrieve.
+ /// Optional, .
+ /// .
+ public async Task RetrieveAssistantAsync(string assistantId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{assistantId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Modifies an assistant.
+ ///
+ /// The ID of the assistant to modify.
+ /// .
+ /// Optional, .
+ /// .
+ public async Task ModifyAssistantAsync(string assistantId, CreateAssistantRequest request, CancellationToken cancellationToken = default)
+ {
+ var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl($"/{assistantId}"), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Delete an assistant.
+ ///
+ /// The ID of the assistant to delete.
+ /// Optional, .
+ /// True, if the assistant was deleted.
+ public async Task DeleteAssistantAsync(string assistantId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.DeleteAsync(GetUrl($"/{assistantId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false;
+ }
+
+ #region Files
+
+ ///
+ /// Returns a list of assistant files.
+ ///
+ /// The ID of the assistant the file belongs to.
+ /// .
+ /// Optional, .
+ /// .
+ public async Task> ListFilesAsync(string assistantId, ListQuery query = null, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{assistantId}/files", query), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize>(responseAsString, Api);
+ }
+
+ ///
+ /// Attach a file to an assistant.
+ ///
+ /// The ID of the assistant for which to attach a file.
+ ///
+ /// A (with purpose="assistants") that the assistant should use.
+ /// Useful for tools like retrieval and code_interpreter that can access files.
+ ///
+ /// Optional, .
+ /// .
+ public async Task AttachFileAsync(string assistantId, FileResponse file, CancellationToken cancellationToken = default)
+ {
+ if (file?.Purpose?.Equals("assistants") != true)
+ {
+ throw new InvalidOperationException($"{nameof(file)}.{nameof(file.Purpose)} must be 'assistants'!");
+ }
+
+ var jsonContent = JsonSerializer.Serialize(new { file_id = file.Id }, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl($"/{assistantId}/files"), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Retrieves an AssistantFile.
+ ///
+ /// The ID of the assistant who the file belongs to.
+ /// The ID of the file we're getting.
+ /// Optional, .
+ /// .
+ public async Task RetrieveFileAsync(string assistantId, string fileId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{assistantId}/files/{fileId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Remove an assistant file.
+ ///
+ ///
+ /// Note that removing an AssistantFile does not delete the original File object,
+ /// it simply removes the association between that File and the Assistant.
+ /// To delete a File, use the File delete endpoint instead.
+ ///
+ /// The ID of the assistant that the file belongs to.
+ /// The ID of the file to delete.
+ /// Optional, .
+ /// True, if file was removed.
+ public async Task RemoveFileAsync(string assistantId, string fileId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.DeleteAsync(GetUrl($"/{assistantId}/files/{fileId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false;
+ }
+
+ #endregion Files
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Assistants/CreateAssistantRequest.cs b/OpenAI-DotNet/Assistants/CreateAssistantRequest.cs
new file mode 100644
index 00000000..0a732f8a
--- /dev/null
+++ b/OpenAI-DotNet/Assistants/CreateAssistantRequest.cs
@@ -0,0 +1,149 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Assistants
+{
+ public sealed class CreateAssistantRequest
+ {
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ /// ID of the model to use.
+ /// You can use the List models API to see all of your available models,
+ /// or see our Model overview for descriptions of them.
+ ///
+ ///
+ /// The name of the assistant.
+ /// The maximum length is 256 characters.
+ ///
+ ///
+ /// The description of the assistant.
+ /// The maximum length is 512 characters.
+ ///
+ ///
+ /// The system instructions that the assistant uses.
+ /// The maximum length is 32768 characters.
+ ///
+ ///
+ /// A list of tool enabled on the assistant.
+ /// There can be a maximum of 128 tools per assistant.
+ /// Tools can be of types 'code_interpreter', 'retrieval', or 'function'.
+ ///
+ ///
+ /// A list of file IDs attached to this assistant.
+ /// There can be a maximum of 20 files attached to the assistant.
+ /// Files are ordered by their creation date in ascending order.
+ ///
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ public CreateAssistantRequest(AssistantResponse assistant, string model = null, string name = null, string description = null, string instructions = null, IEnumerable tools = null, IEnumerable files = null, IReadOnlyDictionary metadata = null)
+ : this(string.IsNullOrWhiteSpace(model) ? assistant.Model : model, string.IsNullOrWhiteSpace(name) ? assistant.Name : name, string.IsNullOrWhiteSpace(description) ? assistant.Description : description, string.IsNullOrWhiteSpace(instructions) ? assistant.Instructions : instructions, tools ?? assistant.Tools, files ?? assistant.FileIds, metadata ?? assistant.Metadata)
+ {
+ }
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ /// ID of the model to use.
+ /// You can use the List models API to see all of your available models,
+ /// or see our Model overview for descriptions of them.
+ ///
+ ///
+ /// The name of the assistant.
+ /// The maximum length is 256 characters.
+ ///
+ ///
+ /// The description of the assistant.
+ /// The maximum length is 512 characters.
+ ///
+ ///
+ /// The system instructions that the assistant uses.
+ /// The maximum length is 32768 characters.
+ ///
+ ///
+ /// A list of tool enabled on the assistant.
+ /// There can be a maximum of 128 tools per assistant.
+ /// Tools can be of types 'code_interpreter', 'retrieval', or 'function'.
+ ///
+ ///
+ /// A list of file IDs attached to this assistant.
+ /// There can be a maximum of 20 files attached to the assistant.
+ /// Files are ordered by their creation date in ascending order.
+ ///
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ public CreateAssistantRequest(string model = null, string name = null, string description = null, string instructions = null, IEnumerable tools = null, IEnumerable files = null, IReadOnlyDictionary metadata = null)
+ {
+ Model = string.IsNullOrWhiteSpace(model) ? Models.Model.GPT3_5_Turbo : model;
+ Name = name;
+ Description = description;
+ Instructions = instructions;
+ Tools = tools?.ToList();
+ FileIds = files?.ToList();
+ Metadata = metadata;
+ }
+
+ ///
+ /// ID of the model to use.
+ /// You can use the List models API to see all of your available models,
+ /// or see our Model overview for descriptions of them.
+ ///
+ [JsonPropertyName("model")]
+ public string Model { get; }
+
+ ///
+ /// The name of the assistant.
+ /// The maximum length is 256 characters.
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; }
+
+ ///
+ /// The description of the assistant.
+ /// The maximum length is 512 characters.
+ ///
+ [JsonPropertyName("description")]
+ public string Description { get; }
+
+ ///
+ /// The system instructions that the assistant uses.
+ /// The maximum length is 32768 characters.
+ ///
+ [JsonPropertyName("instructions")]
+ public string Instructions { get; }
+
+ ///
+ /// A list of tool enabled on the assistant.
+ /// There can be a maximum of 128 tools per assistant.
+ /// Tools can be of types 'code_interpreter', 'retrieval', or 'function'.
+ ///
+ [JsonPropertyName("tools")]
+ public IReadOnlyList Tools { get; }
+
+ ///
+ /// A list of file IDs attached to this assistant.
+ /// There can be a maximum of 20 files attached to the assistant.
+ /// Files are ordered by their creation date in ascending order.
+ ///
+ [JsonPropertyName("file_ids")]
+ public IReadOnlyList FileIds { get; }
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Audio/AudioEndpoint.cs b/OpenAI-DotNet/Audio/AudioEndpoint.cs
index bb4284a0..8de57131 100644
--- a/OpenAI-DotNet/Audio/AudioEndpoint.cs
+++ b/OpenAI-DotNet/Audio/AudioEndpoint.cs
@@ -1,5 +1,5 @@
-using System;
-using OpenAI.Extensions;
+using OpenAI.Extensions;
+using System;
using System.IO;
using System.Net.Http;
using System.Text.Json;
diff --git a/OpenAI-DotNet/AuthInfo.cs b/OpenAI-DotNet/Authentication/AuthInfo.cs
similarity index 100%
rename from OpenAI-DotNet/AuthInfo.cs
rename to OpenAI-DotNet/Authentication/AuthInfo.cs
diff --git a/OpenAI-DotNet/OpenAIAuthentication.cs b/OpenAI-DotNet/Authentication/OpenAIAuthentication.cs
similarity index 100%
rename from OpenAI-DotNet/OpenAIAuthentication.cs
rename to OpenAI-DotNet/Authentication/OpenAIAuthentication.cs
diff --git a/OpenAI-DotNet/OpenAIClientSettings.cs b/OpenAI-DotNet/Authentication/OpenAIClientSettings.cs
similarity index 100%
rename from OpenAI-DotNet/OpenAIClientSettings.cs
rename to OpenAI-DotNet/Authentication/OpenAIClientSettings.cs
diff --git a/OpenAI-DotNet/Chat/ChatEndpoint.cs b/OpenAI-DotNet/Chat/ChatEndpoint.cs
index c41b7c01..cd4ba12a 100644
--- a/OpenAI-DotNet/Chat/ChatEndpoint.cs
+++ b/OpenAI-DotNet/Chat/ChatEndpoint.cs
@@ -33,7 +33,7 @@ public async Task GetCompletionAsync(ChatRequest chatRequest, Canc
var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl("/completions"), jsonContent, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return response.DeserializeResponse(responseAsString, OpenAIClient.JsonSerializationOptions);
+ return response.Deserialize(responseAsString, Api);
}
///
@@ -43,7 +43,6 @@ public async Task GetCompletionAsync(ChatRequest chatRequest, Canc
/// An action to be called as each new result arrives.
/// Optional, .
/// .
- /// Raised when the HTTP request fails
public async Task StreamCompletionAsync(ChatRequest chatRequest, Action resultHandler, CancellationToken cancellationToken = default)
{
chatRequest.Stream = true;
@@ -68,7 +67,7 @@ public async Task StreamCompletionAsync(ChatRequest chatRequest, A
Console.WriteLine(eventData);
}
- var partialResponse = response.DeserializeResponse(eventData, OpenAIClient.JsonSerializationOptions);
+ var partialResponse = response.Deserialize(eventData, Api);
if (chatResponse == null)
{
@@ -86,7 +85,7 @@ public async Task StreamCompletionAsync(ChatRequest chatRequest, A
if (chatResponse == null) { return null; }
- chatResponse.SetResponseData(response.Headers);
+ chatResponse.SetResponseData(response.Headers, Api);
resultHandler?.Invoke(chatResponse);
return chatResponse;
}
@@ -99,7 +98,6 @@ public async Task StreamCompletionAsync(ChatRequest chatRequest, A
/// The chat request which contains the message content.
/// Optional, .
/// .
- /// Raised when the HTTP request fails
public async IAsyncEnumerable StreamCompletionEnumerableAsync(ChatRequest chatRequest, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
chatRequest.Stream = true;
@@ -124,7 +122,7 @@ public async IAsyncEnumerable StreamCompletionEnumerableAsync(Chat
Console.WriteLine(eventData);
}
- var partialResponse = response.DeserializeResponse(eventData, OpenAIClient.JsonSerializationOptions);
+ var partialResponse = response.Deserialize(eventData, Api);
if (chatResponse == null)
{
@@ -142,7 +140,7 @@ public async IAsyncEnumerable StreamCompletionEnumerableAsync(Chat
if (chatResponse == null) { yield break; }
- chatResponse.SetResponseData(response.Headers);
+ chatResponse.SetResponseData(response.Headers, Api);
yield return chatResponse;
}
}
diff --git a/OpenAI-DotNet/Chat/Choice.cs b/OpenAI-DotNet/Chat/Choice.cs
index dd357c36..87f12862 100644
--- a/OpenAI-DotNet/Chat/Choice.cs
+++ b/OpenAI-DotNet/Chat/Choice.cs
@@ -28,7 +28,7 @@ public sealed class Choice
public override string ToString() => Message?.Content?.ToString() ?? Delta?.Content ?? string.Empty;
- public static implicit operator string(Choice choice) => choice.ToString();
+ public static implicit operator string(Choice choice) => choice?.ToString();
internal void CopyFrom(Choice other)
{
diff --git a/OpenAI-DotNet/Chat/Delta.cs b/OpenAI-DotNet/Chat/Delta.cs
index ac2b8248..9cd6e0e4 100644
--- a/OpenAI-DotNet/Chat/Delta.cs
+++ b/OpenAI-DotNet/Chat/Delta.cs
@@ -7,7 +7,7 @@ namespace OpenAI.Chat
public sealed class Delta
{
///
- /// The of the author of this message.
+ /// The of the author of this message.
///
[JsonInclude]
[JsonPropertyName("role")]
diff --git a/OpenAI-DotNet/Chat/FinishDetails.cs b/OpenAI-DotNet/Chat/FinishDetails.cs
index d1ab531e..7d2f49fc 100644
--- a/OpenAI-DotNet/Chat/FinishDetails.cs
+++ b/OpenAI-DotNet/Chat/FinishDetails.cs
@@ -10,6 +10,6 @@ public sealed class FinishDetails
public override string ToString() => Type;
- public static implicit operator string(FinishDetails details) => details.ToString();
+ public static implicit operator string(FinishDetails details) => details?.ToString();
}
}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Chat/Message.cs b/OpenAI-DotNet/Chat/Message.cs
index 36edc542..4c5a47de 100644
--- a/OpenAI-DotNet/Chat/Message.cs
+++ b/OpenAI-DotNet/Chat/Message.cs
@@ -23,7 +23,7 @@ public Message(Role role, string content, string name, Function function)
/// Creates a new message to insert into a chat conversation.
///
///
- /// The of the author of this message.
+ /// The of the author of this message.
///
///
/// The contents of the message.
@@ -40,7 +40,7 @@ public Message(Role role, IEnumerable content, string name = null)
/// Creates a new message to insert into a chat conversation.
///
///
- /// The of the author of this message.
+ /// The of the author of this message.
///
///
/// The contents of the message.
@@ -72,7 +72,7 @@ public Message(Tool tool, IEnumerable content)
}
///
- /// The of the author of this message.
+ /// The of the author of this message.
///
[JsonInclude]
[JsonPropertyName("role")]
diff --git a/OpenAI-DotNet/BaseEndPoint.cs b/OpenAI-DotNet/Common/BaseEndPoint.cs
similarity index 100%
rename from OpenAI-DotNet/BaseEndPoint.cs
rename to OpenAI-DotNet/Common/BaseEndPoint.cs
diff --git a/OpenAI-DotNet/BaseResponse.cs b/OpenAI-DotNet/Common/BaseResponse.cs
similarity index 91%
rename from OpenAI-DotNet/BaseResponse.cs
rename to OpenAI-DotNet/Common/BaseResponse.cs
index 3ecc409a..1e29dda7 100644
--- a/OpenAI-DotNet/BaseResponse.cs
+++ b/OpenAI-DotNet/Common/BaseResponse.cs
@@ -5,6 +5,12 @@ namespace OpenAI
{
public abstract class BaseResponse
{
+ ///
+ /// The this response was generated from.
+ ///
+ [JsonIgnore]
+ public OpenAIClient Client { get; internal set; }
+
///
/// The server-side processing time as reported by the API. This can be useful for debugging where a delay occurs.
///
@@ -33,7 +39,7 @@ public abstract class BaseResponse
/// The maximum number of requests that are permitted before exhausting the rate limit.
///
- [JsonIgnore]
+ [JsonIgnore]
public int? LimitRequests { get; internal set; }
///
diff --git a/OpenAI-DotNet/Chat/ContentType.cs b/OpenAI-DotNet/Common/ContentType.cs
similarity index 89%
rename from OpenAI-DotNet/Chat/ContentType.cs
rename to OpenAI-DotNet/Common/ContentType.cs
index 79a75059..35981cc3 100644
--- a/OpenAI-DotNet/Chat/ContentType.cs
+++ b/OpenAI-DotNet/Common/ContentType.cs
@@ -1,6 +1,6 @@
using System.Runtime.Serialization;
-namespace OpenAI.Chat
+namespace OpenAI
{
public enum ContentType
{
diff --git a/OpenAI-DotNet/Common/DeletedResponse.cs b/OpenAI-DotNet/Common/DeletedResponse.cs
new file mode 100644
index 00000000..d6e2db3b
--- /dev/null
+++ b/OpenAI-DotNet/Common/DeletedResponse.cs
@@ -0,0 +1,19 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI
+{
+ internal sealed class DeletedResponse
+ {
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("deleted")]
+ public bool Deleted { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Event.cs b/OpenAI-DotNet/Common/Event.cs
similarity index 58%
rename from OpenAI-DotNet/Event.cs
rename to OpenAI-DotNet/Common/Event.cs
index 865375d5..7193fa20 100644
--- a/OpenAI-DotNet/Event.cs
+++ b/OpenAI-DotNet/Common/Event.cs
@@ -3,7 +3,8 @@
namespace OpenAI
{
- public sealed class Event
+ [Obsolete("use EventResponse")]
+ public sealed class Event : BaseResponse
{
[JsonInclude]
[JsonPropertyName("object")]
@@ -11,10 +12,13 @@ public sealed class Event
[JsonInclude]
[JsonPropertyName("created_at")]
- public int CreatedAtUnixTime { get; private set; }
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ [Obsolete("use CreatedAtUnixTimeSeconds")]
+ public int CreatedAtUnixTime => CreatedAtUnixTimeSeconds;
[JsonIgnore]
- public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTime).DateTime;
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
[JsonInclude]
[JsonPropertyName("level")]
@@ -23,5 +27,7 @@ public sealed class Event
[JsonInclude]
[JsonPropertyName("message")]
public string Message { get; private set; }
+
+ public static implicit operator EventResponse(Event @event) => new EventResponse(@event);
}
}
diff --git a/OpenAI-DotNet/Common/EventResponse.cs b/OpenAI-DotNet/Common/EventResponse.cs
new file mode 100644
index 00000000..46a9f534
--- /dev/null
+++ b/OpenAI-DotNet/Common/EventResponse.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace OpenAI
+{
+ public sealed class EventResponse : BaseResponse
+ {
+ public EventResponse() { }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ internal EventResponse(Event @event)
+ {
+ Object = @event.Object;
+ CreatedAtUnixTimeSeconds = @event.CreatedAtUnixTimeSeconds;
+ Level = @event.Level;
+ Message = @event.Message;
+ }
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
+
+ [JsonInclude]
+ [JsonPropertyName("level")]
+ public string Level { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("message")]
+ public string Message { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Chat/Function.cs b/OpenAI-DotNet/Common/Function.cs
similarity index 96%
rename from OpenAI-DotNet/Chat/Function.cs
rename to OpenAI-DotNet/Common/Function.cs
index 53d89d3a..912988d2 100644
--- a/OpenAI-DotNet/Chat/Function.cs
+++ b/OpenAI-DotNet/Common/Function.cs
@@ -1,13 +1,15 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
-namespace OpenAI.Chat
+namespace OpenAI
{
///
- ///
+ ///
///
public class Function
{
+ public Function() { }
+
internal Function(Function other) => CopyFrom(other);
///
diff --git a/OpenAI-DotNet/Common/IListResponse.cs b/OpenAI-DotNet/Common/IListResponse.cs
new file mode 100644
index 00000000..d5120476
--- /dev/null
+++ b/OpenAI-DotNet/Common/IListResponse.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace OpenAI
+{
+ public interface IListResponse
+ where TObject : BaseResponse
+ {
+ IReadOnlyList Items { get; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Chat/ImageUrl.cs b/OpenAI-DotNet/Common/ImageUrl.cs
similarity index 74%
rename from OpenAI-DotNet/Chat/ImageUrl.cs
rename to OpenAI-DotNet/Common/ImageUrl.cs
index 5cf44354..11a5b275 100644
--- a/OpenAI-DotNet/Chat/ImageUrl.cs
+++ b/OpenAI-DotNet/Common/ImageUrl.cs
@@ -1,14 +1,11 @@
using System.Text.Json.Serialization;
-namespace OpenAI.Chat
+namespace OpenAI
{
public sealed class ImageUrl
{
[JsonConstructor]
- public ImageUrl(string url)
- {
- Url = url;
- }
+ public ImageUrl(string url) => Url = url;
[JsonInclude]
[JsonPropertyName("url")]
diff --git a/OpenAI-DotNet/Common/ListQuery.cs b/OpenAI-DotNet/Common/ListQuery.cs
new file mode 100644
index 00000000..54ec5c79
--- /dev/null
+++ b/OpenAI-DotNet/Common/ListQuery.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+
+namespace OpenAI
+{
+ public sealed class ListQuery
+ {
+ ///
+ /// List Query.
+ ///
+ ///
+ /// A limit on the number of objects to be returned.
+ /// Limit can range between 1 and 100, and the default is 20.
+ ///
+ ///
+ /// Sort order by the 'created_at' timestamp of the objects.
+ ///
+ ///
+ /// A cursor for use in pagination.
+ /// after is an object ID that defines your place in the list.
+ /// For instance, if you make a list request and receive 100 objects, ending with obj_foo,
+ /// your subsequent call can include after=obj_foo in order to fetch the next page of the list.
+ ///
+ ///
+ /// A cursor for use in pagination. before is an object ID that defines your place in the list.
+ /// For instance, if you make a list request and receive 100 objects, ending with obj_foo,
+ /// your subsequent call can include before=obj_foo in order to fetch the previous page of the list.
+ ///
+ public ListQuery(int? limit = null, SortOrder order = SortOrder.Descending, string after = null, string before = null)
+ {
+ Limit = limit;
+ Order = order;
+ After = after;
+ Before = before;
+ }
+
+ public int? Limit { get; set; }
+
+ public SortOrder Order { get; set; }
+
+ public string After { get; set; }
+
+ public string Before { get; set; }
+
+ public static implicit operator Dictionary(ListQuery query)
+ {
+ if (query == null) { return null; }
+ var parameters = new Dictionary();
+
+ if (query.Limit.HasValue)
+ {
+ parameters.Add("limit", query.Limit.ToString());
+ }
+
+ switch (query.Order)
+ {
+ case SortOrder.Descending:
+ parameters.Add("order", "desc");
+ break;
+ case SortOrder.Ascending:
+ parameters.Add("order", "asc");
+ break;
+ }
+
+ if (!string.IsNullOrEmpty(query.After))
+ {
+ parameters.Add("after", query.After);
+ }
+
+ if (!string.IsNullOrEmpty(query.Before))
+ {
+ parameters.Add("before", query.Before);
+ }
+
+ return parameters;
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Common/ListResponse.cs b/OpenAI-DotNet/Common/ListResponse.cs
new file mode 100644
index 00000000..18afb19b
--- /dev/null
+++ b/OpenAI-DotNet/Common/ListResponse.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI
+{
+ public sealed class ListResponse : BaseResponse, IListResponse
+ where TObject : BaseResponse
+ {
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("data")]
+ public IReadOnlyList Items { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("has_more")]
+ public bool HasMore { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("first_id")]
+ public string FirstId { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("last_id")]
+ public string LastId { get; private set; }
+ }
+}
diff --git a/OpenAI-DotNet/Chat/Role.cs b/OpenAI-DotNet/Common/Role.cs
similarity index 88%
rename from OpenAI-DotNet/Chat/Role.cs
rename to OpenAI-DotNet/Common/Role.cs
index 6bf77533..82ab906a 100644
--- a/OpenAI-DotNet/Chat/Role.cs
+++ b/OpenAI-DotNet/Common/Role.cs
@@ -1,6 +1,6 @@
using System;
-namespace OpenAI.Chat
+namespace OpenAI
{
public enum Role
{
diff --git a/OpenAI-DotNet/Common/SortOrder.cs b/OpenAI-DotNet/Common/SortOrder.cs
new file mode 100644
index 00000000..8f92dede
--- /dev/null
+++ b/OpenAI-DotNet/Common/SortOrder.cs
@@ -0,0 +1,12 @@
+using System.Runtime.Serialization;
+
+namespace OpenAI
+{
+ public enum SortOrder
+ {
+ [EnumMember(Value = "desc")]
+ Descending,
+ [EnumMember(Value = "asc")]
+ Ascending,
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Chat/Tool.cs b/OpenAI-DotNet/Common/Tool.cs
similarity index 85%
rename from OpenAI-DotNet/Chat/Tool.cs
rename to OpenAI-DotNet/Common/Tool.cs
index 331cfb17..8c7b8c66 100644
--- a/OpenAI-DotNet/Chat/Tool.cs
+++ b/OpenAI-DotNet/Common/Tool.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace OpenAI.Chat
+namespace OpenAI
{
public sealed class Tool
{
@@ -14,6 +14,12 @@ public Tool(Function function)
Type = nameof(function);
}
+ public static implicit operator Tool(Function function) => new Tool(function);
+
+ public static Tool Retrieval { get; } = new Tool { Type = "retrieval" };
+
+ public static Tool CodeInterpreter { get; } = new Tool { Type = "code_interpreter" };
+
[JsonInclude]
[JsonPropertyName("id")]
public string Id { get; private set; }
@@ -29,10 +35,9 @@ public Tool(Function function)
[JsonInclude]
[JsonPropertyName("function")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Function Function { get; private set; }
- public static implicit operator Tool(Function function) => new Tool(function);
-
internal void CopyFrom(Tool other)
{
if (!string.IsNullOrWhiteSpace(other?.Id))
diff --git a/OpenAI-DotNet/Usage.cs b/OpenAI-DotNet/Common/Usage.cs
similarity index 100%
rename from OpenAI-DotNet/Usage.cs
rename to OpenAI-DotNet/Common/Usage.cs
diff --git a/OpenAI-DotNet/Completions/CompletionResponse.cs b/OpenAI-DotNet/Completions/CompletionResponse.cs
new file mode 100644
index 00000000..4bfad93a
--- /dev/null
+++ b/OpenAI-DotNet/Completions/CompletionResponse.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Completions
+{
+ ///
+ /// Represents a result from calling the .
+ ///
+ public sealed class CompletionResponse : BaseResponse
+ {
+ public CompletionResponse() { }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ internal CompletionResponse(CompletionResult result)
+ {
+ Id = result.Id;
+ Object = result.Object;
+ CreatedUnixTimeSeconds = result.CreatedUnixTime;
+ Model = result.Model;
+ Completions = result.Completions;
+ }
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ ///
+ /// The identifier of the result, which may be used during troubleshooting
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ ///
+ /// The time when the result was generated in unix epoch format
+ ///
+ [JsonInclude]
+ [JsonPropertyName("created")]
+ public int CreatedUnixTimeSeconds { get; private set; }
+
+ ///
+ /// The time when the result was generated.
+ ///
+ [JsonIgnore]
+ public DateTime Created => DateTimeOffset.FromUnixTimeSeconds(CreatedUnixTimeSeconds).DateTime;
+
+ [JsonInclude]
+ [JsonPropertyName("model")]
+ public string Model { get; private set; }
+
+ ///
+ /// The completions returned by the API. Depending on your request, there may be 1 or many choices.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("choices")]
+ public IReadOnlyList Completions { get; private set; }
+
+ [JsonIgnore]
+ public Choice FirstChoice => Completions?.FirstOrDefault(choice => choice.Index == 0);
+
+ public override string ToString() => FirstChoice?.ToString() ?? string.Empty;
+
+ public static implicit operator string(CompletionResponse response) => response?.ToString();
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Completions/CompletionResult.cs b/OpenAI-DotNet/Completions/CompletionResult.cs
index 6e61d22d..0e05cef9 100644
--- a/OpenAI-DotNet/Completions/CompletionResult.cs
+++ b/OpenAI-DotNet/Completions/CompletionResult.cs
@@ -8,6 +8,7 @@ namespace OpenAI.Completions
///
/// Represents a result from calling the .
///
+ [Obsolete("use CompletionResponse")]
public sealed class CompletionResult : BaseResponse
{
///
@@ -50,6 +51,8 @@ public sealed class CompletionResult : BaseResponse
public override string ToString() => FirstChoice?.ToString() ?? string.Empty;
- public static implicit operator string(CompletionResult response) => response.ToString();
+ public static implicit operator string(CompletionResult result) => result?.ToString();
+
+ public static implicit operator CompletionResponse(CompletionResult result) => new CompletionResponse(result);
}
}
diff --git a/OpenAI-DotNet/Completions/CompletionsEndpoint.cs b/OpenAI-DotNet/Completions/CompletionsEndpoint.cs
index 9499675b..a9a537f9 100644
--- a/OpenAI-DotNet/Completions/CompletionsEndpoint.cs
+++ b/OpenAI-DotNet/Completions/CompletionsEndpoint.cs
@@ -50,7 +50,7 @@ internal CompletionsEndpoint(OpenAIClient api) : base(api) { }
/// The scale of the penalty for how often a token is used.
/// Should generally be between 0 and 1, although negative numbers are allowed to encourage token reuse.
/// Include the log probabilities on the logprobs most likely tokens, which can be found
- /// in -> . So for example, if logprobs is 10,
+ /// in -> . So for example, if logprobs is 10,
/// the API will return a list of the 10 most likely tokens. If logprobs is supplied, the API will always return the logprob
/// of the sampled token, so there may be up to logprobs+1 elements in the response.
/// Echo back the prompt in addition to the completion.
@@ -59,9 +59,11 @@ internal CompletionsEndpoint(OpenAIClient api) : base(api) { }
/// Optional, to use when calling the API.
/// Defaults to .
/// Optional, .
- /// Asynchronously returns the completion result.
- /// Look in its property for the completions.
- public async Task CreateCompletionAsync(
+ ///
+ /// Asynchronously returns the completion result.
+ /// Look in its property for the completions.
+ ///
+ public async Task CreateCompletionAsync(
string prompt = null,
IEnumerable prompts = null,
string suffix = null,
@@ -101,16 +103,17 @@ public async Task CreateCompletionAsync(
///
/// The request to send to the API.
/// Optional, .
- /// Asynchronously returns the completion result.
- /// Look in its property for the completions.
- /// Raised when the HTTP request fails
- public async Task CreateCompletionAsync(CompletionRequest completionRequest, CancellationToken cancellationToken = default)
+ ///
+ /// Asynchronously returns the completion result.
+ /// Look in its property for the completions.
+ ///
+ public async Task CreateCompletionAsync(CompletionRequest completionRequest, CancellationToken cancellationToken = default)
{
completionRequest.Stream = false;
var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return response.DeserializeResponse(responseAsString, OpenAIClient.JsonSerializationOptions);
+ return response.Deserialize(responseAsString, Api);
}
#endregion Non-Streaming
@@ -139,7 +142,7 @@ public async Task CreateCompletionAsync(CompletionRequest comp
/// The scale of the penalty for how often a token is used.
/// Should generally be between 0 and 1, although negative numbers are allowed to encourage token reuse.
/// Include the log probabilities on the logProbabilities most likely tokens,
- /// which can be found in -> .
+ /// which can be found in -> .
/// So for example, if logProbabilities is 10, the API will return a list of the 10 most likely tokens.
/// If logProbabilities is supplied, the API will always return the logProbabilities of the sampled token,
/// so there may be up to logProbabilities+1 elements in the response.
@@ -149,11 +152,13 @@ public async Task CreateCompletionAsync(CompletionRequest comp
/// Optional, to use when calling the API.
/// Defaults to .
/// Optional, .
- /// An async enumerable with each of the results as they come in.
+ ///
+ /// An async enumerable with each of the results as they come in.
/// See the C# docs
- /// for more details on how to consume an async enumerable.
+ /// for more details on how to consume an async enumerable.
+ ///
public async Task StreamCompletionAsync(
- Action resultHandler,
+ Action resultHandler,
string prompt = null,
IEnumerable prompts = null,
string suffix = null,
@@ -194,8 +199,7 @@ public async Task StreamCompletionAsync(
/// The request to send to the API.
/// An action to be called as each new result arrives.
/// Optional, .
- /// Raised when the HTTP request fails
- public async Task StreamCompletionAsync(CompletionRequest completionRequest, Action resultHandler, CancellationToken cancellationToken = default)
+ public async Task StreamCompletionAsync(CompletionRequest completionRequest, Action resultHandler, CancellationToken cancellationToken = default)
{
completionRequest.Stream = true;
var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
@@ -214,7 +218,7 @@ public async Task StreamCompletionAsync(CompletionRequest completionRequest, Act
{
if (string.IsNullOrWhiteSpace(eventData)) { continue; }
- resultHandler(response.DeserializeResponse(eventData, OpenAIClient.JsonSerializationOptions));
+ resultHandler(response.Deserialize(eventData, Api));
}
else
{
@@ -226,7 +230,7 @@ public async Task StreamCompletionAsync(CompletionRequest completionRequest, Act
///
/// Ask the API to complete the prompt(s) using the specified request, and stream the results as they come in.
/// If you are not using C# 8 supporting IAsyncEnumerable{T} or if you are using the .NET Framework,
- /// you may need to use instead.
+ /// you may need to use instead.
///
/// The prompt to generate from
/// The prompts to generate from
@@ -245,7 +249,7 @@ public async Task StreamCompletionAsync(CompletionRequest completionRequest, Act
/// The scale of the penalty for how often a token is used.
/// Should generally be between 0 and 1, although negative numbers are allowed to encourage token reuse.
/// Include the log probabilities on the logProbabilities most likely tokens,
- /// which can be found in -> .
+ /// which can be found in -> .
/// So for example, if logProbabilities is 10, the API will return a list of the 10 most likely tokens.
/// If logProbabilities is supplied, the API will always return the logProbabilities of the sampled token,
/// so there may be up to logProbabilities+1 elements in the response.
@@ -258,7 +262,7 @@ public async Task StreamCompletionAsync(CompletionRequest completionRequest, Act
/// An async enumerable with each of the results as they come in.
/// See the C# docs
/// for more details on how to consume an async enumerable.
- public IAsyncEnumerable StreamCompletionEnumerableAsync(
+ public IAsyncEnumerable StreamCompletionEnumerableAsync(
string prompt = null,
IEnumerable prompts = null,
string suffix = null,
@@ -295,15 +299,16 @@ public IAsyncEnumerable StreamCompletionEnumerableAsync(
///
/// Ask the API to complete the prompt(s) using the specified request, and stream the results as they come in.
/// If you are not using C# 8 supporting IAsyncEnumerable{T} or if you are using the .NET Framework,
- /// you may need to use instead.
+ /// you may need to use instead.
///
/// The request to send to the API.
/// Optional, .
- /// An async enumerable with each of the results as they come in.
+ ///
+ /// An async enumerable with each of the results as they come in.
/// See
- /// for more details on how to consume an async enumerable.
- /// Raised when the HTTP request fails
- public async IAsyncEnumerable StreamCompletionEnumerableAsync(CompletionRequest completionRequest, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ /// for more details on how to consume an async enumerable.
+ ///
+ public async IAsyncEnumerable StreamCompletionEnumerableAsync(CompletionRequest completionRequest, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
completionRequest.Stream = true;
var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
@@ -321,7 +326,7 @@ public async IAsyncEnumerable StreamCompletionEnumerableAsync(
if (streamData.TryGetEventStreamData(out var eventData))
{
if (string.IsNullOrWhiteSpace(eventData)) { continue; }
- yield return response.DeserializeResponse(eventData, OpenAIClient.JsonSerializationOptions);
+ yield return response.Deserialize(eventData, Api);
}
else
{
diff --git a/OpenAI-DotNet/Edits/Choice.cs b/OpenAI-DotNet/Edits/Choice.cs
index f9bc27ab..ef5b2eff 100644
--- a/OpenAI-DotNet/Edits/Choice.cs
+++ b/OpenAI-DotNet/Edits/Choice.cs
@@ -1,7 +1,9 @@
-using System.Text.Json.Serialization;
+using System;
+using System.Text.Json.Serialization;
namespace OpenAI.Edits
{
+ [Obsolete("Deprecated")]
public sealed class Choice
{
[JsonInclude]
diff --git a/OpenAI-DotNet/Edits/EditRequest.cs b/OpenAI-DotNet/Edits/EditRequest.cs
index ca19bb84..21e77b18 100644
--- a/OpenAI-DotNet/Edits/EditRequest.cs
+++ b/OpenAI-DotNet/Edits/EditRequest.cs
@@ -1,7 +1,9 @@
-using System.Text.Json.Serialization;
+using System;
+using System.Text.Json.Serialization;
namespace OpenAI.Edits
{
+ [Obsolete("Deprecated")]
public sealed class EditRequest
{
///
diff --git a/OpenAI-DotNet/Edits/EditResponse.cs b/OpenAI-DotNet/Edits/EditResponse.cs
index 3113b49f..65b56128 100644
--- a/OpenAI-DotNet/Edits/EditResponse.cs
+++ b/OpenAI-DotNet/Edits/EditResponse.cs
@@ -4,22 +4,26 @@
namespace OpenAI.Edits
{
+ [Obsolete("deprecated")]
public sealed class EditResponse : BaseResponse
{
[JsonInclude]
[JsonPropertyName("object")]
public string Object { get; private set; }
- ///
- /// The time when the result was generated in unix epoch format
- ///
[JsonInclude]
[JsonPropertyName("created")]
- public int CreatedUnixTime { get; private set; }
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ [Obsolete("use CreatedAtUnixTimeSeconds")]
+ public int CreatedUnixTime => CreatedAtUnixTimeSeconds;
+
+ [JsonIgnore]
+ [Obsolete("use CreatedAt")]
+ public DateTime Created => CreatedAt;
- /// The time when the result was generated
[JsonIgnore]
- public DateTime Created => DateTimeOffset.FromUnixTimeSeconds(CreatedUnixTime).DateTime;
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
[JsonInclude]
[JsonPropertyName("choices")]
@@ -33,10 +37,8 @@ public sealed class EditResponse : BaseResponse
/// Gets the text of the first edit, representing the main result
///
public override string ToString()
- {
- return Choices is { Count: > 0 }
+ => Choices is { Count: > 0 }
? Choices[0]
: "Edit result has no valid output";
- }
}
}
diff --git a/OpenAI-DotNet/Edits/EditsEndpoint.cs b/OpenAI-DotNet/Edits/EditsEndpoint.cs
index 033aecd3..519f814b 100644
--- a/OpenAI-DotNet/Edits/EditsEndpoint.cs
+++ b/OpenAI-DotNet/Edits/EditsEndpoint.cs
@@ -1,5 +1,5 @@
-using System;
-using OpenAI.Extensions;
+using OpenAI.Extensions;
+using System;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -10,7 +10,7 @@ namespace OpenAI.Edits
/// Given a prompt and an instruction, the model will return an edited version of the prompt.
///
///
- [Obsolete]
+ [Obsolete("Deprecated")]
public sealed class EditsEndpoint : BaseEndPoint
{
///
@@ -64,7 +64,7 @@ public async Task CreateEditAsync(EditRequest request, Cancellatio
var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return response.DeserializeResponse(responseAsString, OpenAIClient.JsonSerializationOptions);
+ return response.Deserialize(responseAsString, Api);
}
}
}
diff --git a/OpenAI-DotNet/Embeddings/EmbeddingsEndpoint.cs b/OpenAI-DotNet/Embeddings/EmbeddingsEndpoint.cs
index f0da9c4b..a6e0eb30 100644
--- a/OpenAI-DotNet/Embeddings/EmbeddingsEndpoint.cs
+++ b/OpenAI-DotNet/Embeddings/EmbeddingsEndpoint.cs
@@ -68,7 +68,7 @@ public async Task CreateEmbeddingAsync(EmbeddingsRequest req
var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return response.DeserializeResponse(responseAsString, OpenAIClient.JsonSerializationOptions);
+ return response.Deserialize(responseAsString, Api);
}
}
}
diff --git a/OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs b/OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs
index 3b3a9acd..16554f34 100644
--- a/OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs
+++ b/OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs
@@ -18,7 +18,7 @@ public sealed class EmbeddingsRequest
///
///
/// ID of the model to use.
- /// Defaults to:
+ /// Defaults to:
///
///
/// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
diff --git a/OpenAI-DotNet/Extensions/ResponseExtensions.cs b/OpenAI-DotNet/Extensions/ResponseExtensions.cs
index 8706c034..0fc56def 100644
--- a/OpenAI-DotNet/Extensions/ResponseExtensions.cs
+++ b/OpenAI-DotNet/Extensions/ResponseExtensions.cs
@@ -29,8 +29,18 @@ internal static class ResponseExtensions
NumberDecimalSeparator = "."
};
- internal static void SetResponseData(this BaseResponse response, HttpResponseHeaders headers)
+ internal static void SetResponseData(this BaseResponse response, HttpResponseHeaders headers, OpenAIClient client)
{
+ if (response is IListResponse listResponse)
+ {
+ foreach (var item in listResponse.Items)
+ {
+ SetResponseData(item, headers, client);
+ }
+ }
+
+ response.Client = client;
+
if (headers == null) { return; }
if (headers.TryGetValues(RequestId, out var requestId))
@@ -116,10 +126,10 @@ internal static async Task CheckResponseAsync(this HttpResponseMessage response,
}
}
- internal static T DeserializeResponse(this HttpResponseMessage response, string json, JsonSerializerOptions settings) where T : BaseResponse
+ internal static T Deserialize(this HttpResponseMessage response, string json, OpenAIClient client) where T : BaseResponse
{
- var result = JsonSerializer.Deserialize(json, settings);
- result.SetResponseData(response.Headers);
+ var result = JsonSerializer.Deserialize(json, OpenAIClient.JsonSerializationOptions);
+ result.SetResponseData(response.Headers, client);
return result;
}
}
diff --git a/OpenAI-DotNet/Files/FileData.cs b/OpenAI-DotNet/Files/FileData.cs
index 2cc9cb31..e781cfd1 100644
--- a/OpenAI-DotNet/Files/FileData.cs
+++ b/OpenAI-DotNet/Files/FileData.cs
@@ -3,39 +3,66 @@
namespace OpenAI.Files
{
- public sealed class FileData
+ ///
+ /// The File object represents a document that has been uploaded to OpenAI.
+ ///
+ [Obsolete("use FileResponse")]
+ public sealed class FileData : BaseResponse
{
+ ///
+ /// The file identifier, which can be referenced in the API endpoints.
+ ///
[JsonInclude]
[JsonPropertyName("id")]
public string Id { get; private set; }
+ ///
+ /// The object type, which is always 'file'.
+ ///
[JsonInclude]
[JsonPropertyName("object")]
public string Object { get; private set; }
+ ///
+ /// The size of the file, in bytes.
+ ///
[JsonInclude]
[JsonPropertyName("bytes")]
public int Size { get; private set; }
+ ///
+ /// The Unix timestamp (in seconds) for when the file was created.
+ ///
[JsonInclude]
[JsonPropertyName("created_at")]
- public int CreatedUnixTime { get; private set; }
+ public int CreatedAtUnixTimeSeconds { get; private set; }
[JsonIgnore]
- public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedUnixTime).DateTime;
+ [Obsolete("Use CreatedAtUnixTimeSeconds")]
+ public int CreatedUnixTime => CreatedAtUnixTimeSeconds;
+ [JsonIgnore]
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
+
+ ///
+ /// The name of the file.
+ ///
[JsonInclude]
[JsonPropertyName("filename")]
public string FileName { get; private set; }
+ ///
+ /// The intended purpose of the file.
+ /// Supported values are 'fine-tune', 'fine-tune-results', 'assistants', and 'assistants_output'.
+ ///
[JsonInclude]
[JsonPropertyName("purpose")]
public string Purpose { get; private set; }
- [JsonInclude]
- [JsonPropertyName("status")]
- public string Status { get; private set; }
+ public static implicit operator string(FileData fileData) => fileData?.ToString();
+
+ public static implicit operator FileResponse(FileData fileData) => new FileResponse(fileData);
- public static implicit operator string(FileData fileData) => fileData.Id;
+ public override string ToString() => Id;
}
}
diff --git a/OpenAI-DotNet/Files/FileResponse.cs b/OpenAI-DotNet/Files/FileResponse.cs
new file mode 100644
index 00000000..1437bfa3
--- /dev/null
+++ b/OpenAI-DotNet/Files/FileResponse.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Files
+{
+ ///
+ /// The File object represents a document that has been uploaded to OpenAI.
+ ///
+ public sealed class FileResponse : BaseResponse
+ {
+ public FileResponse() { }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ internal FileResponse(FileData file)
+ {
+ Id = file.Id;
+ Object = file.Object;
+ Size = file.Size;
+ CreatedAtUnixTimeSeconds = file.CreatedAtUnixTimeSeconds;
+ FileName = file.FileName;
+ Purpose = file.Purpose;
+ }
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ ///
+ /// The file identifier, which can be referenced in the API endpoints.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ ///
+ /// The object type, which is always 'file'.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ ///
+ /// The size of the file, in bytes.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("bytes")]
+ public int Size { get; private set; }
+
+ ///
+ /// The Unix timestamp (in seconds) for when the file was created.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ [Obsolete("Use CreatedAtUnixTimeSeconds")]
+ public int CreatedUnixTime => CreatedAtUnixTimeSeconds;
+
+ [JsonIgnore]
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
+
+ ///
+ /// The name of the file.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("filename")]
+ public string FileName { get; private set; }
+
+ ///
+ /// The intended purpose of the file.
+ /// Supported values are 'fine-tune', 'fine-tune-results', 'assistants', and 'assistants_output'.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("purpose")]
+ public string Purpose { get; private set; }
+
+ public static implicit operator string(FileResponse file) => file?.ToString();
+
+ public override string ToString() => Id;
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Files/FilesEndpoint.cs b/OpenAI-DotNet/Files/FilesEndpoint.cs
index 6f957c93..f8e74193 100644
--- a/OpenAI-DotNet/Files/FilesEndpoint.cs
+++ b/OpenAI-DotNet/Files/FilesEndpoint.cs
@@ -19,13 +19,7 @@ public sealed class FilesEndpoint : BaseEndPoint
private class FilesList
{
[JsonPropertyName("data")]
- public List Data { get; set; }
- }
-
- private class FileDeleteResponse
- {
- [JsonPropertyName("deleted")]
- public bool Deleted { get; set; }
+ public IReadOnlyList Files { get; set; }
}
///
@@ -37,14 +31,21 @@ public FilesEndpoint(OpenAIClient api) : base(api) { }
///
/// Returns a list of files that belong to the user's organization.
///
+ /// List files with a specific purpose.
/// Optional, .
- /// List of .
- ///
- public async Task> ListFilesAsync(CancellationToken cancellationToken = default)
+ /// List of .
+ public async Task> ListFilesAsync(string purpose = null, CancellationToken cancellationToken = default)
{
- var response = await Api.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false);
+ Dictionary query = null;
+
+ if (!string.IsNullOrWhiteSpace(purpose))
+ {
+ query = new Dictionary { { nameof(purpose), purpose } };
+ }
+
+ var response = await Api.Client.GetAsync(GetUrl(queryParameters: query), cancellationToken).ConfigureAwait(false);
var resultAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(resultAsString, OpenAIClient.JsonSerializationOptions)?.Data;
+ return JsonSerializer.Deserialize(resultAsString, OpenAIClient.JsonSerializationOptions)?.Files;
}
///
@@ -61,9 +62,8 @@ public async Task> ListFilesAsync(CancellationToken canc
/// fields representing your training examples.
///
/// Optional, .
- /// .
- ///
- public async Task UploadFileAsync(string filePath, string purpose, CancellationToken cancellationToken = default)
+ /// .
+ public async Task UploadFileAsync(string filePath, string purpose, CancellationToken cancellationToken = default)
=> await UploadFileAsync(new FileUploadRequest(filePath, purpose), cancellationToken).ConfigureAwait(false);
///
@@ -73,9 +73,8 @@ public async Task UploadFileAsync(string filePath, string purpose, Can
///
/// .
/// Optional, .
- /// .
- ///
- public async Task UploadFileAsync(FileUploadRequest request, CancellationToken cancellationToken = default)
+ /// .
+ public async Task UploadFileAsync(FileUploadRequest request, CancellationToken cancellationToken = default)
{
using var fileData = new MemoryStream();
using var content = new MultipartFormDataContent();
@@ -86,7 +85,7 @@ public async Task UploadFileAsync(FileUploadRequest request, Cancellat
var response = await Api.Client.PostAsync(GetUrl(), content, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
+ return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
}
///
@@ -95,7 +94,6 @@ public async Task UploadFileAsync(FileUploadRequest request, Cancellat
/// The ID of the file to use for this request
/// Optional, .
/// True, if file was successfully deleted.
- ///
public async Task DeleteFileAsync(string fileId, CancellationToken cancellationToken = default)
{
return await InternalDeleteFileAsync(1).ConfigureAwait(false);
@@ -118,7 +116,7 @@ async Task InternalDeleteFileAsync(int attempt)
throw new HttpRequestException($"{nameof(DeleteFileAsync)} Failed! HTTP status code: {response.StatusCode}. Response: {responseAsString}");
}
- return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false;
+ return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false;
}
}
@@ -127,13 +125,12 @@ async Task InternalDeleteFileAsync(int attempt)
///
/// The ID of the file to use for this request.
/// Optional, .
- ///
- ///
- public async Task GetFileInfoAsync(string fileId, CancellationToken cancellationToken = default)
+ ///
+ public async Task GetFileInfoAsync(string fileId, CancellationToken cancellationToken = default)
{
var response = await Api.Client.GetAsync(GetUrl($"/{fileId}"), cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
+ return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
}
///
@@ -144,7 +141,6 @@ public async Task GetFileInfoAsync(string fileId, CancellationToken ca
/// Optional, delete cached file. Default is false.
/// Optional,
/// The full path of the downloaded file.
- ///
public async Task DownloadFileAsync(string fileId, string directory, bool deleteCachedFile = false, CancellationToken cancellationToken = default)
{
var fileData = await GetFileInfoAsync(fileId, cancellationToken).ConfigureAwait(false);
@@ -154,13 +150,12 @@ public async Task DownloadFileAsync(string fileId, string directory, boo
///
/// Downloads the specified file.
///
- /// to download.
+ /// to download.
/// The directory to download the file into.
/// Optional, delete cached file. Default is false.
/// Optional,
/// The full path of the downloaded file.
- ///
- public async Task DownloadFileAsync(FileData fileData, string directory, bool deleteCachedFile = false, CancellationToken cancellationToken = default)
+ public async Task DownloadFileAsync(FileResponse fileData, string directory, bool deleteCachedFile = false, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(directory))
{
diff --git a/OpenAI-DotNet/FineTuning/EventList.cs b/OpenAI-DotNet/FineTuning/EventList.cs
index a3d0c177..af6746ba 100644
--- a/OpenAI-DotNet/FineTuning/EventList.cs
+++ b/OpenAI-DotNet/FineTuning/EventList.cs
@@ -1,8 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace OpenAI.FineTuning
{
+ [Obsolete("Use ListResponse")]
public sealed class EventList
{
[JsonInclude]
diff --git a/OpenAI-DotNet/FineTuning/FineTuneJob.cs b/OpenAI-DotNet/FineTuning/FineTuneJob.cs
index 96f17261..785db76f 100644
--- a/OpenAI-DotNet/FineTuning/FineTuneJob.cs
+++ b/OpenAI-DotNet/FineTuning/FineTuneJob.cs
@@ -4,7 +4,8 @@
namespace OpenAI.FineTuning
{
- public sealed class FineTuneJob
+ [Obsolete("use FineTuneJobResponse")]
+ public sealed class FineTuneJob : BaseResponse
{
[JsonInclude]
[JsonPropertyName("object")]
@@ -20,17 +21,31 @@ public sealed class FineTuneJob
[JsonInclude]
[JsonPropertyName("created_at")]
- public int? CreatedAtUnixTime { get; private set; }
+ public int? CreateAtUnixTimeSeconds { get; private set; }
[JsonIgnore]
- public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTime ?? 0).DateTime;
+ [Obsolete("Use CreateAtUnixTimeSeconds")]
+ public int? CreatedAtUnixTime => CreateAtUnixTimeSeconds;
+
+ [JsonIgnore]
+ public DateTime? CreatedAt
+ => CreateAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(CreateAtUnixTimeSeconds.Value).DateTime
+ : null;
[JsonInclude]
[JsonPropertyName("finished_at")]
- public int? FinishedAtUnixTime { get; private set; }
+ public int? FinishedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ [Obsolete("Use FinishedAtUnixTimeSeconds")]
+ public int? FinishedAtUnixTime => CreateAtUnixTimeSeconds;
[JsonIgnore]
- public DateTime FinishedAt => DateTimeOffset.FromUnixTimeSeconds(FinishedAtUnixTime ?? 0).DateTime;
+ public DateTime? FinishedAt
+ => FinishedAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(FinishedAtUnixTimeSeconds.Value).DateTime
+ : null;
[JsonInclude]
[JsonPropertyName("fine_tuned_model")]
@@ -67,6 +82,10 @@ public sealed class FineTuneJob
[JsonIgnore]
public IReadOnlyList Events { get; internal set; } = new List();
- public static implicit operator string(FineTuneJob job) => job.Id;
+ public static implicit operator FineTuneJobResponse(FineTuneJob job) => new FineTuneJobResponse(job);
+
+ public static implicit operator string(FineTuneJob job) => job?.ToString();
+
+ public override string ToString() => Id;
}
}
diff --git a/OpenAI-DotNet/FineTuning/FineTuneJobList.cs b/OpenAI-DotNet/FineTuning/FineTuneJobList.cs
index 8bec5b60..53966a7f 100644
--- a/OpenAI-DotNet/FineTuning/FineTuneJobList.cs
+++ b/OpenAI-DotNet/FineTuning/FineTuneJobList.cs
@@ -1,8 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace OpenAI.FineTuning
{
+ [Obsolete("Use ListResponse")]
public sealed class FineTuneJobList
{
[JsonInclude]
diff --git a/OpenAI-DotNet/FineTuning/FineTuneJobResponse.cs b/OpenAI-DotNet/FineTuning/FineTuneJobResponse.cs
new file mode 100644
index 00000000..48f5941a
--- /dev/null
+++ b/OpenAI-DotNet/FineTuning/FineTuneJobResponse.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.FineTuning
+{
+ public sealed class FineTuneJobResponse : BaseResponse
+ {
+ public FineTuneJobResponse() { }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ internal FineTuneJobResponse(FineTuneJob job)
+ {
+ Object = job.Object;
+ Id = job.Id;
+ Model = job.Model;
+ CreateAtUnixTimeSeconds = job.CreateAtUnixTimeSeconds;
+ FinishedAtUnixTimeSeconds = job.FinishedAtUnixTimeSeconds;
+ FineTunedModel = job.FineTunedModel;
+ OrganizationId = job.OrganizationId;
+ ResultFiles = job.ResultFiles;
+ Status = job.Status;
+ ValidationFile = job.ValidationFile;
+ TrainingFile = job.TrainingFile;
+ HyperParameters = job.HyperParameters;
+ TrainedTokens = job.TrainedTokens;
+ events = new List(job.Events.Count);
+
+ foreach (var jobEvent in job.Events)
+ {
+ jobEvent.Client = Client;
+ events.Add(jobEvent);
+ }
+ }
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("model")]
+ public string Model { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int? CreateAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? CreatedAt
+ => CreateAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(CreateAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ [JsonInclude]
+ [JsonPropertyName("finished_at")]
+ public int? FinishedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? FinishedAt
+ => FinishedAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(FinishedAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ [JsonInclude]
+ [JsonPropertyName("fine_tuned_model")]
+ public string FineTunedModel { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("organization_id")]
+ public string OrganizationId { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("result_files")]
+ public IReadOnlyList ResultFiles { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("status")]
+ public JobStatus Status { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("validation_file")]
+ public string ValidationFile { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("training_file")]
+ public string TrainingFile { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("hyperparameters")]
+ public HyperParams HyperParameters { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("trained_tokens")]
+ public int? TrainedTokens { get; private set; }
+
+ private List events = new List();
+
+ [JsonIgnore]
+ public IReadOnlyList Events
+ {
+ get => events;
+ internal set
+ {
+ events = value?.ToList() ?? new List();
+
+ foreach (var @event in events)
+ {
+ @event.Client = Client;
+ }
+ }
+ }
+
+ public static implicit operator string(FineTuneJobResponse job) => job?.ToString();
+
+ public override string ToString() => Id;
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/FineTuning/FineTuningEndpoint.cs b/OpenAI-DotNet/FineTuning/FineTuningEndpoint.cs
index 661a0640..d04a80b2 100644
--- a/OpenAI-DotNet/FineTuning/FineTuningEndpoint.cs
+++ b/OpenAI-DotNet/FineTuning/FineTuningEndpoint.cs
@@ -1,6 +1,6 @@
using OpenAI.Extensions;
+using System;
using System.Collections.Generic;
-using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -9,7 +9,8 @@ namespace OpenAI.FineTuning
{
///
/// Manage fine-tuning jobs to tailor a model to your specific training data.
- ///
+ ///
+ ///
///
public sealed class FineTuningEndpoint : BaseEndPoint
{
@@ -27,24 +28,16 @@ public FineTuningEndpoint(OpenAIClient api) : base(api) { }
/// .
/// Optional, .
/// .
- /// .
- public async Task CreateJobAsync(CreateFineTuneJobRequest jobRequest, CancellationToken cancellationToken = default)
+ public async Task CreateJobAsync(CreateFineTuneJobRequest jobRequest, CancellationToken cancellationToken = default)
{
var jsonContent = JsonSerializer.Serialize(jobRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl("/jobs"), jsonContent, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
+ return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
}
- ///
- /// List your organization's fine-tuning jobs.
- ///
- /// Number of fine-tuning jobs to retrieve (Default 20).
- /// Identifier for the last job from the previous pagination request.
- /// Optional, .
- /// List of s.
- /// .
- public async Task ListJobsAsync(int? limit = null, string after = null, CancellationToken cancellationToken = default)
+ [Obsolete("Use new overload")]
+ public async Task ListJobsAsync(int? limit, string after, CancellationToken cancellationToken)
{
var parameters = new Dictionary();
@@ -63,19 +56,31 @@ public async Task ListJobsAsync(int? limit = null, string after
return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
}
+ ///
+ /// List your organization's fine-tuning jobs.
+ ///
+ /// .
+ /// Optional, .
+ /// List of s.
+ public async Task> ListJobsAsync(ListQuery query = null, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl("/jobs", query), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize>(responseAsString, Api);
+ }
+
///
/// Gets info about the fine-tune job.
///
/// .
/// Optional, .
/// .
- ///
- public async Task GetJobInfoAsync(string jobId, CancellationToken cancellationToken = default)
+ public async Task GetJobInfoAsync(string jobId, CancellationToken cancellationToken = default)
{
var response = await Api.Client.GetAsync(GetUrl($"/jobs/{jobId}"), cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- var job = JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
- job.Events = (await ListJobEventsAsync(job, cancellationToken: cancellationToken).ConfigureAwait(false)).Events;
+ var job = JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
+ job.Events = (await ListJobEventsAsync(job, cancellationToken: cancellationToken).ConfigureAwait(false))?.Items;
return job;
}
@@ -85,25 +90,16 @@ public async Task GetJobInfoAsync(string jobId, CancellationToken c
/// to cancel.
/// Optional, .
/// .
- ///
public async Task CancelJobAsync(string jobId, CancellationToken cancellationToken = default)
{
var response = await Api.Client.PostAsync(GetUrl($"/jobs/{jobId}/cancel"), null!, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- var result = JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
+ var result = JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
return result.Status == JobStatus.Cancelled;
}
- ///
- /// Get fine-grained status updates for a fine-tune job.
- ///
- /// .
- /// Number of fine-tuning jobs to retrieve (Default 20).
- /// Identifier for the last from the previous pagination request.
- /// Optional, .
- /// List of events for .
- ///
- public async Task ListJobEventsAsync(string jobId, int? limit = null, string after = null, CancellationToken cancellationToken = default)
+ [Obsolete("use new overload")]
+ public async Task ListJobEventsAsync(string jobId, int? limit, string after, CancellationToken cancellationToken)
{
var parameters = new Dictionary();
@@ -121,5 +117,19 @@ public async Task ListJobEventsAsync(string jobId, int? limit = null,
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions);
}
+
+ ///
+ /// Get fine-grained status updates for a fine-tune job.
+ ///
+ /// .
+ /// .
+ /// Optional, .
+ /// List of events for .
+ public async Task> ListJobEventsAsync(string jobId, ListQuery query = null, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/jobs/{jobId}/events", query), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize>(responseAsString, Api);
+ }
}
}
diff --git a/OpenAI-DotNet/Images/ImageResult.cs b/OpenAI-DotNet/Images/ImageResult.cs
index 92d30762..079aef15 100644
--- a/OpenAI-DotNet/Images/ImageResult.cs
+++ b/OpenAI-DotNet/Images/ImageResult.cs
@@ -2,7 +2,7 @@
namespace OpenAI.Images
{
- internal class ImageResult
+ public sealed class ImageResult
{
[JsonInclude]
[JsonPropertyName("url")]
@@ -11,5 +11,17 @@ internal class ImageResult
[JsonInclude]
[JsonPropertyName("b64_json")]
public string B64_Json { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("revised_prompt")]
+ public string RevisedPrompt { get; private set; }
+
+ public static implicit operator string(ImageResult result) => result?.ToString();
+
+ public override string ToString()
+ => !string.IsNullOrWhiteSpace(Url)
+ ? Url
+ : !string.IsNullOrWhiteSpace(B64_Json)
+ ? B64_Json : null;
}
}
diff --git a/OpenAI-DotNet/Images/ImagesEndpoint.cs b/OpenAI-DotNet/Images/ImagesEndpoint.cs
index fbd271f1..c732e915 100644
--- a/OpenAI-DotNet/Images/ImagesEndpoint.cs
+++ b/OpenAI-DotNet/Images/ImagesEndpoint.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
@@ -46,7 +45,7 @@ internal ImagesEndpoint(OpenAIClient api) : base(api) { }
///
/// A list of generated texture urls to download.
[Obsolete]
- public async Task> GenerateImageAsync(
+ public async Task> GenerateImageAsync(
string prompt,
int numberOfResults = 1,
ImageSize size = ImageSize.Large,
@@ -61,12 +60,10 @@ public async Task> GenerateImageAsync(
///
/// Optional, .
/// A list of generated texture urls to download.
- ///
- public async Task> GenerateImageAsync(ImageGenerationRequest request, CancellationToken cancellationToken = default)
+ public async Task> GenerateImageAsync(ImageGenerationRequest request, CancellationToken cancellationToken = default)
{
var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
- var endpoint = GetUrl($"/generations{(Api.OpenAIClientSettings.IsAzureDeployment ? ":submit" : string.Empty)}");
- var response = await Api.Client.PostAsync(endpoint, jsonContent, cancellationToken).ConfigureAwait(false);
+ var response = await Api.Client.PostAsync(GetUrl("/generations"), jsonContent, cancellationToken).ConfigureAwait(false);
return await DeserializeResponseAsync(response, cancellationToken).ConfigureAwait(false);
}
@@ -99,9 +96,8 @@ public async Task> GenerateImageAsync(ImageGenerationReque
///
/// Optional, .
/// A list of generated texture urls to download.
- ///
- [Obsolete]
- public async Task> CreateImageEditAsync(
+ [Obsolete("Use new constructor")]
+ public async Task> CreateImageEditAsync(
string image,
string mask,
string prompt,
@@ -118,8 +114,7 @@ public async Task> CreateImageEditAsync(
///
/// Optional, .
/// A list of generated texture urls to download.
- ///
- public async Task> CreateImageEditAsync(ImageEditRequest request, CancellationToken cancellationToken = default)
+ public async Task> CreateImageEditAsync(ImageEditRequest request, CancellationToken cancellationToken = default)
{
using var content = new MultipartFormDataContent();
using var imageData = new MemoryStream();
@@ -170,9 +165,8 @@ public async Task> CreateImageEditAsync(ImageEditRequest r
///
/// Optional, .
/// A list of generated texture urls to download.
- ///
- [Obsolete]
- public async Task> CreateImageVariationAsync(
+ [Obsolete("Use new constructor")]
+ public async Task> CreateImageVariationAsync(
string imagePath,
int numberOfResults = 1,
ImageSize size = ImageSize.Large,
@@ -187,8 +181,7 @@ public async Task> CreateImageVariationAsync(
///
/// Optional, .
/// A list of generated texture urls to download.
- ///
- public async Task> CreateImageVariationAsync(ImageVariationRequest request, CancellationToken cancellationToken = default)
+ public async Task> CreateImageVariationAsync(ImageVariationRequest request, CancellationToken cancellationToken = default)
{
using var content = new MultipartFormDataContent();
using var imageData = new MemoryStream();
@@ -208,17 +201,17 @@ public async Task> CreateImageVariationAsync(ImageVariatio
return await DeserializeResponseAsync(response, cancellationToken).ConfigureAwait(false);
}
- private async Task> DeserializeResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken = default)
+ private async Task> DeserializeResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken = default)
{
var resultAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- var imagesResponse = response.DeserializeResponse(resultAsString, OpenAIClient.JsonSerializationOptions);
+ var imagesResponse = response.Deserialize(resultAsString, Api);
if (imagesResponse?.Results is not { Count: not 0 })
{
throw new HttpRequestException($"{nameof(DeserializeResponseAsync)} returned no results! HTTP status code: {response.StatusCode}. Response body: {resultAsString}");
}
- return imagesResponse.Results.Select(imageResult => string.IsNullOrWhiteSpace(imageResult.Url) ? imageResult.B64_Json : imageResult.Url).ToList();
+ return imagesResponse.Results;
}
}
}
diff --git a/OpenAI-DotNet/Images/ImagesResponse.cs b/OpenAI-DotNet/Images/ImagesResponse.cs
index 7bc011a7..79254f75 100644
--- a/OpenAI-DotNet/Images/ImagesResponse.cs
+++ b/OpenAI-DotNet/Images/ImagesResponse.cs
@@ -3,7 +3,7 @@
namespace OpenAI.Images
{
- internal class ImagesResponse : BaseResponse
+ internal sealed class ImagesResponse : BaseResponse
{
[JsonInclude]
[JsonPropertyName("created")]
diff --git a/OpenAI-DotNet/Models/Model.cs b/OpenAI-DotNet/Models/Model.cs
index 359f8358..583a5374 100644
--- a/OpenAI-DotNet/Models/Model.cs
+++ b/OpenAI-DotNet/Models/Model.cs
@@ -30,7 +30,7 @@ public Model(string id, string ownedBy = null)
/// Allows a model to be implicitly cast to the string of its id.
///
/// The to cast to a string.
- public static implicit operator string(Model model) => model.Id;
+ public static implicit operator string(Model model) => model?.ToString();
///
/// Allows a string to be implicitly cast as a
diff --git a/OpenAI-DotNet/Models/ModelsEndpoint.cs b/OpenAI-DotNet/Models/ModelsEndpoint.cs
index 13de8856..c0be66d8 100644
--- a/OpenAI-DotNet/Models/ModelsEndpoint.cs
+++ b/OpenAI-DotNet/Models/ModelsEndpoint.cs
@@ -1,7 +1,6 @@
using OpenAI.Extensions;
using System;
using System.Collections.Generic;
-using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
@@ -23,21 +22,6 @@ private sealed class ModelsList
public List Models { get; private set; }
}
- private sealed class DeleteModelResponse
- {
- [JsonInclude]
- [JsonPropertyName("id")]
- public string Id { get; private set; }
-
- [JsonInclude]
- [JsonPropertyName("object")]
- public string Object { get; private set; }
-
- [JsonInclude]
- [JsonPropertyName("deleted")]
- public bool Deleted { get; private set; }
- }
-
///
public ModelsEndpoint(OpenAIClient api) : base(api) { }
@@ -49,7 +33,6 @@ public ModelsEndpoint(OpenAIClient api) : base(api) { }
///
/// Optional,
/// Asynchronously returns the list of all s
- /// Raised when the HTTP request fails
public async Task> GetModelsAsync(CancellationToken cancellationToken = default)
{
var response = await Api.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false);
@@ -63,7 +46,6 @@ public async Task> GetModelsAsync(CancellationToken cancell
/// The id/name of the model to get more details about
/// Optional,
/// Asynchronously returns the with all available properties
- /// Raised when the HTTP request fails
public async Task GetModelDetailsAsync(string id, CancellationToken cancellationToken = default)
{
var response = await Api.Client.GetAsync(GetUrl($"/{id}"), cancellationToken).ConfigureAwait(false);
@@ -77,7 +59,6 @@ public async Task GetModelDetailsAsync(string id, CancellationToken cance
/// The to delete.
/// Optional,
/// True, if fine-tuned model was successfully deleted.
- ///
public async Task DeleteFineTuneModelAsync(string modelId, CancellationToken cancellationToken = default)
{
var model = await GetModelDetailsAsync(modelId, cancellationToken).ConfigureAwait(false);
@@ -93,7 +74,7 @@ public async Task DeleteFineTuneModelAsync(string modelId, CancellationTok
{
var response = await Api.Client.DeleteAsync(GetUrl($"/{model.Id}"), cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false;
+ return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false;
}
catch (Exception e)
{
diff --git a/OpenAI-DotNet/Models/Permission.cs b/OpenAI-DotNet/Models/Permission.cs
index c252a02f..c046cf12 100644
--- a/OpenAI-DotNet/Models/Permission.cs
+++ b/OpenAI-DotNet/Models/Permission.cs
@@ -15,10 +15,13 @@ public sealed class Permission
[JsonInclude]
[JsonPropertyName("created")]
- public int CreatedAtUnixTime { get; private set; }
+ public int CreatedAtUnitTimeSeconds { get; private set; }
+
+ [Obsolete("use CreatedAtUnitTimeSeconds")]
+ public int CreatedAtUnixTime => CreatedAtUnitTimeSeconds;
[JsonIgnore]
- public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTime).DateTime;
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnitTimeSeconds).DateTime;
[JsonInclude]
[JsonPropertyName("allow_create_engine")]
diff --git a/OpenAI-DotNet/Moderations/ModerationsEndpoint.cs b/OpenAI-DotNet/Moderations/ModerationsEndpoint.cs
index 92709066..0dc918f5 100644
--- a/OpenAI-DotNet/Moderations/ModerationsEndpoint.cs
+++ b/OpenAI-DotNet/Moderations/ModerationsEndpoint.cs
@@ -1,6 +1,5 @@
using OpenAI.Extensions;
using System.Linq;
-using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -46,13 +45,12 @@ public async Task GetModerationAsync(string input, string model = null, Ca
///
///
/// Optional, .
- /// Raised when the HTTP request fails
public async Task CreateModerationAsync(ModerationsRequest request, CancellationToken cancellationToken = default)
{
var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false);
- var resultAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
- return response.DeserializeResponse(resultAsString, OpenAIClient.JsonSerializationOptions);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
}
}
}
diff --git a/OpenAI-DotNet/OpenAI-DotNet.csproj b/OpenAI-DotNet/OpenAI-DotNet.csproj
index d05ef468..3e0733df 100644
--- a/OpenAI-DotNet/OpenAI-DotNet.csproj
+++ b/OpenAI-DotNet/OpenAI-DotNet.csproj
@@ -5,10 +5,11 @@
false
Stephen Hodgson
OpenAI-DotNet
- A simple C# .NET client library for OpenAI to use though their RESTful API. Independently developed, this is not an official library and I am not affiliated with OpenAI. An OpenAI API account is required.
+ A simple C# .NET client library for OpenAI to use though their RESTful API.
+Independently developed, this is not an official library and I am not affiliated with OpenAI.
+An OpenAI API account is required.
Forked from [OpenAI-API-dotnet](https://github.com/OkGoDoIt/OpenAI-API-dotnet).
-
More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet-api).
2023
@@ -17,8 +18,22 @@ More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet-
https://github.com/RageAgainstThePixel/OpenAI-DotNet
OpenAI, AI, ML, API, gpt-4, gpt-3.5-tubo, gpt-3, chatGPT, chat-gpt, gpt-2, gpt, dall-e-2, dall-e-3
OpenAI API
- 7.2.3
- Version 7.2.3
+ 7.3.0
+ Version 7.3.0
+- Added AgentsEndpoint
+- Added ThreadsEndpoint
+- Updated ImagesEndpoint return types to ImageResult list
+- Updated FilesEndpoint.ListFilesAsync with optional purpose filter query parameter.
+- Refactored list responses with a more generic ListQuery and ListResponse<TObject> pattern
+ - EventList -> ListResponse<EventResponse>
+ - FineTuneJobList -> ListResponse<FineTuneJobResponse>
+- Standardized names for timestamps to have suffix: UnixTimeSeconds
+- Standardized response class names (existing classes depreciated)
+ - FileData -> FileResponse
+ - CompletionResult -> CompletonResponse
+ - Event -> EventResponse
+ - FineTuneJob -> FineTuneJobResponse
+Version 7.2.3
- Added support for reading RateLimit information from the Headers
Version 7.2.2
- Fixed Image Generation for Azure
diff --git a/OpenAI-DotNet/OpenAIClient.cs b/OpenAI-DotNet/OpenAIClient.cs
index 501665f2..af69bb1e 100644
--- a/OpenAI-DotNet/OpenAIClient.cs
+++ b/OpenAI-DotNet/OpenAIClient.cs
@@ -1,4 +1,5 @@
-using OpenAI.Audio;
+using OpenAI.Assistants;
+using OpenAI.Audio;
using OpenAI.Chat;
using OpenAI.Completions;
using OpenAI.Edits;
@@ -9,6 +10,7 @@
using OpenAI.Images;
using OpenAI.Models;
using OpenAI.Moderations;
+using OpenAI.Threads;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -50,15 +52,17 @@ public OpenAIClient(OpenAIAuthentication openAIAuthentication = null, OpenAIClie
ModelsEndpoint = new ModelsEndpoint(this);
CompletionsEndpoint = new CompletionsEndpoint(this);
ChatEndpoint = new ChatEndpoint(this);
-#pragma warning disable CS0612 // Type or member is obsolete
+#pragma warning disable CS0618 // Type or member is obsolete
EditsEndpoint = new EditsEndpoint(this);
-#pragma warning restore CS0612 // Type or member is obsolete
+#pragma warning restore CS0618 // Type or member is obsolete
ImagesEndPoint = new ImagesEndpoint(this);
EmbeddingsEndpoint = new EmbeddingsEndpoint(this);
AudioEndpoint = new AudioEndpoint(this);
FilesEndpoint = new FilesEndpoint(this);
FineTuningEndpoint = new FineTuningEndpoint(this);
ModerationsEndpoint = new ModerationsEndpoint(this);
+ ThreadsEndpoint = new ThreadsEndpoint(this);
+ AssistantsEndpoint = new AssistantsEndpoint(this);
}
private HttpClient SetupClient(HttpClient client = null)
@@ -68,6 +72,7 @@ private HttpClient SetupClient(HttpClient client = null)
PooledConnectionLifetime = TimeSpan.FromMinutes(15)
});
client.DefaultRequestHeaders.Add("User-Agent", "OpenAI-DotNet");
+ client.DefaultRequestHeaders.Add("OpenAI-Beta", "assistants=v1");
if (!OpenAIClientSettings.BaseRequestUrlFormat.Contains(OpenAIClientSettings.AzureOpenAIDomain) &&
(string.IsNullOrWhiteSpace(OpenAIAuthentication.ApiKey) ||
@@ -102,13 +107,12 @@ private HttpClient SetupClient(HttpClient client = null)
///
/// The to use when making calls to the API.
///
- internal static readonly JsonSerializerOptions JsonSerializationOptions = new JsonSerializerOptions
+ internal static JsonSerializerOptions JsonSerializationOptions { get; private set; } = DefaultJsonSerializerOptions;
+
+ internal static JsonSerializerOptions DefaultJsonSerializerOptions { get; } = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- Converters =
- {
- new JsonStringEnumConverterFactory()
- }
+ Converters = { new JsonStringEnumConverterFactory() }
};
///
@@ -121,10 +125,28 @@ private HttpClient SetupClient(HttpClient client = null)
///
internal OpenAIClientSettings OpenAIClientSettings { get; }
+ private bool enableDebug;
+
///
- /// Enables or disables debugging for the whole client.
+ /// Enables or disables debugging for all endpoints.
///
- public bool EnableDebug { get; set; }
+ public bool EnableDebug
+ {
+ get => enableDebug;
+ set
+ {
+ enableDebug = value;
+
+ JsonSerializationOptions = enableDebug
+ ? DefaultJsonSerializerOptions
+ : new JsonSerializerOptions
+ {
+ WriteIndented = enableDebug,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ Converters = { new JsonStringEnumConverterFactory() }
+ };
+ }
+ }
///
/// List and describe the various models available in the API.
@@ -153,7 +175,7 @@ private HttpClient SetupClient(HttpClient client = null)
/// Given a prompt and an instruction, the model will return an edited version of the prompt.
///
///
- [Obsolete]
+ [Obsolete("Deprecated")]
public EditsEndpoint EditsEndpoint { get; }
///
@@ -182,7 +204,8 @@ private HttpClient SetupClient(HttpClient client = null)
///
/// Manage fine-tuning jobs to tailor a model to your specific training data.
- ///
+ ///
+ ///
///
public FineTuningEndpoint FineTuningEndpoint { get; }
@@ -192,5 +215,17 @@ private HttpClient SetupClient(HttpClient client = null)
///
///
public ModerationsEndpoint ModerationsEndpoint { get; }
+
+ ///
+ /// Build assistants that can call models and use tools to perform tasks.
+ ///
+ ///
+ public AssistantsEndpoint AssistantsEndpoint { get; }
+
+ ///
+ /// Create threads that assistants can interact with.
+ ///
+ ///
+ public ThreadsEndpoint ThreadsEndpoint { get; }
}
-}
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/Annotation.cs b/OpenAI-DotNet/Threads/Annotation.cs
new file mode 100644
index 00000000..204fc425
--- /dev/null
+++ b/OpenAI-DotNet/Threads/Annotation.cs
@@ -0,0 +1,45 @@
+using OpenAI.Extensions;
+using OpenAI.Threads;
+using System.Text.Json.Serialization;
+
+namespace OpenAI
+{
+ public sealed class Annotation
+ {
+ [JsonInclude]
+ [JsonPropertyName("type")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public AnnotationType Type { get; private set; }
+
+ ///
+ /// The text in the message content that needs to be replaced.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("text")]
+ public string Text { get; private set; }
+
+ ///
+ /// A citation within the message that points to a specific quote from a
+ /// specific File associated with the assistant or the message.
+ /// Generated when the assistant uses the 'retrieval' tool to search files.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("file_citation")]
+ public FileCitation FileCitation { get; private set; }
+
+ ///
+ /// A URL for the file that's generated when the assistant used the 'code_interpreter' tool to generate a file.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("file_path")]
+ public FilePath FilePath { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("start_index")]
+ public int StartIndex { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("end_index")]
+ public int EndIndex { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/AnnotationType.cs b/OpenAI-DotNet/Threads/AnnotationType.cs
new file mode 100644
index 00000000..2f180f0c
--- /dev/null
+++ b/OpenAI-DotNet/Threads/AnnotationType.cs
@@ -0,0 +1,12 @@
+using System.Runtime.Serialization;
+
+namespace OpenAI
+{
+ public enum AnnotationType
+ {
+ [EnumMember(Value = "file_citation")]
+ FileCitation,
+ [EnumMember(Value = "file_path")]
+ FilePath
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/CodeInterpreter.cs b/OpenAI-DotNet/Threads/CodeInterpreter.cs
new file mode 100644
index 00000000..260bc8fc
--- /dev/null
+++ b/OpenAI-DotNet/Threads/CodeInterpreter.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class CodeInterpreter
+ {
+ ///
+ /// The input to the Code Interpreter tool call.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("input")]
+ public string Input { get; private set; }
+
+ ///
+ /// The outputs from the Code Interpreter tool call.
+ /// Code Interpreter can output one or more items, including text (logs) or images (image).
+ /// Each of these are represented by a different object type.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("outputs")]
+ public IReadOnlyList Outputs { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/CodeInterpreterImageOutput.cs b/OpenAI-DotNet/Threads/CodeInterpreterImageOutput.cs
new file mode 100644
index 00000000..1bef8dd3
--- /dev/null
+++ b/OpenAI-DotNet/Threads/CodeInterpreterImageOutput.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class CodeInterpreterImageOutput
+ {
+ ///
+ /// The file ID of the image.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("file_id")]
+ public string FileId { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/CodeInterpreterOutputType.cs b/OpenAI-DotNet/Threads/CodeInterpreterOutputType.cs
new file mode 100644
index 00000000..9353318e
--- /dev/null
+++ b/OpenAI-DotNet/Threads/CodeInterpreterOutputType.cs
@@ -0,0 +1,12 @@
+using System.Runtime.Serialization;
+
+namespace OpenAI.Threads
+{
+ public enum CodeInterpreterOutputType
+ {
+ [EnumMember(Value = "logs")]
+ Logs,
+ [EnumMember(Value = "image")]
+ Image
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/CodeInterpreterOutputs.cs b/OpenAI-DotNet/Threads/CodeInterpreterOutputs.cs
new file mode 100644
index 00000000..77c5662e
--- /dev/null
+++ b/OpenAI-DotNet/Threads/CodeInterpreterOutputs.cs
@@ -0,0 +1,30 @@
+using OpenAI.Extensions;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class CodeInterpreterOutputs
+ {
+ ///
+ /// Output type
+ ///
+ [JsonInclude]
+ [JsonPropertyName("type")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public CodeInterpreterOutputType Type { get; private set; }
+
+ ///
+ /// The text output from the Code Interpreter tool call.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("logs")]
+ public string Logs { get; private set; }
+
+ ///
+ /// Code interpreter image output
+ ///
+ [JsonInclude]
+ [JsonPropertyName("image")]
+ public CodeInterpreterImageOutput Image { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/Content.cs b/OpenAI-DotNet/Threads/Content.cs
new file mode 100644
index 00000000..85002bed
--- /dev/null
+++ b/OpenAI-DotNet/Threads/Content.cs
@@ -0,0 +1,32 @@
+using OpenAI.Extensions;
+using System;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class Content
+ {
+ [JsonInclude]
+ [JsonPropertyName("type")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public ContentType Type { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("text")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public TextContent Text { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("image_url")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public ImageUrl ImageUrl { get; private set; }
+
+ public override string ToString()
+ => Type switch
+ {
+ ContentType.Text => Text.Value,
+ ContentType.ImageUrl => ImageUrl.Url,
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/ContentText.cs b/OpenAI-DotNet/Threads/ContentText.cs
new file mode 100644
index 00000000..2da02852
--- /dev/null
+++ b/OpenAI-DotNet/Threads/ContentText.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI
+{
+ public sealed class ContentText
+ {
+ public ContentText(string value) => Value = value;
+
+ ///
+ /// The data that makes up the text.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("value")]
+ public string Value { get; private set; }
+
+ ///
+ /// Annotations
+ ///
+ [JsonInclude]
+ [JsonPropertyName("annotations")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public IReadOnlyList Annotations { get; private set; }
+
+ public static implicit operator ContentText(string value) => new ContentText(value);
+
+ public static implicit operator string(ContentText text) => text?.ToString();
+
+ public override string ToString() => Value;
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/CreateMessageRequest.cs b/OpenAI-DotNet/Threads/CreateMessageRequest.cs
new file mode 100644
index 00000000..8aa8a03a
--- /dev/null
+++ b/OpenAI-DotNet/Threads/CreateMessageRequest.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class CreateMessageRequest
+ {
+ public static implicit operator CreateMessageRequest(string content)
+ => new CreateMessageRequest(content);
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ ///
+ ///
+ public CreateMessageRequest(string content, IEnumerable fieldIds = null, IReadOnlyDictionary metadata = null)
+ {
+ Role = Role.User;
+ Content = content;
+ FileIds = fieldIds?.ToList();
+ Metadata = metadata;
+ }
+
+ ///
+ /// The role of the entity that is creating the message.
+ ///
+ ///
+ /// Currently only user is supported.
+ ///
+ [JsonPropertyName("role")]
+ public Role Role { get; }
+
+ ///
+ /// The content of the message.
+ ///
+ [JsonPropertyName("content")]
+ public string Content { get; }
+
+ ///
+ /// A list of File IDs that the message should use. There can be a maximum of 10 files attached to a message.
+ /// Useful for tools like retrieval and code_interpreter that can access and use files.
+ ///
+ [JsonPropertyName("file_ids")]
+ public IReadOnlyList FileIds { get; }
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/CreateRunRequest.cs b/OpenAI-DotNet/Threads/CreateRunRequest.cs
new file mode 100644
index 00000000..88fba8e6
--- /dev/null
+++ b/OpenAI-DotNet/Threads/CreateRunRequest.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class CreateRunRequest
+ {
+ public CreateRunRequest(string assistantId, CreateRunRequest request)
+ : this(assistantId, request?.Model, request?.Instructions, request?.Tools, request?.Metadata)
+ {
+ }
+
+ public CreateRunRequest(string assistantId, string model = null, string instructions = null, IEnumerable tools = null, IReadOnlyDictionary metadata = null)
+ {
+ AssistantId = assistantId;
+ Model = model;
+ Instructions = instructions;
+ Tools = tools?.ToList();
+ Metadata = metadata;
+ }
+
+ ///
+ /// The ID of the assistant used for execution of this run.
+ ///
+ [JsonPropertyName("assistant_id")]
+ public string AssistantId { get; }
+
+ ///
+ /// The model that the assistant used for this run.
+ ///
+ [JsonPropertyName("model")]
+ public string Model { get; }
+
+ ///
+ /// The instructions that the assistant used for this run.
+ ///
+ [JsonPropertyName("instructions")]
+ public string Instructions { get; }
+
+ ///
+ /// The list of tools that the assistant used for this run.
+ ///
+ [JsonPropertyName("tools")]
+ public IReadOnlyList Tools { get; }
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/CreateThreadAndRunRequest.cs b/OpenAI-DotNet/Threads/CreateThreadAndRunRequest.cs
new file mode 100644
index 00000000..9ae286a2
--- /dev/null
+++ b/OpenAI-DotNet/Threads/CreateThreadAndRunRequest.cs
@@ -0,0 +1,89 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class CreateThreadAndRunRequest
+ {
+ public CreateThreadAndRunRequest(string assistantId, CreateThreadAndRunRequest request)
+ : this(assistantId, request?.Model, request?.Instructions, request?.Tools, request?.Metadata)
+ {
+ }
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ /// The ID of the assistant to use to execute this run.
+ ///
+ ///
+ /// The ID of the Model to be used to execute this run.
+ /// If a value is provided here, it will override the model associated with the assistant.
+ /// If not, the model associated with the assistant will be used.
+ ///
+ ///
+ /// Override the default system message of the assistant.
+ /// This is useful for modifying the behavior on a per-run basis.
+ ///
+ ///
+ /// Override the tools the assistant can use for this run.
+ /// This is useful for modifying the behavior on a per-run basis.
+ ///
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ ///
+ /// Optional, .
+ ///
+ public CreateThreadAndRunRequest(string assistantId, string model = null, string instructions = null, IReadOnlyList tools = null, IReadOnlyDictionary metadata = null, CreateThreadRequest createThreadRequest = null)
+ {
+ AssistantId = assistantId;
+ Model = model;
+ Instructions = instructions;
+ Tools = tools;
+ Metadata = metadata;
+ ThreadRequest = createThreadRequest;
+ }
+
+ ///
+ /// The ID of the assistant to use to execute this run.
+ ///
+ [JsonPropertyName("assistant_id")]
+ public string AssistantId { get; }
+
+ ///
+ /// The ID of the Model to be used to execute this run.
+ /// If a value is provided here, it will override the model associated with the assistant.
+ /// If not, the model associated with the assistant will be used.
+ ///
+ [JsonPropertyName("model")]
+ public string Model { get; }
+
+ ///
+ /// Override the default system message of the assistant.
+ /// This is useful for modifying the behavior on a per-run basis.
+ ///
+ [JsonPropertyName("instructions")]
+ public string Instructions { get; }
+
+ ///
+ /// Override the tools the assistant can use for this run.
+ /// This is useful for modifying the behavior on a per-run basis.
+ ///
+ [JsonPropertyName("tools")]
+ public IReadOnlyList Tools { get; }
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; }
+
+ [JsonPropertyName("thread")]
+ public CreateThreadRequest ThreadRequest { get; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/CreateThreadRequest.cs b/OpenAI-DotNet/Threads/CreateThreadRequest.cs
new file mode 100644
index 00000000..101dcd7c
--- /dev/null
+++ b/OpenAI-DotNet/Threads/CreateThreadRequest.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class CreateThreadRequest
+ {
+ ///
+ /// Constructor.
+ ///
+ ///
+ /// A list of messages to start the thread with.
+ ///
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ public CreateThreadRequest(IEnumerable messages = null, IReadOnlyDictionary metadata = null)
+ {
+ Messages = messages?.ToList();
+ Metadata = metadata;
+ }
+
+ ///
+ /// A list of messages to start the thread with.
+ ///
+ [JsonPropertyName("messages")]
+ public IReadOnlyList Messages { get; }
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; }
+
+ public static implicit operator CreateThreadRequest(string message) => new CreateThreadRequest(new[] { new Message(message) });
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/FileCitation.cs b/OpenAI-DotNet/Threads/FileCitation.cs
new file mode 100644
index 00000000..798616fb
--- /dev/null
+++ b/OpenAI-DotNet/Threads/FileCitation.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class FileCitation
+ {
+ ///
+ /// The ID of the specific File the citation is from.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("file_id")]
+ public string FileId { get; private set; }
+
+ ///
+ /// The specific quote in the file.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("file_id")]
+ public string Quote { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/FilePath.cs b/OpenAI-DotNet/Threads/FilePath.cs
new file mode 100644
index 00000000..77d0b768
--- /dev/null
+++ b/OpenAI-DotNet/Threads/FilePath.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class FilePath
+ {
+ ///
+ /// The ID of the file that was generated.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("file_id")]
+ public string FileId { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/FunctionCall.cs b/OpenAI-DotNet/Threads/FunctionCall.cs
new file mode 100644
index 00000000..df75e1bb
--- /dev/null
+++ b/OpenAI-DotNet/Threads/FunctionCall.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class FunctionCall
+ {
+ ///
+ /// The name of the function.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("name")]
+ public string Name { get; private set; }
+
+ ///
+ /// The arguments that the model expects you to pass to the function.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("arguments")]
+ public string Arguments { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/Message.cs b/OpenAI-DotNet/Threads/Message.cs
new file mode 100644
index 00000000..64017a46
--- /dev/null
+++ b/OpenAI-DotNet/Threads/Message.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class Message
+ {
+ public static implicit operator Message(string content) => new Message(content);
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ /// The content of the message.
+ ///
+ ///
+ /// A list of File IDs that the message should use.
+ /// There can be a maximum of 10 files attached to a message.
+ /// Useful for tools like 'retrieval' and 'code_interpreter' that can access and use files.
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ public Message(string content, IEnumerable fileIds = null, IReadOnlyDictionary metadata = null)
+ {
+ Role = Role.User;
+ Content = content;
+ FileIds = fileIds?.ToList();
+ Metadata = metadata;
+ }
+
+ ///
+ /// The role of the entity that is creating the message.
+ /// Currently only user is supported.
+ ///
+ [JsonPropertyName("role")]
+ public Role Role { get; }
+
+ ///
+ /// The content of the message.
+ ///
+ [JsonPropertyName("content")]
+ public string Content { get; }
+
+ ///
+ /// A list of File IDs that the message should use.
+ /// There can be a maximum of 10 files attached to a message.
+ /// Useful for tools like 'retrieval' and 'code_interpreter' that can access and use files.
+ ///
+ [JsonPropertyName("file_ids")]
+ public IReadOnlyList FileIds { get; }
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/MessageFileResponse.cs b/OpenAI-DotNet/Threads/MessageFileResponse.cs
new file mode 100644
index 00000000..242a390e
--- /dev/null
+++ b/OpenAI-DotNet/Threads/MessageFileResponse.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class MessageFileResponse : BaseResponse
+ {
+ ///
+ /// The identifier, which can be referenced in API endpoints.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ ///
+ /// The object type, which is always thread.message.file.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ ///
+ /// The Unix timestamp (in seconds) for when the message file was created.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
+
+ ///
+ /// The ID of the message that the File is attached to.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("message_id")]
+ public string MessageId { get; private set; }
+
+ public static implicit operator string(MessageFileResponse response) => response?.ToString();
+
+ public override string ToString() => Id;
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/MessageResponse.cs b/OpenAI-DotNet/Threads/MessageResponse.cs
new file mode 100644
index 00000000..bcffb26a
--- /dev/null
+++ b/OpenAI-DotNet/Threads/MessageResponse.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ ///
+ /// A message created by an Assistant or a user.
+ /// Messages can include text, images, and other files.
+ /// Messages stored as a list on the Thread.
+ ///
+ public sealed class MessageResponse : BaseResponse
+ {
+ ///
+ /// The identifier, which can be referenced in API endpoints.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ ///
+ /// The object type, which is always thread.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ ///
+ /// The Unix timestamp (in seconds) for when the thread was created.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
+
+ ///
+ /// The thread ID that this message belongs to.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("thread_id")]
+ public string ThreadId { get; private set; }
+
+ ///
+ /// The entity that produced the message. One of user or assistant.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("role")]
+ public Role Role { get; private set; }
+
+ ///
+ /// The content of the message in array of text and/or images.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("content")]
+ public IReadOnlyList Content { get; private set; }
+
+ ///
+ /// If applicable, the ID of the assistant that authored this message.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("assistant_id")]
+ public string AssistantId { get; private set; }
+
+ ///
+ /// If applicable, the ID of the run associated with the authoring of this message.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("run_id")]
+ public string RunId { get; private set; }
+
+ ///
+ /// A list of file IDs that the assistant should use.
+ /// Useful for tools like 'retrieval' and 'code_interpreter' that can access files.
+ /// A maximum of 10 files can be attached to a message.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("file_ids")]
+ public IReadOnlyList FileIds { get; private set; }
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; private set; }
+
+ public static implicit operator string(MessageResponse message) => message?.ToString();
+
+ public override string ToString() => Id;
+
+ ///
+ /// Formats all of the items into a single string,
+ /// putting each item on a new line.
+ ///
+ /// of all .
+ public string PrintContent() => string.Join("\n", Content.Select(content => content.ToString()));
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/RequiredAction.cs b/OpenAI-DotNet/Threads/RequiredAction.cs
new file mode 100644
index 00000000..3025d684
--- /dev/null
+++ b/OpenAI-DotNet/Threads/RequiredAction.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class RequiredAction
+ {
+ [JsonInclude]
+ [JsonPropertyName("type")]
+ public string Type { get; private set; }
+
+ ///
+ /// Details on the tool outputs needed for this run to continue.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("submit_tool_outputs")]
+ public SubmitToolOutputs SubmitToolOutputs { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/RunLastError.cs b/OpenAI-DotNet/Threads/RunLastError.cs
new file mode 100644
index 00000000..b529a48b
--- /dev/null
+++ b/OpenAI-DotNet/Threads/RunLastError.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class RunLastError
+ {
+ ///
+ /// One of server_error or rate_limit_exceeded.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("code")]
+ public string Code { get; private set; }
+
+ ///
+ /// A human-readable description of the error.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("message")]
+ public string Message { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/RunResponse.cs b/OpenAI-DotNet/Threads/RunResponse.cs
new file mode 100644
index 00000000..b627fa2e
--- /dev/null
+++ b/OpenAI-DotNet/Threads/RunResponse.cs
@@ -0,0 +1,183 @@
+using OpenAI.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ ///
+ /// An invocation of an Assistant on a Thread.
+ /// The Assistant uses it�s configuration and the Thread�s Messages to perform tasks by calling models and tools.
+ /// As part of a Run, the Assistant appends Messages to the Thread.
+ ///
+ public sealed class RunResponse : BaseResponse
+ {
+ ///
+ /// The identifier, which can be referenced in API endpoints.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ ///
+ /// The object type, which is always run.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ ///
+ /// The thread ID that this run belongs to.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("thread_id")]
+ public string ThreadId { get; private set; }
+
+ ///
+ /// The ID of the assistant used for execution of this run.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("assistant_id")]
+ public string AssistantId { get; private set; }
+
+ ///
+ /// The status of the run.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("status")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public RunStatus Status { get; private set; }
+
+ ///
+ /// Details on the action required to continue the run.
+ /// Will be null if no action is required.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("required_action")]
+ public RequiredAction RequiredAction { get; private set; }
+
+ ///
+ /// The Last error Associated with this run.
+ /// Will be null if there are no errors.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("last_error")]
+ public RunLastError LastError { get; private set; }
+
+ ///
+ /// The Unix timestamp (in seconds) for when the thread was created.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run will expire.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("expires_at")]
+ public int? ExpiresAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? ExpiresAt
+ => ExpiresAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(ExpiresAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run was started.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("started_at")]
+ public int? StartedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? StartedAt
+ => StartedAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(StartedAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run was cancelled.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("cancelled_at")]
+ public int? CancelledAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? CancelledAt
+ => CancelledAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(CancelledAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run failed.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("failed_at")]
+ public int? FailedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? FailedAt
+ => FailedAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(FailedAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run was completed.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("completed_at")]
+ public int? CompletedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? CompletedAt
+ => CompletedAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(CompletedAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// The model that the assistant used for this run.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("model")]
+ public string Model { get; private set; }
+
+ ///
+ /// The instructions that the assistant used for this run.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("instructions")]
+ public string Instructions { get; private set; }
+
+ ///
+ /// The list of tools that the assistant used for this run.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("tools")]
+ public IReadOnlyList Tools { get; private set; }
+
+ ///
+ /// The list of File IDs the assistant used for this run.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("file_ids")]
+ public IReadOnlyList FileIds { get; private set; }
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; private set; }
+
+ public static implicit operator string(RunResponse run) => run?.ToString();
+
+ public override string ToString() => Id;
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/RunStatus.cs b/OpenAI-DotNet/Threads/RunStatus.cs
new file mode 100644
index 00000000..da50011d
--- /dev/null
+++ b/OpenAI-DotNet/Threads/RunStatus.cs
@@ -0,0 +1,24 @@
+using System.Runtime.Serialization;
+
+namespace OpenAI.Threads
+{
+ public enum RunStatus
+ {
+ [EnumMember(Value = "queued")]
+ Queued,
+ [EnumMember(Value = "in_progress")]
+ InProgress,
+ [EnumMember(Value = "requires_action")]
+ RequiresAction,
+ [EnumMember(Value = "cancelling")]
+ Cancelling,
+ [EnumMember(Value = "cancelled")]
+ Cancelled,
+ [EnumMember(Value = "failed")]
+ Failed,
+ [EnumMember(Value = "completed")]
+ Completed,
+ [EnumMember(Value = "expired")]
+ Expired
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/RunStepMessageCreation.cs b/OpenAI-DotNet/Threads/RunStepMessageCreation.cs
new file mode 100644
index 00000000..a3697097
--- /dev/null
+++ b/OpenAI-DotNet/Threads/RunStepMessageCreation.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class RunStepMessageCreation
+ {
+ ///
+ /// The ID of the message that was created by this run step.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("message_id")]
+ public string MessageId { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/RunStepResponse.cs b/OpenAI-DotNet/Threads/RunStepResponse.cs
new file mode 100644
index 00000000..c9359d1a
--- /dev/null
+++ b/OpenAI-DotNet/Threads/RunStepResponse.cs
@@ -0,0 +1,154 @@
+using OpenAI.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ ///
+ /// A detailed list of steps the Assistant took as part of a Run.
+ /// An Assistant can call tools or create Messages during it�s run.
+ /// Examining Run Steps allows you to introspect how the Assistant is getting to it�s final results.
+ ///
+ public sealed class RunStepResponse : BaseResponse
+ {
+ ///
+ /// The identifier of the run step, which can be referenced in API endpoints.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+ ///
+ /// The ID of the assistant associated with the run step.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("assistant_id")]
+ public string AssistantId { get; private set; }
+
+ ///
+ /// The ID of the thread that was run.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("thread_id")]
+ public string ThreadId { get; private set; }
+
+ ///
+ /// The ID of the run that this run step is a part of.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("run_id")]
+ public string RunId { get; private set; }
+
+ ///
+ /// The type of run step.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("type")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public RunStepType Type { get; private set; }
+
+ ///
+ /// The status of the run step.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("status")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public RunStatus Status { get; private set; }
+
+ ///
+ /// The details of the run step.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("step_details")]
+ public StepDetails StepDetails { get; private set; }
+
+ ///
+ /// The last error associated with this run step. Will be null if there are no errors.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("last_error")]
+ public RunLastError LastError { get; private set; }
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run step was created.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int? CreatedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? CreatedAt
+ => CreatedAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run step expired. A step is considered expired if the parent run is expired.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("expires_at")]
+ public int? ExpiresAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? ExpiresAt
+ => ExpiresAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(ExpiresAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run step was cancelled.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("cancelled_at")]
+ public int? CancelledAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? CancelledAt
+ => CancelledAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(CancelledAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run step failed.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("failed_at")]
+ public int? FailedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? FailedAt
+ => FailedAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(FailedAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// The Unix timestamp (in seconds) for when the run step completed.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("completed_at")]
+ public int? CompletedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime? CompletedAt
+ => CompletedAtUnixTimeSeconds.HasValue
+ ? DateTimeOffset.FromUnixTimeSeconds(CompletedAtUnixTimeSeconds.Value).DateTime
+ : null;
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; private set; }
+
+ public static implicit operator string(RunStepResponse runStep) => runStep?.ToString();
+
+ public override string ToString() => Id;
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/RunStepType.cs b/OpenAI-DotNet/Threads/RunStepType.cs
new file mode 100644
index 00000000..6ba91a26
--- /dev/null
+++ b/OpenAI-DotNet/Threads/RunStepType.cs
@@ -0,0 +1,12 @@
+using System.Runtime.Serialization;
+
+namespace OpenAI.Threads
+{
+ public enum RunStepType
+ {
+ [EnumMember(Value = "message_creation")]
+ MessageCreation,
+ [EnumMember(Value = "tool_calls")]
+ ToolCalls
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/StepDetails.cs b/OpenAI-DotNet/Threads/StepDetails.cs
new file mode 100644
index 00000000..32b3c684
--- /dev/null
+++ b/OpenAI-DotNet/Threads/StepDetails.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ ///
+ /// The details of the run step.
+ ///
+ public sealed class StepDetails
+ {
+ ///
+ /// Details of the message creation by the run step.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("message_creation")]
+ public RunStepMessageCreation MessageCreation { get; private set; }
+
+ ///
+ /// An array of tool calls the run step was involved in.
+ /// These can be associated with one of three types of tools: 'code_interpreter', 'retrieval', or 'function'.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("tool_calls")]
+ public IReadOnlyList ToolCalls { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/SubmitToolOutputs.cs b/OpenAI-DotNet/Threads/SubmitToolOutputs.cs
new file mode 100644
index 00000000..c496c3f7
--- /dev/null
+++ b/OpenAI-DotNet/Threads/SubmitToolOutputs.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class SubmitToolOutputs
+ {
+ ///
+ /// A list of the relevant tool calls.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("tool_calls")]
+ public IReadOnlyList ToolCalls { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/SubmitToolOutputsRequest.cs b/OpenAI-DotNet/Threads/SubmitToolOutputsRequest.cs
new file mode 100644
index 00000000..97fab3c0
--- /dev/null
+++ b/OpenAI-DotNet/Threads/SubmitToolOutputsRequest.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class SubmitToolOutputsRequest
+ {
+ ///
+ /// Tool output to be submitted.
+ ///
+ /// .
+ public SubmitToolOutputsRequest(ToolOutput toolOutput)
+ : this(new[] { toolOutput })
+ {
+ }
+
+ ///
+ /// A list of tools for which the outputs are being submitted.
+ ///
+ /// Collection of tools for which the outputs are being submitted.
+ public SubmitToolOutputsRequest(IEnumerable toolOutputs)
+ {
+ ToolOutputs = toolOutputs?.ToList();
+ }
+
+ ///
+ /// A list of tools for which the outputs are being submitted.
+ ///
+ [JsonPropertyName("tool_outputs")]
+ public IReadOnlyList ToolOutputs { get; }
+
+ public static implicit operator SubmitToolOutputsRequest(ToolOutput toolOutput) => new SubmitToolOutputsRequest(toolOutput);
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/TextContent.cs b/OpenAI-DotNet/Threads/TextContent.cs
new file mode 100644
index 00000000..1485e661
--- /dev/null
+++ b/OpenAI-DotNet/Threads/TextContent.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class TextContent
+ {
+ ///
+ /// The data that makes up the text.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("value")]
+ public string Value { get; private set; }
+
+ ///
+ /// Annotations
+ ///
+ [JsonInclude]
+ [JsonPropertyName("annotations")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public IReadOnlyList Annotations { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/ThreadExtensions.cs b/OpenAI-DotNet/Threads/ThreadExtensions.cs
new file mode 100644
index 00000000..8d84470f
--- /dev/null
+++ b/OpenAI-DotNet/Threads/ThreadExtensions.cs
@@ -0,0 +1,325 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OpenAI.Threads
+{
+ public static class ThreadExtensions
+ {
+ ///
+ /// Updates this thread with the latest snapshot from OpenAI.
+ ///
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task UpdateAsync(this ThreadResponse thread, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.RetrieveThreadAsync(thread, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Modify the thread.
+ ///
+ ///
+ /// Only the can be modified.
+ ///
+ /// .
+ /// The metadata to set on the thread.
+ /// Optional, .
+ /// .
+ public static async Task ModifyAsync(this ThreadResponse thread, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.ModifyThreadAsync(thread, metadata, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Deletes the thread.
+ ///
+ /// .
+ /// Optional, .
+ /// True, if the thread was successfully deleted.
+ public static async Task DeleteAsync(this ThreadResponse thread, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.DeleteThreadAsync(thread, cancellationToken).ConfigureAwait(false);
+
+ #region Messages
+
+ ///
+ /// Create a new message for this thread.
+ ///
+ /// .
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task CreateMessageAsync(this ThreadResponse thread, CreateMessageRequest request, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.CreateMessageAsync(thread.Id, request, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// List the messages associated to this thread.
+ ///
+ /// .
+ /// Optional, .
+ /// Optional, .
+ ///
+ public static async Task> ListMessagesAsync(this ThreadResponse thread, ListQuery query = null, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.ListMessagesAsync(thread.Id, query, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Retrieve a message.
+ ///
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task UpdateAsync(this MessageResponse message, CancellationToken cancellationToken = default)
+ => await message.Client.ThreadsEndpoint.RetrieveMessageAsync(message.ThreadId, message.Id, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Retrieve a message.
+ ///
+ /// .
+ /// The id of the message to get.
+ /// Optional, .
+ /// .
+ public static async Task RetrieveMessageAsync(this ThreadResponse thread, string messageId, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.RetrieveMessageAsync(thread.Id, messageId, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Modify a message.
+ ///
+ ///
+ /// Only the can be modified.
+ ///
+ /// .
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ /// Optional, .
+ /// .
+ public static async Task ModifyAsync(this MessageResponse message, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default)
+ => await message.Client.ThreadsEndpoint.ModifyMessageAsync(message, metadata, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Modifies a message.
+ ///
+ ///
+ /// Only the can be modified.
+ ///
+ /// .
+ /// The id of the message to modify.
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ /// Optional, .
+ /// .
+ public static async Task ModifyMessageAsync(this ThreadResponse thread, string messageId, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.ModifyMessageAsync(thread, messageId, metadata, cancellationToken).ConfigureAwait(false);
+
+ #endregion Messages
+
+ #region Files
+
+ ///
+ /// Returns a list of message files.
+ ///
+ /// .
+ /// The id of the message that the files belongs to.
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task> ListFilesAsync(this ThreadResponse thread, string messageId, ListQuery query = null, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.ListFilesAsync(thread.Id, messageId, query, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Returns a list of message files.
+ ///
+ /// .
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task> ListFilesAsync(this MessageResponse message, ListQuery query = null, CancellationToken cancellationToken = default)
+ => await message.Client.ThreadsEndpoint.ListFilesAsync(message.ThreadId, message.Id, query, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Retrieve message file.
+ ///
+ /// .
+ /// The id of the file being retrieved.
+ /// Optional, .
+ /// .
+ public static async Task RetrieveFileAsync(this MessageResponse message, string fileId, CancellationToken cancellationToken = default)
+ => await message.Client.ThreadsEndpoint.RetrieveFileAsync(message.ThreadId, message.Id, fileId, cancellationToken).ConfigureAwait(false);
+
+ // TODO 400 bad request errors. Likely OpenAI bug downloading message file content.
+ /////
+ ///// Downloads a message file content to local disk.
+ /////
+ ///// .
+ ///// The id of the file being retrieved.
+ ///// Directory to save the file content.
+ ///// Optional, delete cached file. Defaults to false.
+ ///// Optional, .
+ ///// Path to the downloaded file content.
+ //public static async Task DownloadFileContentAsync(this MessageResponse message, string fileId, string directory, bool deleteCachedFile = false, CancellationToken cancellationToken = default)
+ // => await message.Client.FilesEndpoint.DownloadFileAsync(fileId, directory, deleteCachedFile, cancellationToken).ConfigureAwait(false);
+
+ // TODO 400 bad request errors. Likely OpenAI bug downloading message file content.
+ /////
+ ///// Downloads a message file content to local disk.
+ /////
+ ///// .
+ ///// Directory to save the file content.
+ ///// Optional, delete cached file. Defaults to false.
+ ///// Optional, .
+ ///// Path to the downloaded file content.
+ //public static async Task DownloadContentAsync(this MessageFileResponse file, string directory, bool deleteCachedFile = false, CancellationToken cancellationToken = default)
+ // => await file.Client.FilesEndpoint.DownloadFileAsync(file.Id, directory, deleteCachedFile, cancellationToken).ConfigureAwait(false);
+
+ #endregion Files
+
+ #region Runs
+
+ ///
+ /// Create a run.
+ ///
+ /// .
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task CreateRunAsync(this ThreadResponse thread, CreateRunRequest request = null, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.CreateRunAsync(thread, request, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Create a run.
+ ///
+ /// .
+ /// Id of the assistant to use for the run.
+ /// Optional, .
+ /// .
+ public static async Task CreateRunAsync(this ThreadResponse thread, string assistantId, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.CreateRunAsync(thread, new CreateRunRequest(assistantId), cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Gets the thread associated to the .
+ ///
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task GetThreadAsync(this RunResponse run, CancellationToken cancellationToken = default)
+ => await run.Client.ThreadsEndpoint.RetrieveThreadAsync(run.ThreadId, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// List all of the runs associated to a thread.
+ ///
+ /// .
+ /// .
+ /// Optional, .
+ ///
+ public static async Task> ListRunsAsync(this ThreadResponse thread, ListQuery query = null, CancellationToken cancellationToken = default)
+ => await thread.Client.ThreadsEndpoint.ListRunsAsync(thread.Id, query, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Get the latest status of the .
+ ///
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task UpdateAsync(this RunResponse run, CancellationToken cancellationToken = default)
+ => await run.Client.ThreadsEndpoint.RetrieveRunAsync(run.ThreadId, run.Id, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Modifies a run.
+ ///
+ ///
+ /// Only the can be modified.
+ ///
+ /// to modify.
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ /// Optional, .
+ /// .
+ public static async Task ModifyAsync(this RunResponse run, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default)
+ => await run.Client.ThreadsEndpoint.ModifyRunAsync(run.ThreadId, run.Id, metadata, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Waits for to change.
+ ///
+ /// .
+ /// Optional, time in milliseconds to wait before polling status.
+ /// Optional, timeout in seconds to cancel polling.
Defaults to 30 seconds.
Set to -1 for indefinite.
+ /// Optional, .
+ /// .
+ public static async Task WaitForStatusChangeAsync(this RunResponse run, int? pollingInterval = null, int? timeout = null, CancellationToken cancellationToken = default)
+ {
+ using var cts = timeout is null or < 0 ? new CancellationTokenSource(TimeSpan.FromSeconds(timeout ?? 30)) : new CancellationTokenSource();
+ using var chainedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken);
+ RunResponse result;
+ do
+ {
+ result = await run.UpdateAsync(cancellationToken: chainedCts.Token).ConfigureAwait(false);
+ await Task.Delay(pollingInterval ?? 500, chainedCts.Token).ConfigureAwait(false);
+ } while (result.Status is RunStatus.Queued or RunStatus.InProgress or RunStatus.Cancelling);
+ return result;
+ }
+
+ ///
+ /// When a run has the status: "requires_action" and required_action.type is submit_tool_outputs,
+ /// this endpoint can be used to submit the outputs from the tool calls once they're all completed.
+ /// All outputs must be submitted in a single request.
+ ///
+ /// to submit outputs for.
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task SubmitToolOutputsAsync(this RunResponse run, SubmitToolOutputsRequest request, CancellationToken cancellationToken = default)
+ => await run.Client.ThreadsEndpoint.SubmitToolOutputsAsync(run.ThreadId, run.Id, request, cancellationToken);
+
+ ///
+ /// Returns a list of run steps belonging to a run.
+ ///
+ /// to list run steps for.
+ /// Optional, .
+ /// Optional, .
+ /// .
+ public static async Task> ListRunStepsAsync(this RunResponse run, ListQuery query = null, CancellationToken cancellationToken = default)
+ => await run.Client.ThreadsEndpoint.ListRunStepsAsync(run.ThreadId, run.Id, query, cancellationToken);
+
+ ///
+ /// Retrieves a run step.
+ ///
+ /// to retrieve step for.
+ /// Id of the run step.
+ /// Optional, .
+ /// .
+ public static async Task RetrieveRunStepAsync(this RunResponse run, string runStepId, CancellationToken cancellationToken = default)
+ => await run.Client.ThreadsEndpoint.RetrieveRunStepAsync(run.ThreadId, run.Id, runStepId, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Retrieves a run step.
+ ///
+ /// to retrieve.
+ /// Optional, .
+ /// .
+ public static async Task UpdateAsync(this RunStepResponse runStep, CancellationToken cancellationToken = default)
+ => await runStep.Client.ThreadsEndpoint.RetrieveRunStepAsync(runStep.ThreadId, runStep.RunId, runStep.Id, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Cancels a run that is .
+ ///
+ /// to cancel.
+ /// Optional, .
+ /// .
+ public static async Task CancelAsync(this RunResponse run, CancellationToken cancellationToken = default)
+ => await run.Client.ThreadsEndpoint.CancelRunAsync(run.ThreadId, run.Id, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Returns a list of messages for a given thread that the run belongs to.
+ ///
+ /// .
+ /// .
+ /// Optional, .
+ /// .
+ public static async Task> ListMessagesAsync(this RunResponse run, ListQuery query = null, CancellationToken cancellationToken = default)
+ => await run.Client.ThreadsEndpoint.ListMessagesAsync(run.ThreadId, query, cancellationToken);
+
+ #endregion Runs
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/ThreadResponse.cs b/OpenAI-DotNet/Threads/ThreadResponse.cs
new file mode 100644
index 00000000..b860abf1
--- /dev/null
+++ b/OpenAI-DotNet/Threads/ThreadResponse.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ ///
+ /// A conversation session between an Assistant and a user.
+ /// Threads store Messages and automatically handle truncation to fit content into a model�s context.
+ ///
+ public sealed class ThreadResponse : BaseResponse
+ {
+ ///
+ /// The identifier, which can be referenced in API endpoints.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ ///
+ /// The object type, which is always thread.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("object")]
+ public string Object { get; private set; }
+
+ ///
+ /// The Unix timestamp (in seconds) for when the thread was created.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("created_at")]
+ public int CreatedAtUnixTimeSeconds { get; private set; }
+
+ [JsonIgnore]
+ public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime;
+
+ ///
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("metadata")]
+ public IReadOnlyDictionary Metadata { get; private set; }
+
+ public static implicit operator string(ThreadResponse thread) => thread?.ToString();
+
+ public override string ToString() => Id;
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/ThreadsEndpoint.cs b/OpenAI-DotNet/Threads/ThreadsEndpoint.cs
new file mode 100644
index 00000000..c5f5fdca
--- /dev/null
+++ b/OpenAI-DotNet/Threads/ThreadsEndpoint.cs
@@ -0,0 +1,354 @@
+using OpenAI.Extensions;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OpenAI.Threads
+{
+ ///
+ /// Create threads that assistants can interact with.
+ ///
+ ///
+ public sealed class ThreadsEndpoint : BaseEndPoint
+ {
+ public ThreadsEndpoint(OpenAIClient api) : base(api) { }
+
+ protected override string Root => "threads";
+
+ ///
+ /// Create a thread.
+ ///
+ /// .
+ /// Optional, .
+ /// .
+ public async Task CreateThreadAsync(CreateThreadRequest request = null, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.PostAsync(GetUrl(), request != null ? JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug) : null, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Retrieves a thread.
+ ///
+ /// The id of the to retrieve.
+ /// Optional, .
+ /// .
+ public async Task RetrieveThreadAsync(string threadId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{threadId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Modifies a thread.
+ ///
+ ///
+ /// Only the can be modified.
+ ///
+ /// The id of the to modify.
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ /// Optional, .
+ /// .
+ public async Task ModifyThreadAsync(string threadId, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default)
+ {
+ var jsonContent = JsonSerializer.Serialize(new { metadata }, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl($"/{threadId}"), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Delete a thread.
+ ///
+ /// The id of the to delete.
+ /// Optional, .
+ /// True, if was successfully deleted.
+ public async Task DeleteThreadAsync(string threadId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.DeleteAsync(GetUrl($"/{threadId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false;
+ }
+
+ #region Messages
+
+ ///
+ /// Create a message.
+ ///
+ /// The id of the thread to create a message for.
+ ///
+ /// Optional, .
+ /// .
+ public async Task CreateMessageAsync(string threadId, CreateMessageRequest request, CancellationToken cancellationToken = default)
+ {
+ var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/messages"), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Returns a list of messages for a given thread.
+ ///
+ /// The id of the thread the messages belong to.
+ /// .
+ /// Optional, .
+ /// .
+ public async Task> ListMessagesAsync(string threadId, ListQuery query = null, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/messages", query), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize>(responseAsString, Api);
+ }
+
+ ///
+ /// Retrieve a message.
+ ///
+ /// The id of the thread to which this message belongs.
+ /// The id of the message to retrieve.
+ /// Optional, .
+ /// .
+ public async Task RetrieveMessageAsync(string threadId, string messageId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/messages/{messageId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Modifies a message.
+ ///
+ ///
+ /// Only the can be modified.
+ ///
+ /// to modify.
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ /// Optional, .
+ /// .
+ public async Task ModifyMessageAsync(MessageResponse message, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default)
+ => await ModifyMessageAsync(message.ThreadId, message.Id, metadata, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// Modifies a message.
+ ///
+ ///
+ /// Only the can be modified.
+ ///
+ /// The id of the thread to which this message belongs.
+ /// The id of the message to modify.
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ ///
+ /// Optional, .
+ /// .
+ public async Task ModifyMessageAsync(string threadId, string messageId, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default)
+ {
+ var jsonContent = JsonSerializer.Serialize(new { metadata }, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/messages/{messageId}"), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ #endregion Messages
+
+ #region Files
+
+ ///
+ /// Returns a list of message files.
+ ///
+ /// The id of the thread that the message and files belong to.
+ /// The id of the message that the files belongs to.
+ /// .
+ /// Optional, .
+ /// .
+ public async Task> ListFilesAsync(string threadId, string messageId, ListQuery query = null, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/messages/{messageId}/files", query), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize>(responseAsString, Api);
+ }
+
+ ///
+ /// Retrieve message file.
+ ///
+ /// The id of the thread to which the message and file belong.
+ /// The id of the message the file belongs to.
+ /// The id of the file being retrieved.
+ /// Optional, .
+ /// .
+ public async Task RetrieveFileAsync(string threadId, string messageId, string fileId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/messages/{messageId}/files/{fileId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ #endregion Files
+
+ #region Runs
+
+ ///
+ /// Returns a list of runs belonging to a thread.
+ ///
+ /// The id of the thread the run belongs to.
+ /// .
+ /// Optional, .
+ ///
+ public async Task> ListRunsAsync(string threadId, ListQuery query = null, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/runs", query), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize>(responseAsString, Api);
+ }
+
+ ///
+ /// Create a run.
+ ///
+ /// The id of the thread to run.
+ ///
+ /// Optional, .
+ /// .
+ public async Task CreateRunAsync(string threadId, CreateRunRequest request = null, CancellationToken cancellationToken = default)
+ {
+ if (request == null || string.IsNullOrWhiteSpace(request.AssistantId))
+ {
+ var assistant = await Api.AssistantsEndpoint.CreateAssistantAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+ request = new CreateRunRequest(assistant, request);
+ }
+
+ var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/runs"), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Create a thread and run it in one request.
+ ///
+ /// .
+ /// Optional, .
+ /// .
+ public async Task CreateThreadAndRunAsync(CreateThreadAndRunRequest request = null, CancellationToken cancellationToken = default)
+ {
+ if (request == null || string.IsNullOrWhiteSpace(request.AssistantId))
+ {
+ var assistant = await Api.AssistantsEndpoint.CreateAssistantAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+ request = new CreateThreadAndRunRequest(assistant, request);
+ }
+
+ var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl("/runs"), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Retrieves a run.
+ ///
+ /// The id of the thread that was run.
+ /// The id of the run to retrieve.
+ /// Optional, .
+ /// .
+ public async Task RetrieveRunAsync(string threadId, string runId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/runs/{runId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Modifies a run.
+ ///
+ ///
+ /// Only the can be modified.
+ ///
+ /// The id of the thread that was run.
+ /// The id of the to modify.
+ /// Set of 16 key-value pairs that can be attached to an object.
+ /// This can be useful for storing additional information about the object in a structured format.
+ /// Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ /// Optional, .
+ /// .
+ public async Task ModifyRunAsync(string threadId, string runId, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default)
+ {
+ var jsonContent = JsonSerializer.Serialize(new { metadata }, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/runs/{runId}"), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// When a run has the status: "requires_action" and required_action.type is submit_tool_outputs,
+ /// this endpoint can be used to submit the outputs from the tool calls once they're all completed.
+ /// All outputs must be submitted in a single request.
+ ///
+ /// The id of the thread to which this run belongs.
+ /// The id of the run that requires the tool output submission.
+ /// .
+ /// Optional, .
+ /// .
+ public async Task SubmitToolOutputsAsync(string threadId, string runId, SubmitToolOutputsRequest request, CancellationToken cancellationToken = default)
+ {
+ var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
+ var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/runs/{runId}/submit_tool_outputs"), jsonContent, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Returns a list of run steps belonging to a run.
+ ///
+ /// The id of the thread to which the run and run step belongs.
+ /// The id of the run to which the run step belongs.
+ /// Optional, .
+ /// Optional, .
+ /// .
+ public async Task> ListRunStepsAsync(string threadId, string runId, ListQuery query = null, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/runs/{runId}/steps", query), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize>(responseAsString, Api);
+ }
+
+ ///
+ /// Retrieves a run step.
+ ///
+ /// The id of the thread to which the run and run step belongs.
+ /// The id of the run to which the run step belongs.
+ /// The id of the run step to retrieve.
+ /// Optional, .
+ /// .
+ public async Task RetrieveRunStepAsync(string threadId, string runId, string stepId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/runs/{runId}/steps/{stepId}"), cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ ///
+ /// Cancels a run that is .
+ ///
+ /// The id of the thread to which this run belongs.
+ /// The id of the run to cancel.
+ /// Optional, .
+ /// .
+ public async Task CancelRunAsync(string threadId, string runId, CancellationToken cancellationToken = default)
+ {
+ var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/runs/{runId}/cancel"), content: null, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ return response.Deserialize(responseAsString, Api);
+ }
+
+ #endregion Runs
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/ToolCall.cs b/OpenAI-DotNet/Threads/ToolCall.cs
new file mode 100644
index 00000000..37dc4a0d
--- /dev/null
+++ b/OpenAI-DotNet/Threads/ToolCall.cs
@@ -0,0 +1,29 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ public sealed class ToolCall
+ {
+ ///
+ /// The ID of the tool call.
+ /// This ID must be referenced when you submit the tool outputs in using the Submit tool outputs to run endpoint.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("id")]
+ public string Id { get; private set; }
+
+ ///
+ /// The type of tool call the output is required for. For now, this is always 'function'.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("type")]
+ public string Type { get; private set; }
+
+ ///
+ /// The function definition.
+ ///
+ [JsonInclude]
+ [JsonPropertyName("function")]
+ public FunctionCall FunctionCall { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenAI-DotNet/Threads/ToolOutput.cs b/OpenAI-DotNet/Threads/ToolOutput.cs
new file mode 100644
index 00000000..a89f2e26
--- /dev/null
+++ b/OpenAI-DotNet/Threads/ToolOutput.cs
@@ -0,0 +1,38 @@
+using System.Text.Json.Serialization;
+
+namespace OpenAI.Threads
+{
+ ///
+ /// Tool function call output
+ ///
+ public sealed class ToolOutput
+ {
+ ///
+ /// Constructor.
+ ///
+ ///
+ /// The ID of the tool call in the within the the output is being submitted for.
+ ///
+ ///
+ /// The output of the tool call to be submitted to continue the run.
+ ///
+ [JsonConstructor]
+ public ToolOutput(string toolCallId, string output)
+ {
+ ToolCallId = toolCallId;
+ Output = output;
+ }
+
+ ///
+ /// The ID of the tool call in the within the the output is being submitted for.
+ ///
+ [JsonPropertyName("tool_call_id")]
+ public string ToolCallId { get; }
+
+ ///
+ /// The output of the tool call to be submitted to continue the run.
+ ///
+ [JsonPropertyName("output")]
+ public string Output { get; }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index f7c2c83d..fa292bad 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,11 @@
[![NuGet version (OpenAI-DotNet-Proxy)](https://img.shields.io/nuget/v/OpenAI-DotNet-Proxy.svg?label=OpenAI-DotNet-Proxy&logo=nuget)](https://www.nuget.org/packages/OpenAI-DotNet-Proxy/)
[![Nuget Publish](https://github.com/RageAgainstThePixel/OpenAI-DotNet/actions/workflows/Publish-Nuget.yml/badge.svg)](https://github.com/RageAgainstThePixel/OpenAI-DotNet/actions/workflows/Publish-Nuget.yml)
-A simple C# .NET client library for [OpenAI](https://openai.com/) to use though their RESTful API. Independently developed, this is not an official library and I am not affiliated with OpenAI. An OpenAI API account is required.
+A simple C# .NET client library for [OpenAI](https://openai.com/) to use though their RESTful API.
+Independently developed, this is not an official library and I am not affiliated with OpenAI.
+An OpenAI API account is required.
Forked from [OpenAI-API-dotnet](https://github.com/OkGoDoIt/OpenAI-API-dotnet).
-
More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet-api).
> This repository is available to transfer to the OpenAI organization if they so choose to accept it.
@@ -45,41 +46,76 @@ Install-Package OpenAI-DotNet
- [List Models](#list-models)
- [Retrieve Models](#retrieve-model)
- [Delete Fine Tuned Model](#delete-fine-tuned-model)
-- [Completions](#completions)
- - [Streaming](#completion-streaming)
+- [Assistants](#assistants) :new:
+ - [List Assistants](#list-assistants) :new:
+ - [Create Assistant](#create-assistant) :new:
+ - [Retrieve Assistant](#retrieve-assistant) :new:
+ - [Modify Assistant](#modify-assistant) :new:
+ - [Delete Assistant](#delete-assistant) :new:
+ - [List Assistant Files](#list-assistant-files) :new:
+ - [Attach File to Assistant](#attach-file-to-assistant) :new:
+ - [Upload File to Assistant](#upload-file-to-assistant) :new:
+ - [Retrieve File from Assistant](#retrieve-file-from-assistant) :new:
+ - [Remove File from Assistant](#remove-file-from-assistant) :new:
+ - [Delete File from Assistant](#delete-file-from-assistant) :new:
+- [Threads](#threads) :new:
+ - [Create Thread](#create-thread) :new:
+ - [Create Thread and Run](#create-thread-and-run) :new:
+ - [Retrieve Thread](#retrieve-thread) :new:
+ - [Modify Thread](#modify-thread) :new:
+ - [Delete Thread](#delete-thread) :new:
+ - [Thread Messages](#thread-messages) :new:
+ - [List Messages](#list-thread-messages) :new:
+ - [Create Message](#create-thread-message) :new:
+ - [Retrieve Message](#retrieve-thread-message) :new:
+ - [Modify Message](#modify-thread-message) :new:
+ - [Thread Message Files](#thread-message-files) :new:
+ - [List Message Files](#list-thread-message-files) :new:
+ - [Retrieve Message File](#retrieve-thread-message-file) :new:
+ - [Thread Runs](#thread-runs) :new:
+ - [List Runs](#list-thread-runs) :new:
+ - [Create Run](#create-thread-run) :new:
+ - [Retrieve Run](#retrieve-thread-run) :new:
+ - [Modify Run](#modify-thread-run) :new:
+ - [Submit Tool Outputs to Run](#thread-submit-tool-outputs-to-run) :new:
+ - [List Run Steps](#list-thread-run-steps) :new:
+ - [Retrieve Run Step](#retrieve-thread-run-step) :new:
+ - [Cancel Run](#cancel-thread-run) :new:
- [Chat](#chat)
- [Chat Completions](#chat-completions)
- [Streaming](#chat-streaming)
- [Tools](#chat-tools) :new:
- [Vision](#chat-vision) :new:
-- [Edits](#edits)
- - [Create Edit](#create-edit)
-- [Embeddings](#embeddings)
- - [Create Embedding](#create-embeddings)
- [Audio](#audio)
- [Create Speech](#create-speech)
- [Create Transcription](#create-transcription)
- [Create Translation](#create-translation)
-- [Images](#images)
- - [Create Image](#create-image)
- - [Edit Image](#edit-image)
- - [Create Image Variation](#create-image-variation)
-- [Files](#files)
- - [List Files](#list-files)
+- [Images](#images) :construction:
+ - [Create Image](#create-image) :construction:
+ - [Edit Image](#edit-image) :construction:
+ - [Create Image Variation](#create-image-variation) :construction:
+- [Files](#files) :construction:
+ - [List Files](#list-files) :construction:
- [Upload File](#upload-file)
- [Delete File](#delete-file)
- - [Retrieve File Info](#retrieve-file-info)
+ - [Retrieve File](#retrieve-file-info) :construction:
- [Download File Content](#download-file-content)
-- [Fine Tuning](#fine-tuning)
- - [Create Fine Tune Job](#create-fine-tune-job)
- - [List Fine Tune Jobs](#list-fine-tune-jobs)
- - [Retrieve Fine Tune Job Info](#retrieve-fine-tune-job-info)
+- [Fine Tuning](#fine-tuning) :construction:
+ - [Create Fine Tune Job](#create-fine-tune-job) :construction:
+ - [List Fine Tune Jobs](#list-fine-tune-jobs) :construction:
+ - [Retrieve Fine Tune Job Info](#retrieve-fine-tune-job-info) :construction:
- [Cancel Fine Tune Job](#cancel-fine-tune-job)
- - [List Fine Tune Job Events](#list-fine-tune-job-events)
+ - [List Fine Tune Job Events](#list-fine-tune-job-events) :construction:
+- [Embeddings](#embeddings)
+ - [Create Embedding](#create-embeddings)
+- [Completions](#completions) :construction:
+ - [Streaming](#completion-streaming) :construction:
- [Moderations](#moderations)
- [Create Moderation](#create-moderation)
+- ~~[Edits](#edits)~~ :warning: Deprecated
+ - ~~[Create Edit](#create-edit)~~ :warning: Deprecated
-### Authentication
+### [Authentication](https://platform.openai.com/docs/api-reference/authentication)
There are 3 ways to provide your API keys, in order of precedence:
@@ -152,7 +188,7 @@ Use your system's environment variables specify an api key and organization to u
var api = new OpenAIClient(OpenAIAuthentication.LoadFromEnv());
```
-### [Azure OpenAI](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/)
+### [Azure OpenAI](https://learn.microsoft.com/en-us/azure/cognitive-services/openai)
You can also choose to use Microsoft's Azure OpenAI deployments as well.
@@ -302,50 +338,495 @@ Delete a fine-tuned model. You must have the Owner role in your organization.
```csharp
var api = new OpenAIClient();
-var result = await api.ModelsEndpoint.DeleteFineTuneModelAsync("your-fine-tuned-model");
-Assert.IsTrue(result);
+var isDeleted = await api.ModelsEndpoint.DeleteFineTuneModelAsync("your-fine-tuned-model");
+Assert.IsTrue(isDeleted);
```
-### [Completions](https://platform.openai.com/docs/api-reference/completions)
+### [Assistants](https://platform.openai.com/docs/api-reference/assistants)
-Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position.
+> :warning: Beta Feature
-The Completions API is accessed via `OpenAIClient.CompletionsEndpoint`
+Build assistants that can call models and use tools to perform tasks.
+
+- [Assistants Guide](https://platform.openai.com/docs/assistants)
+- [OpenAI Assistants Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/Assistants_API_overview_python.ipynb)
+
+The Assistants API is accessed via `OpenAIClient.AssistantsEndpoint`
+
+#### [List Assistants](https://platform.openai.com/docs/api-reference/assistants/listAssistants)
+
+Returns a list of assistants.
```csharp
var api = new OpenAIClient();
-var result = await api.CompletionsEndpoint.CreateCompletionAsync("One Two Three One Two", temperature: 0.1, model: Model.Davinci);
-Console.WriteLine(result);
+var assistantsList = await OpenAIClient.AssistantsEndpoint.ListAssistantsAsync();
+
+foreach (var assistant in assistantsList.Items)
+{
+ Console.WriteLine($"{assistant} -> {assistant.CreatedAt}");
+}
```
-> To get the `CompletionResult` (which is mostly metadata), use its implicit string operator to get the text if all you want is the completion choice.
+#### [Create Assistant](https://platform.openai.com/docs/api-reference/assistants/createAssistant)
-#### Completion Streaming
+Create an assistant with a model and instructions.
-Streaming allows you to get results are they are generated, which can help your application feel more responsive, especially on slow models like Davinci.
+```csharp
+var api = new OpenAIClient();
+var request = new CreateAssistantRequest("gpt-3.5-turbo-1106");
+var assistant = await OpenAIClient.AssistantsEndpoint.CreateAssistantAsync(request);
+```
+
+#### [Retrieve Assistant](https://platform.openai.com/docs/api-reference/assistants/getAssistant)
+
+Retrieves an assistant.
+
+```csharp
+var api = new OpenAIClient();
+var assistant = await OpenAIClient.AssistantsEndpoint.RetrieveAssistantAsync("assistant-id");
+Console.WriteLine($"{assistant} -> {assistant.CreatedAt}");
+```
+
+#### [Modify Assistant](https://platform.openai.com/docs/api-reference/assistants/modifyAssistant)
+
+Modifies an assistant.
+
+```csharp
+var api = new OpenAIClient();
+var createRequest = new CreateAssistantRequest("gpt-3.5-turbo-1106");
+var assistant = await api.AssistantsEndpoint.CreateAssistantAsync(createRequest);
+var modifyRequest = new CreateAssistantRequest("gpt-4-1106-preview");
+var modifiedAssistant = await api.AssistantsEndpoint.ModifyAsync(assistant.Id, modifyRequest);
+// OR AssistantExtension for easier use!
+var modifiedAssistantEx = await assistant.ModifyAsync(modifyRequest);
+```
+
+#### [Delete Assistant](https://platform.openai.com/docs/api-reference/assistants/deleteAssistant)
+
+Delete an assistant.
+
+```csharp
+var api = new OpenAIClient();
+var isDeleted = await api.AssistantsEndpoint.DeleteAssistantAsync("assistant-id");
+// OR AssistantExtension for easier use!
+var isDeleted = await assistant.DeleteAsync();
+Assert.IsTrue(isDeleted);
+```
+
+#### [List Assistant Files](https://platform.openai.com/docs/api-reference/assistants/listAssistantFiles)
+
+Returns a list of assistant files.
+
+```csharp
+var api = new OpenAIClient();
+var filesList = await api.AssistantsEndpoint.ListFilesAsync("assistant-id");
+// OR AssistantExtension for easier use!
+var filesList = await assistant.ListFilesAsync();
+
+foreach (var file in filesList.Items)
+{
+ Console.WriteLine($"{file.AssistantId}'s file -> {file.Id}");
+}
+```
+
+#### [Attach File to Assistant](https://platform.openai.com/docs/api-reference/assistants/createAssistantFile)
+
+Create an assistant file by attaching a File to an assistant.
+
+```csharp
+var api = new OpenAIClient();
+var filePath = "assistant_test_2.txt";
+await File.WriteAllTextAsync(filePath, "Knowledge is power!");
+var fileUploadRequest = new FileUploadRequest(filePath, "assistant");
+var file = await api.FilesEndpoint.UploadFileAsync(fileUploadRequest);
+var assistantFile = await api.AssistantsEndpoint.AttachFileAsync("assistant-id", file.Id);
+// OR use extension method for convenience!
+var assistantFIle = await assistant.AttachFileAsync(file);
+```
+
+#### [Upload File to Assistant](#upload-file)
+
+Uploads ***and*** attaches a file to an assistant.
+
+> Assistant extension method, for extra convenience!
+
+```csharp
+var api = new OpenAIClient();
+var filePath = "assistant_test_2.txt";
+await File.WriteAllTextAsync(filePath, "Knowledge is power!");
+var assistantFile = await assistant.UploadFileAsync(filePath);
+```
+
+#### [Retrieve File from Assistant](https://platform.openai.com/docs/api-reference/assistants/getAssistantFile)
+
+Retrieves an AssistantFile.
+
+```csharp
+var api = new OpenAIClient();
+var assistantFile = await api.AssistantsEndpoint.RetrieveFileAsync("assistant-id", "file-id");
+// OR AssistantExtension for easier use!
+var assistantFile = await assistant.RetrieveFileAsync(fileId);
+Console.WriteLine($"{assistantFile.AssistantId}'s file -> {assistantFile.Id}");
+```
+
+#### [Remove File from Assistant](https://platform.openai.com/docs/api-reference/assistants/deleteAssistantFile)
+
+Remove a file from an assistant.
+
+> Note: The file will remain in your organization until [deleted with FileEndpoint](#delete-file).
+
+```csharp
+var api = new OpenAIClient();
+var isRemoved = await api.AssistantsEndpoint.RemoveFileAsync("assistant-id", "file-id");
+// OR use extension method for convenience!
+var isRemoved = await assistant.RemoveFileAsync("file-id");
+Assert.IsTrue(isRemoved);
+```
+
+#### [Delete File from Assistant](#delete-file)
+
+Removes a file from the assistant and then deletes the file from the organization.
+
+> Assistant extension method, for extra convenience!
+
+```csharp
+var api = new OpenAIClient();
+var isDeleted = await assistant.DeleteFileAsync("file-id");
+Assert.IsTrue(isDeleted);
+```
+
+### [Threads](https://platform.openai.com/docs/api-reference/threads)
+
+> :warning: Beta Feature
+
+Create Threads that [Assistants](#assistants) can interact with.
+
+The Threads API is accessed via `OpenAIClient.ThreadsEndpoint`
+
+#### [Create Thread](https://platform.openai.com/docs/api-reference/threads/createThread)
+
+Create a thread.
+
+```csharp
+var api = new OpenAIClient();
+var thread = await api.ThreadsEndpoint.CreateThreadAsync();
+Console.WriteLine($"Create thread {thread.Id} -> {thread.CreatedAt}");
+```
+
+#### [Create Thread and Run](https://platform.openai.com/docs/api-reference/runs/createThreadAndRun)
+
+Create a thread and run it in one request.
+
+> See also: [Thread Runs](#thread-runs)
+
+```csharp
+var api = new OpenAIClient();
+var assistant = await api.AssistantsEndpoint.CreateAssistantAsync(
+ 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"));
+var messages = new List { "I need to solve the equation `3x + 11 = 14`. Can you help me?" };
+var threadRequest = new CreateThreadRequest(messages);
+var run = await assistant.CreateThreadAndRunAsync(threadRequest);
+Console.WriteLine($"Created thread and run: {run.ThreadId} -> {run.Id} -> {run.CreatedAt}");
+```
+
+#### [Retrieve Thread](https://platform.openai.com/docs/api-reference/threads/getThread)
+
+Retrieves a thread.
+
+```csharp
+var api = new OpenAIClient();
+var thread = await api.ThreadsEndpoint.RetrieveThreadAsync("thread-id");
+// OR if you simply wish to get the latest state of a thread
+thread = await thread.UpdateAsync();
+Console.WriteLine($"Retrieve thread {thread.Id} -> {thread.CreatedAt}");
+```
+
+#### [Modify Thread](https://platform.openai.com/docs/api-reference/threads/modifyThread)
+
+Modifies a thread.
+
+> Note: Only the metadata can be modified.
+
+```csharp
+var api = new OpenAIClient();
+var thread = await api.ThreadsEndpoint.CreateThreadAsync();
+var metadata = new Dictionary
+{
+ { "key", "custom thread metadata" }
+}
+thread = await api.ThreadsEndpoint.ModifyThreadAsync(thread.Id, metadata);
+// OR use extension method for convenience!
+thread = await thread.ModifyAsync(metadata);
+Console.WriteLine($"Modify thread {thread.Id} -> {thread.Metadata["key"]}");
+```
+
+#### [Delete Thread](https://platform.openai.com/docs/api-reference/threads/deleteThread)
+
+Delete a thread.
+
+```csharp
+var api = new OpenAIClient();
+var isDeleted = await api.ThreadsEndpoint.DeleteThreadAsync("thread-id");
+// OR use extension method for convenience!
+var isDeleted = await thread.DeleteAsync();
+Assert.IsTrue(isDeleted);
+```
+
+#### [Thread Messages](https://platform.openai.com/docs/api-reference/messages)
+
+Create messages within threads.
+
+##### [List Thread Messages](https://platform.openai.com/docs/api-reference/messages/listMessages)
+
+Returns a list of messages for a given thread.
+
+```csharp
+var api = new OpenAIClient();
+var messageList = await api.ThreadsEndpoint.ListMessagesAsync("thread-id");
+// OR use extension method for convenience!
+var messageList = await thread.ListMessagesAsync();
+
+foreach (var message in messageList.Items)
+{
+ Console.WriteLine($"{message.Id}: {message.Role}: {message.PrintContent()}");
+}
+```
+
+##### [Create Thread Message](https://platform.openai.com/docs/api-reference/messages/createMessage)
+
+Create a message.
+
+```csharp
+var api = new OpenAIClient();
+var thread = await api.ThreadsEndpoint.CreateThreadAsync();
+var request = new CreateMessageRequest("Hello world!");
+var message = await api.ThreadsEndpoint.CreateMessageAsync(thread.Id, request);
+// OR use extension method for convenience!
+var message = await thread.CreateMessageAsync("Hello World!");
+Console.WriteLine($"{message.Id}: {message.Role}: {message.PrintContent()}");
+```
+
+##### [Retrieve Thread Message](https://platform.openai.com/docs/api-reference/messages/getMessage)
+
+Retrieve a message.
+
+```csharp
+var api = new OpenAIClient();
+var message = await api.ThreadsEndpoint.RetrieveMessageAsync("thread-id", "message-id");
+// OR use extension methods for convenience!
+var message = await thread.RetrieveMessageAsync("message-id");
+var message = await message.UpdateAsync();
+Console.WriteLine($"{message.Id}: {message.Role}: {message.PrintContent()}");
+```
+
+##### [Modify Thread Message](https://platform.openai.com/docs/api-reference/messages/modifyMessage)
+
+Modify a message.
+
+> Note: Only the message metadata can be modified.
+
+```csharp
+var api = new OpenAIClient();
+var metadata = new Dictionary
+{
+ { "key", "custom message metadata" }
+};
+var message = await api.ThreadsEndpoint.ModifyMessageAsync("thread-id", "message-id", metadata);
+// OR use extension method for convenience!
+var message = await message.ModifyAsync(metadata);
+Console.WriteLine($"Modify message metadata: {message.Id} -> {message.Metadata["key"]}");
+```
+
+##### Thread Message Files
+
+###### [List Thread Message Files](https://platform.openai.com/docs/api-reference/messages/listMessageFiles)
+
+Returns a list of message files.
+
+```csharp
+var api = new OpenAIClient();
+var fileList = await api.ThreadsEndpoint.ListFilesAsync("thread-id", "message-Id");
+// OR use extension method for convenience!
+var fileList = await thread.ListFilesAsync("message-id");
+var fileList = await message.ListFilesAsync();
+
+foreach (var file in fileList.Items)
+{
+ Console.WriteLine(file.Id);
+}
+```
+
+###### [Retrieve Thread Message File](https://platform.openai.com/docs/api-reference/messages/getMessageFile)
+
+Retrieves a message file.
+
+```csharp
+var api = new OpenAIClient();
+var file = await api.ThreadsEndpoint.RetrieveFileAsync("thread-id", "message-id", "file-id");
+// OR use extension method for convenience!
+var file = await message.RetrieveFileAsync();
+Console.WriteLine(file.Id);
+```
+
+#### [Thread Runs](https://platform.openai.com/docs/api-reference/runs)
+
+Represents an execution run on a thread.
+
+##### [List Thread Runs](https://platform.openai.com/docs/api-reference/runs/listRuns)
+
+Returns a list of runs belonging to a thread.
+
+```csharp
+var api = new OpenAIClient();
+var runList = await api.ThreadsEndpoint.ListRunsAsync("thread-id");
+// OR use extension method for convenience!
+var runList = await thread.ListRunsAsync();
+
+foreach (var run in runList.Items)
+{
+ Console.WriteLine($"[{run.Id}] {run.Status} | {run.CreatedAt}");
+}
+```
+
+##### [Create Thread Run](https://platform.openai.com/docs/api-reference/runs/createRun)
+
+Create a run.
```csharp
var api = new OpenAIClient();
+var assistant = await api.AssistantsEndpoint.CreateAssistantAsync(
+ 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"));
+var thread = await OpenAIClient.ThreadsEndpoint.CreateThreadAsync();
+var message = await thread.CreateMessageAsync("I need to solve the equation `3x + 11 = 14`. Can you help me?");
+var run = await thread.CreateRunAsync(assistant);
+Console.WriteLine($"[{run.Id}] {run.Status} | {run.CreatedAt}");
+```
+
+##### [Retrieve Thread Run](https://platform.openai.com/docs/api-reference/runs/getRun)
+
+Retrieves a run.
+
+```csharp
+var api = new OpenAIClient();
+var run = await api.ThreadsEndpoint.RetrieveRunAsync("thread-id", "run-id");
+// OR use extension method for convenience!
+var run = await thread.RetrieveRunAsync("run-id");
+var run = await run.UpdateAsync();
+Console.WriteLine($"[{run.Id}] {run.Status} | {run.CreatedAt}");
+```
+
+##### [Modify Thread Run](https://platform.openai.com/docs/api-reference/runs/modifyRun)
+
+Modifies a run.
+
+> Note: Only the metadata can be modified.
-await api.CompletionsEndpoint.StreamCompletionAsync(result =>
+```csharp
+var api = new OpenAIClient();
+var metadata = new Dictionary
{
- foreach (var choice in result.Completions)
+ { "key", "custom run metadata" }
+};
+var run = await api.ThreadsEndpoint.ModifyRunAsync("thread-id", "run-id", metadata);
+// OR use extension method for convenience!
+var run = await run.ModifyAsync(metadata);
+Console.WriteLine($"Modify run {run.Id} -> {run.Metadata["key"]}");
+```
+
+##### [Thread Submit Tool Outputs to Run](https://platform.openai.com/docs/api-reference/runs/submitToolOutputs)
+
+When a run has the status: `requires_action` and `required_action.type` is `submit_tool_outputs`, this endpoint can be used to submit the outputs from the tool calls once they're all completed. All outputs must be submitted in a single request.
+
+```csharp
+var api = new OpenAIClient();
+var function = new Function(
+ nameof(WeatherService.GetCurrentWeather),
+ "Get the current weather in a given location",
+ new JsonObject
{
- Console.WriteLine(choice);
- }
-}, "My name is Roger and I am a principal software engineer at Salesforce. This is my resume:", maxTokens: 200, temperature: 0.5, presencePenalty: 0.1, frequencyPenalty: 0.1, model: Model.Davinci);
+ ["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 api.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?");
+// waiting while run is Queued and InProgress
+run = await run.WaitForStatusChangeAsync();
+var toolCall = run.RequiredAction.SubmitToolOutputs.ToolCalls[0];
+Console.WriteLine($"tool call arguments: {toolCall.FunctionCall.Arguments}");
+var functionArgs = JsonSerializer.Deserialize(toolCall.FunctionCall.Arguments);
+var functionResult = WeatherService.GetCurrentWeather(functionArgs);
+var toolOutput = new ToolOutput(toolCall.Id, functionResult);
+run = await run.SubmitToolOutputsAsync(toolOutput);
+// waiting while run in Queued and InProgress
+run = await run.WaitForStatusChangeAsync();
+var messages = await run.ListMessagesAsync();
+
+foreach (var message in messages.Items.OrderBy(response => response.CreatedAt))
+{
+ Console.WriteLine($"{message.Role}: {message.PrintContent()}");
+}
```
-Or if using [`IAsyncEnumerable{T}`](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-5.0) ([C# 8.0+](https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8))
+##### [List Thread Run Steps](https://platform.openai.com/docs/api-reference/runs/listRunSteps)
+
+Returns a list of run steps belonging to a run.
```csharp
var api = new OpenAIClient();
-await foreach (var token in api.CompletionsEndpoint.StreamCompletionEnumerableAsync("My name is Roger and I am a principal software engineer at Salesforce. This is my resume:", maxTokens: 200, temperature: 0.5, presencePenalty: 0.1, frequencyPenalty: 0.1, model: Model.Davinci))
+var runStepList = await api.ThreadsEndpoint.ListRunStepsAsync("thread-id", "run-id");
+// OR use extension method for convenience!
+var runStepList = await run.ListRunStepsAsync();
+
+foreach (var runStep in runStepList.Items)
{
- Console.WriteLine(token);
+ Console.WriteLine($"[{runStep.Id}] {runStep.Status} {runStep.CreatedAt} -> {runStep.ExpiresAt}");
}
```
+##### [Retrieve Thread Run Step](https://platform.openai.com/docs/api-reference/runs/getRunStep)
+
+Retrieves a run step.
+
+```csharp
+var api = new OpenAIClient();
+var runStep = await api.ThreadsEndpoint.RetrieveRunStepAsync("thread-id", "run-id", "step-id");
+// OR use extension method for convenience!
+var runStep = await run.RetrieveRunStepAsync("step-id");
+var runStep = await runStep.UpdateAsync();
+Console.WriteLine($"[{runStep.Id}] {runStep.Status} {runStep.CreatedAt} -> {runStep.ExpiresAt}");
+```
+
+##### [Cancel Thread Run](https://platform.openai.com/docs/api-reference/runs/cancelRun)
+
+Cancels a run that is `in_progress`.
+
+```csharp
+var api = new OpenAIClient();
+var isCancelled = await api.ThreadsEndpoint.CancelRunAsync("thread-id", "run-id");
+// OR use extension method for convenience!
+var isCancelled = await run.CancelAsync();
+Assert.IsTrue(isCancelled);
+```
+
### [Chat](https://platform.openai.com/docs/api-reference/chat)
Given a chat conversation, the model will return a chat completion response.
@@ -365,12 +846,12 @@ var messages = new List
new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new Message(Role.User, "Where was it played?"),
};
-var chatRequest = new ChatRequest(messages, Model.GPT3_5_Turbo);
-var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
-Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content}");
+var chatRequest = new ChatRequest(messages, Model.GPT4);
+var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
+Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content}");
```
-##### [Chat Streaming](https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream)
+#### [Chat Streaming](https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream)
```csharp
var api = new OpenAIClient();
@@ -381,21 +862,16 @@ var messages = new List
new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new Message(Role.User, "Where was it played?"),
};
-var chatRequest = new ChatRequest(messages, Model.GPT3_5_Turbo, number: 2);
-await api.ChatEndpoint.StreamCompletionAsync(chatRequest, result =>
+var chatRequest = new ChatRequest(messages);
+var response = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
- foreach (var choice in result.Choices.Where(choice => !string.IsNullOrEmpty(choice.Delta?.Content)))
- {
- // Partial response content
- Console.WriteLine(choice.Delta.Content);
- }
-
- foreach (var choice in result.Choices.Where(choice => !string.IsNullOrEmpty(choice.Message?.Content)))
+ foreach (var choice in partialResponse.Choices.Where(choice => choice.Delta?.Content != null))
{
- // Completed response content
- Console.WriteLine($"{choice.Message.Role}: {choice.Message.Content}");
+ Console.Write(choice.Delta.Content);
}
});
+var choice = response.FirstChoice;
+Console.WriteLine($"[{choice.Index}] {choice.Message.Role}: {choice.Message.Content} | Finish Reason: {choice.FinishReason}");
```
Or if using [`IAsyncEnumerable{T}`](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-5.0) ([C# 8.0+](https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8))
@@ -409,24 +885,20 @@ var messages = new List
new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new Message(Role.User, "Where was it played?"),
};
-var chatRequest = new ChatRequest(messages, Model.GPT4); // gpt4 access required
-await foreach (var result in api.ChatEndpoint.StreamCompletionEnumerableAsync(chatRequest))
+var cumulativeDelta = string.Empty;
+var chatRequest = new ChatRequest(messages);
+await foreach (var partialResponse in OpenAIClient.ChatEndpoint.StreamCompletionEnumerableAsync(chatRequest))
{
- foreach (var choice in result.Choices.Where(choice => !string.IsNullOrEmpty(choice.Delta?.Content)))
+ foreach (var choice in partialResponse.Choices.Where(choice => choice.Delta?.Content != null))
{
- // Partial response content
- Console.WriteLine(choice.Delta.Content);
- }
-
- foreach (var choice in result.Choices.Where(choice => !string.IsNullOrEmpty(choice.Message?.Content)))
- {
- // Completed response content
- Console.WriteLine($"{choice.Message.Role}: {choice.Message.Content}");
+ cumulativeDelta += choice.Delta.Content;
}
}
+
+Console.WriteLine(cumulativeDelta);
```
-##### [Chat Tools](https://platform.openai.com/docs/guides/function-calling)
+#### [Chat Tools](https://platform.openai.com/docs/guides/function-calling)
```csharp
var api = new OpenAIClient();
@@ -468,32 +940,32 @@ var tools = new List
};
var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
-var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
-messages.Add(result.FirstChoice.Message);
+var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
+messages.Add(response.FirstChoice.Message);
-Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland");
messages.Add(locationMessage);
Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}");
chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
-result = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
+response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
-messages.Add(result.FirstChoice.Message);
+messages.Add(response.FirstChoice.Message);
-if (!string.IsNullOrEmpty(result.FirstChoice.Message.Content))
+if (!string.IsNullOrEmpty(response.FirstChoice.Message.Content))
{
- Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");
+ Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishReason}");
var unitMessage = new Message(Role.User, "celsius");
messages.Add(unitMessage);
Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}");
chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
- result = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
+ response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
}
-var usedTool = result.FirstChoice.Message.ToolCalls[0];
-Console.WriteLine($"{result.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}");
+var usedTool = response.FirstChoice.Message.ToolCalls[0];
+Console.WriteLine($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}");
Console.WriteLine($"{usedTool.Function.Arguments}");
var functionArgs = JsonSerializer.Deserialize(usedTool.Function.Arguments.ToString());
var functionResult = WeatherService.GetCurrentWeather(functionArgs);
@@ -511,7 +983,9 @@ Console.WriteLine($"{Role.Tool}: {functionResult}");
// Tool: The current weather in Glasgow, Scotland is 20 celsius
```
-##### [Chat Vision](https://platform.openai.com/docs/guides/vision)
+#### [Chat Vision](https://platform.openai.com/docs/guides/vision)
+
+> :warning: Beta Feature
```csharp
var api = new OpenAIClient();
@@ -525,45 +999,8 @@ var messages = new List
})
};
var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview");
-var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
-Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}");
-```
-
-### [Edits](https://platform.openai.com/docs/api-reference/edits)
-
-> Deprecated, and soon to be removed.
-
-Given a prompt and an instruction, the model will return an edited version of the prompt.
-
-The Edits API is accessed via `OpenAIClient.EditsEndpoint`
-
-#### [Create Edit](https://platform.openai.com/docs/api-reference/edits/create)
-
-Creates a new edit for the provided input, instruction, and parameters using the provided input and instruction.
-
-```csharp
-var api = new OpenAIClient();
-var request = new EditRequest("What day of the wek is it?", "Fix the spelling mistakes");
-var result = await api.EditsEndpoint.CreateEditAsync(request);
-Console.WriteLine(result);
-```
-
-### [Embeddings](https://platform.openai.com/docs/api-reference/embeddings)
-
-Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms.
-
-Related guide: [Embeddings](https://platform.openai.com/docs/guides/embeddings)
-
-The Edits API is accessed via `OpenAIClient.EmbeddingsEndpoint`
-
-#### [Create Embeddings](https://platform.openai.com/docs/api-reference/embeddings/create)
-
-Creates an embedding vector representing the input text.
-
-```csharp
-var api = new OpenAIClient();
-var result = await api.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter...", Models.Embedding_Ada_002);
-Console.WriteLine(result);
+var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
+Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishDetails}");
```
### [Audio](https://platform.openai.com/docs/api-reference/audio)
@@ -585,8 +1022,8 @@ async Task ChunkCallback(ReadOnlyMemory chunkCallback)
await Task.CompletedTask;
}
-var result = await api.AudioEndpoint.CreateSpeechAsync(request, ChunkCallback);
-await File.WriteAllBytesAsync(@"..\..\..\Assets\HelloWorld.mp3", result.ToArray());
+var response = await api.AudioEndpoint.CreateSpeechAsync(request, ChunkCallback);
+await File.WriteAllBytesAsync("../../../Assets/HelloWorld.mp3", response.ToArray());
```
#### [Create Transcription](https://platform.openai.com/docs/api-reference/audio/createTranscription)
@@ -596,8 +1033,8 @@ Transcribes audio into the input language.
```csharp
var api = new OpenAIClient();
var request = new AudioTranscriptionRequest(Path.GetFullPath(audioAssetPath), language: "en");
-var result = await api.AudioEndpoint.CreateTranscriptionAsync(request);
-Console.WriteLine(result);
+var response = await api.AudioEndpoint.CreateTranscriptionAsync(request);
+Console.WriteLine(response);
```
#### [Create Translation](https://platform.openai.com/docs/api-reference/audio/create)
@@ -607,8 +1044,8 @@ Translates audio into into English.
```csharp
var api = new OpenAIClient();
var request = new AudioTranslationRequest(Path.GetFullPath(audioAssetPath));
-var result = await api.AudioEndpoint.CreateTranslationAsync(request);
-Console.WriteLine(result);
+var response = await api.AudioEndpoint.CreateTranslationAsync(request);
+Console.WriteLine(response);
```
### [Images](https://platform.openai.com/docs/api-reference/images)
@@ -623,13 +1060,13 @@ Creates an image given a prompt.
```csharp
var api = new OpenAIClient();
-var request = new ImageGenerationRequest("A house riding a velociraptor", Models.Model.DallE_2);
-var results = await api.ImagesEndPoint.GenerateImageAsync(request);
+var request = new ImageGenerationRequest("A house riding a velociraptor", Models.Model.DallE_3);
+var imageResults = await api.ImagesEndPoint.GenerateImageAsync(request);
-foreach (var result in results)
+foreach (var image in imageResults)
{
- Console.WriteLine(result);
- // result == file://path/to/image.png or b64_string
+ Console.WriteLine(image);
+ // image == url or b64_string
}
```
@@ -640,12 +1077,12 @@ Creates an edited or extended image given an original image and a prompt.
```csharp
var api = new OpenAIClient();
var request = new ImageEditRequest(imageAssetPath, maskAssetPath, "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small);
-var results = await api.ImagesEndPoint.CreateImageEditAsync(request);
+var imageResults = await api.ImagesEndPoint.CreateImageEditAsync(request);
-foreach (var result in results)
+foreach (var image in imageResults)
{
- Console.WriteLine(result);
- // result == file://path/to/image.png or b64_string
+ Console.WriteLine(image);
+ // image == url or b64_string
}
```
@@ -656,12 +1093,12 @@ Creates a variation of a given image.
```csharp
var api = new OpenAIClient();
var request = new ImageVariationRequest(imageAssetPath, size: ImageSize.Small);
-var results = await api.ImagesEndPoint.CreateImageVariationAsync(request);
+var imageResults = await api.ImagesEndPoint.CreateImageVariationAsync(request);
-foreach (var result in results)
+foreach (var image in imageResults)
{
- Console.WriteLine(result);
- // result == file://path/to/image.png or b64_string
+ Console.WriteLine(image);
+ // image == url or b64_string
}
```
@@ -677,22 +1114,24 @@ Returns a list of files that belong to the user's organization.
```csharp
var api = new OpenAIClient();
-var files = await api.FilesEndpoint.ListFilesAsync();
+var fileList = await api.FilesEndpoint.ListFilesAsync();
-foreach (var file in files)
+foreach (var file in fileList)
{
Console.WriteLine($"{file.Id} -> {file.Object}: {file.FileName} | {file.Size} bytes");
}
```
-#### [Upload File](https://platform.openai.com/docs/api-reference/files/upload)
+#### [Upload File](https://platform.openai.com/docs/api-reference/files/create)
+
+Upload a file that can be used across various endpoints. The size of all the files uploaded by one organization can be up to 100 GB.
-Upload a file that contains document(s) to be used across various endpoints/features. Currently, the size of all the files uploaded by one organization can be up to 1 GB. Please contact us if you need to increase the storage limit.
+The size of individual files can be a maximum of 512 MB. See the Assistants Tools guide to learn more about the types of files supported. The Fine-tuning API only supports .jsonl files.
```csharp
var api = new OpenAIClient();
-var fileData = await api.FilesEndpoint.UploadFileAsync("path/to/your/file.jsonl", "fine-tune");
-Console.WriteLine(fileData.Id);
+var file = await api.FilesEndpoint.UploadFileAsync("path/to/your/file.jsonl", "fine-tune");
+Console.WriteLine(file.Id);
```
#### [Delete File](https://platform.openai.com/docs/api-reference/files/delete)
@@ -701,8 +1140,8 @@ Delete a file.
```csharp
var api = new OpenAIClient();
-var result = await api.FilesEndpoint.DeleteFileAsync(fileData);
-Assert.IsTrue(result);
+var isDeleted = await api.FilesEndpoint.DeleteFileAsync(fileId);
+Assert.IsTrue(isDeleted);
```
#### [Retrieve File Info](https://platform.openai.com/docs/api-reference/files/retrieve)
@@ -711,13 +1150,13 @@ Returns information about a specific file.
```csharp
var api = new OpenAIClient();
-var fileData = await GetFileInfoAsync(fileId);
-Console.WriteLine($"{fileData.Id} -> {fileData.Object}: {fileData.FileName} | {fileData.Size} bytes");
+var file = await GetFileInfoAsync(fileId);
+Console.WriteLine($"{file.Id} -> {file.Object}: {file.FileName} | {file.Size} bytes");
```
#### [Download File Content](https://platform.openai.com/docs/api-reference/files/retrieve-content)
-Downloads the specified file.
+Downloads the file content to the specified directory.
```csharp
var api = new OpenAIClient();
@@ -754,11 +1193,11 @@ List your organization's fine-tuning jobs.
```csharp
var api = new OpenAIClient();
-var list = await api.FineTuningEndpoint.ListJobsAsync();
+var jobList = await api.FineTuningEndpoint.ListJobsAsync();
-foreach (var job in list.Jobs)
+foreach (var job in jobList.Items.OrderByDescending(job => job.CreatedAt)))
{
- Console.WriteLine($"{job.Id} -> {job.Status}");
+ Console.WriteLine($"{job.Id} -> {job.CreatedAt} | {job.Status}");
}
```
@@ -769,7 +1208,7 @@ Gets info about the fine-tune job.
```csharp
var api = new OpenAIClient();
var job = await api.FineTuningEndpoint.GetJobInfoAsync(fineTuneJob);
-Console.WriteLine($"{job.Id} -> {job.Status}");
+Console.WriteLine($"{job.Id} -> {job.CreatedAt} | {job.Status}");
```
#### [Cancel Fine Tune Job](https://platform.openai.com/docs/api-reference/fine-tunes/cancel)
@@ -778,8 +1217,8 @@ Immediately cancel a fine-tune job.
```csharp
var api = new OpenAIClient();
-var result = await api.FineTuningEndpoint.CancelFineTuneJobAsync(fineTuneJob);
-Assert.IsTrue(result);
+var isCancelled = await api.FineTuningEndpoint.CancelFineTuneJobAsync(fineTuneJob);
+Assert.IsTrue(isCancelled);
```
#### [List Fine Tune Job Events](https://platform.openai.com/docs/api-reference/fine-tuning/list-events)
@@ -791,9 +1230,67 @@ var api = new OpenAIClient();
var eventList = await api.FineTuningEndpoint.ListJobEventsAsync(fineTuneJob);
Console.WriteLine($"{fineTuneJob.Id} -> status: {fineTuneJob.Status} | event count: {eventList.Events.Count}");
-foreach (var @event in eventList.Events.OrderByDescending(@event => @event.CreatedAt))
+foreach (var @event in eventList.Items.OrderByDescending(@event => @event.CreatedAt))
+{
+ Console.WriteLine($" {@event.CreatedAt} [{@event.Level}] {@event.Message}");
+}
+```
+
+### [Embeddings](https://platform.openai.com/docs/api-reference/embeddings)
+
+Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms.
+
+Related guide: [Embeddings](https://platform.openai.com/docs/guides/embeddings)
+
+The Edits API is accessed via `OpenAIClient.EmbeddingsEndpoint`
+
+#### [Create Embeddings](https://platform.openai.com/docs/api-reference/embeddings/create)
+
+Creates an embedding vector representing the input text.
+
+```csharp
+var api = new OpenAIClient();
+var response = await api.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter...", Models.Embedding_Ada_002);
+Console.WriteLine(response);
+```
+
+### [Completions](https://platform.openai.com/docs/api-reference/completions)
+
+Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position.
+
+The Completions API is accessed via `OpenAIClient.CompletionsEndpoint`
+
+```csharp
+var api = new OpenAIClient();
+var response = await api.CompletionsEndpoint.CreateCompletionAsync("One Two Three One Two", temperature: 0.1, model: Model.Davinci);
+Console.WriteLine(response);
+```
+
+> To get the `CompletionResponse` (which is mostly metadata), use its implicit string operator to get the text if all you want is the completion choice.
+
+#### Completion Streaming
+
+Streaming allows you to get results are they are generated, which can help your application feel more responsive, especially on slow models like Davinci.
+
+```csharp
+var api = new OpenAIClient();
+
+await api.CompletionsEndpoint.StreamCompletionAsync(response =>
+{
+ foreach (var choice in response.Completions)
+ {
+ Console.WriteLine(choice);
+ }
+}, "My name is Roger and I am a principal software engineer at Salesforce. This is my resume:", maxTokens: 200, temperature: 0.5, presencePenalty: 0.1, frequencyPenalty: 0.1, model: Model.Davinci);
+```
+
+Or if using [`IAsyncEnumerable{T}`](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-5.0) ([C# 8.0+](https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8))
+
+```csharp
+var api = new OpenAIClient();
+await foreach (var partialResponse in api.CompletionsEndpoint.StreamCompletionEnumerableAsync("My name is Roger and I am a principal software engineer at Salesforce. This is my resume:", maxTokens: 200, temperature: 0.5, presencePenalty: 0.1, frequencyPenalty: 0.1, model: Model.Davinci))
{
- Console.WriteLine($" {@event.CreatedAt} [{@event.Level}] {@event.Message.Replace("\n", " ")}");
+ Console.WriteLine(partialResponse);
}
```
@@ -811,14 +1308,41 @@ Classifies if text violates OpenAI's Content Policy.
```csharp
var api = new OpenAIClient();
-var response = await api.ModerationsEndpoint.GetModerationAsync("I want to kill them.");
-Assert.IsTrue(response);
+var isViolation = await api.ModerationsEndpoint.GetModerationAsync("I want to kill them.");
+Assert.IsTrue(isViolation);
+```
+
+Additionally you can also get the scores of a given input.
+
+```csharp
+var response = await OpenAIClient.ModerationsEndpoint.CreateModerationAsync(new ModerationsRequest("I love you"));
+Assert.IsNotNull(response);
+Console.WriteLine(response.Results?[0]?.Scores?.ToString());
```
---
+### [Edits](https://platform.openai.com/docs/api-reference/edits)
+
+> Deprecated, and soon to be removed.
+
+Given a prompt and an instruction, the model will return an edited version of the prompt.
+
+The Edits API is accessed via `OpenAIClient.EditsEndpoint`
+
+#### [Create Edit](https://platform.openai.com/docs/api-reference/edits/create)
+
+Creates a new edit for the provided input, instruction, and parameters using the provided input and instruction.
+
+```csharp
+var api = new OpenAIClient();
+var request = new EditRequest("What day of the wek is it?", "Fix the spelling mistakes");
+var response = await api.EditsEndpoint.CreateEditAsync(request);
+Console.WriteLine(response);
+```
+
## License
![CC-0 Public Domain](https://licensebuttons.net/p/zero/1.0/88x31.png)
-This library is licensed CC-0, in the public domain. You can use it for whatever you want, publicly or privately, without worrying about permission or licensing or whatever. It's just a wrapper around the OpenAI API, so you still need to get access to OpenAI from them directly. I am not affiliated with OpenAI and this library is not endorsed by them, I just have beta access and wanted to make a C# library to access it more easily. Hopefully others find this useful as well. Feel free to open a PR if there's anything you want to contribute.
\ No newline at end of file
+This library is licensed CC-0, in the public domain. You can use it for whatever you want, publicly or privately, without worrying about permission or licensing or whatever. It's just a wrapper around the OpenAI API, so you still need to get access to OpenAI from them directly. I am not affiliated with OpenAI and this library is not endorsed by them, I just have beta access and wanted to make a C# library to access it more easily. Hopefully others find this useful as well. Feel free to open a PR if there's anything you want to contribute.