Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/live-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Run live tests
run: dotnet test ./tests/OpenAI.Tests.csproj
--configuration Release
--filter="TestCategory!=Smoke&TestCategory!=Assistants&TestCategory!=StoredChat&TestCategory!=Images&TestCategory!=Uploads&TestCategory!=Moderations&TestCategory!=FineTuning&TestCategory!=Conversation&TestCategory!=Manual"
--filter="TestCategory!=Smoke&TestCategory!=Assistants&TestCategory!=StoredChat&TestCategory!=Images&TestCategory!=Uploads&TestCategory!=Moderations&TestCategory!=FineTuning&TestCategory!=Conversation&TestCategory!=MCP&TestCategory!=Manual"
--logger "trx;LogFilePrefix=live"
--results-directory ${{github.workspace}}/artifacts/test-results
${{ env.version_suffix_args}}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
- name: Run Live Tests
run: dotnet test ./tests/OpenAI.Tests.csproj
--configuration Release
--filter="TestCategory!=Smoke&TestCategory!=Assistants&TestCategory!=StoredChat&TestCategory!=Images&TestCategory!=Uploads&TestCategory!=Moderations&TestCategory!=FineTuning&TestCategory!=Conversation&TestCategory!=Manual"
--filter="TestCategory!=Smoke&TestCategory!=Assistants&TestCategory!=StoredChat&TestCategory!=Images&TestCategory!=Uploads&TestCategory!=Moderations&TestCategory!=FineTuning&TestCategory!=Conversation&TestCategory!=MCP&TestCategory!=Manual"
--logger "trx;LogFilePrefix=live"
--results-directory ${{ github.workspace }}/artifacts/test-results
${{ env.version_suffix_args }}
Expand Down
188 changes: 186 additions & 2 deletions api/OpenAI.net8.0.cs

Large diffs are not rendered by default.

