Skip to content

Commit 389df09

Browse files
much refactor so wow
1 parent eb768c5 commit 389df09

31 files changed

Lines changed: 660 additions & 245 deletions

OpenAI-DotNet-Tests/TestFixture_14_Responses.cs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ public async Task Test_01_02_SimpleTestInput_Streaming()
6969
Assert.NotNull(OpenAIClient.ResponsesEndpoint);
7070
var response = await OpenAIClient.ResponsesEndpoint.CreateModelResponseAsync("Tell me a three sentence bedtime story about a unicorn.", async (@event, sseEvent) =>
7171
{
72-
Console.WriteLine($"{@event}: {sseEvent.ToJsonString()}");
72+
Assert.NotNull(@event);
73+
Assert.NotNull(sseEvent);
7374
await Task.CompletedTask;
7475
});
7576
Assert.NotNull(response);
@@ -464,5 +465,94 @@ public async Task Test_05_01_Prompts()
464465
Console.WriteLine($"{messageItem.Role}: {messageItem}");
465466
response.PrintUsage();
466467
}
468+
469+
[Test]
470+
public async Task Test_06_01_ImageGenerationTool()
471+
{
472+
Assert.NotNull(OpenAIClient.ResponsesEndpoint);
473+
var tools = new List<Tool>
474+
{
475+
new ImageGenerationTool(
476+
model: Model.GPT_Image_1,
477+
size: "1024x1024",
478+
quality: "low",
479+
outputFormat: "png")
480+
};
481+
var request = new CreateResponseRequest(
482+
input: new Message(Role.User, "Create an image of a futuristic city with flying cars."),
483+
model: Model.GPT4_1_Nano,
484+
tools: tools,
485+
toolChoice: "auto");
486+
var response = await OpenAIClient.ResponsesEndpoint.CreateModelResponseAsync(request, serverSentEvent =>
487+
{
488+
if (serverSentEvent is ImageGenerationCall { Status: ResponseStatus.Generating } imageGenerationCall)
489+
{
490+
Assert.IsFalse(string.IsNullOrWhiteSpace(imageGenerationCall.Result));
491+
}
492+
return Task.CompletedTask;
493+
});
494+
Assert.NotNull(response);
495+
Assert.IsNotEmpty(response.Id);
496+
Assert.AreEqual(ResponseStatus.Completed, response.Status);
497+
498+
// make sure we have at least the image generation call in the response output array
499+
var imageCall = response.Output.FirstOrDefault(i => i.Type == ResponseItemType.ImageGenerationCall) as ImageGenerationCall;
500+
Assert.NotNull(imageCall);
501+
Assert.AreEqual(ResponseStatus.Generating, imageCall.Status);
502+
Assert.IsFalse(string.IsNullOrWhiteSpace(imageCall.Result));
503+
504+
response.PrintUsage();
505+
}
506+
507+
[Test]
508+
public async Task Test_07_01_MCPTool()
509+
{
510+
try
511+
{
512+
Assert.NotNull(OpenAIClient.ResponsesEndpoint);
513+
await Task.CompletedTask;
514+
515+
var conversation = new List<IResponseItem>
516+
{
517+
new Message(Role.System, "You are a Dungeons and Dragons Master. Guide the players through the game turn by turn."),
518+
new Message(Role.User, "Roll 2d4+1")
519+
};
520+
var tools = new List<Tool>
521+
{
522+
new MCPTool(
523+
serverLabel: "dmcp",
524+
serverDescription: "A Dungeons and Dragons MCP server to assist with dice rolling.",
525+
serverUrl: "https://dmcp-server.deno.dev/sse",
526+
requireApproval: MCPToolRequireApproval.Never)
527+
};
528+
529+
Task StreamEventHandler(string @event, IServerSentEvent serverSentEvent)
530+
{
531+
switch (serverSentEvent)
532+
{
533+
case MCPListTools mcpListTools:
534+
Assert.NotNull(mcpListTools);
535+
break;
536+
case MCPToolCall mcpToolCall:
537+
Assert.NotNull(mcpToolCall);
538+
break;
539+
}
540+
541+
return Task.CompletedTask;
542+
}
543+
544+
var request = new CreateResponseRequest(conversation, Model.GPT4_1_Nano, tools: tools, toolChoice: "auto");
545+
var response = await OpenAIClient.ResponsesEndpoint.CreateModelResponseAsync(request, StreamEventHandler);
546+
547+
Assert.NotNull(response);
548+
Assert.IsNotEmpty(response.Id);
549+
Assert.AreEqual(ResponseStatus.Completed, response.Status);
550+
}
551+
catch (Exception e)
552+
{
553+
Console.WriteLine(e);
554+
throw;
555+
}
556+
}
467557
}
468558
}

