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: 2 additions & 0 deletions .dotnet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

### Bugs Fixed

- Corrected an internal deserialization issue that caused recent updates to Assistants `file_search` to fail when streaming a run. Strongly typed support for `ranking_options` is not included but will arrive soon. (commit_hash)

### Other Changes

- Reverted the removal of the version path parameter "v1" from the default endpoint URL. (commit_hash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,19 @@ void IJsonModel<InternalBatchRequestOutputResponse>.Write(Utf8JsonWriter writer,
foreach (var item in Body)
{
writer.WritePropertyName(item.Key);
writer.WriteStringValue(item.Value);
if (item.Value == null)
{
writer.WriteNullValue();
continue;
}
#if NET6_0_OR_GREATER
writer.WriteRawValue(item.Value);
#else
using (JsonDocument document = JsonDocument.Parse(item.Value))
{
JsonSerializer.Serialize(writer, document.RootElement);
}
#endif
}
writer.WriteEndObject();
}
Expand Down Expand Up @@ -86,7 +98,7 @@ internal static InternalBatchRequestOutputResponse DeserializeInternalBatchReque
}
int? statusCode = default;
string requestId = default;
IReadOnlyDictionary<string, string> body = default;
IReadOnlyDictionary<string, BinaryData> body = default;
IDictionary<string, BinaryData> serializedAdditionalRawData = default;
Dictionary<string, BinaryData> rawDataDictionary = new Dictionary<string, BinaryData>();
foreach (var property in element.EnumerateObject())
Expand All @@ -111,10 +123,17 @@ internal static InternalBatchRequestOutputResponse DeserializeInternalBatchReque
{
continue;
}
Dictionary<string, string> dictionary = new Dictionary<string, string>();
Dictionary<string, BinaryData> dictionary = new Dictionary<string, BinaryData>();
foreach (var property0 in property.Value.EnumerateObject())
{
dictionary.Add(property0.Name, property0.Value.GetString());
if (property0.Value.ValueKind == JsonValueKind.Null)
{
dictionary.Add(property0.Name, null);
}
else
{
dictionary.Add(property0.Name, BinaryData.FromString(property0.Value.GetRawText()));
}
}
body = dictionary;
continue;
Expand All @@ -126,7 +145,7 @@ internal static InternalBatchRequestOutputResponse DeserializeInternalBatchReque
}
}
serializedAdditionalRawData = rawDataDictionary;
return new InternalBatchRequestOutputResponse(statusCode, requestId, body ?? new ChangeTrackingDictionary<string, string>(), serializedAdditionalRawData);
return new InternalBatchRequestOutputResponse(statusCode, requestId, body ?? new ChangeTrackingDictionary<string, BinaryData>(), serializedAdditionalRawData);
}