170 changes: 168 additions & 2 deletions api/OpenAI.netstandard2.0.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/Responses/Example01_SimpleResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public partial class ResponseExamples
[Test]
public void Example01_SimpleResponse()
{
OpenAIResponseClient client = new(model: "gpt-4o", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
OpenAIResponseClient client = new(model: "gpt-5", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

OpenAIResponse response = client.CreateResponse("Say 'this is a test.'");

Expand Down
2 changes: 1 addition & 1 deletion examples/Responses/Example01_SimpleResponseAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public partial class ResponseExamples
[Test]
public async Task Example01_SimpleResponseAsync()
{
OpenAIResponseClient client = new(model: "gpt-4o", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
OpenAIResponseClient client = new(model: "gpt-5", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

OpenAIResponse response = await client.CreateResponseAsync("Say 'this is a test.'");

Expand Down
2 changes: 1 addition & 1 deletion examples/Responses/Example02_SimpleResponseStreaming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public partial class ResponseExamples
[Test]
public void Example02_SimpleResponseStreaming()
{
OpenAIResponseClient client = new(model: "gpt-4o-mini", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
OpenAIResponseClient client = new(model: "gpt-5", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

CollectionResult<StreamingResponseUpdate> responseUpdates = client.CreateResponseStreaming("Say 'this is a test.'");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public partial class ResponseExamples
[Test]
public async Task Example02_SimpleResponseStreamingAsync()
{
OpenAIResponseClient client = new(model: "gpt-4o-mini", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
OpenAIResponseClient client = new(model: "gpt-5", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

AsyncCollectionResult<StreamingResponseUpdate> responseUpdates = client.CreateResponseStreamingAsync("Say 'this is a test.'");

Expand Down
165 changes: 165 additions & 0 deletions examples/Responses/Example03_FunctionCalling.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using NUnit.Framework;
using OpenAI.Responses;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

namespace OpenAI.Examples;

// This example uses experimental APIs which are subject to change. To use experimental APIs,
// please acknowledge their experimental status by suppressing the corresponding warning.
#pragma warning disable OPENAI001

public partial class ResponseExamples
{
#region
private static string GetCurrentLocation()
{
// Call the location API here.
return "San Francisco";
}

private static string GetCurrentWeather(string location, string unit = "celsius")
{
// Call the weather API here.
return $"31 {unit}";
}
#endregion

#region
private static readonly FunctionTool getCurrentLocationTool = ResponseTool.CreateFunctionTool(
functionName: nameof(GetCurrentLocation),
functionDescription: "Get the user's current location",
functionParameters: null,
strictModeEnabled: false
);

private static readonly FunctionTool getCurrentWeatherTool = ResponseTool.CreateFunctionTool(
functionName: nameof(GetCurrentWeather),
functionDescription: "Get the current weather in a given location",
functionParameters: BinaryData.FromBytes("""
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. Boston, MA"
},
"unit": {
"type": "string",
"enum": [ "celsius", "fahrenheit" ],
"description": "The temperature unit to use. Infer this from the specified location."
}
},
"required": [ "location" ]
}
"""u8.ToArray()),
strictModeEnabled: false
);
#endregion

[Test]
public void Example03_FunctionCalling()
{
OpenAIResponseClient client = new("gpt-5", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

List<ResponseItem> inputItems =
[
ResponseItem.CreateUserMessageItem("What's the weather like today for my current location?"),
];

ResponseCreationOptions options = new()
{
Tools = { getCurrentLocationTool, getCurrentWeatherTool },
};

PrintMessageItems(inputItems.OfType<MessageResponseItem>());

bool requiresAction;

do
{
requiresAction = false;
OpenAIResponse response = client.CreateResponse(inputItems, options);

inputItems.AddRange(response.OutputItems);

foreach (ResponseItem outputItem in response.OutputItems)
{
if (outputItem is FunctionCallResponseItem functionCall)
{
switch (functionCall.FunctionName)
{
case nameof(GetCurrentLocation):
{
string functionOutput = GetCurrentLocation();
inputItems.Add(new FunctionCallOutputResponseItem(functionCall.CallId, functionOutput));
break;
}

case nameof(GetCurrentWeather):
{
// The arguments that the model wants to use to call the function are specified as a
// stringified JSON object based on the schema defined in the tool definition. Note that
// the model may hallucinate arguments too. Consequently, it is important to do the
// appropriate parsing and validation before calling the function.
using JsonDocument argumentsJson = JsonDocument.Parse(functionCall.FunctionArguments);
bool hasLocation = argumentsJson.RootElement.TryGetProperty("location", out JsonElement location);
bool hasUnit = argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unit);

if (!hasLocation)
{
throw new ArgumentNullException(nameof(location), "The location argument is required.");
}

string functionOutput = hasUnit
? GetCurrentWeather(location.GetString(), unit.GetString())
: GetCurrentWeather(location.GetString());
inputItems.Add(new FunctionCallOutputResponseItem(functionCall.CallId, functionOutput));
break;
}

default:
{
// Handle other unexpected calls.
throw new NotImplementedException();
}
}

requiresAction = true;
break;
}
}

PrintMessageItems(response.OutputItems.OfType<MessageResponseItem>());

} while (requiresAction);
}

private void PrintMessageItems(IEnumerable<ResponseItem> messageItems)
{
foreach (MessageResponseItem messageItem in messageItems)
{
switch (messageItem.Role)
{
case MessageRole.User:
Console.WriteLine($"[USER]:");
Console.WriteLine($"{messageItem.Content[0].Text}");
Console.WriteLine();
break;

case MessageRole.Assistant:
Console.WriteLine($"[ASSISTANT]:");
Console.WriteLine($"{messageItem.Content[0].Text}");
Console.WriteLine();
break;

default:
break;
}
}
}
}

#pragma warning restore OPENAI001
98 changes: 98 additions & 0 deletions examples/Responses/Example03_FunctionCallingAsync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using NUnit.Framework;
using OpenAI.Responses;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

namespace OpenAI.Examples;

// This example uses experimental APIs which are subject to change. To use experimental APIs,
// please acknowledge their experimental status by suppressing the corresponding warning.
#pragma warning disable OPENAI001

public partial class ResponseExamples
{
// See Example03_FunctionCalling.cs for the tool and function definitions.

[Test]
public async Task Example03_FunctionCallingAsync()
{
OpenAIResponseClient client = new("gpt-5", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

List<ResponseItem> inputItems =
[
ResponseItem.CreateUserMessageItem("What's the weather like today for my current location?"),
];

ResponseCreationOptions options = new()
{
Tools = { getCurrentLocationTool, getCurrentWeatherTool },
};

PrintMessageItems(inputItems.OfType<MessageResponseItem>());

bool requiresAction;

do
{
requiresAction = false;
OpenAIResponse response = await client.CreateResponseAsync(inputItems, options);

inputItems.AddRange(response.OutputItems);

foreach (ResponseItem outputItem in response.OutputItems)
{
if (outputItem is FunctionCallResponseItem functionCall)
{
switch (functionCall.FunctionName)
{
case nameof(GetCurrentLocation):
{
string functionOutput = GetCurrentLocation();
inputItems.Add(new FunctionCallOutputResponseItem(functionCall.CallId, functionOutput));
break;
}

case nameof(GetCurrentWeather):
{
// The arguments that the model wants to use to call the function are specified as a
// stringified JSON object based on the schema defined in the tool definition. Note that
// the model may hallucinate arguments too. Consequently, it is important to do the
// appropriate parsing and validation before calling the function.
using JsonDocument argumentsJson = JsonDocument.Parse(functionCall.FunctionArguments);
bool hasLocation = argumentsJson.RootElement.TryGetProperty("location", out JsonElement location);
bool hasUnit = argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unit);

if (!hasLocation)
{
throw new ArgumentNullException(nameof(location), "The location argument is required.");
}

string functionOutput = hasUnit
? GetCurrentWeather(location.GetString(), unit.GetString())
: GetCurrentWeather(location.GetString());
inputItems.Add(new FunctionCallOutputResponseItem(functionCall.CallId, functionOutput));
break;
}

default:
{
// Handle other unexpected calls.
throw new NotImplementedException();
}
}

requiresAction = true;
break;
}
}

PrintMessageItems(response.OutputItems.OfType<MessageResponseItem>());

} while (requiresAction);
}
}

#pragma warning restore OPENAI001
Loading