OpenAI-DotNet/Assistants/AssistantsEndpoint.cs

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Licensed under the MIT License. See LICENSE in the project root for license information.
22

33
using OpenAI.Extensions;
4-
using System.Net.Http;
4+
using System.Collections.Generic;
55
using System.Text.Json;
66
using System.Threading;
77
using System.Threading.Tasks;
@@ -14,6 +14,11 @@ internal AssistantsEndpoint(OpenAIClient client) : base(client) { }
1414

1515
protected override string Root => "assistants";
1616

17+
internal override IReadOnlyDictionary<string, IEnumerable<string>> Headers { get; } = new Dictionary<string, IEnumerable<string>>
18+
{
19+
{ "OpenAI-Beta", ["assistants=v2"] }
20+
};
21+
1722
/// <summary>
1823
/// Get list of assistants.
1924
/// </summary>
@@ -22,9 +27,7 @@ internal AssistantsEndpoint(OpenAIClient client) : base(client) { }
2227
/// <returns><see cref="ListResponse{AssistantResponse}"/>.</returns>
2328
public async Task<ListResponse<AssistantResponse>> ListAssistantsAsync(ListQuery query = null, CancellationToken cancellationToken = default)
2429
{
25-
using var message = new HttpRequestMessage(HttpMethod.Get, GetUrl(queryParameters: query));
26-
message.Headers.Add("OpenAI-Beta", "assistants=v2");
27-
using var response = await HttpClient.SendAsync(message, cancellationToken).ConfigureAwait(false);
30+
using var response = await GetAsync(GetUrl(queryParameters: query), cancellationToken).ConfigureAwait(false);
2831
return await response.DeserializeAsync<ListResponse<AssistantResponse>>(EnableDebug, client, cancellationToken).ConfigureAwait(false);
2932
}
3033