BinaryData IPersistableModel<InternalBatchRequestOutputResponse>.Write(ModelReaderWriterOptions options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ internal partial class InternalBatchRequestOutputResponse
internal IDictionary<string, BinaryData> SerializedAdditionalRawData { get; set; }
internal InternalBatchRequestOutputResponse()
{
Body = new ChangeTrackingDictionary<string, string>();
Body = new ChangeTrackingDictionary<string, BinaryData>();
}

internal InternalBatchRequestOutputResponse(int? statusCode, string requestId, IReadOnlyDictionary<string, string> body, IDictionary<string, BinaryData> serializedAdditionalRawData)
internal InternalBatchRequestOutputResponse(int? statusCode, string requestId, IReadOnlyDictionary<string, BinaryData> body, IDictionary<string, BinaryData> serializedAdditionalRawData)
{
StatusCode = statusCode;
RequestId = requestId;
Expand All @@ -25,6 +25,6 @@ internal InternalBatchRequestOutputResponse(int? statusCode, string requestId, I

public int? StatusCode { get; }
public string RequestId { get; }
public IReadOnlyDictionary<string, string> Body { get; }
public IReadOnlyDictionary<string, BinaryData> Body { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,19 @@ void IJsonModel<InternalRunStepDeltaStepDetailsToolCallsFileSearchObject>.Write(
foreach (var item in FileSearch)
{
writer.WritePropertyName(item.Key);
writer.WriteStringValue(item.Value);
if (item.Value == null)
{
writer.WriteNullValue();
continue;
}
#if NET6_0_OR_GREATER
writer.WriteRawValue(item.Value);
#else
using (JsonDocument document = JsonDocument.Parse(item.Value))
{
JsonSerializer.Serialize(writer, document.RootElement);
}
#endif
}
writer.WriteEndObject();
}
Expand Down Expand Up @@ -91,7 +103,7 @@ internal static InternalRunStepDeltaStepDetailsToolCallsFileSearchObject Deseria
}
int index = default;
string id = default;
IReadOnlyDictionary<string, string> fileSearch = default;
IReadOnlyDictionary<string, BinaryData> fileSearch = default;
string type = default;
IDictionary<string, BinaryData> serializedAdditionalRawData = default;
Dictionary<string, BinaryData> rawDataDictionary = new Dictionary<string, BinaryData>();
Expand All @@ -109,10 +121,17 @@ internal static InternalRunStepDeltaStepDetailsToolCallsFileSearchObject Deseria
}
if (property.NameEquals("file_search"u8))
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
Dictionary<string, BinaryData> dictionary = new Dictionary<string, BinaryData>();
foreach (var property0 in property.Value.EnumerateObject())
{
dictionary.Add(property0.Name, property0.Value.GetString());
if (property0.Value.ValueKind == JsonValueKind.Null)
{
dictionary.Add(property0.Name, null);
}
else
{
dictionary.Add(property0.Name, BinaryData.FromString(property0.Value.GetRawText()));
}
}
fileSearch = dictionary;
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace OpenAI.Assistants
{
internal partial class InternalRunStepDeltaStepDetailsToolCallsFileSearchObject : InternalRunStepDeltaStepDetailsToolCallsObjectToolCallsObject
{
internal InternalRunStepDeltaStepDetailsToolCallsFileSearchObject(int index, IReadOnlyDictionary<string, string> fileSearch)
internal InternalRunStepDeltaStepDetailsToolCallsFileSearchObject(int index, IReadOnlyDictionary<string, BinaryData> fileSearch)
{
Argument.AssertNotNull(fileSearch, nameof(fileSearch));

Expand All @@ -18,7 +18,7 @@ internal InternalRunStepDeltaStepDetailsToolCallsFileSearchObject(int index, IRe
FileSearch = fileSearch;
}

internal InternalRunStepDeltaStepDetailsToolCallsFileSearchObject(string type, IDictionary<string, BinaryData> serializedAdditionalRawData, int index, string id, IReadOnlyDictionary<string, string> fileSearch) : base(type, serializedAdditionalRawData)
internal InternalRunStepDeltaStepDetailsToolCallsFileSearchObject(string type, IDictionary<string, BinaryData> serializedAdditionalRawData, int index, string id, IReadOnlyDictionary<string, BinaryData> fileSearch) : base(type, serializedAdditionalRawData)
{
Index = index;
Id = id;
Expand All @@ -31,6 +31,6 @@ internal InternalRunStepDeltaStepDetailsToolCallsFileSearchObject()

public int Index { get; }
public string Id { get; }
public IReadOnlyDictionary<string, string> FileSearch { get; }
public IReadOnlyDictionary<string, BinaryData> FileSearch { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,19 @@ void IJsonModel<InternalRunStepFileSearchToolCallDetails>.Write(Utf8JsonWriter w
foreach (var item in FileSearch)
{
writer.WritePropertyName(item.Key);
writer.WriteStringValue(item.Value);
if (item.Value == null)
{
writer.WriteNullValue();
continue;
}
#if NET6_0_OR_GREATER
writer.WriteRawValue(item.Value);
#else
using (JsonDocument document = JsonDocument.Parse(item.Value))
{
JsonSerializer.Serialize(writer, document.RootElement);
}
#endif
}
writer.WriteEndObject();
}
Expand Down Expand Up @@ -85,7 +97,7 @@ internal static InternalRunStepFileSearchToolCallDetails DeserializeInternalRunS
return null;
}
string id = default;
IReadOnlyDictionary<string, string> fileSearch = default;
IReadOnlyDictionary<string, BinaryData> fileSearch = default;
string type = default;
IDictionary<string, BinaryData> serializedAdditionalRawData = default;
Dictionary<string, BinaryData> rawDataDictionary = new Dictionary<string, BinaryData>();
Expand All @@ -98,10 +110,17 @@ internal static InternalRunStepFileSearchToolCallDetails DeserializeInternalRunS
}
if (property.NameEquals("file_search"u8))
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
Dictionary<string, BinaryData> dictionary = new Dictionary<string, BinaryData>();
foreach (var property0 in property.Value.EnumerateObject())
{
dictionary.Add(property0.Name, property0.Value.GetString());
if (property0.Value.ValueKind == JsonValueKind.Null)
{
dictionary.Add(property0.Name, null);
}
else
{
dictionary.Add(property0.Name, BinaryData.FromString(property0.Value.GetRawText()));
}
}
fileSearch = dictionary;
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace OpenAI.Assistants
{
internal partial class InternalRunStepFileSearchToolCallDetails : RunStepToolCall
{
internal InternalRunStepFileSearchToolCallDetails(string id, IReadOnlyDictionary<string, string> fileSearch)
internal InternalRunStepFileSearchToolCallDetails(string id, IReadOnlyDictionary<string, BinaryData> fileSearch)
{
Argument.AssertNotNull(id, nameof(id));
Argument.AssertNotNull(fileSearch, nameof(fileSearch));
Expand All @@ -19,7 +19,7 @@ internal InternalRunStepFileSearchToolCallDetails(string id, IReadOnlyDictionary
FileSearch = fileSearch;
}

internal InternalRunStepFileSearchToolCallDetails(string type, IDictionary<string, BinaryData> serializedAdditionalRawData, string id, IReadOnlyDictionary<string, string> fileSearch) : base(type, serializedAdditionalRawData)
internal InternalRunStepFileSearchToolCallDetails(string type, IDictionary<string, BinaryData> serializedAdditionalRawData, string id, IReadOnlyDictionary<string, BinaryData> fileSearch) : base(type, serializedAdditionalRawData)
{
Id = id;
FileSearch = fileSearch;
Expand All @@ -30,6 +30,6 @@ internal InternalRunStepFileSearchToolCallDetails()
}

public string Id { get; }
public IReadOnlyDictionary<string, string> FileSearch { get; }
public IReadOnlyDictionary<string, BinaryData> FileSearch { get; }
}
}
63 changes: 61 additions & 2 deletions .dotnet/tests/Assistants/AssistantTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using NUnit.Framework;
using OpenAI.Assistants;
using OpenAI.Chat;
using OpenAI.Files;
using OpenAI.VectorStores;
using System;
Expand All @@ -9,7 +8,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using static OpenAI.Tests.TestHelpers;
Expand Down Expand Up @@ -679,6 +677,67 @@ This file describes the favorite foods of several people.
Assert.That(hasCake, Is.True);
}

[Test]
public async Task BasicFileSearchStreamingWorks()
{
const string fileContent = """
The favorite food of several people:
- Summanus Ferdinand: tacos
- Tekakwitha Effie: pizza
- Filip Carola: cake
""";

const string fileName = "favorite_foods.txt";

FileClient fileClient = GetTestClient<FileClient>(TestScenario.Files);
AssistantClient client = GetTestClient<AssistantClient>(TestScenario.Assistants);

// First, upload a simple test file.
OpenAIFileInfo testFile = fileClient.UploadFile(BinaryData.FromString(fileContent), fileName, FileUploadPurpose.Assistants);
Validate(testFile);

// Create an assistant, using the creation helper to make a new vector store.
AssistantCreationOptions assistantCreationOptions = new()
{
Tools = { new FileSearchToolDefinition() },
ToolResources = new()
{
FileSearch = new()
{
NewVectorStores = { new VectorStoreCreationHelper([testFile.Id]) }
}
}
};
Assistant assistant = client.CreateAssistant("gpt-4o-mini", assistantCreationOptions);
Validate(assistant);

Assert.That(assistant.ToolResources?.FileSearch?.VectorStoreIds, Has.Count.EqualTo(1));
string vectorStoreId = assistant.ToolResources.FileSearch.VectorStoreIds[0];
_vectorStoreIdsToDelete.Add(vectorStoreId);

// Create a thread.
ThreadCreationOptions threadCreationOptions = new()
{
InitialMessages = { "Using the files you have available, what's Filip's favorite food?" }
};
AssistantThread thread = client.CreateThread(threadCreationOptions);
Validate(thread);

// Create run and stream the results.
AsyncCollectionResult<StreamingUpdate> streamingResult = client.CreateRunStreamingAsync(thread.Id, assistant.Id);
string message = string.Empty;

await foreach (StreamingUpdate update in streamingResult)
{
if (update is MessageContentUpdate contentUpdate)
{
message += $"{contentUpdate.Text}";
}
}

Assert.That(message, Does.Contain("cake"));
}

[Test]
public async Task Pagination_CanEnumerateAssistants()
{
Expand Down
9 changes: 3 additions & 6 deletions .openapi3/openapi3-openai.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2879,8 +2879,7 @@ components:
description: An unique identifier for the OpenAI API request. Please include this request ID when contacting support.
body:
type: object
additionalProperties:
type: string
additionalProperties: {}
description: The JSON body of the response
x-oaiTypeLabel: map
nullable: true
Expand Down Expand Up @@ -8261,8 +8260,7 @@ components:
description: The type of tool call. This is always going to be `file_search` for this type of tool call.
file_search:
type: object
additionalProperties:
type: string
additionalProperties: {}
description: For now, this is always going to be an empty object.
x-oaiTypeLabel: map
allOf:
Expand Down Expand Up @@ -8460,8 +8458,7 @@ components:
description: The type of tool call. This is always going to be `file_search` for this type of tool call.
file_search:
type: object
additionalProperties:
type: string
additionalProperties: {}
description: For now, this is always going to be an empty object.
x-oaiTypeLabel: map
allOf:
Expand Down
3 changes: 3 additions & 0 deletions .typespec/assistants/models.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ model CreateAssistantRequest {
file_search?: ToolResourcesFileSearch;
} | null;

// Tool customization: specialize known metadata string maps
/** 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 maxium of 512 characters long. */
@extension("x-oaiTypeLabel", "map")
metadata?: Record<string> | null;
Expand Down Expand Up @@ -153,6 +154,7 @@ model ModifyAssistantRequest {
file_search?: ToolResourcesFileSearchIdsOnly;
} | null;

// Tool customization: specialize known metadata string maps
/** 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 maxium of 512 characters long. */
@extension("x-oaiTypeLabel", "map")
metadata?: Record<string> | null;
Expand Down Expand Up @@ -291,6 +293,7 @@ model AssistantObject {
file_search?: ToolResourcesFileSearchIdsOnly;
} | null;

// Tool customization: specialize known metadata string maps
/** 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 maxium of 512 characters long. */
@extension("x-oaiTypeLabel", "map")
metadata: Record<string> | null;
Expand Down
3 changes: 2 additions & 1 deletion .typespec/batch/models.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ model Batch {
failed: int32;
};

// Tool customization: specialize known metadata string maps
/** 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 maxium of 512 characters long. */
@extension("x-oaiTypeLabel", "map")
metadata?: Record<string> | null;
Expand Down Expand Up @@ -158,7 +159,7 @@ model BatchRequestOutput {

/** The JSON body of the response */
@extension("x-oaiTypeLabel", "map")
body?: Record<string>;
body?: Record<unknown>;
} | null;

/** For requests that failed with a non-HTTP error, this will contain more information on the cause of the failure. */
Expand Down
Loading