@@ -59,10 +62,7 @@ public async Task<AssistantResponse> CreateAssistantAsync(CreateAssistantRequest
5962
{
6063
request ??= new CreateAssistantRequest();
6164
using var payload = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
62-
using var message = new HttpRequestMessage(HttpMethod.Post, GetUrl());
63-
message.Headers.Add("OpenAI-Beta", "assistants=v2");
64-
message.Content = payload;
65-
using var response = await HttpClient.SendAsync(message, cancellationToken).ConfigureAwait(false);
65+
using var response = await PostAsync(GetUrl(), payload, cancellationToken).ConfigureAwait(false);
6666
return await response.DeserializeAsync<AssistantResponse>(EnableDebug, payload, client, cancellationToken).ConfigureAwait(false);
6767
}
6868

@@ -74,9 +74,7 @@ public async Task<AssistantResponse> CreateAssistantAsync(CreateAssistantRequest
7474
/// <returns><see cref="AssistantResponse"/>.</returns>
7575
public async Task<AssistantResponse> RetrieveAssistantAsync(string assistantId, CancellationToken cancellationToken = default)
7676
{
77-
using var message = new HttpRequestMessage(HttpMethod.Get, GetUrl($"/{assistantId}"));
78-
message.Headers.Add("OpenAI-Beta", "assistants=v2");
79-
using var response = await HttpClient.SendAsync(message, cancellationToken).ConfigureAwait(false);
77+
using var response = await GetAsync(GetUrl($"/{assistantId}"), cancellationToken).ConfigureAwait(false);
8078
return await response.DeserializeAsync<AssistantResponse>(EnableDebug, client, cancellationToken).ConfigureAwait(false);
8179
}
8280

@@ -90,10 +88,7 @@ public async Task<AssistantResponse> RetrieveAssistantAsync(string assistantId,
9088
public async Task<AssistantResponse> ModifyAssistantAsync(string assistantId, CreateAssistantRequest request, CancellationToken cancellationToken = default)
9189
{
9290
using var payload = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
93-
using var message = new HttpRequestMessage(HttpMethod.Post, GetUrl($"/{assistantId}"));
94-
message.Headers.Add("OpenAI-Beta", "assistants=v2");
95-
message.Content = payload;
96-
using var response = await HttpClient.SendAsync(message, cancellationToken).ConfigureAwait(false);
91+
using var response = await PostAsync(GetUrl($"/{assistantId}"), payload, cancellationToken).ConfigureAwait(false);
9792
return await response.DeserializeAsync<AssistantResponse>(EnableDebug, payload, client, cancellationToken).ConfigureAwait(false);
9893
}
9994

@@ -105,9 +100,7 @@ public async Task<AssistantResponse> ModifyAssistantAsync(string assistantId, Cr
105100
/// <returns>True, if the assistant was deleted.</returns>
106101
public async Task<bool> DeleteAssistantAsync(string assistantId, CancellationToken cancellationToken = default)
107102
{
108-
using var message = new HttpRequestMessage(HttpMethod.Delete, GetUrl($"/{assistantId}"));
109-
message.Headers.Add("OpenAI-Beta", "assistants=v2");
110-
using var response = await HttpClient.SendAsync(message, cancellationToken).ConfigureAwait(false);
103+
using var response = await DeleteAsync(GetUrl($"/{assistantId}"), cancellationToken).ConfigureAwait(false);
111104
var result = await response.DeserializeAsync<DeletedResponse>(EnableDebug, client, cancellationToken).ConfigureAwait(false);
112105
return result?.Deleted ?? false;
113106
}

OpenAI-DotNet/Audio/AudioEndpoint.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public AudioEndpoint(OpenAIClient client) : base(client) { }
3535
public async Task<ReadOnlyMemory<byte>> CreateSpeechAsync(SpeechRequest request, Func<ReadOnlyMemory<byte>, Task> chunkCallback = null, CancellationToken cancellationToken = default)
3636
{
3737
using var payload = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
38-
using var response = await HttpClient.PostAsync(GetUrl("/speech"), payload, cancellationToken).ConfigureAwait(false);
38+
using var response = await PostAsync(GetUrl("/speech"), payload, cancellationToken).ConfigureAwait(false);
3939
await response.CheckResponseAsync(false, payload, cancellationToken: cancellationToken).ConfigureAwait(false);
4040
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
4141
await using var memoryStream = new MemoryStream();
@@ -155,7 +155,7 @@ public async Task<AudioResponse> CreateTranscriptionJsonAsync(AudioTranscription
155155
request.Dispose();
156156
}
157157

158-
using var response = await HttpClient.PostAsync(GetUrl("/transcriptions"), payload, cancellationToken).ConfigureAwait(false);
158+
using var response = await PostAsync(GetUrl("/transcriptions"), payload, cancellationToken).ConfigureAwait(false);
159159
var responseAsString = await response.ReadAsStringAsync(EnableDebug, payload, cancellationToken).ConfigureAwait(false);
160160
return (response, responseAsString);
161161
}
@@ -220,7 +220,7 @@ public async Task<AudioResponse> CreateTranslationJsonAsync(AudioTranslationRequ
220220
request.Dispose();
221221
}
222222

223-
using var response = await HttpClient.PostAsync(GetUrl("/translations"), payload, cancellationToken).ConfigureAwait(false);
223+
using var response = await PostAsync(GetUrl("/translations"), payload, cancellationToken).ConfigureAwait(false);
224224
var responseAsString = await response.ReadAsStringAsync(EnableDebug, payload, cancellationToken).ConfigureAwait(false);
225225
return (response, responseAsString);
226226
}

OpenAI-DotNet/Batch/BatchEndpoint.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public BatchEndpoint(OpenAIClient client) : base(client) { }
2828
public async Task<BatchResponse> CreateBatchAsync(CreateBatchRequest request, CancellationToken cancellationToken = default)
2929
{
3030
using var payload = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
31-
using var response = await HttpClient.PostAsync(GetUrl(), payload, cancellationToken).ConfigureAwait(false);
31+
using var response = await PostAsync(GetUrl(), payload, cancellationToken).ConfigureAwait(false);
3232
return await response.DeserializeAsync<BatchResponse>(EnableDebug, payload, client, cancellationToken).ConfigureAwait(false);
3333
}
3434

@@ -40,7 +40,7 @@ public async Task<BatchResponse> CreateBatchAsync(CreateBatchRequest request, Ca
4040
/// <returns><see cref="ListResponse{BatchResponse}"/>.</returns>
4141
public async Task<ListResponse<BatchResponse>> ListBatchesAsync(ListQuery query = null, CancellationToken cancellationToken = default)
4242
{
43-
using var response = await HttpClient.GetAsync(GetUrl(queryParameters: query), cancellationToken).ConfigureAwait(false);
43+
using var response = await GetAsync(GetUrl(queryParameters: query), cancellationToken).ConfigureAwait(false);
4444
return await response.DeserializeAsync<ListResponse<BatchResponse>>(EnableDebug, client, cancellationToken).ConfigureAwait(false);
4545
}
4646

@@ -52,7 +52,7 @@ public async Task<ListResponse<BatchResponse>> ListBatchesAsync(ListQuery query
5252
/// <returns><see cref="BatchResponse"/>.</returns>
5353
public async Task<BatchResponse> RetrieveBatchAsync(string batchId, CancellationToken cancellationToken = default)
5454
{
55-
using var response = await HttpClient.GetAsync(GetUrl($"/{batchId}"), cancellationToken).ConfigureAwait(false);
55+
using var response = await GetAsync(GetUrl($"/{batchId}"), cancellationToken).ConfigureAwait(false);
5656
return await response.DeserializeAsync<BatchResponse>(EnableDebug, client, cancellationToken).ConfigureAwait(false);
5757
}
5858

@@ -64,7 +64,7 @@ public async Task<BatchResponse> RetrieveBatchAsync(string batchId, Cancellation
6464
/// <returns>True, if the batch was cancelled, otherwise false.</returns>
6565
public async Task<bool> CancelBatchAsync(string batchId, CancellationToken cancellationToken = default)
6666
{
67-
using var response = await HttpClient.PostAsync(GetUrl($"/{batchId}/cancel"), null!, cancellationToken).ConfigureAwait(false);
67+
using var response = await PostAsync(GetUrl($"/{batchId}/cancel"), null!, cancellationToken).ConfigureAwait(false);
6868
var batch = await response.DeserializeAsync<BatchResponse>(EnableDebug, client, cancellationToken).ConfigureAwait(false);
6969

7070
if (batch.Status < BatchStatus.Cancelling)

OpenAI-DotNet/Chat/ChatEndpoint.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public ChatEndpoint(OpenAIClient client) : base(client) { }
3737
public async Task<ChatResponse> GetCompletionAsync(ChatRequest chatRequest, CancellationToken cancellationToken = default)
3838
{
3939
using var payload = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
40-
using var response = await HttpClient.PostAsync(GetUrl("/completions"), payload, cancellationToken).ConfigureAwait(false);
40+
using var response = await PostAsync(GetUrl("/completions"), payload, cancellationToken).ConfigureAwait(false);
4141
return await response.DeserializeAsync<ChatResponse>(EnableDebug, payload, client, cancellationToken).ConfigureAwait(false);
4242
}
4343

@@ -154,7 +154,7 @@ public async Task<ChatResponse> StreamCompletionAsync(ChatRequest chatRequest, F
154154
}
155155

156156
return resultHandler(partialResponse);
157-
}, null, cancellationToken);
157+
}, cancellationToken);
158158

159159
if (chatResponse == null) { return null; }
160160
chatResponse.SetResponseData(response.Headers, client);
@@ -183,7 +183,7 @@ public async IAsyncEnumerable<ChatResponse> StreamCompletionEnumerableAsync(Chat
183183
using var payload = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
184184
using var request = new HttpRequestMessage(HttpMethod.Post, GetUrl("/completions"));
185185
request.Content = payload;
186-
using var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
186+
using var response = await ServerSentEventStreamAsync(request, cancellationToken).ConfigureAwait(false);
187187
await response.CheckResponseAsync(false, payload, cancellationToken: cancellationToken).ConfigureAwait(false);
188188
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
189189
using var reader = new StreamReader(stream);

0 commit comments

Comments
 (0)