diff --git a/api/OpenAI.netstandard2.0.cs b/api/OpenAI.netstandard2.0.cs index 24d7c45a..33e6bbf3 100644 --- a/api/OpenAI.netstandard2.0.cs +++ b/api/OpenAI.netstandard2.0.cs @@ -1146,15 +1146,24 @@ public class BatchClient { public BatchClient(ApiKeyCredential credential, OpenAIClientOptions options = null); protected internal BatchClient(ClientPipeline pipeline, Uri endpoint, OpenAIClientOptions options); public virtual ClientPipeline Pipeline { get; } - public virtual ClientResult CancelBatch(string batchId, RequestOptions options); - public virtual Task CancelBatchAsync(string batchId, RequestOptions options); - public virtual ClientResult CreateBatch(BinaryContent content, RequestOptions options = null); - public virtual Task CreateBatchAsync(BinaryContent content, RequestOptions options = null); - public virtual ClientResult GetBatch(string batchId, RequestOptions options); - public virtual Task GetBatchAsync(string batchId, RequestOptions options); + public virtual CreateBatchOperation CreateBatch(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null); + public virtual Task CreateBatchAsync(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null); public virtual ClientResult GetBatches(string after, int? limit, RequestOptions options); public virtual Task GetBatchesAsync(string after, int? limit, RequestOptions options); } + public class CreateBatchOperation : OperationResult { + public string BatchId { get; } + public override bool IsCompleted { get; protected set; } + public override ContinuationToken? RehydrationToken { get; protected set; } + public virtual ClientResult CancelBatch(RequestOptions? options); + public virtual Task CancelBatchAsync(RequestOptions? options); + public virtual ClientResult GetBatch(RequestOptions? options); + public virtual Task GetBatchAsync(RequestOptions? options); + public static CreateBatchOperation Rehydrate(BatchClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public static Task RehydrateAsync(BatchClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public override void WaitForCompletion(CancellationToken cancellationToken = default); + public override Task WaitForCompletionAsync(CancellationToken cancellationToken = default); + } } namespace OpenAI.Chat { public class AssistantChatMessage : ChatMessage, IJsonModel, IPersistableModel { @@ -1719,22 +1728,31 @@ public class OpenAIFileInfoCollection : ObjectModel.ReadOnlyCollection CancelJobAsync(RequestOptions? options); + public virtual ClientResult GetJob(RequestOptions? options); + public virtual Task GetJobAsync(RequestOptions? options); + public virtual ClientResult GetJobCheckpoints(string after, int? limit, RequestOptions? options); + public virtual Task GetJobCheckpointsAsync(string after, int? limit, RequestOptions? options); + public virtual ClientResult GetJobEvents(string after, int? limit, RequestOptions? options); + public virtual Task GetJobEventsAsync(string after, int? limit, RequestOptions? options); + public static CreateJobOperation Rehydrate(FineTuningClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public static Task RehydrateAsync(FineTuningClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public override void WaitForCompletion(CancellationToken cancellationToken = default); + public override Task WaitForCompletionAsync(CancellationToken cancellationToken = default); + } public class FineTuningClient { protected FineTuningClient(); public FineTuningClient(OpenAIClientOptions options = null); public FineTuningClient(ApiKeyCredential credential, OpenAIClientOptions options = null); protected internal FineTuningClient(ClientPipeline pipeline, Uri endpoint, OpenAIClientOptions options); public virtual ClientPipeline Pipeline { get; } - public virtual ClientResult CancelJob(string jobId, RequestOptions options); - public virtual Task CancelJobAsync(string jobId, RequestOptions options); - public virtual ClientResult CreateJob(BinaryContent content, RequestOptions options = null); - public virtual Task CreateJobAsync(BinaryContent content, RequestOptions options = null); - public virtual ClientResult GetJob(string jobId, RequestOptions options); - public virtual Task GetJobAsync(string jobId, RequestOptions options); - public virtual ClientResult GetJobCheckpoints(string fineTuningJobId, string after, int? limit, RequestOptions options); - public virtual Task GetJobCheckpointsAsync(string fineTuningJobId, string after, int? limit, RequestOptions options); - public virtual ClientResult GetJobEvents(string jobId, string after, int? limit, RequestOptions options); - public virtual Task GetJobEventsAsync(string jobId, string after, int? limit, RequestOptions options); + public virtual CreateJobOperation CreateJob(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null); + public virtual Task CreateJobAsync(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null); public virtual ClientResult GetJobs(string after, int? limit, RequestOptions options); public virtual Task GetJobsAsync(string after, int? limit, RequestOptions options); } @@ -1986,6 +2004,75 @@ public class ModerationResult : IJsonModel, IPersistableModel< } } namespace OpenAI.VectorStores { + public class AddFileToVectorStoreOperation : OperationResult { + public AddFileToVectorStoreOperation(ClientPipeline pipeline, Uri endpoint, ClientResult result) : base(default!); + public string FileId { get; } + public override bool IsCompleted { get; protected set; } + public override ContinuationToken? RehydrationToken { get; protected set; } + public VectorStoreFileAssociationStatus? Status { get; } + public VectorStoreFileAssociation? Value { get; } + public string VectorStoreId { get; } + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult GetFileAssociation(RequestOptions? options); + public virtual ClientResult GetFileAssociation(CancellationToken cancellationToken = default); + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual Task GetFileAssociationAsync(RequestOptions? options); + public virtual Task> GetFileAssociationAsync(CancellationToken cancellationToken = default); + public static AddFileToVectorStoreOperation Rehydrate(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public static Task RehydrateAsync(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public override void WaitForCompletion(CancellationToken cancellationToken = default); + public override Task WaitForCompletionAsync(CancellationToken cancellationToken = default); + } + public class CreateBatchFileJobOperation : OperationResult { + public string BatchId { get; } + public override bool IsCompleted { get; protected set; } + public override ContinuationToken? RehydrationToken { get; protected set; } + public VectorStoreBatchFileJobStatus? Status { get; } + public VectorStoreBatchFileJob? Value { get; } + public string VectorStoreId { get; } + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult CancelFileBatch(RequestOptions? options); + public virtual ClientResult CancelFileBatch(CancellationToken cancellationToken = default); + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual Task CancelFileBatchAsync(RequestOptions? options); + public virtual Task> CancelFileBatchAsync(CancellationToken cancellationToken = default); + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult GetFileBatch(RequestOptions? options); + public virtual ClientResult GetFileBatch(CancellationToken cancellationToken = default); + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual Task GetFileBatchAsync(RequestOptions? options); + public virtual Task> GetFileBatchAsync(CancellationToken cancellationToken = default); + public virtual PageCollection GetFilesInBatch(VectorStoreFileAssociationCollectionOptions? options = null, CancellationToken cancellationToken = default); + public virtual PageCollection GetFilesInBatch(ContinuationToken firstPageToken, CancellationToken cancellationToken = default); + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual IEnumerable GetFilesInBatch(int? limit, string? order, string? after, string? before, string? filter, RequestOptions? options); + public virtual AsyncPageCollection GetFilesInBatchAsync(VectorStoreFileAssociationCollectionOptions? options = null, CancellationToken cancellationToken = default); + public virtual AsyncPageCollection GetFilesInBatchAsync(ContinuationToken firstPageToken, CancellationToken cancellationToken = default); + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual IAsyncEnumerable GetFilesInBatchAsync(int? limit, string? order, string? after, string? before, string? filter, RequestOptions? options); + public static CreateBatchFileJobOperation Rehydrate(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public static Task RehydrateAsync(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public override void WaitForCompletion(CancellationToken cancellationToken = default); + public override Task WaitForCompletionAsync(CancellationToken cancellationToken = default); + } + public class CreateVectorStoreOperation : OperationResult { + public CreateVectorStoreOperation(ClientPipeline pipeline, Uri endpoint, ClientResult result) : base(default!); + public override bool IsCompleted { get; protected set; } + public override ContinuationToken? RehydrationToken { get; protected set; } + public VectorStoreStatus? Status { get; } + public VectorStore? Value { get; } + public string VectorStoreId { get; } + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult GetVectorStore(RequestOptions? options); + public virtual ClientResult GetVectorStore(CancellationToken cancellationToken = default); + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual Task GetVectorStoreAsync(RequestOptions? options); + public virtual Task> GetVectorStoreAsync(CancellationToken cancellationToken = default); + public static CreateVectorStoreOperation Rehydrate(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public static Task RehydrateAsync(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default); + public override void WaitForCompletion(CancellationToken cancellationToken = default); + public override Task WaitForCompletionAsync(CancellationToken cancellationToken = default); + } public abstract class FileChunkingStrategy : IJsonModel, IPersistableModel { public static FileChunkingStrategy Auto { get; } public static FileChunkingStrategy Unknown { get; } @@ -2059,34 +2146,29 @@ public class VectorStoreClient { public VectorStoreClient(ApiKeyCredential credential, OpenAIClientOptions options = null); protected internal VectorStoreClient(ClientPipeline pipeline, Uri endpoint, OpenAIClientOptions options); public virtual ClientPipeline Pipeline { get; } - public virtual ClientResult AddFileToVectorStore(VectorStore vectorStore, OpenAIFileInfo file); + public virtual AddFileToVectorStoreOperation AddFileToVectorStore(bool waitUntilCompleted, VectorStore vectorStore, OpenAIFileInfo file); [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult AddFileToVectorStore(string vectorStoreId, BinaryContent content, RequestOptions options = null); - public virtual ClientResult AddFileToVectorStore(string vectorStoreId, string fileId, CancellationToken cancellationToken = default); - public virtual Task> AddFileToVectorStoreAsync(VectorStore vectorStore, OpenAIFileInfo file); + public virtual AddFileToVectorStoreOperation AddFileToVectorStore(bool waitUntilCompleted, string vectorStoreId, BinaryContent content, RequestOptions options = null); + public virtual AddFileToVectorStoreOperation AddFileToVectorStore(bool waitUntilCompleted, string vectorStoreId, string fileId, CancellationToken cancellationToken = default); + public virtual Task AddFileToVectorStoreAsync(bool waitUntilCompleted, VectorStore vectorStore, OpenAIFileInfo file); [EditorBrowsable(EditorBrowsableState.Never)] - public virtual Task AddFileToVectorStoreAsync(string vectorStoreId, BinaryContent content, RequestOptions options = null); - public virtual Task> AddFileToVectorStoreAsync(string vectorStoreId, string fileId, CancellationToken cancellationToken = default); - public virtual ClientResult CancelBatchFileJob(VectorStoreBatchFileJob batchJob); + public virtual Task AddFileToVectorStoreAsync(bool waitUntilCompleted, string vectorStoreId, BinaryContent content, RequestOptions options = null); + public virtual Task AddFileToVectorStoreAsync(bool waitUntilCompleted, string vectorStoreId, string fileId, CancellationToken cancellationToken = default); + public virtual CreateBatchFileJobOperation CreateBatchFileJob(bool waitUntilCompleted, VectorStore vectorStore, IEnumerable files); [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult CancelBatchFileJob(string vectorStoreId, string batchId, RequestOptions options); - public virtual ClientResult CancelBatchFileJob(string vectorStoreId, string batchJobId, CancellationToken cancellationToken = default); - public virtual Task> CancelBatchFileJobAsync(VectorStoreBatchFileJob batchJob); + public virtual CreateBatchFileJobOperation CreateBatchFileJob(bool waitUntilCompleted, string vectorStoreId, BinaryContent content, RequestOptions options = null); + public virtual CreateBatchFileJobOperation CreateBatchFileJob(bool waitUntilCompleted, string vectorStoreId, IEnumerable fileIds, CancellationToken cancellationToken = default); + public virtual Task CreateBatchFileJobAsync(bool waitUntilCompleted, VectorStore vectorStore, IEnumerable files); [EditorBrowsable(EditorBrowsableState.Never)] - public virtual Task CancelBatchFileJobAsync(string vectorStoreId, string batchId, RequestOptions options); - public virtual Task> CancelBatchFileJobAsync(string vectorStoreId, string batchJobId, CancellationToken cancellationToken = default); - public virtual ClientResult CreateBatchFileJob(VectorStore vectorStore, IEnumerable files); - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult CreateBatchFileJob(string vectorStoreId, BinaryContent content, RequestOptions options = null); - public virtual ClientResult CreateBatchFileJob(string vectorStoreId, IEnumerable fileIds, CancellationToken cancellationToken = default); - public virtual Task> CreateBatchFileJobAsync(VectorStore vectorStore, IEnumerable files); - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual Task CreateBatchFileJobAsync(string vectorStoreId, BinaryContent content, RequestOptions options = null); - public virtual Task> CreateBatchFileJobAsync(string vectorStoreId, IEnumerable fileIds, CancellationToken cancellationToken = default); - public virtual ClientResult CreateVectorStore(VectorStoreCreationOptions vectorStore = null, CancellationToken cancellationToken = default); + public virtual Task CreateBatchFileJobAsync(bool waitUntilCompleted, string vectorStoreId, BinaryContent content, RequestOptions options = null); + public virtual Task CreateBatchFileJobAsync(bool waitUntilCompleted, string vectorStoreId, IEnumerable fileIds, CancellationToken cancellationToken = default); + public virtual CreateVectorStoreOperation CreateVectorStore(bool waitUntilCompleted, VectorStoreCreationOptions vectorStore = null, CancellationToken cancellationToken = default); [EditorBrowsable(EditorBrowsableState.Never)] + public virtual CreateVectorStoreOperation CreateVectorStore(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null); public virtual ClientResult CreateVectorStore(BinaryContent content, RequestOptions options = null); - public virtual Task> CreateVectorStoreAsync(VectorStoreCreationOptions vectorStore = null, CancellationToken cancellationToken = default); + public virtual Task CreateVectorStoreAsync(bool waitUntilCompleted, VectorStoreCreationOptions vectorStore = null, CancellationToken cancellationToken = default); + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual Task CreateVectorStoreAsync(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null); public virtual Task CreateVectorStoreAsync(BinaryContent content, RequestOptions options = null); public virtual ClientResult DeleteVectorStore(VectorStore vectorStore); [EditorBrowsable(EditorBrowsableState.Never)] @@ -2096,14 +2178,6 @@ public class VectorStoreClient { [EditorBrowsable(EditorBrowsableState.Never)] public virtual Task DeleteVectorStoreAsync(string vectorStoreId, RequestOptions options); public virtual Task> DeleteVectorStoreAsync(string vectorStoreId, CancellationToken cancellationToken = default); - public virtual ClientResult GetBatchFileJob(VectorStoreBatchFileJob batchJob); - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult GetBatchFileJob(string vectorStoreId, string batchId, RequestOptions options); - public virtual ClientResult GetBatchFileJob(string vectorStoreId, string batchJobId, CancellationToken cancellationToken = default); - public virtual Task> GetBatchFileJobAsync(VectorStoreBatchFileJob batchJob); - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual Task GetBatchFileJobAsync(string vectorStoreId, string batchId, RequestOptions options); - public virtual Task> GetBatchFileJobAsync(string vectorStoreId, string batchJobId, CancellationToken cancellationToken = default); public virtual ClientResult GetFileAssociation(VectorStore vectorStore, OpenAIFileInfo file); [EditorBrowsable(EditorBrowsableState.Never)] public virtual ClientResult GetFileAssociation(string vectorStoreId, string fileId, RequestOptions options); @@ -2113,25 +2187,15 @@ public class VectorStoreClient { public virtual Task GetFileAssociationAsync(string vectorStoreId, string fileId, RequestOptions options); public virtual Task> GetFileAssociationAsync(string vectorStoreId, string fileId, CancellationToken cancellationToken = default); public virtual PageCollection GetFileAssociations(VectorStore vectorStore, VectorStoreFileAssociationCollectionOptions options = null); - public virtual PageCollection GetFileAssociations(VectorStoreBatchFileJob batchJob, VectorStoreFileAssociationCollectionOptions options = null); public virtual PageCollection GetFileAssociations(ContinuationToken firstPageToken, CancellationToken cancellationToken = default); public virtual PageCollection GetFileAssociations(string vectorStoreId, VectorStoreFileAssociationCollectionOptions options = null, CancellationToken cancellationToken = default); [EditorBrowsable(EditorBrowsableState.Never)] public virtual IEnumerable GetFileAssociations(string vectorStoreId, int? limit, string order, string after, string before, string filter, RequestOptions options); - public virtual PageCollection GetFileAssociations(string vectorStoreId, string batchJobId, VectorStoreFileAssociationCollectionOptions options = null, CancellationToken cancellationToken = default); - public virtual PageCollection GetFileAssociations(string vectorStoreId, string batchJobId, ContinuationToken firstPageToken, CancellationToken cancellationToken = default); - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual IEnumerable GetFileAssociations(string vectorStoreId, string batchId, int? limit, string order, string after, string before, string filter, RequestOptions options); public virtual AsyncPageCollection GetFileAssociationsAsync(VectorStore vectorStore, VectorStoreFileAssociationCollectionOptions options = null); - public virtual AsyncPageCollection GetFileAssociationsAsync(VectorStoreBatchFileJob batchJob, VectorStoreFileAssociationCollectionOptions options = null); public virtual AsyncPageCollection GetFileAssociationsAsync(ContinuationToken firstPageToken, CancellationToken cancellationToken = default); public virtual AsyncPageCollection GetFileAssociationsAsync(string vectorStoreId, VectorStoreFileAssociationCollectionOptions options = null, CancellationToken cancellationToken = default); [EditorBrowsable(EditorBrowsableState.Never)] public virtual IAsyncEnumerable GetFileAssociationsAsync(string vectorStoreId, int? limit, string order, string after, string before, string filter, RequestOptions options); - public virtual AsyncPageCollection GetFileAssociationsAsync(string vectorStoreId, string batchJobId, VectorStoreFileAssociationCollectionOptions options = null, CancellationToken cancellationToken = default); - public virtual AsyncPageCollection GetFileAssociationsAsync(string vectorStoreId, string batchJobId, ContinuationToken firstPageToken, CancellationToken cancellationToken = default); - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual IAsyncEnumerable GetFileAssociationsAsync(string vectorStoreId, string batchId, int? limit, string order, string after, string before, string filter, RequestOptions options); public virtual ClientResult GetVectorStore(VectorStore vectorStore); [EditorBrowsable(EditorBrowsableState.Never)] public virtual ClientResult GetVectorStore(string vectorStoreId, RequestOptions options); diff --git a/src/Custom/Assistants/AssistantClient.Protocol.cs b/src/Custom/Assistants/AssistantClient.Protocol.cs index d96ee961..dace80d8 100644 --- a/src/Custom/Assistants/AssistantClient.Protocol.cs +++ b/src/Custom/Assistants/AssistantClient.Protocol.cs @@ -70,7 +70,7 @@ public virtual ClientResult CreateAssistant(BinaryContent content, RequestOption [EditorBrowsable(EditorBrowsableState.Never)] public virtual IAsyncEnumerable GetAssistantsAsync(int? limit, string order, string after, string before, RequestOptions options) { - AssistantsPageEnumerator enumerator = new AssistantsPageEnumerator(_pipeline, _endpoint, limit, order, after, before, options); + AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, limit, order, after, before, options); return PageCollectionHelpers.CreateAsync(enumerator); } @@ -101,7 +101,7 @@ public virtual IAsyncEnumerable GetAssistantsAsync(int? limit, str [EditorBrowsable(EditorBrowsableState.Never)] public virtual IEnumerable GetAssistants(int? limit, string order, string after, string before, RequestOptions options) { - AssistantsPageEnumerator enumerator = new AssistantsPageEnumerator(_pipeline, _endpoint, limit, order, after, before, options); + AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, limit, order, after, before, options); return PageCollectionHelpers.Create(enumerator); } @@ -259,7 +259,7 @@ public virtual IAsyncEnumerable GetMessagesAsync(string threadId, { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - MessagesPageEnumerator enumerator = new MessagesPageEnumerator(_pipeline, _endpoint, threadId, limit, order, after, before, options); + MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, threadId, limit, order, after, before, options); return PageCollectionHelpers.CreateAsync(enumerator); } @@ -295,7 +295,7 @@ public virtual IEnumerable GetMessages(string threadId, int? limit { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - MessagesPageEnumerator enumerator = new MessagesPageEnumerator(_pipeline, _endpoint, threadId, limit, order, after, before, options); + MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, threadId, limit, order, after, before, options); return PageCollectionHelpers.Create(enumerator); } @@ -381,7 +381,7 @@ public virtual IAsyncEnumerable GetRunsAsync(string threadId, int? { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - RunsPageEnumerator enumerator = new RunsPageEnumerator(_pipeline, _endpoint, threadId, limit, order, after, before, options); + RunsPageEnumerator enumerator = new(_pipeline, _endpoint, threadId, limit, order, after, before, options); return PageCollectionHelpers.CreateAsync(enumerator); } @@ -417,7 +417,7 @@ public virtual IEnumerable GetRuns(string threadId, int? limit, st { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - RunsPageEnumerator enumerator = new RunsPageEnumerator(_pipeline, _endpoint, threadId, limit, order, after, before, options); + RunsPageEnumerator enumerator = new(_pipeline, _endpoint, threadId, limit, order, after, before, options); return PageCollectionHelpers.Create(enumerator); } @@ -495,7 +495,7 @@ public virtual IAsyncEnumerable GetRunStepsAsync(string threadId, Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - RunStepsPageEnumerator enumerator = new RunStepsPageEnumerator(_pipeline, _endpoint, threadId, runId, limit, order, after, before, options); + RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, threadId, runId, limit, order, after, before, options); return PageCollectionHelpers.CreateAsync(enumerator); } @@ -533,7 +533,7 @@ public virtual IEnumerable GetRunSteps(string threadId, string run Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - RunStepsPageEnumerator enumerator = new RunStepsPageEnumerator(_pipeline, _endpoint, threadId, runId, limit, order, after, before, options); + RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, threadId, runId, limit, order, after, before, options); return PageCollectionHelpers.Create(enumerator); } diff --git a/src/Custom/Assistants/AssistantClient.cs b/src/Custom/Assistants/AssistantClient.cs index 611ef7ed..3ca8be2f 100644 --- a/src/Custom/Assistants/AssistantClient.cs +++ b/src/Custom/Assistants/AssistantClient.cs @@ -118,14 +118,8 @@ public virtual AsyncPageCollection GetAssistantsAsync( AssistantCollectionOptions options = default, CancellationToken cancellationToken = default) { - AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetAssistantsAsync(options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -144,14 +138,8 @@ public virtual AsyncPageCollection GetAssistantsAsync( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); AssistantsPageToken pageToken = AssistantsPageToken.FromToken(firstPageToken); - AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetAssistantsAsync(pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken.Before, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -167,14 +155,8 @@ public virtual PageCollection GetAssistants( AssistantCollectionOptions options = default, CancellationToken cancellationToken = default) { - AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetAssistants(options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as PageCollection; } /// @@ -193,14 +175,8 @@ public virtual PageCollection GetAssistants( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); AssistantsPageToken pageToken = AssistantsPageToken.FromToken(firstPageToken); - AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetAssistants(pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken.Before, cancellationToken.ToRequestOptions()) + as PageCollection; } /// @@ -486,15 +462,8 @@ public virtual AsyncPageCollection GetMessagesAsync( { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, - threadId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetMessagesAsync(threadId, options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -513,15 +482,8 @@ public virtual AsyncPageCollection GetMessagesAsync( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); MessagesPageToken pageToken = MessagesPageToken.FromToken(firstPageToken); - MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.ThreadId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetMessagesAsync(pageToken?.ThreadId, pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -541,15 +503,8 @@ public virtual PageCollection GetMessages( { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, - threadId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetMessages(threadId, options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as PageCollection; } /// @@ -568,15 +523,9 @@ public virtual PageCollection GetMessages( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); MessagesPageToken pageToken = MessagesPageToken.FromToken(firstPageToken); - MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.ThreadId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetMessages(pageToken?.ThreadId, pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, cancellationToken.ToRequestOptions()) + as PageCollection; + } /// @@ -888,15 +837,8 @@ public virtual AsyncPageCollection GetRunsAsync( { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - RunsPageEnumerator enumerator = new(_pipeline, _endpoint, - threadId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetRunsAsync(threadId, options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -915,15 +857,8 @@ public virtual AsyncPageCollection GetRunsAsync( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); RunsPageToken pageToken = RunsPageToken.FromToken(firstPageToken); - RunsPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.ThreadId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetRunsAsync(pageToken?.ThreadId, pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -943,15 +878,8 @@ public virtual PageCollection GetRuns( { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - RunsPageEnumerator enumerator = new(_pipeline, _endpoint, - threadId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetRuns(threadId, options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as PageCollection; } /// @@ -970,15 +898,8 @@ public virtual PageCollection GetRuns( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); RunsPageToken pageToken = RunsPageToken.FromToken(firstPageToken); - RunsPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.ThreadId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetRuns(pageToken?.ThreadId, pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, cancellationToken.ToRequestOptions()) + as PageCollection; } /// @@ -1168,16 +1089,8 @@ public virtual AsyncPageCollection GetRunStepsAsync( Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, - threadId, - runId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetRunStepsAsync(threadId, runId, options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -1196,16 +1109,8 @@ public virtual AsyncPageCollection GetRunStepsAsync( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); RunStepsPageToken pageToken = RunStepsPageToken.FromToken(firstPageToken); - RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.ThreadId, - pageToken.RunId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetRunStepsAsync(pageToken?.ThreadId, pageToken?.RunId, pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -1228,16 +1133,8 @@ public virtual PageCollection GetRunSteps( Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, - threadId, - runId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetRunSteps(threadId, runId, options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as PageCollection; } /// @@ -1256,16 +1153,8 @@ public virtual PageCollection GetRunSteps( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); RunStepsPageToken pageToken = RunStepsPageToken.FromToken(firstPageToken); - RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.ThreadId, - pageToken.RunId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetRunSteps(pageToken?.ThreadId, pageToken?.RunId, pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, cancellationToken.ToRequestOptions()) + as PageCollection; } /// diff --git a/src/Custom/Assistants/Internal/Pagination/AssistantsPageEnumerator.cs b/src/Custom/Assistants/Internal/Pagination/AssistantsPageEnumerator.cs index 634dd6b1..66019bbb 100644 --- a/src/Custom/Assistants/Internal/Pagination/AssistantsPageEnumerator.cs +++ b/src/Custom/Assistants/Internal/Pagination/AssistantsPageEnumerator.cs @@ -21,6 +21,8 @@ internal partial class AssistantsPageEnumerator : PageEnumerator private readonly string _before; private readonly RequestOptions _options; + public virtual ClientPipeline Pipeline => _pipeline; + public AssistantsPageEnumerator( ClientPipeline pipeline, Uri endpoint, diff --git a/src/Custom/Assistants/Internal/Pagination/MessagesPageEnumerator.cs b/src/Custom/Assistants/Internal/Pagination/MessagesPageEnumerator.cs index c8ee0603..53e09fec 100644 --- a/src/Custom/Assistants/Internal/Pagination/MessagesPageEnumerator.cs +++ b/src/Custom/Assistants/Internal/Pagination/MessagesPageEnumerator.cs @@ -22,6 +22,8 @@ internal partial class MessagesPageEnumerator : PageEnumerator private readonly string _before; private readonly RequestOptions _options; + public virtual ClientPipeline Pipeline => _pipeline; + public MessagesPageEnumerator( ClientPipeline pipeline, Uri endpoint, diff --git a/src/Custom/Assistants/Internal/Pagination/RunStepsPageEnumerator.cs b/src/Custom/Assistants/Internal/Pagination/RunStepsPageEnumerator.cs index d6d74d7d..b5992ce7 100644 --- a/src/Custom/Assistants/Internal/Pagination/RunStepsPageEnumerator.cs +++ b/src/Custom/Assistants/Internal/Pagination/RunStepsPageEnumerator.cs @@ -23,6 +23,8 @@ internal partial class RunStepsPageEnumerator : PageEnumerator private string? _after; + public virtual ClientPipeline Pipeline => _pipeline; + public RunStepsPageEnumerator( ClientPipeline pipeline, Uri endpoint, diff --git a/src/Custom/Assistants/Internal/Pagination/RunsPageEnumerator.cs b/src/Custom/Assistants/Internal/Pagination/RunsPageEnumerator.cs index 3a0b827d..f48ceb3c 100644 --- a/src/Custom/Assistants/Internal/Pagination/RunsPageEnumerator.cs +++ b/src/Custom/Assistants/Internal/Pagination/RunsPageEnumerator.cs @@ -22,6 +22,8 @@ internal partial class RunsPageEnumerator : PageEnumerator private readonly string _before; private readonly RequestOptions _options; + public virtual ClientPipeline Pipeline => _pipeline; + public RunsPageEnumerator( ClientPipeline pipeline, Uri endpoint, diff --git a/src/Custom/Batch/BatchClient.Protocol.cs b/src/Custom/Batch/BatchClient.Protocol.cs index 11cc1097..78aacf24 100644 --- a/src/Custom/Batch/BatchClient.Protocol.cs +++ b/src/Custom/Batch/BatchClient.Protocol.cs @@ -1,6 +1,7 @@ using System; using System.ClientModel; using System.ClientModel.Primitives; +using System.Text.Json; using System.Threading.Tasks; namespace OpenAI.Batch; @@ -10,33 +11,58 @@ public partial class BatchClient /// /// [Protocol Method] Creates and executes a batch from an uploaded file of requests /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// is null. /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task CreateBatchAsync(BinaryContent content, RequestOptions options = null) + /// A that can be used to wait for + /// the operation to complete, or cancel the operation. + public virtual async Task CreateBatchAsync(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null) { Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateCreateBatchRequest(content, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + + PipelineResponse response = await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string batchId = doc.RootElement.GetProperty("id"u8).GetString(); + string status = doc.RootElement.GetProperty("status"u8).GetString(); + + CreateBatchOperation operation = new(_pipeline, _endpoint, batchId, status, response); + return await operation.WaitUntilAsync(waitUntilCompleted, options).ConfigureAwait(false); } /// /// [Protocol Method] Creates and executes a batch from an uploaded file of requests /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// is null. /// Service returned a non-success status code. - /// The response returned from the service. - public virtual ClientResult CreateBatch(BinaryContent content, RequestOptions options = null) + /// A that can be used to wait for + /// the operation to complete, or cancel the operation. + public virtual CreateBatchOperation CreateBatch(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null) { Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateCreateBatchRequest(content, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + PipelineResponse response = _pipeline.ProcessMessage(message, options); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string batchId = doc.RootElement.GetProperty("id"u8).GetString(); + string status = doc.RootElement.GetProperty("status"u8).GetString(); + + CreateBatchOperation operation = new(_pipeline, _endpoint, batchId, status, response); + return operation.WaitUntil(waitUntilCompleted, options); } /// @@ -76,7 +102,7 @@ public virtual ClientResult GetBatches(string after, int? limit, RequestOptions /// is an empty string, and was expected to be non-empty. /// Service returned a non-success status code. /// The response returned from the service. - public virtual async Task GetBatchAsync(string batchId, RequestOptions options) + internal virtual async Task GetBatchAsync(string batchId, RequestOptions options) { Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); @@ -93,45 +119,11 @@ public virtual async Task GetBatchAsync(string batchId, RequestOpt /// is an empty string, and was expected to be non-empty. /// Service returned a non-success status code. /// The response returned from the service. - public virtual ClientResult GetBatch(string batchId, RequestOptions options) + internal virtual ClientResult GetBatch(string batchId, RequestOptions options) { Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); using PipelineMessage message = CreateRetrieveBatchRequest(batchId, options); return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); } - - /// - /// [Protocol Method] Cancels an in-progress batch. - /// - /// The ID of the batch to cancel. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task CancelBatchAsync(string batchId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); - - using PipelineMessage message = CreateCancelBatchRequest(batchId, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); - } - - /// - /// [Protocol Method] Cancels an in-progress batch. - /// - /// The ID of the batch to cancel. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual ClientResult CancelBatch(string batchId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); - - using PipelineMessage message = CreateCancelBatchRequest(batchId, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); - } } diff --git a/src/Custom/Batch/BatchClient.cs b/src/Custom/Batch/BatchClient.cs index adce1b0d..484444de 100644 --- a/src/Custom/Batch/BatchClient.cs +++ b/src/Custom/Batch/BatchClient.cs @@ -2,7 +2,6 @@ using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; -using System.Threading.Tasks; namespace OpenAI.Batch; @@ -10,16 +9,22 @@ namespace OpenAI.Batch; [CodeGenSuppress("BatchClient", typeof(ClientPipeline), typeof(ApiKeyCredential), typeof(Uri))] [CodeGenSuppress("CreateBatch", typeof(string), typeof(InternalCreateBatchRequestEndpoint), typeof(InternalBatchCompletionTimeframe), typeof(IDictionary))] [CodeGenSuppress("CreateBatchAsync", typeof(string), typeof(InternalCreateBatchRequestEndpoint), typeof(InternalBatchCompletionTimeframe), typeof(IDictionary))] +[CodeGenSuppress("CreateBatch", typeof(BinaryContent), typeof(RequestOptions))] +[CodeGenSuppress("CreateBatchAsync", typeof(BinaryContent), typeof(RequestOptions))] [CodeGenSuppress("RetrieveBatch", typeof(string))] [CodeGenSuppress("RetrieveBatchAsync", typeof(string))] [CodeGenSuppress("RetrieveBatch", typeof(string), typeof(RequestOptions))] [CodeGenSuppress("RetrieveBatchAsync", typeof(string), typeof(RequestOptions))] [CodeGenSuppress("CancelBatch", typeof(string))] [CodeGenSuppress("CancelBatchAsync", typeof(string))] +[CodeGenSuppress("CancelBatch", typeof(string), typeof(RequestOptions))] +[CodeGenSuppress("CancelBatchAsync", typeof(string), typeof(RequestOptions))] [CodeGenSuppress("GetBatches", typeof(string), typeof(int?))] [CodeGenSuppress("GetBatchesAsync", typeof(string), typeof(int?))] public partial class BatchClient { + internal Uri Endpoint => _endpoint; + /// /// Initializes a new instance of that will use an API key when authenticating. /// diff --git a/src/Custom/Batch/CreateBatchOperation.Protocol.cs b/src/Custom/Batch/CreateBatchOperation.Protocol.cs new file mode 100644 index 00000000..4f237429 --- /dev/null +++ b/src/Custom/Batch/CreateBatchOperation.Protocol.cs @@ -0,0 +1,255 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.Batch; + +/// +/// A long-running operation for executing a batch from an uploaded file of +/// requests. +/// +public partial class CreateBatchOperation : OperationResult +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _batchId; + + private PollingInterval? _pollingInterval; + + internal CreateBatchOperation( + ClientPipeline pipeline, + Uri endpoint, + string batchId, + string status, + PipelineResponse response) + : base(response) + { + _pipeline = pipeline; + _endpoint = endpoint; + _batchId = batchId; + + IsCompleted = GetIsCompleted(status); + RehydrationToken = new CreateBatchOperationToken(batchId); + } + + public string BatchId => _batchId; + + /// + public override ContinuationToken? RehydrationToken { get; protected set; } + + /// + public override bool IsCompleted { get; protected set; } + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static async Task RehydrateAsync(BatchClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + CreateBatchOperationToken token = CreateBatchOperationToken.FromToken(rehydrationToken); + + ClientResult result = await client.GetBatchAsync(token.BatchId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string status = doc.RootElement.GetProperty("status"u8).GetString()!; + + return new CreateBatchOperation(client.Pipeline, client.Endpoint, token.BatchId, status, response); + } + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static CreateBatchOperation Rehydrate(BatchClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + CreateBatchOperationToken token = CreateBatchOperationToken.FromToken(rehydrationToken); + + ClientResult result = client.GetBatch(token.BatchId, cancellationToken.ToRequestOptions()); + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string status = doc.RootElement.GetProperty("status"u8).GetString()!; + + return new CreateBatchOperation(client.Pipeline, client.Endpoint, token.BatchId, status, response); + } + + /// + public override async Task WaitForCompletionAsync(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + await _pollingInterval.WaitAsync(response, cancellationToken); + + ClientResult result = await GetBatchAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); + + ApplyUpdate(result); + } + } + + /// + public override void WaitForCompletion(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + _pollingInterval.Wait(response, cancellationToken); + + ClientResult result = GetBatch(cancellationToken.ToRequestOptions()); + + ApplyUpdate(result); + } + } + + internal async Task WaitUntilAsync(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + await WaitForCompletionAsync(options?.CancellationToken ?? default).ConfigureAwait(false); + return this; + } + + internal CreateBatchOperation WaitUntil(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + WaitForCompletion(options?.CancellationToken ?? default); + return this; + } + + private void ApplyUpdate(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string? status = doc.RootElement.GetProperty("status"u8).GetString(); + + IsCompleted = GetIsCompleted(status); + SetRawResponse(response); + } + + private static bool GetIsCompleted(string? status) + { + return status == InternalBatchStatus.Completed || + status == InternalBatchStatus.Cancelled || + status == InternalBatchStatus.Expired || + status == InternalBatchStatus.Failed; + } + + // Generated protocol methods + + /// + /// [Protocol Method] Retrieves a batch. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task GetBatchAsync(RequestOptions? options) + { + using PipelineMessage message = CreateRetrieveBatchRequest(_batchId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] Retrieves a batch. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult GetBatch(RequestOptions? options) + { + using PipelineMessage message = CreateRetrieveBatchRequest(_batchId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + /// + /// [Protocol Method] Cancels an in-progress batch. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task CancelBatchAsync( RequestOptions? options) + { + using PipelineMessage message = CreateCancelBatchRequest(_batchId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] Cancels an in-progress batch. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult CancelBatch(RequestOptions? options) + { + using PipelineMessage message = CreateCancelBatchRequest(_batchId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + internal PipelineMessage CreateRetrieveBatchRequest(string batchId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/batches/", false); + uri.AppendPath(batchId, true); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + internal PipelineMessage CreateCancelBatchRequest(string batchId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "POST"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/batches/", false); + uri.AppendPath(batchId, true); + uri.AppendPath("/cancel", false); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} \ No newline at end of file diff --git a/src/Custom/Batch/CreateBatchOperationToken.cs b/src/Custom/Batch/CreateBatchOperationToken.cs new file mode 100644 index 00000000..78b0d61c --- /dev/null +++ b/src/Custom/Batch/CreateBatchOperationToken.cs @@ -0,0 +1,90 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.Batch; + +internal class CreateBatchOperationToken : ContinuationToken +{ + public CreateBatchOperationToken(string batchId) + { + BatchId = batchId; + } + + public string BatchId { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + writer.WriteStartObject(); + + writer.WriteString("batchId", BatchId); + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public static CreateBatchOperationToken FromToken(ContinuationToken continuationToken) + { + if (continuationToken is CreateBatchOperationToken token) + { + return token; + } + + BinaryData data = continuationToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create CreateBatchOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + Utf8JsonReader reader = new(data); + + string batchId = null!; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "batchId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + batchId = reader.GetString()!; + break; + + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (batchId is null) + { + throw new ArgumentException("Failed to create CreateBatchOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + return new(batchId); + } +} + diff --git a/src/Custom/FineTuning/CreateJobOperation.Protocol.cs b/src/Custom/FineTuning/CreateJobOperation.Protocol.cs new file mode 100644 index 00000000..e9601dd0 --- /dev/null +++ b/src/Custom/FineTuning/CreateJobOperation.Protocol.cs @@ -0,0 +1,380 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.FineTuning; + +/// +/// A long-running operation for creating a new model from a given dataset. +/// +public partial class CreateJobOperation : OperationResult +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _jobId; + + private PollingInterval? _pollingInterval; + + internal CreateJobOperation( + ClientPipeline pipeline, + Uri endpoint, + string jobId, + string status, + PipelineResponse response) : base(response) + { + _pipeline = pipeline; + _endpoint = endpoint; + _jobId = jobId; + + IsCompleted = GetIsCompleted(status); + RehydrationToken = new CreateJobOperationToken(jobId); + } + + public string JobId => _jobId; + + /// + public override ContinuationToken? RehydrationToken { get; protected set; } + + /// + public override bool IsCompleted { get; protected set; } + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static async Task RehydrateAsync(FineTuningClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + CreateJobOperationToken token = CreateJobOperationToken.FromToken(rehydrationToken); + + ClientResult result = await client.GetJobAsync(token.JobId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string status = doc.RootElement.GetProperty("status"u8).GetString()!; + + return new CreateJobOperation(client.Pipeline, client.Endpoint, token.JobId, status, response); + } + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static CreateJobOperation Rehydrate(FineTuningClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + CreateJobOperationToken token = CreateJobOperationToken.FromToken(rehydrationToken); + + ClientResult result = client.GetJob(token.JobId, cancellationToken.ToRequestOptions()); + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string status = doc.RootElement.GetProperty("status"u8).GetString()!; + + return new CreateJobOperation(client.Pipeline, client.Endpoint, token.JobId, status, response); + } + + /// + public override async Task WaitForCompletionAsync(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + await _pollingInterval.WaitAsync(response, cancellationToken); + + ClientResult result = await GetJobAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); + + ApplyUpdate(result); + } + } + + /// + public override void WaitForCompletion(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + _pollingInterval.Wait(response, cancellationToken); + + ClientResult result = GetJob(cancellationToken.ToRequestOptions()); + + ApplyUpdate(result); + } + } + + internal async Task WaitUntilAsync(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + await WaitForCompletionAsync(options?.CancellationToken ?? default).ConfigureAwait(false); + return this; + } + + internal CreateJobOperation WaitUntil(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + WaitForCompletion(options?.CancellationToken ?? default); + return this; + } + + private void ApplyUpdate(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string? status = doc.RootElement.GetProperty("status"u8).GetString(); + + IsCompleted = GetIsCompleted(status); + SetRawResponse(response); + } + + private static bool GetIsCompleted(string? status) + { + return status == InternalFineTuningJobStatus.Succeeded || + status == InternalFineTuningJobStatus.Failed || + status == InternalFineTuningJobStatus.Cancelled; + } + + // Generated protocol methods + + // CUSTOM: + // - Renamed. + // - Edited doc comment. + /// + /// [Protocol Method] Get info about a fine-tuning job. + /// + /// [Learn more about fine-tuning](/docs/guides/fine-tuning) + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task GetJobAsync(RequestOptions? options) + { + using PipelineMessage message = CreateRetrieveFineTuningJobRequest(_jobId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + // CUSTOM: + // - Renamed. + // - Edited doc comment. + /// + /// [Protocol Method] Get info about a fine-tuning job. + /// + /// [Learn more about fine-tuning](/docs/guides/fine-tuning) + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult GetJob(RequestOptions? options) + { + using PipelineMessage message = CreateRetrieveFineTuningJobRequest(_jobId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + // CUSTOM: + // - Renamed. + // - Edited doc comment. + /// + /// [Protocol Method] Immediately cancel a fine-tune job. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task CancelJobAsync(RequestOptions? options) + { + using PipelineMessage message = CreateCancelFineTuningJobRequest(_jobId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + // CUSTOM: + // - Renamed. + // - Edited doc comment. + /// + /// [Protocol Method] Immediately cancel a fine-tune job. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult CancelJob(RequestOptions? options) + { + using PipelineMessage message = CreateCancelFineTuningJobRequest(_jobId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + // CUSTOM: + // - Renamed. + // - Edited doc comment. + /// + /// [Protocol Method] Get status updates for a fine-tuning job. + /// + /// Identifier for the last event from the previous pagination request. + /// Number of events to retrieve. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task GetJobEventsAsync(string after, int? limit, RequestOptions? options) + { + using PipelineMessage message = CreateGetFineTuningEventsRequest(_jobId, after, limit, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + // CUSTOM: + // - Renamed. + // - Edited doc comment. + /// + /// [Protocol Method] Get status updates for a fine-tuning job. + /// + /// Identifier for the last event from the previous pagination request. + /// Number of events to retrieve. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult GetJobEvents(string after, int? limit, RequestOptions? options) + { + using PipelineMessage message = CreateGetFineTuningEventsRequest(_jobId, after, limit, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + /// + /// [Protocol Method] List the checkpoints for a fine-tuning job. + /// + /// Identifier for the last checkpoint ID from the previous pagination request. + /// Number of checkpoints to retrieve. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task GetJobCheckpointsAsync(string after, int? limit, RequestOptions? options) + { + using PipelineMessage message = CreateGetFineTuningJobCheckpointsRequest(_jobId, after, limit, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] List the checkpoints for a fine-tuning job. + /// + /// Identifier for the last checkpoint ID from the previous pagination request. + /// Number of checkpoints to retrieve. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult GetJobCheckpoints(string after, int? limit, RequestOptions? options) + { + using PipelineMessage message = CreateGetFineTuningJobCheckpointsRequest(_jobId, after, limit, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + internal PipelineMessage CreateRetrieveFineTuningJobRequest(string fineTuningJobId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/fine_tuning/jobs/", false); + uri.AppendPath(fineTuningJobId, true); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + internal PipelineMessage CreateCancelFineTuningJobRequest(string fineTuningJobId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "POST"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/fine_tuning/jobs/", false); + uri.AppendPath(fineTuningJobId, true); + uri.AppendPath("/cancel", false); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + internal PipelineMessage CreateGetFineTuningJobCheckpointsRequest(string fineTuningJobId, string after, int? limit, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/fine_tuning/jobs/", false); + uri.AppendPath(fineTuningJobId, true); + uri.AppendPath("/checkpoints", false); + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + internal PipelineMessage CreateGetFineTuningEventsRequest(string fineTuningJobId, string after, int? limit, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/fine_tuning/jobs/", false); + uri.AppendPath(fineTuningJobId, true); + uri.AppendPath("/events", false); + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/Custom/FineTuning/CreateJobOperationToken.cs b/src/Custom/FineTuning/CreateJobOperationToken.cs new file mode 100644 index 00000000..9050970b --- /dev/null +++ b/src/Custom/FineTuning/CreateJobOperationToken.cs @@ -0,0 +1,91 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.FineTuning; + +internal class CreateJobOperationToken : ContinuationToken +{ + public CreateJobOperationToken(string jobId) + { + JobId = jobId; + } + + public string JobId { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + + writer.WriteStartObject(); + + writer.WriteString("jobId", JobId); + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public static CreateJobOperationToken FromToken(ContinuationToken continuationToken) + { + if (continuationToken is CreateJobOperationToken token) + { + return token; + } + + BinaryData data = continuationToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create FineTuningJobOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + Utf8JsonReader reader = new(data); + + string jobId = null!; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "jobId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + jobId = reader.GetString()!; + break; + + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (jobId is null) + { + throw new ArgumentException("Failed to create FineTuningJobOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + return new(jobId); + } +} + diff --git a/src/Custom/FineTuning/FineTuningClient.Protocol.cs b/src/Custom/FineTuning/FineTuningClient.Protocol.cs index 2483478d..d5bc3233 100644 --- a/src/Custom/FineTuning/FineTuningClient.Protocol.cs +++ b/src/Custom/FineTuning/FineTuningClient.Protocol.cs @@ -1,6 +1,7 @@ using System; using System.ClientModel; using System.ClientModel.Primitives; +using System.Text.Json; using System.Threading.Tasks; namespace OpenAI.FineTuning; @@ -29,17 +30,33 @@ public partial class FineTuningClient /// /// [Learn more about fine-tuning](/docs/guides/fine-tuning) /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// is null. /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task CreateJobAsync(BinaryContent content, RequestOptions options = null) + /// A that can be used to wait for + /// the operation to complete, get information about the fine tuning job, or + /// cancel the operation. + public virtual async Task CreateJobAsync( + bool waitUntilCompleted, + BinaryContent content, + RequestOptions options = null) { Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateCreateFineTuningJobRequest(content, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + PipelineResponse response = await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string jobId = doc.RootElement.GetProperty("id"u8).GetString(); + string status = doc.RootElement.GetProperty("status"u8).GetString(); + + CreateJobOperation operation = new(_pipeline, _endpoint, jobId, status, response); + return await operation.WaitUntilAsync(waitUntilCompleted, options).ConfigureAwait(false); } // CUSTOM: @@ -52,17 +69,33 @@ public virtual async Task CreateJobAsync(BinaryContent content, Re /// /// [Learn more about fine-tuning](/docs/guides/fine-tuning) /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// is null. /// Service returned a non-success status code. - /// The response returned from the service. - public virtual ClientResult CreateJob(BinaryContent content, RequestOptions options = null) + /// A that can be used to wait for + /// the operation to complete, get information about the fine tuning job, or + /// cancel the operation. + public virtual CreateJobOperation CreateJob( + bool waitUntilCompleted, + BinaryContent content, + RequestOptions options = null) { Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateCreateFineTuningJobRequest(content, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + PipelineResponse response = _pipeline.ProcessMessage(message, options); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string jobId = doc.RootElement.GetProperty("id"u8).GetString(); + string status = doc.RootElement.GetProperty("status"u8).GetString(); + + CreateJobOperation operation = new(_pipeline, _endpoint, jobId, status, response); + return operation.WaitUntil(waitUntilCompleted, options); } // CUSTOM: @@ -113,7 +146,7 @@ public virtual ClientResult GetJobs(string after, int? limit, RequestOptions opt /// is an empty string, and was expected to be non-empty. /// Service returned a non-success status code. /// The response returned from the service. - public virtual async Task GetJobAsync(string jobId, RequestOptions options) + internal virtual async Task GetJobAsync(string jobId, RequestOptions options) { Argument.AssertNotNullOrEmpty(jobId, nameof(jobId)); @@ -135,133 +168,11 @@ public virtual async Task GetJobAsync(string jobId, RequestOptions /// is an empty string, and was expected to be non-empty. /// Service returned a non-success status code. /// The response returned from the service. - public virtual ClientResult GetJob(string jobId, RequestOptions options) + internal virtual ClientResult GetJob(string jobId, RequestOptions options) { Argument.AssertNotNullOrEmpty(jobId, nameof(jobId)); using PipelineMessage message = CreateRetrieveFineTuningJobRequest(jobId, options); return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); } - - // CUSTOM: - // - Renamed. - // - Edited doc comment. - /// - /// [Protocol Method] Immediately cancel a fine-tune job. - /// - /// The ID of the fine-tuning job to cancel. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task CancelJobAsync(string jobId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(jobId, nameof(jobId)); - - using PipelineMessage message = CreateCancelFineTuningJobRequest(jobId, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); - } - - // CUSTOM: - // - Renamed. - // - Edited doc comment. - /// - /// [Protocol Method] Immediately cancel a fine-tune job. - /// - /// The ID of the fine-tuning job to cancel. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual ClientResult CancelJob(string jobId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(jobId, nameof(jobId)); - - using PipelineMessage message = CreateCancelFineTuningJobRequest(jobId, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); - } - - // CUSTOM: - // - Renamed. - // - Edited doc comment. - /// - /// [Protocol Method] Get status updates for a fine-tuning job. - /// - /// The ID of the fine-tuning job to get events for. - /// Identifier for the last event from the previous pagination request. - /// Number of events to retrieve. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task GetJobEventsAsync(string jobId, string after, int? limit, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(jobId, nameof(jobId)); - - using PipelineMessage message = CreateGetFineTuningEventsRequest(jobId, after, limit, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); - } - - // CUSTOM: - // - Renamed. - // - Edited doc comment. - /// - /// [Protocol Method] Get status updates for a fine-tuning job. - /// - /// The ID of the fine-tuning job to get events for. - /// Identifier for the last event from the previous pagination request. - /// Number of events to retrieve. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual ClientResult GetJobEvents(string jobId, string after, int? limit, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(jobId, nameof(jobId)); - - using PipelineMessage message = CreateGetFineTuningEventsRequest(jobId, after, limit, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); - } - - /// - /// [Protocol Method] List the checkpoints for a fine-tuning job. - /// - /// The ID of the fine-tuning job to get checkpoints for. - /// Identifier for the last checkpoint ID from the previous pagination request. - /// Number of checkpoints to retrieve. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task GetJobCheckpointsAsync(string fineTuningJobId, string after, int? limit, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(fineTuningJobId, nameof(fineTuningJobId)); - - using PipelineMessage message = CreateGetFineTuningJobCheckpointsRequest(fineTuningJobId, after, limit, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); - } - - /// - /// [Protocol Method] List the checkpoints for a fine-tuning job. - /// - /// The ID of the fine-tuning job to get checkpoints for. - /// Identifier for the last checkpoint ID from the previous pagination request. - /// Number of checkpoints to retrieve. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual ClientResult GetJobCheckpoints(string fineTuningJobId, string after, int? limit, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(fineTuningJobId, nameof(fineTuningJobId)); - - using PipelineMessage message = CreateGetFineTuningJobCheckpointsRequest(fineTuningJobId, after, limit, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); - } } diff --git a/src/Custom/FineTuning/FineTuningClient.cs b/src/Custom/FineTuning/FineTuningClient.cs index 6313c783..53ac0b08 100644 --- a/src/Custom/FineTuning/FineTuningClient.cs +++ b/src/Custom/FineTuning/FineTuningClient.cs @@ -24,6 +24,8 @@ public partial class FineTuningClient { // Customization: documented constructors, apply protected visibility + internal Uri Endpoint => _endpoint; + /// /// Initializes a new instance of that will use an API key when authenticating. /// diff --git a/src/Custom/VectorStores/AddFileToVectorStoreOperation.Protocol.cs b/src/Custom/VectorStores/AddFileToVectorStoreOperation.Protocol.cs new file mode 100644 index 00000000..b5711c9a --- /dev/null +++ b/src/Custom/VectorStores/AddFileToVectorStoreOperation.Protocol.cs @@ -0,0 +1,73 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.ComponentModel; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.VectorStores; + +public partial class AddFileToVectorStoreOperation : OperationResult +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _vectorStoreId; + private readonly string _fileId; + + private PollingInterval? _pollingInterval; + + /// + public override ContinuationToken? RehydrationToken { get; protected set; } + + /// + public override bool IsCompleted { get; protected set; } + + /// + /// [Protocol Method] Retrieves a vector store file. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task GetFileAssociationAsync(RequestOptions? options) + { + using PipelineMessage message = CreateGetVectorStoreFileRequest(_vectorStoreId, _fileId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] Retrieves a vector store file. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult GetFileAssociation(RequestOptions? options) + { + using PipelineMessage message = CreateGetVectorStoreFileRequest(_vectorStoreId, _fileId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + internal PipelineMessage CreateGetVectorStoreFileRequest(string vectorStoreId, string fileId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/vector_stores/", false); + uri.AppendPath(vectorStoreId, true); + uri.AppendPath("/files/", false); + uri.AppendPath(fileId, true); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/Custom/VectorStores/AddFileToVectorStoreOperation.cs b/src/Custom/VectorStores/AddFileToVectorStoreOperation.cs new file mode 100644 index 00000000..de4fc796 --- /dev/null +++ b/src/Custom/VectorStores/AddFileToVectorStoreOperation.cs @@ -0,0 +1,194 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.VectorStores; + +[Experimental("OPENAI001")] +public partial class AddFileToVectorStoreOperation : OperationResult +{ + public AddFileToVectorStoreOperation( + ClientPipeline pipeline, + Uri endpoint, + ClientResult result) + : base(result.GetRawResponse()) + { + _pipeline = pipeline; + _endpoint = endpoint; + + Value = result; + Status = Value.Status; + + _vectorStoreId = Value.VectorStoreId; + _fileId = Value.FileId; + + IsCompleted = GetIsCompleted(Value.Status); + RehydrationToken = new AddFileToVectorStoreOperationToken(VectorStoreId, FileId); + } + + /// + /// The current value of the add file to vector store operation in progress. + /// + public VectorStoreFileAssociation? Value { get; private set; } + + /// + /// The current status of the add file to vector store operation in progress. + /// + public VectorStoreFileAssociationStatus? Status { get; private set; } + + /// + /// The ID of the vector store the file is being added to. + /// + public string VectorStoreId { get => _vectorStoreId; } + + /// + /// The ID of the file being added to the vector store. + /// + public string FileId { get => _fileId; } + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static async Task RehydrateAsync(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + AddFileToVectorStoreOperationToken token = AddFileToVectorStoreOperationToken.FromToken(rehydrationToken); + + ClientResult result = await client.GetFileAssociationAsync(token.VectorStoreId, token.FileId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); + PipelineResponse response = result.GetRawResponse(); + VectorStoreFileAssociation value = VectorStoreFileAssociation.FromResponse(response); + + return new AddFileToVectorStoreOperation(client.Pipeline, client.Endpoint, FromValue(value, response)); + } + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static AddFileToVectorStoreOperation Rehydrate(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + AddFileToVectorStoreOperationToken token = AddFileToVectorStoreOperationToken.FromToken(rehydrationToken); + + ClientResult result = client.GetFileAssociation(token.VectorStoreId, token.FileId, cancellationToken.ToRequestOptions()); + PipelineResponse response = result.GetRawResponse(); + VectorStoreFileAssociation value = VectorStoreFileAssociation.FromResponse(response); + + return new AddFileToVectorStoreOperation(client.Pipeline, client.Endpoint, FromValue(value, response)); + } + + /// + public override async Task WaitForCompletionAsync(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + await _pollingInterval.WaitAsync(response, cancellationToken); + + ClientResult result = await GetFileAssociationAsync(cancellationToken).ConfigureAwait(false); + + ApplyUpdate(result); + } + } + + public override void WaitForCompletion(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + _pollingInterval.Wait(response, cancellationToken); + + ClientResult result = GetFileAssociation(cancellationToken); + + ApplyUpdate(result); + } + } + + internal async Task WaitUntilAsync(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + await WaitForCompletionAsync(options?.CancellationToken ?? default).ConfigureAwait(false); + return this; + } + + internal AddFileToVectorStoreOperation WaitUntil(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + WaitForCompletion(options?.CancellationToken ?? default); + return this; + } + + private void ApplyUpdate(ClientResult update) + { + Value = update; + Status = Value.Status; + + IsCompleted = GetIsCompleted(Value.Status); + SetRawResponse(update.GetRawResponse()); + } + + private static bool GetIsCompleted(VectorStoreFileAssociationStatus status) + { + return status == VectorStoreFileAssociationStatus.Completed || + status == VectorStoreFileAssociationStatus.Cancelled || + status == VectorStoreFileAssociationStatus.Failed; + } + + /// + /// Gets a instance representing an existing association between a known + /// vector store ID and file ID. + /// + /// A token that can be used to cancel this method call. + /// A instance. + public virtual async Task> GetFileAssociationAsync(CancellationToken cancellationToken = default) + { + ClientResult result = await GetFileAssociationAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); + PipelineResponse response = result.GetRawResponse(); + VectorStoreFileAssociation value = VectorStoreFileAssociation.FromResponse(response); + return ClientResult.FromValue(value, response); + } + + /// + /// Gets a instance representing an existing association between a known + /// vector store ID and file ID. + /// + /// A token that can be used to cancel this method call. + /// A instance. + public virtual ClientResult GetFileAssociation(CancellationToken cancellationToken = default) + { + ClientResult result = GetFileAssociation(cancellationToken.ToRequestOptions()); + PipelineResponse response = result.GetRawResponse(); + VectorStoreFileAssociation value = VectorStoreFileAssociation.FromResponse(response); + return ClientResult.FromValue(value, response); + } +} diff --git a/src/Custom/VectorStores/AddFileToVectorStoreOperationToken.cs b/src/Custom/VectorStores/AddFileToVectorStoreOperationToken.cs new file mode 100644 index 00000000..1f8555eb --- /dev/null +++ b/src/Custom/VectorStores/AddFileToVectorStoreOperationToken.cs @@ -0,0 +1,101 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.VectorStores; + +internal class AddFileToVectorStoreOperationToken : ContinuationToken +{ + public AddFileToVectorStoreOperationToken(string vectorStoreId, string fileId) + { + VectorStoreId = vectorStoreId; + FileId = fileId; + } + + public string VectorStoreId { get; } + + public string FileId { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + writer.WriteStartObject(); + + writer.WriteString("vectorStoreId", VectorStoreId); + writer.WriteString("fileId", FileId); + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public static AddFileToVectorStoreOperationToken FromToken(ContinuationToken continuationToken) + { + if (continuationToken is AddFileToVectorStoreOperationToken token) + { + return token; + } + + BinaryData data = continuationToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create AddFileToVectorStoreOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + Utf8JsonReader reader = new(data); + + string vectorStoreId = null!; + string fileId = null!; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "vectorStoreId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + vectorStoreId = reader.GetString()!; + break; + + case "fileId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + fileId = reader.GetString()!; + break; + + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (vectorStoreId is null || fileId is null) + { + throw new ArgumentException("Failed to create AddFileToVectorStoreOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + return new(vectorStoreId, fileId); + } +} + diff --git a/src/Custom/VectorStores/CreateBatchFileJobOperation.Protocol.cs b/src/Custom/VectorStores/CreateBatchFileJobOperation.Protocol.cs new file mode 100644 index 00000000..44c604e8 --- /dev/null +++ b/src/Custom/VectorStores/CreateBatchFileJobOperation.Protocol.cs @@ -0,0 +1,227 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.VectorStores; + +/// +/// Long-running operation for creating a vector store file batch. +/// +public partial class CreateBatchFileJobOperation : OperationResult +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _vectorStoreId; + private readonly string _batchId; + + private PollingInterval? _pollingInterval; + + /// + public override ContinuationToken? RehydrationToken { get; protected set; } + + /// + public override bool IsCompleted { get; protected set; } + + // Generated protocol methods + + /// + /// [Protocol Method] Retrieves a vector store file batch. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task GetFileBatchAsync(RequestOptions? options) + { + using PipelineMessage message = CreateGetVectorStoreFileBatchRequest(_vectorStoreId, _batchId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] Retrieves a vector store file batch. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult GetFileBatch(RequestOptions? options) + { + using PipelineMessage message = CreateGetVectorStoreFileBatchRequest(_vectorStoreId, _batchId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + /// + /// [Protocol Method] Cancel a vector store file batch. This attempts to cancel the processing of files in this batch as soon as possible. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task CancelFileBatchAsync(RequestOptions? options) + { + using PipelineMessage message = CreateCancelVectorStoreFileBatchRequest(_vectorStoreId, _batchId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] Cancel a vector store file batch. This attempts to cancel the processing of files in this batch as soon as possible. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult CancelFileBatch(RequestOptions? options) + { + using PipelineMessage message = CreateCancelVectorStoreFileBatchRequest(_vectorStoreId, _batchId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + /// + /// [Protocol Method] Returns a paginated collection of vector store files in a batch. + /// + /// + /// 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. `asc` for ascending order and`desc` + /// for descending order. Allowed values: "asc" | "desc" + /// + /// + /// 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. + /// + /// Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// A collection of service responses, each holding a page of values. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual IAsyncEnumerable GetFilesInBatchAsync(int? limit, string? order, string? after, string? before, string? filter, RequestOptions? options) + { + VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, _vectorStoreId, _batchId, limit, order, after, before, filter, options); + return PageCollectionHelpers.CreateAsync(enumerator); + } + + /// + /// [Protocol Method] Returns a paginated collection of vector store files in a batch. + /// + /// + /// 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. `asc` for ascending order and`desc` + /// for descending order. Allowed values: "asc" | "desc" + /// + /// + /// 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. + /// + /// Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// A collection of service responses, each holding a page of values. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual IEnumerable GetFilesInBatch(int? limit, string? order, string? after, string? before, string? filter, RequestOptions? options) + { + VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, _vectorStoreId, _batchId, limit, order, after, before, filter, options); + return PageCollectionHelpers.Create(enumerator); + } + + internal PipelineMessage CreateGetVectorStoreFileBatchRequest(string vectorStoreId, string batchId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/vector_stores/", false); + uri.AppendPath(vectorStoreId, true); + uri.AppendPath("/file_batches/", false); + uri.AppendPath(batchId, true); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + internal PipelineMessage CreateCancelVectorStoreFileBatchRequest(string vectorStoreId, string batchId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "POST"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/vector_stores/", false); + uri.AppendPath(vectorStoreId, true); + uri.AppendPath("/file_batches/", false); + uri.AppendPath(batchId, true); + uri.AppendPath("/cancel", false); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + internal PipelineMessage CreateGetFilesInVectorStoreBatchesRequest(string vectorStoreId, string batchId, int? limit, string order, string after, string before, string filter, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/vector_stores/", false); + uri.AppendPath(vectorStoreId, true); + uri.AppendPath("/file_batches/", false); + uri.AppendPath(batchId, true); + uri.AppendPath("/files", false); + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + if (order != null) + { + uri.AppendQuery("order", order, true); + } + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (before != null) + { + uri.AppendQuery("before", before, true); + } + if (filter != null) + { + uri.AppendQuery("filter", filter, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} \ No newline at end of file diff --git a/src/Custom/VectorStores/CreateBatchFileJobOperation.cs b/src/Custom/VectorStores/CreateBatchFileJobOperation.cs new file mode 100644 index 00000000..a34cbae5 --- /dev/null +++ b/src/Custom/VectorStores/CreateBatchFileJobOperation.cs @@ -0,0 +1,335 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.VectorStores; + +/// +/// Long-running operation for creating a vector store file batch. +/// +[Experimental("OPENAI001")] +public partial class CreateBatchFileJobOperation : OperationResult +{ + internal CreateBatchFileJobOperation( + ClientPipeline pipeline, + Uri endpoint, + ClientResult result) + : base(result.GetRawResponse()) + { + _pipeline = pipeline; + _endpoint = endpoint; + + Value = result; + Status = Value.Status; + + _vectorStoreId = Value.VectorStoreId; + _batchId = Value.BatchId; + + IsCompleted = GetIsCompleted(Value.Status); + RehydrationToken = new CreateBatchFileJobOperationToken(VectorStoreId, BatchId); + } + + /// + /// The current value of the in progress. + /// + public VectorStoreBatchFileJob? Value { get; private set; } + + /// + /// The current status of the in progress. + /// + public VectorStoreBatchFileJobStatus? Status { get; private set; } + + /// + /// The ID of the vector store corresponding to this batch file operation. + /// + public string VectorStoreId { get => _vectorStoreId; } + + /// + /// The ID of the batch file job represented by this operation. + /// + public string BatchId { get => _batchId; } + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static async Task RehydrateAsync(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + CreateBatchFileJobOperationToken token = CreateBatchFileJobOperationToken.FromToken(rehydrationToken); + + ClientResult result = await client.GetBatchFileJobAsync(token.VectorStoreId, token.BatchId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); + PipelineResponse response = result.GetRawResponse(); + VectorStoreBatchFileJob job = VectorStoreBatchFileJob.FromResponse(response); + + return new CreateBatchFileJobOperation(client.Pipeline, client.Endpoint, FromValue(job, response)); + } + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static CreateBatchFileJobOperation Rehydrate(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + CreateBatchFileJobOperationToken token = CreateBatchFileJobOperationToken.FromToken(rehydrationToken); + + ClientResult result = client.GetBatchFileJob(token.VectorStoreId, token.BatchId, cancellationToken.ToRequestOptions()); + PipelineResponse response = result.GetRawResponse(); + VectorStoreBatchFileJob job = VectorStoreBatchFileJob.FromResponse(response); + + return new CreateBatchFileJobOperation(client.Pipeline, client.Endpoint, FromValue(job, response)); + } + + /// + public override async Task WaitForCompletionAsync(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + await _pollingInterval.WaitAsync(response, cancellationToken); + + ClientResult result = await GetFileBatchAsync(cancellationToken).ConfigureAwait(false); + + ApplyUpdate(result); + } + } + + /// + public override void WaitForCompletion(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + _pollingInterval.Wait(response, cancellationToken); + + ClientResult result = GetFileBatch(cancellationToken); + + ApplyUpdate(result); + } + } + + internal async Task WaitUntilAsync(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + await WaitForCompletionAsync(options?.CancellationToken ?? default).ConfigureAwait(false); + return this; + } + + internal CreateBatchFileJobOperation WaitUntil(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + WaitForCompletion(options?.CancellationToken ?? default); + return this; + } + + private void ApplyUpdate(ClientResult update) + { + Value = update; + Status = Value.Status; + + IsCompleted = GetIsCompleted(Value.Status); + SetRawResponse(update.GetRawResponse()); + } + + private static bool GetIsCompleted(VectorStoreBatchFileJobStatus status) + { + return status == VectorStoreBatchFileJobStatus.Completed || + status == VectorStoreBatchFileJobStatus.Cancelled || + status == VectorStoreBatchFileJobStatus.Failed; + } + + // Generated convenience methods + + /// + /// Gets an existing vector store batch file ingestion job from a known vector store ID and job ID. + /// + /// A token that can be used to cancel this method call. + /// A instance representing the ingestion operation. + public virtual async Task> GetFileBatchAsync(CancellationToken cancellationToken = default) + { + ClientResult result = await GetFileBatchAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); + PipelineResponse response = result.GetRawResponse(); + VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); + return ClientResult.FromValue(value, response); + } + + /// + /// Gets an existing vector store batch file ingestion job from a known vector store ID and job ID. + /// + /// A token that can be used to cancel this method call. + /// A instance representing the ingestion operation. + public virtual ClientResult GetFileBatch(CancellationToken cancellationToken = default) + { + ClientResult result = GetFileBatch(cancellationToken.ToRequestOptions()); + PipelineResponse response = result.GetRawResponse(); + VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); + return ClientResult.FromValue(value, response); + } + + /// + /// Cancels an in-progress . + /// + /// A token that can be used to cancel this method call. + /// An updated instance. + public virtual async Task> CancelFileBatchAsync(CancellationToken cancellationToken = default) + { + ClientResult result = await CancelFileBatchAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); + PipelineResponse response = result.GetRawResponse(); + VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); + return ClientResult.FromValue(value, response); + } + + /// + /// Cancels an in-progress . + /// + /// A token that can be used to cancel this method call. + /// An updated instance. + public virtual ClientResult CancelFileBatch(CancellationToken cancellationToken = default) + { + ClientResult result = CancelFileBatch(cancellationToken.ToRequestOptions()); + PipelineResponse response = result.GetRawResponse(); + VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); + return ClientResult.FromValue(value, response); + } + + /// + /// Gets a page collection of file associations associated with a vector store batch file job, representing the files + /// that were scheduled for ingestion into the vector store. + /// + /// Options describing the collection to return. + /// A token that can be used to cancel this method call. + /// holds pages of values. To obtain a collection of values, call + /// . To obtain the current + /// page of values, call . + /// A collection of pages of . + public virtual AsyncPageCollection GetFilesInBatchAsync( + VectorStoreFileAssociationCollectionOptions? options = default, + CancellationToken cancellationToken = default) + { + return GetFilesInBatchAsync(options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, options?.Filter?.ToString(), cancellationToken.ToRequestOptions()) is not AsyncPageCollection pages + ? throw new NotSupportedException("Failed to cast protocol method return type to AsyncPageCollection.") + : pages; + } + + /// + /// Rehydrates a page collection of file associations from a page token. + /// + /// Page token corresponding to the first page of the collection to rehydrate. + /// A token that can be used to cancel this method call. + /// holds pages of values. To obtain a collection of values, call + /// . To obtain the current + /// page of values, call . + /// A collection of pages of . + public virtual AsyncPageCollection GetFilesInBatchAsync( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + VectorStoreFileBatchesPageToken pageToken = VectorStoreFileBatchesPageToken.FromToken(firstPageToken); + + if (_vectorStoreId != pageToken.VectorStoreId) + { + throw new ArgumentException( + "Invalid page token. 'VectorStoreId' value does not match page token value.", + nameof(VectorStoreId)); + } + + if (_batchId != pageToken.BatchId) + { + throw new ArgumentException( + "Invalid page token. 'BatchId' value does not match page token value.", + nameof(BatchId)); + } + + return GetFilesInBatchAsync(pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, pageToken?.Filter, cancellationToken.ToRequestOptions()) is not AsyncPageCollection pages + ? throw new NotSupportedException("Failed to cast protocol method return type to PageCollection.") + : pages; + } + + /// + /// Gets a page collection of file associations associated with a vector store batch file job, representing the files + /// that were scheduled for ingestion into the vector store. + /// + /// Options describing the collection to return. + /// A token that can be used to cancel this method call. + /// holds pages of values. To obtain a collection of values, call + /// . To obtain the current + /// page of values, call . + /// A collection of pages of . + public virtual PageCollection GetFilesInBatch( + VectorStoreFileAssociationCollectionOptions? options = default, + CancellationToken cancellationToken = default) + { + return GetFilesInBatch(options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, options?.Filter?.ToString(), cancellationToken.ToRequestOptions()) is not PageCollection pages + ? throw new NotSupportedException("Failed to cast protocol method return type to AsyncPageCollection.") + : pages; + } + + /// + /// Rehydrates a page collection of file associations from a page token. + /// that were scheduled for ingestion into the vector store. + /// + /// Page token corresponding to the first page of the collection to rehydrate. + /// A token that can be used to cancel this method call. + /// holds pages of values. To obtain a collection of values, call + /// . To obtain the current + /// page of values, call . + /// A collection of pages of . + public virtual PageCollection GetFilesInBatch( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + VectorStoreFileBatchesPageToken pageToken = VectorStoreFileBatchesPageToken.FromToken(firstPageToken); + + if (VectorStoreId != pageToken.VectorStoreId) + { + throw new ArgumentException( + "Invalid page token. 'VectorStoreId' value does not match page token value.", + nameof(VectorStoreId)); + } + + if (BatchId != pageToken.BatchId) + { + throw new ArgumentException( + "Invalid page token. 'BatchId' value does not match page token value.", + nameof(BatchId)); + } + + return GetFilesInBatch(pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, pageToken?.Filter, cancellationToken.ToRequestOptions()) is not PageCollection pages + ? throw new NotSupportedException("Failed to cast protocol method return type to PageCollection.") + : pages; + } +} \ No newline at end of file diff --git a/src/Custom/VectorStores/CreateBatchFileJobOperationToken.cs b/src/Custom/VectorStores/CreateBatchFileJobOperationToken.cs new file mode 100644 index 00000000..20e47b4d --- /dev/null +++ b/src/Custom/VectorStores/CreateBatchFileJobOperationToken.cs @@ -0,0 +1,101 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.VectorStores; + +internal class CreateBatchFileJobOperationToken : ContinuationToken +{ + public CreateBatchFileJobOperationToken(string vectorStoreId, string batchId) + { + VectorStoreId = vectorStoreId; + BatchId = batchId; + } + + public string VectorStoreId { get; } + + public string BatchId { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + writer.WriteStartObject(); + + writer.WriteString("vectorStoreId", VectorStoreId); + writer.WriteString("batchId", BatchId); + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public static CreateBatchFileJobOperationToken FromToken(ContinuationToken continuationToken) + { + if (continuationToken is CreateBatchFileJobOperationToken token) + { + return token; + } + + BinaryData data = continuationToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create AddFileBatchToVectorStoreOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + Utf8JsonReader reader = new(data); + + string vectorStoreId = null!; + string batchId = null!; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "vectorStoreId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + vectorStoreId = reader.GetString()!; + break; + + case "batchId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + batchId = reader.GetString()!; + break; + + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (vectorStoreId is null || batchId is null) + { + throw new ArgumentException("Failed to create AddFileBatchToVectorStoreOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + return new(vectorStoreId, batchId); + } +} + diff --git a/src/Custom/VectorStores/CreateVectorStoreOperation.Protocol.cs b/src/Custom/VectorStores/CreateVectorStoreOperation.Protocol.cs new file mode 100644 index 00000000..62669416 --- /dev/null +++ b/src/Custom/VectorStores/CreateVectorStoreOperation.Protocol.cs @@ -0,0 +1,70 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.ComponentModel; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.VectorStores; + +public partial class CreateVectorStoreOperation : OperationResult +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _vectorStoreId; + + private PollingInterval? _pollingInterval; + + /// + public override ContinuationToken? RehydrationToken { get; protected set; } + + /// + public override bool IsCompleted { get; protected set; } + + /// + /// [Protocol Method] Retrieves a vector store. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task GetVectorStoreAsync(RequestOptions? options) + { + using PipelineMessage message = CreateGetVectorStoreRequest(_vectorStoreId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] Retrieves a vector store. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult GetVectorStore(RequestOptions? options) + { + using PipelineMessage message = CreateGetVectorStoreRequest(_vectorStoreId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + internal PipelineMessage CreateGetVectorStoreRequest(string vectorStoreId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/vector_stores/", false); + uri.AppendPath(vectorStoreId, true); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/Custom/VectorStores/CreateVectorStoreOperation.cs b/src/Custom/VectorStores/CreateVectorStoreOperation.cs new file mode 100644 index 00000000..47aac950 --- /dev/null +++ b/src/Custom/VectorStores/CreateVectorStoreOperation.cs @@ -0,0 +1,184 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.VectorStores; + +[Experimental("OPENAI001")] +public partial class CreateVectorStoreOperation : OperationResult +{ + public CreateVectorStoreOperation( + ClientPipeline pipeline, + Uri endpoint, + ClientResult result) + : base(result.GetRawResponse()) + { + _pipeline = pipeline; + _endpoint = endpoint; + + Value = result; + Status = Value.Status; + + _vectorStoreId = Value.Id; + + IsCompleted = GetIsCompleted(Value.Status); + RehydrationToken = new CreateVectorStoreOperationToken(VectorStoreId); + } + + /// + /// The current value of the create operation in progress. + /// + public VectorStore? Value { get; private set; } + + /// + /// The current status of the create operation in progress. + /// + public VectorStoreStatus? Status { get; private set; } + + /// + /// The ID of the vector store being created. + /// + public string VectorStoreId { get => _vectorStoreId; } + + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static async Task RehydrateAsync(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + CreateVectorStoreOperationToken token = CreateVectorStoreOperationToken.FromToken(rehydrationToken); + + ClientResult result = await client.GetVectorStoreAsync(token.VectorStoreId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); + PipelineResponse response = result.GetRawResponse(); + VectorStore vectorStore = VectorStore.FromResponse(response); + + return new CreateVectorStoreOperation(client.Pipeline, client.Endpoint, FromValue(vectorStore, response)); + } + + /// + /// Recreates a from a rehydration token. + /// + /// The used to obtain the + /// operation status from the service. + /// The rehydration token corresponding to + /// the operation to rehydrate. + /// A token that can be used to cancel the + /// request. + /// The rehydrated operation. + /// or is null. + public static CreateVectorStoreOperation Rehydrate(VectorStoreClient client, ContinuationToken rehydrationToken, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(client, nameof(client)); + Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken)); + + CreateVectorStoreOperationToken token = CreateVectorStoreOperationToken.FromToken(rehydrationToken); + + ClientResult result = client.GetVectorStore(token.VectorStoreId, cancellationToken.ToRequestOptions()); + PipelineResponse response = result.GetRawResponse(); + VectorStore vectorStore = VectorStore.FromResponse(response); + + return new CreateVectorStoreOperation(client.Pipeline, client.Endpoint, FromValue(vectorStore, response)); + } + + /// + public override async Task WaitForCompletionAsync(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + await _pollingInterval.WaitAsync(response, cancellationToken); + + ClientResult result = await GetVectorStoreAsync(cancellationToken).ConfigureAwait(false); + + ApplyUpdate(result); + } + } + + public override void WaitForCompletion(CancellationToken cancellationToken = default) + { + _pollingInterval ??= new(); + + while (!IsCompleted) + { + PipelineResponse response = GetRawResponse(); + + _pollingInterval.Wait(response, cancellationToken); + + ClientResult result = GetVectorStore(cancellationToken); + + ApplyUpdate(result); + } + } + + internal async Task WaitUntilAsync(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + await WaitForCompletionAsync(options?.CancellationToken ?? default).ConfigureAwait(false); + return this; + } + + internal CreateVectorStoreOperation WaitUntil(bool waitUntilCompleted, RequestOptions? options) + { + if (!waitUntilCompleted) return this; + WaitForCompletion(options?.CancellationToken ?? default); + return this; + } + + private void ApplyUpdate(ClientResult update) + { + Value = update; + Status = Value.Status; + + IsCompleted = GetIsCompleted(Value.Status); + SetRawResponse(update.GetRawResponse()); + } + + private static bool GetIsCompleted(VectorStoreStatus status) + { + return status == VectorStoreStatus.Completed || + status == VectorStoreStatus.Expired; + } + + /// + /// Gets an instance representing an existing . + /// + /// A token that can be used to cancel this method call. + /// A representation of an existing . + public virtual async Task> GetVectorStoreAsync(CancellationToken cancellationToken = default) + { + ClientResult result + = await GetVectorStoreAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); + return ClientResult.FromValue( + VectorStore.FromResponse(result.GetRawResponse()), result.GetRawResponse()); + } + + /// + /// Gets an instance representing an existing . + /// + /// A token that can be used to cancel this method call. + /// A representation of an existing . + public virtual ClientResult GetVectorStore(CancellationToken cancellationToken = default) + { + ClientResult result = GetVectorStore(cancellationToken.ToRequestOptions()); + return ClientResult.FromValue(VectorStore.FromResponse(result.GetRawResponse()), result.GetRawResponse()); + } +} diff --git a/src/Custom/VectorStores/CreateVectorStoreOperationToken.cs b/src/Custom/VectorStores/CreateVectorStoreOperationToken.cs new file mode 100644 index 00000000..00eca795 --- /dev/null +++ b/src/Custom/VectorStores/CreateVectorStoreOperationToken.cs @@ -0,0 +1,90 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.VectorStores; + +internal class CreateVectorStoreOperationToken : ContinuationToken +{ + public CreateVectorStoreOperationToken(string vectorStoreId) + { + VectorStoreId = vectorStoreId; + } + + public string VectorStoreId { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + writer.WriteStartObject(); + + writer.WriteString("vectorStoreId", VectorStoreId); + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public static CreateVectorStoreOperationToken FromToken(ContinuationToken continuationToken) + { + if (continuationToken is CreateVectorStoreOperationToken token) + { + return token; + } + + BinaryData data = continuationToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create CreateVectorStoreOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + Utf8JsonReader reader = new(data); + + string vectorStoreId = null!; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "vectorStoreId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + vectorStoreId = reader.GetString()!; + break; + + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (vectorStoreId is null) + { + throw new ArgumentException("Failed to create CreateVectorStoreOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + return new(vectorStoreId); + } +} + diff --git a/src/Custom/VectorStores/Internal/Pagination/VectorStoreFileBatchesPageEnumerator.cs b/src/Custom/VectorStores/Internal/Pagination/VectorStoreFileBatchesPageEnumerator.cs index cd4f938e..71ce2862 100644 --- a/src/Custom/VectorStores/Internal/Pagination/VectorStoreFileBatchesPageEnumerator.cs +++ b/src/Custom/VectorStores/Internal/Pagination/VectorStoreFileBatchesPageEnumerator.cs @@ -20,15 +20,17 @@ internal partial class VectorStoreFileBatchesPageEnumerator : PageEnumerator _pipeline; + public VectorStoreFileBatchesPageEnumerator( ClientPipeline pipeline, Uri endpoint, string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, - RequestOptions options) + RequestOptions? options) { _pipeline = pipeline; _endpoint = endpoint; @@ -93,7 +95,7 @@ public override PageResult GetPageFromResult(ClientR return PageResult.Create(list.Data, pageToken, nextPageToken, response); } - internal virtual async Task GetFileAssociationsAsync(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions options) + internal virtual async Task GetFileAssociationsAsync(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions? options) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); @@ -102,7 +104,7 @@ internal virtual async Task GetFileAssociationsAsync(string vector return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); } - internal virtual ClientResult GetFileAssociations(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions options) + internal virtual ClientResult GetFileAssociations(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions? options) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); @@ -111,7 +113,7 @@ internal virtual ClientResult GetFileAssociations(string vectorStoreId, string b return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); } - internal PipelineMessage CreateGetFilesInVectorStoreBatchesRequest(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions options) + internal PipelineMessage CreateGetFilesInVectorStoreBatchesRequest(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions? options) { var message = _pipeline.CreateMessage(); message.ResponseClassifier = PipelineMessageClassifier200; diff --git a/src/Custom/VectorStores/Internal/Pagination/VectorStoreFilesPageEnumerator.cs b/src/Custom/VectorStores/Internal/Pagination/VectorStoreFilesPageEnumerator.cs index 3063fb39..9a5e9bec 100644 --- a/src/Custom/VectorStores/Internal/Pagination/VectorStoreFilesPageEnumerator.cs +++ b/src/Custom/VectorStores/Internal/Pagination/VectorStoreFilesPageEnumerator.cs @@ -23,6 +23,8 @@ internal partial class VectorStoreFilesPageEnumerator : PageEnumerator _pipeline; + public VectorStoreFilesPageEnumerator( ClientPipeline pipeline, Uri endpoint, diff --git a/src/Custom/VectorStores/Internal/Pagination/VectorStoresPageEnumerator.cs b/src/Custom/VectorStores/Internal/Pagination/VectorStoresPageEnumerator.cs index 3e454f38..311a8837 100644 --- a/src/Custom/VectorStores/Internal/Pagination/VectorStoresPageEnumerator.cs +++ b/src/Custom/VectorStores/Internal/Pagination/VectorStoresPageEnumerator.cs @@ -20,6 +20,8 @@ internal partial class VectorStoresPageEnumerator : PageEnumerator private string _after; + public virtual ClientPipeline Pipeline => _pipeline; + public VectorStoresPageEnumerator( ClientPipeline pipeline, Uri endpoint, @@ -96,7 +98,7 @@ internal virtual ClientResult GetVectorStores(int? limit, string order, string a return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); } - private PipelineMessage CreateGetVectorStoresRequest(int? limit, string order, string after, string before, RequestOptions options) + internal PipelineMessage CreateGetVectorStoresRequest(int? limit, string order, string after, string before, RequestOptions options) { var message = _pipeline.CreateMessage(); message.ResponseClassifier = PipelineMessageClassifier200; diff --git a/src/Custom/VectorStores/VectorStoreClient.Convenience.cs b/src/Custom/VectorStores/VectorStoreClient.Convenience.cs index ee48aaca..581b07f2 100644 --- a/src/Custom/VectorStores/VectorStoreClient.Convenience.cs +++ b/src/Custom/VectorStores/VectorStoreClient.Convenience.cs @@ -61,24 +61,30 @@ public virtual ClientResult DeleteVectorStore(VectorStore vectorStore) /// /// Associates an uploaded file with a vector store, beginning ingestion of the file into the vector store. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The vector store to associate the file with. /// The file to associate with the vector store. - /// - /// A instance that represents the new association. - /// - public virtual Task> AddFileToVectorStoreAsync(VectorStore vectorStore, OpenAIFileInfo file) - => AddFileToVectorStoreAsync(vectorStore?.Id, file?.Id); + /// A that can be used to wait for + /// the vector store file addition to complete. + public async virtual Task AddFileToVectorStoreAsync(bool waitUntilCompleted, VectorStore vectorStore, OpenAIFileInfo file) + => await AddFileToVectorStoreAsync(waitUntilCompleted, vectorStore?.Id, file?.Id).ConfigureAwait(false); /// /// Associates an uploaded file with a vector store, beginning ingestion of the file into the vector store. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The vector store to associate the file with. /// The file to associate with the vector store. - /// - /// A instance that represents the new association. - /// - public virtual ClientResult AddFileToVectorStore(VectorStore vectorStore, OpenAIFileInfo file) - => AddFileToVectorStore(vectorStore?.Id, file?.Id); + /// A that can be used to wait for + /// the vector store file addition to complete. + public virtual AddFileToVectorStoreOperation AddFileToVectorStore(bool waitUntilCompleted, VectorStore vectorStore, OpenAIFileInfo file) + => AddFileToVectorStore(waitUntilCompleted, vectorStore?.Id, file?.Id); /// /// Gets a page collection holding instances that represent file inclusions in the @@ -167,81 +173,28 @@ public virtual ClientResult RemoveFileFromStore(VectorStore vectorStore, O /// /// Begins a batch job to associate multiple jobs with a vector store, beginning the ingestion process. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The vector store to associate files with. /// The files to associate with the vector store. - /// A instance representing the batch operation. - public virtual Task> CreateBatchFileJobAsync(VectorStore vectorStore, IEnumerable files) - => CreateBatchFileJobAsync(vectorStore?.Id, files?.Select(file => file.Id)); + /// A that can be used to wait for + /// the operation to complete, get information about the batch file job, or cancel the operation. + public virtual Task CreateBatchFileJobAsync(bool waitUntilCompleted, VectorStore vectorStore, IEnumerable files) + => CreateBatchFileJobAsync(waitUntilCompleted, vectorStore?.Id, files?.Select(file => file.Id)); /// /// Begins a batch job to associate multiple jobs with a vector store, beginning the ingestion process. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The vector store to associate files with. /// The files to associate with the vector store. - /// A instance representing the batch operation. - public virtual ClientResult CreateBatchFileJob(VectorStore vectorStore, IEnumerable files) - => CreateBatchFileJob(vectorStore?.Id, files?.Select(file => file.Id)); - - /// - /// Gets an updated instance of an existing , refreshing its status. - /// - /// The job to refresh. - /// The refreshed instance of . - public virtual Task> GetBatchFileJobAsync(VectorStoreBatchFileJob batchJob) - => GetBatchFileJobAsync(batchJob?.VectorStoreId, batchJob?.BatchId); - - /// - /// Gets an updated instance of an existing , refreshing its status. - /// - /// The job to refresh. - /// The refreshed instance of . - public virtual ClientResult GetBatchFileJob(VectorStoreBatchFileJob batchJob) - => GetBatchFileJob(batchJob?.VectorStoreId, batchJob?.BatchId); - - /// - /// Cancels an in-progress . - /// - /// The that should be canceled. - /// An updated instance. - public virtual Task> CancelBatchFileJobAsync(VectorStoreBatchFileJob batchJob) - => CancelBatchFileJobAsync(batchJob?.VectorStoreId, batchJob?.BatchId); - - /// - /// Cancels an in-progress . - /// - /// The that should be canceled. - /// An updated instance. - public virtual ClientResult CancelBatchFileJob(VectorStoreBatchFileJob batchJob) - => CancelBatchFileJob(batchJob?.VectorStoreId, batchJob?.BatchId); - - /// - /// Gets a page collection holding file associations associated with a vector store batch file job, representing the files - /// that were scheduled for ingestion into the vector store. - /// - /// The vector store batch file job to retrieve file associations from. - /// Options describing the collection to return. - /// holds pages of values. To obtain a collection of values, call - /// . To obtain the current - /// page of values, call . - /// A collection of pages of . - public virtual AsyncPageCollection GetFileAssociationsAsync( - VectorStoreBatchFileJob batchJob, - VectorStoreFileAssociationCollectionOptions options = default) - => GetFileAssociationsAsync(batchJob?.VectorStoreId, batchJob?.BatchId, options); - - /// - /// Gets a page collection holding file associations associated with a vector store batch file job, representing the files - /// that were scheduled for ingestion into the vector store. - /// - /// The vector store batch file job to retrieve file associations from. - /// Options describing the collection to return. - /// holds pages of values. To obtain a collection of values, call - /// . To obtain the current - /// page of values, call . - /// A collection of pages of . - public virtual PageCollection GetFileAssociations( - VectorStoreBatchFileJob batchJob, - VectorStoreFileAssociationCollectionOptions options = default) - => GetFileAssociations(batchJob?.VectorStoreId, batchJob?.BatchId, options); - + /// A that can be used to wait for + /// the operation to complete, get information about the batch file job, or cancel the operation. + public virtual CreateBatchFileJobOperation CreateBatchFileJob(bool waitUntilCompleted, VectorStore vectorStore, IEnumerable files) + => CreateBatchFileJob(waitUntilCompleted, vectorStore?.Id, files?.Select(file => file.Id)); } diff --git a/src/Custom/VectorStores/VectorStoreClient.Protocol.cs b/src/Custom/VectorStores/VectorStoreClient.Protocol.cs index ca3fa76b..864716e4 100644 --- a/src/Custom/VectorStores/VectorStoreClient.Protocol.cs +++ b/src/Custom/VectorStores/VectorStoreClient.Protocol.cs @@ -90,30 +90,49 @@ public virtual IEnumerable GetVectorStores(int? limit, string orde /// /// [Protocol Method] Creates a vector store. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// is null. /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task CreateVectorStoreAsync(BinaryContent content, RequestOptions options = null) + /// A that can be used to wait for + /// the vector store creation to complete. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task CreateVectorStoreAsync(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null) { using PipelineMessage message = CreateCreateVectorStoreRequest(content, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + PipelineResponse response = await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false); + VectorStore value = VectorStore.FromResponse(response); + + CreateVectorStoreOperation operation = new(_pipeline, _endpoint, ClientResult.FromValue(value, response)); + return await operation.WaitUntilAsync(waitUntilCompleted, options).ConfigureAwait(false); } /// /// [Protocol Method] Creates a vector store. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// is null. /// Service returned a non-success status code. - /// The response returned from the service. + /// A that can be used to wait for + /// the vector store creation to complete. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult CreateVectorStore(BinaryContent content, RequestOptions options = null) + public virtual CreateVectorStoreOperation CreateVectorStore(bool waitUntilCompleted, BinaryContent content, RequestOptions options = null) { using PipelineMessage message = CreateCreateVectorStoreRequest(content, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + PipelineResponse response = _pipeline.ProcessMessage(message, options); + VectorStore value = VectorStore.FromResponse(response); + + CreateVectorStoreOperation operation = new(_pipeline, _endpoint, ClientResult.FromValue(value, response)); + return operation.WaitUntil(waitUntilCompleted, options); } /// @@ -305,41 +324,59 @@ public virtual IEnumerable GetFileAssociations(string vectorStoreI /// /// [Protocol Method] Create a vector store file by attaching a [File](/docs/api-reference/files) to a [vector store](/docs/api-reference/vector-stores/object). /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The ID of the vector store for which to create a File. /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// or is null. /// is an empty string, and was expected to be non-empty. /// Service returned a non-success status code. - /// The response returned from the service. + /// A that can be used to wait for + /// the vector store file addition to complete. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual async Task AddFileToVectorStoreAsync(string vectorStoreId, BinaryContent content, RequestOptions options = null) + public virtual async Task AddFileToVectorStoreAsync(bool waitUntilCompleted, string vectorStoreId, BinaryContent content, RequestOptions options = null) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateCreateVectorStoreFileRequest(vectorStoreId, content, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + PipelineResponse response = await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false); + VectorStoreFileAssociation value = VectorStoreFileAssociation.FromResponse(response); + + AddFileToVectorStoreOperation operation = new(_pipeline, _endpoint, ClientResult.FromValue(value, response)); + return await operation.WaitUntilAsync(waitUntilCompleted, options).ConfigureAwait(false); } /// /// [Protocol Method] Create a vector store file by attaching a [File](/docs/api-reference/files) to a [vector store](/docs/api-reference/vector-stores/object). /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The ID of the vector store for which to create a File. /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// or is null. /// is an empty string, and was expected to be non-empty. /// Service returned a non-success status code. - /// The response returned from the service. + /// A that can be used to wait for + /// the vector store file addition to complete. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult AddFileToVectorStore(string vectorStoreId, BinaryContent content, RequestOptions options = null) + public virtual AddFileToVectorStoreOperation AddFileToVectorStore(bool waitUntilCompleted, string vectorStoreId, BinaryContent content, RequestOptions options = null) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateCreateVectorStoreFileRequest(vectorStoreId, content, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + PipelineResponse response = _pipeline.ProcessMessage(message, options); + VectorStoreFileAssociation value = VectorStoreFileAssociation.FromResponse(response); + + AddFileToVectorStoreOperation operation = new(_pipeline, _endpoint, ClientResult.FromValue(value, response)); + return operation.WaitUntil(waitUntilCompleted, options); } /// @@ -425,41 +462,67 @@ public virtual ClientResult RemoveFileFromStore(string vectorStoreId, string fil /// /// [Protocol Method] Create a vector store file batch. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The ID of the vector store for which to create a file batch. /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// or is null. /// is an empty string, and was expected to be non-empty. /// Service returned a non-success status code. - /// The response returned from the service. + /// A that can be used to wait for + /// the operation to complete, get information about the batch file job, or cancel the operation. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual async Task CreateBatchFileJobAsync(string vectorStoreId, BinaryContent content, RequestOptions options = null) + public virtual async Task CreateBatchFileJobAsync( + bool waitUntilCompleted, + string vectorStoreId, + BinaryContent content, + RequestOptions options = null) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateCreateVectorStoreFileBatchRequest(vectorStoreId, content, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + PipelineResponse response = await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false); + VectorStoreBatchFileJob job = VectorStoreBatchFileJob.FromResponse(response); + + CreateBatchFileJobOperation operation = new(_pipeline, _endpoint, ClientResult.FromValue(job, response)); + return await operation.WaitUntilAsync(waitUntilCompleted, options).ConfigureAwait(false); } /// /// [Protocol Method] Create a vector store file batch. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The ID of the vector store for which to create a file batch. /// The content to send as the body of the request. /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// or is null. /// is an empty string, and was expected to be non-empty. /// Service returned a non-success status code. - /// The response returned from the service. + /// A that can be used to wait for + /// the operation to complete, get information about the batch file job, or cancel the operation. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult CreateBatchFileJob(string vectorStoreId, BinaryContent content, RequestOptions options = null) + public virtual CreateBatchFileJobOperation CreateBatchFileJob( + bool waitUntilCompleted, + string vectorStoreId, + BinaryContent content, + RequestOptions options = null) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNull(content, nameof(content)); using PipelineMessage message = CreateCreateVectorStoreFileBatchRequest(vectorStoreId, content, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + PipelineResponse response = _pipeline.ProcessMessage(message, options); + VectorStoreBatchFileJob job = VectorStoreBatchFileJob.FromResponse(response); + + CreateBatchFileJobOperation operation = new(_pipeline, _endpoint, ClientResult.FromValue(job, response)); + return operation.WaitUntil(waitUntilCompleted, options); } /// @@ -473,7 +536,7 @@ public virtual ClientResult CreateBatchFileJob(string vectorStoreId, BinaryConte /// Service returned a non-success status code. /// The response returned from the service. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual async Task GetBatchFileJobAsync(string vectorStoreId, string batchId, RequestOptions options) + internal virtual async Task GetBatchFileJobAsync(string vectorStoreId, string batchId, RequestOptions options) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); @@ -493,7 +556,7 @@ public virtual async Task GetBatchFileJobAsync(string vectorStoreI /// Service returned a non-success status code. /// The response returned from the service. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult GetBatchFileJob(string vectorStoreId, string batchId, RequestOptions options) + internal virtual ClientResult GetBatchFileJob(string vectorStoreId, string batchId, RequestOptions options) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); @@ -501,122 +564,4 @@ public virtual ClientResult GetBatchFileJob(string vectorStoreId, string batchId using PipelineMessage message = CreateGetVectorStoreFileBatchRequest(vectorStoreId, batchId, options); return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); } - - /// - /// [Protocol Method] Cancel a vector store file batch. This attempts to cancel the processing of files in this batch as soon as possible. - /// - /// The ID of the vector store that the file batch belongs to. - /// The ID of the file batch to cancel. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// or is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual async Task CancelBatchFileJobAsync(string vectorStoreId, string batchId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); - - using PipelineMessage message = CreateCancelVectorStoreFileBatchRequest(vectorStoreId, batchId, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); - } - - /// - /// [Protocol Method] Cancel a vector store file batch. This attempts to cancel the processing of files in this batch as soon as possible. - /// - /// The ID of the vector store that the file batch belongs to. - /// The ID of the file batch to cancel. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// or is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// The response returned from the service. - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult CancelBatchFileJob(string vectorStoreId, string batchId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); - - using PipelineMessage message = CreateCancelVectorStoreFileBatchRequest(vectorStoreId, batchId, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); - } - - /// - /// [Protocol Method] Returns a paginated collection of vector store files in a batch. - /// - /// The ID of the vector store that the file batch belongs to. - /// The ID of the file batch that the files belong to. - /// - /// 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. `asc` for ascending order and`desc` - /// for descending order. Allowed values: "asc" | "desc" - /// - /// - /// 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. - /// - /// Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// or is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// A collection of service responses, each holding a page of values. - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual IAsyncEnumerable GetFileAssociationsAsync(string vectorStoreId, string batchId, int? limit, string order, string after, string before, string filter, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); - - VectorStoreFileBatchesPageEnumerator enumerator = new VectorStoreFileBatchesPageEnumerator(_pipeline, _endpoint, vectorStoreId, batchId, limit, order, after, before, filter, options); - return PageCollectionHelpers.CreateAsync(enumerator); - } - - /// - /// [Protocol Method] Returns a paginated collection of vector store files in a batch. - /// - /// The ID of the vector store that the file batch belongs to. - /// The ID of the file batch that the files belong to. - /// - /// 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. `asc` for ascending order and`desc` - /// for descending order. Allowed values: "asc" | "desc" - /// - /// - /// 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. - /// - /// Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// or is an empty string, and was expected to be non-empty. - /// Service returned a non-success status code. - /// A collection of service responses, each holding a page of values. - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual IEnumerable GetFileAssociations(string vectorStoreId, string batchId, int? limit, string order, string after, string before, string filter, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); - - VectorStoreFileBatchesPageEnumerator enumerator = new VectorStoreFileBatchesPageEnumerator(_pipeline, _endpoint, vectorStoreId, batchId, limit, order, after, before, filter, options); - return PageCollectionHelpers.Create(enumerator); - } } diff --git a/src/Custom/VectorStores/VectorStoreClient.cs b/src/Custom/VectorStores/VectorStoreClient.cs index 161f4f6a..e0d61c27 100644 --- a/src/Custom/VectorStores/VectorStoreClient.cs +++ b/src/Custom/VectorStores/VectorStoreClient.cs @@ -42,6 +42,8 @@ namespace OpenAI.VectorStores; [Experimental("OPENAI001")] public partial class VectorStoreClient { + internal Uri Endpoint => _endpoint; + /// /// Initializes a new instance of that will use an API key when authenticating. /// @@ -82,27 +84,35 @@ protected internal VectorStoreClient(ClientPipeline pipeline, Uri endpoint, Open } /// Creates a vector store. + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The to use. /// A token that can be used to cancel this method call. /// is null. - /// Create vector store. - public virtual async Task> CreateVectorStoreAsync(VectorStoreCreationOptions vectorStore = null, CancellationToken cancellationToken = default) + /// A that can be used to wait for + /// the vector store creation to complete. + public virtual async Task CreateVectorStoreAsync(bool waitUntilCompleted, VectorStoreCreationOptions vectorStore = null, CancellationToken cancellationToken = default) { using BinaryContent content = vectorStore?.ToBinaryContent(); - ClientResult result = await CreateVectorStoreAsync(content, cancellationToken.ToRequestOptions()).ConfigureAwait(false); - return ClientResult.FromValue(VectorStore.FromResponse(result.GetRawResponse()), result.GetRawResponse()); + return await CreateVectorStoreAsync(waitUntilCompleted, content, cancellationToken.ToRequestOptions()).ConfigureAwait(false); } /// Creates a vector store. + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The to use. /// A token that can be used to cancel this method call. /// is null. - /// Create vector store. - public virtual ClientResult CreateVectorStore(VectorStoreCreationOptions vectorStore = null, CancellationToken cancellationToken = default) + /// A that can be used to wait for + /// the vector store creation to complete. + public virtual CreateVectorStoreOperation CreateVectorStore(bool waitUntilCompleted, VectorStoreCreationOptions vectorStore = null, CancellationToken cancellationToken = default) { using BinaryContent content = vectorStore?.ToBinaryContent(); - ClientResult result = CreateVectorStore(content, cancellationToken.ToRequestOptions()); - return ClientResult.FromValue(VectorStore.FromResponse(result.GetRawResponse()), result.GetRawResponse()); + return CreateVectorStore(waitUntilCompleted, content, cancellationToken.ToRequestOptions()); } /// @@ -214,14 +224,8 @@ public virtual AsyncPageCollection GetVectorStoresAsync( VectorStoreCollectionOptions options = default, CancellationToken cancellationToken = default) { - VectorStoresPageEnumerator enumerator = new(_pipeline, _endpoint, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetVectorStoresAsync(options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -240,14 +244,8 @@ public virtual AsyncPageCollection GetVectorStoresAsync( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); VectorStoresPageToken pageToken = VectorStoresPageToken.FromToken(firstPageToken); - VectorStoresPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetVectorStoresAsync(pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -263,14 +261,8 @@ public virtual PageCollection GetVectorStores( VectorStoreCollectionOptions options = default, CancellationToken cancellationToken = default) { - VectorStoresPageEnumerator enumerator = new(_pipeline, _endpoint, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetVectorStores(options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, cancellationToken.ToRequestOptions()) + as PageCollection; } /// @@ -289,50 +281,52 @@ public virtual PageCollection GetVectorStores( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); VectorStoresPageToken pageToken = VectorStoresPageToken.FromToken(firstPageToken); - VectorStoresPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetVectorStores(pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, cancellationToken.ToRequestOptions()) + as PageCollection; } /// /// Associates a single, uploaded file with a vector store, beginning ingestion of the file into the vector store. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The ID of the vector store to associate the file with. /// The ID of the file to associate with the vector store. /// A token that can be used to cancel this method call. - /// - /// A instance that represents the new association. - /// - public virtual async Task> AddFileToVectorStoreAsync(string vectorStoreId, string fileId, CancellationToken cancellationToken = default) + /// A that can be used to wait for + /// the vector store file addition to complete. + /// or is null. + public virtual async Task AddFileToVectorStoreAsync(bool waitUntilCompleted, string vectorStoreId, string fileId, CancellationToken cancellationToken = default) { + Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); + Argument.AssertNotNullOrEmpty(fileId, nameof(fileId)); + InternalCreateVectorStoreFileRequest internalRequest = new(fileId); - ClientResult protocolResult = await AddFileToVectorStoreAsync(vectorStoreId, internalRequest.ToBinaryContent(), cancellationToken.ToRequestOptions()).ConfigureAwait(false); - PipelineResponse protocolResponse = protocolResult?.GetRawResponse(); - VectorStoreFileAssociation fileAssociation = VectorStoreFileAssociation.FromResponse(protocolResponse); - return ClientResult.FromValue(fileAssociation, protocolResponse); + return await AddFileToVectorStoreAsync(waitUntilCompleted, vectorStoreId, internalRequest.ToBinaryContent(), cancellationToken.ToRequestOptions()).ConfigureAwait(false); } /// /// Associates a single, uploaded file with a vector store, beginning ingestion of the file into the vector store. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The ID of the vector store to associate the file with. /// The ID of the file to associate with the vector store. /// A token that can be used to cancel this method call. - /// - /// A instance that represents the new association. - /// - public virtual ClientResult AddFileToVectorStore(string vectorStoreId, string fileId, CancellationToken cancellationToken = default) + /// A that can be used to wait for + /// the vector store file addition to complete. + /// or is null. + public virtual AddFileToVectorStoreOperation AddFileToVectorStore(bool waitUntilCompleted, string vectorStoreId, string fileId, CancellationToken cancellationToken = default) { + Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); + Argument.AssertNotNullOrEmpty(fileId, nameof(fileId)); + InternalCreateVectorStoreFileRequest internalRequest = new(fileId); - ClientResult protocolResult = AddFileToVectorStore(vectorStoreId, internalRequest.ToBinaryContent(), cancellationToken.ToRequestOptions()); - PipelineResponse protocolResponse = protocolResult?.GetRawResponse(); - VectorStoreFileAssociation fileAssociation = VectorStoreFileAssociation.FromResponse(protocolResponse); - return ClientResult.FromValue(fileAssociation, protocolResponse); + return AddFileToVectorStore(waitUntilCompleted, vectorStoreId, internalRequest.ToBinaryContent(), cancellationToken.ToRequestOptions()); } /// @@ -355,16 +349,8 @@ public virtual AsyncPageCollection GetFileAssociatio { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - VectorStoreFilesPageEnumerator enumerator = new(_pipeline, _endpoint, - vectorStoreId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - options?.Filter?.ToString(), - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetFileAssociationsAsync(vectorStoreId, options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, options?.Filter?.ToString(), cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -383,16 +369,8 @@ public virtual AsyncPageCollection GetFileAssociatio Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); VectorStoreFilesPageToken pageToken = VectorStoreFilesPageToken.FromToken(firstPageToken); - VectorStoreFilesPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.VectorStoreId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - pageToken.Filter, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); + return GetFileAssociationsAsync(pageToken?.VectorStoreId, pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, pageToken?.Filter, cancellationToken.ToRequestOptions()) + as AsyncPageCollection; } /// @@ -415,16 +393,8 @@ public virtual PageCollection GetFileAssociations( { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - VectorStoreFilesPageEnumerator enumerator = new(_pipeline, _endpoint, - vectorStoreId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - options?.Filter?.ToString(), - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetFileAssociations(vectorStoreId, options?.PageSize, options?.Order?.ToString(), options?.AfterId, options?.BeforeId, options?.Filter?.ToString(), cancellationToken.ToRequestOptions()) + as PageCollection; } /// @@ -443,16 +413,8 @@ public virtual PageCollection GetFileAssociations( Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); VectorStoreFilesPageToken pageToken = VectorStoreFilesPageToken.FromToken(firstPageToken); - VectorStoreFilesPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.VectorStoreId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - pageToken.Filter, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return GetFileAssociations(pageToken?.VectorStoreId, pageToken?.Limit, pageToken?.Order, pageToken?.After, pageToken?.Before, pageToken?.Filter, cancellationToken.ToRequestOptions()) + as PageCollection; } /// @@ -538,301 +500,56 @@ public virtual ClientResult RemoveFileFromStore(string vectorStoreId, stri } /// - /// Begins a batch job to associate multiple jobs with a vector store, beginning the ingestion process. + /// Adds multiple files in a batch to the vector store, beginning the ingestion process. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The ID of the vector store to associate files with. /// The IDs of the files to associate with the vector store. /// A token that can be used to cancel this method call. - /// A instance representing the batch operation. - public virtual async Task> CreateBatchFileJobAsync(string vectorStoreId, IEnumerable fileIds, CancellationToken cancellationToken = default) + /// A that can be used to wait for + /// the operation to complete, get information about the batch file job, or cancel the operation. + public virtual async Task CreateBatchFileJobAsync( + bool waitUntilCompleted, + string vectorStoreId, + IEnumerable fileIds, + CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNullOrEmpty(fileIds, nameof(fileIds)); BinaryContent content = new InternalCreateVectorStoreFileBatchRequest(fileIds).ToBinaryContent(); - ClientResult result = await CreateBatchFileJobAsync(vectorStoreId, content, cancellationToken.ToRequestOptions()).ConfigureAwait(false); - PipelineResponse response = result?.GetRawResponse(); - VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); - return ClientResult.FromValue(value, response); + RequestOptions options = cancellationToken.ToRequestOptions(); + + return await CreateBatchFileJobAsync(waitUntilCompleted, vectorStoreId, content, options).ConfigureAwait(false); } /// /// Begins a batch job to associate multiple jobs with a vector store, beginning the ingestion process. /// + /// Value indicating whether the method + /// should return after the operation has been started and is still running + /// on the service, or wait until the operation has completed to return. + /// /// The ID of the vector store to associate files with. /// The IDs of the files to associate with the vector store. /// A token that can be used to cancel this method call. - /// A instance representing the batch operation. - public virtual ClientResult CreateBatchFileJob(string vectorStoreId, IEnumerable fileIds, CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(fileIds, nameof(fileIds)); - - BinaryContent content = new InternalCreateVectorStoreFileBatchRequest(fileIds).ToBinaryContent(); - ClientResult result = CreateBatchFileJob(vectorStoreId, content, cancellationToken.ToRequestOptions()); - PipelineResponse response = result?.GetRawResponse(); - VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); - return ClientResult.FromValue(value, response); - } - - /// - /// Gets an existing vector store batch file ingestion job from a known vector store ID and job ID. - /// - /// The ID of the vector store into which the batch of files was started. - /// The ID of the batch operation adding files to the vector store. - /// A token that can be used to cancel this method call. - /// A instance representing the ingestion operation. - public virtual async Task> GetBatchFileJobAsync(string vectorStoreId, string batchJobId, CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchJobId, nameof(batchJobId)); - - ClientResult result = await GetBatchFileJobAsync(vectorStoreId, batchJobId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); - PipelineResponse response = result?.GetRawResponse(); - VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); - return ClientResult.FromValue(value, response); - } - - /// - /// Gets an existing vector store batch file ingestion job from a known vector store ID and job ID. - /// - /// The ID of the vector store into which the batch of files was started. - /// The ID of the batch operation adding files to the vector store. - /// A token that can be used to cancel this method call. - /// A instance representing the ingestion operation. - public virtual ClientResult GetBatchFileJob(string vectorStoreId, string batchJobId, CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchJobId, nameof(batchJobId)); - - ClientResult result = GetBatchFileJob(vectorStoreId, batchJobId, cancellationToken.ToRequestOptions()); - PipelineResponse response = result?.GetRawResponse(); - VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); - return ClientResult.FromValue(value, response); - } - - /// - /// Cancels an in-progress . - /// - /// - /// The ID of the that is the ingestion target of the batch job being cancelled. - /// - /// - /// The ID of the that should be canceled. - /// - /// A token that can be used to cancel this method call. - /// An updated instance. - public virtual async Task> CancelBatchFileJobAsync(string vectorStoreId, string batchJobId, CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchJobId, nameof(batchJobId)); - - ClientResult result = await CancelBatchFileJobAsync(vectorStoreId, batchJobId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); - PipelineResponse response = result?.GetRawResponse(); - VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); - return ClientResult.FromValue(value, response); - } - - /// - /// Cancels an in-progress . - /// - /// - /// The ID of the that is the ingestion target of the batch job being cancelled. - /// - /// - /// The ID of the that should be canceled. - /// - /// A token that can be used to cancel this method call. - /// An updated instance. - public virtual ClientResult CancelBatchFileJob(string vectorStoreId, string batchJobId, CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchJobId, nameof(batchJobId)); - - ClientResult result = CancelBatchFileJob(vectorStoreId, batchJobId, cancellationToken.ToRequestOptions()); - PipelineResponse response = result?.GetRawResponse(); - VectorStoreBatchFileJob value = VectorStoreBatchFileJob.FromResponse(response); - return ClientResult.FromValue(value, response); - } - - /// - /// Gets a page collection of file associations associated with a vector store batch file job, representing the files - /// that were scheduled for ingestion into the vector store. - /// - /// - /// The ID of the vector store into which the file batch was scheduled for ingestion. - /// - /// - /// The ID of the batch file job that was previously scheduled. - /// - /// Options describing the collection to return. - /// A token that can be used to cancel this method call. - /// holds pages of values. To obtain a collection of values, call - /// . To obtain the current - /// page of values, call . - /// A collection of pages of . - public virtual AsyncPageCollection GetFileAssociationsAsync( + /// A that can be used to wait for + /// the operation to complete, get information about the batch file job, or cancel the operation. + public virtual CreateBatchFileJobOperation CreateBatchFileJob( + bool waitUntilCompleted, string vectorStoreId, - string batchJobId, - VectorStoreFileAssociationCollectionOptions options = default, + IEnumerable fileIds, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchJobId, nameof(batchJobId)); - - VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, - vectorStoreId, - batchJobId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - options?.Filter?.ToString(), - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); - } - - /// - /// Rehydrates a page collection of file associations from a page token. - /// - /// - /// The ID of the vector store into which the file batch was scheduled for ingestion. - /// - /// - /// The ID of the batch file job that was previously scheduled. - /// - /// Page token corresponding to the first page of the collection to rehydrate. - /// A token that can be used to cancel this method call. - /// holds pages of values. To obtain a collection of values, call - /// . To obtain the current - /// page of values, call . - /// A collection of pages of . - public virtual AsyncPageCollection GetFileAssociationsAsync( - string vectorStoreId, - string batchJobId, - ContinuationToken firstPageToken, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); - - VectorStoreFileBatchesPageToken pageToken = VectorStoreFileBatchesPageToken.FromToken(firstPageToken); - - if (vectorStoreId != pageToken.VectorStoreId) - { - throw new ArgumentException( - "Invalid page token. 'vectorStoreId' value does not match page token value.", - nameof(vectorStoreId)); - } - - if (batchJobId != pageToken.BatchId) - { - throw new ArgumentException( - "Invalid page token. 'batchJobId' value does not match page token value.", - nameof(vectorStoreId)); - } - - VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.VectorStoreId, - pageToken.BatchId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - pageToken.Filter, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.CreateAsync(enumerator); - } - - /// - /// Gets a page collection of file associations associated with a vector store batch file job, representing the files - /// that were scheduled for ingestion into the vector store. - /// - /// - /// The ID of the vector store into which the file batch was scheduled for ingestion. - /// - /// - /// The ID of the batch file job that was previously scheduled. - /// - /// Options describing the collection to return. - /// A token that can be used to cancel this method call. - /// holds pages of values. To obtain a collection of values, call - /// . To obtain the current - /// page of values, call . - /// A collection of pages of . - public virtual PageCollection GetFileAssociations( - string vectorStoreId, - string batchJobId, - VectorStoreFileAssociationCollectionOptions options = default, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - Argument.AssertNotNullOrEmpty(batchJobId, nameof(batchJobId)); - - VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, - vectorStoreId, - batchJobId, - options?.PageSize, - options?.Order?.ToString(), - options?.AfterId, - options?.BeforeId, - options?.Filter?.ToString(), - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); - } + Argument.AssertNotNullOrEmpty(fileIds, nameof(fileIds)); - /// - /// Rehydrates a page collection of file associations from a page token. - /// that were scheduled for ingestion into the vector store. - /// - /// - /// The ID of the vector store into which the file batch was scheduled for ingestion. - /// - /// - /// The ID of the batch file job that was previously scheduled. - /// - /// Page token corresponding to the first page of the collection to rehydrate. - /// A token that can be used to cancel this method call. - /// holds pages of values. To obtain a collection of values, call - /// . To obtain the current - /// page of values, call . - /// A collection of pages of . - public virtual PageCollection GetFileAssociations( - string vectorStoreId, - string batchJobId, - ContinuationToken firstPageToken, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + BinaryContent content = new InternalCreateVectorStoreFileBatchRequest(fileIds).ToBinaryContent(); + RequestOptions options = cancellationToken.ToRequestOptions(); - VectorStoreFileBatchesPageToken pageToken = VectorStoreFileBatchesPageToken.FromToken(firstPageToken); - - if (vectorStoreId != pageToken.VectorStoreId) - { - throw new ArgumentException( - "Invalid page token. 'vectorStoreId' value does not match page token value.", - nameof(vectorStoreId)); - } - - if (batchJobId != pageToken.BatchId) - { - throw new ArgumentException( - "Invalid page token. 'batchJobId' value does not match page token value.", - nameof(vectorStoreId)); - } - - VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, - pageToken.VectorStoreId, - pageToken.BatchId, - pageToken.Limit, - pageToken.Order, - pageToken.After, - pageToken.Before, - pageToken.Filter, - cancellationToken.ToRequestOptions()); - - return PageCollectionHelpers.Create(enumerator); + return CreateBatchFileJob(waitUntilCompleted, vectorStoreId, content, options); } } diff --git a/src/Generated/VectorStoreClient.cs b/src/Generated/VectorStoreClient.cs index 6f2be46a..7b0bcc35 100644 --- a/src/Generated/VectorStoreClient.cs +++ b/src/Generated/VectorStoreClient.cs @@ -31,6 +31,22 @@ internal VectorStoreClient(ClientPipeline pipeline, ApiKeyCredential keyCredenti _endpoint = endpoint; } + public virtual async Task CreateVectorStoreAsync(BinaryContent content, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + + using PipelineMessage message = CreateCreateVectorStoreRequest(content, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + public virtual ClientResult CreateVectorStore(BinaryContent content, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + + using PipelineMessage message = CreateCreateVectorStoreRequest(content, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + internal PipelineMessage CreateGetVectorStoresRequest(int? limit, string order, string after, string before, RequestOptions options) { var message = _pipeline.CreateMessage(); diff --git a/src/OpenAI.csproj b/src/OpenAI.csproj index eed204d5..5318c8fc 100644 --- a/src/OpenAI.csproj +++ b/src/OpenAI.csproj @@ -72,6 +72,6 @@ - + diff --git a/src/Utility/PollingInterval.cs b/src/Utility/PollingInterval.cs new file mode 100644 index 00000000..7f593736 --- /dev/null +++ b/src/Utility/PollingInterval.cs @@ -0,0 +1,70 @@ +using System; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI; + +internal class PollingInterval +{ + private const string RetryAfterHeaderName = "Retry-After"; + private static readonly TimeSpan DefaultDelay = TimeSpan.FromSeconds(0.8); + + private readonly TimeSpan _interval; + + public PollingInterval(TimeSpan? interval = default) + { + _interval = interval ?? DefaultDelay; + } + + public async Task WaitAsync(PipelineResponse response, CancellationToken cancellationToken) + { + TimeSpan delay = GetDelay(response); + + await Task.Delay(delay, cancellationToken); + } + + public void Wait(PipelineResponse response, CancellationToken cancellationToken) + { + TimeSpan delay = GetDelay(response); + + if (!cancellationToken.CanBeCanceled) + { + Thread.Sleep(delay); + return; + } + + if (cancellationToken.WaitHandle.WaitOne(delay)) + { + cancellationToken.ThrowIfCancellationRequested(); + } + } + + private TimeSpan GetDelay(PipelineResponse response) + => TryGetRetryAfter(response, out TimeSpan retryAfter) && retryAfter > _interval ? + retryAfter : _interval; + + private static bool TryGetRetryAfter(PipelineResponse response, out TimeSpan value) + { + // See: https://www.rfc-editor.org/rfc/rfc7231#section-7.1.3 + if (response.Headers.TryGetValue(RetryAfterHeaderName, out string? retryAfter)) + { + if (int.TryParse(retryAfter, out var delaySeconds)) + { + value = TimeSpan.FromSeconds(delaySeconds); + return true; + } + + if (DateTimeOffset.TryParse(retryAfter, out DateTimeOffset retryAfterDate)) + { + value = retryAfterDate - DateTimeOffset.Now; + return true; + } + } + + value = default; + return false; + } +} diff --git a/tests/Assistants/VectorStoreTests.cs b/tests/Assistants/VectorStoreTests.cs index ea1d2b7a..f345a7b0 100644 --- a/tests/Assistants/VectorStoreTests.cs +++ b/tests/Assistants/VectorStoreTests.cs @@ -23,7 +23,8 @@ public void CanCreateGetAndDeleteVectorStores() { VectorStoreClient client = GetTestClient(); - VectorStore vectorStore = client.CreateVectorStore(); + CreateVectorStoreOperation createOperation = client.CreateVectorStore(waitUntilCompleted: false); + VectorStore vectorStore = createOperation.Value; Validate(vectorStore); bool deleted = client.DeleteVectorStore(vectorStore); Assert.That(deleted, Is.True); @@ -31,7 +32,7 @@ public void CanCreateGetAndDeleteVectorStores() IReadOnlyList testFiles = GetNewTestFiles(5); - vectorStore = client.CreateVectorStore(new VectorStoreCreationOptions() + createOperation = client.CreateVectorStore(waitUntilCompleted: false, new VectorStoreCreationOptions() { FileIds = { testFiles[0].Id }, Name = "test vector store", @@ -45,19 +46,21 @@ public void CanCreateGetAndDeleteVectorStores() ["test-key"] = "test-value", }, }); - Validate(vectorStore); + Validate(createOperation.Value); + Assert.Multiple(() => { - Assert.That(vectorStore.Name, Is.EqualTo("test vector store")); - Assert.That(vectorStore.ExpirationPolicy?.Anchor, Is.EqualTo(VectorStoreExpirationAnchor.LastActiveAt)); - Assert.That(vectorStore.ExpirationPolicy?.Days, Is.EqualTo(3)); - Assert.That(vectorStore.FileCounts.Total, Is.EqualTo(1)); - Assert.That(vectorStore.CreatedAt, Is.GreaterThan(s_2024)); - Assert.That(vectorStore.ExpiresAt, Is.GreaterThan(s_2024)); - Assert.That(vectorStore.Status, Is.EqualTo(VectorStoreStatus.InProgress)); - Assert.That(vectorStore.Metadata?.TryGetValue("test-key", out string metadataValue) == true && metadataValue == "test-value"); + Assert.That(createOperation.Value.Name, Is.EqualTo("test vector store")); + Assert.That(createOperation.Value.ExpirationPolicy?.Anchor, Is.EqualTo(VectorStoreExpirationAnchor.LastActiveAt)); + Assert.That(createOperation.Value.ExpirationPolicy?.Days, Is.EqualTo(3)); + Assert.That(createOperation.Value.FileCounts.Total, Is.EqualTo(1)); + Assert.That(createOperation.Value.CreatedAt, Is.GreaterThan(s_2024)); + Assert.That(createOperation.Value.ExpiresAt, Is.GreaterThan(s_2024)); + Assert.That(createOperation.Status, Is.EqualTo(VectorStoreStatus.InProgress)); + Assert.That(createOperation.Value.Metadata?.TryGetValue("test-key", out string metadataValue) == true && metadataValue == "test-value"); }); - vectorStore = client.GetVectorStore(vectorStore); + + vectorStore = createOperation.Value; Assert.Multiple(() => { Assert.That(vectorStore.Name, Is.EqualTo("test vector store")); @@ -73,10 +76,11 @@ public void CanCreateGetAndDeleteVectorStores() Assert.That(deleted, Is.True); _vectorStoresToDelete.RemoveAt(_vectorStoresToDelete.Count - 1); - vectorStore = client.CreateVectorStore(new VectorStoreCreationOptions() + createOperation = client.CreateVectorStore(waitUntilCompleted: false, new VectorStoreCreationOptions() { FileIds = testFiles.Select(file => file.Id).ToList() }); + vectorStore = createOperation.Value; Validate(vectorStore); Assert.Multiple(() => { @@ -91,10 +95,11 @@ public void CanEnumerateVectorStores() VectorStoreClient client = GetTestClient(); for (int i = 0; i < 10; i++) { - VectorStore vectorStore = client.CreateVectorStore(new VectorStoreCreationOptions() + CreateVectorStoreOperation createOperation = client.CreateVectorStore(waitUntilCompleted: true, new VectorStoreCreationOptions() { Name = $"Test Vector Store {i}", }); + VectorStore vectorStore = createOperation.Value; Validate(vectorStore); Assert.That(vectorStore.Name, Is.EqualTo($"Test Vector Store {i}")); } @@ -128,11 +133,14 @@ public async Task CanEnumerateVectorStoresAsync() VectorStoreClient client = GetTestClient(); for (int i = 0; i < 10; i++) { - VectorStore vectorStore = await client.CreateVectorStoreAsync(new VectorStoreCreationOptions() - { - Name = $"Test Vector Store {i}", - }); + CreateVectorStoreOperation createOperation = await client.CreateVectorStoreAsync(waitUntilCompleted: true, + new VectorStoreCreationOptions() + { + Name = $"Test Vector Store {i}", + }); + VectorStore vectorStore = createOperation.Value; Validate(vectorStore); + Assert.That(vectorStore.Name, Is.EqualTo($"Test Vector Store {i}")); } @@ -163,14 +171,16 @@ public async Task CanEnumerateVectorStoresAsync() public void CanAssociateFiles() { VectorStoreClient client = GetTestClient(); - VectorStore vectorStore = client.CreateVectorStore(); + CreateVectorStoreOperation createOperation = client.CreateVectorStore(waitUntilCompleted: true); + VectorStore vectorStore = createOperation.Value; Validate(vectorStore); IReadOnlyList files = GetNewTestFiles(3); foreach (OpenAIFileInfo file in files) { - VectorStoreFileAssociation association = client.AddFileToVectorStore(vectorStore, file); + AddFileToVectorStoreOperation addOperation = client.AddFileToVectorStore(waitUntilCompleted: false, vectorStore, file); + VectorStoreFileAssociation association = addOperation.Value; Validate(association); Assert.Multiple(() => { @@ -203,14 +213,16 @@ public void CanAssociateFiles() public void Pagination_CanRehydrateFileAssociationCollection() { VectorStoreClient client = GetTestClient(); - VectorStore vectorStore = client.CreateVectorStore(); + CreateVectorStoreOperation createOperation = client.CreateVectorStore(waitUntilCompleted: true); + VectorStore vectorStore = createOperation.Value; Validate(vectorStore); IReadOnlyList files = GetNewTestFiles(3); foreach (OpenAIFileInfo file in files) { - VectorStoreFileAssociation association = client.AddFileToVectorStore(vectorStore, file); + AddFileToVectorStoreOperation addOperation = client.AddFileToVectorStore(waitUntilCompleted: false, vectorStore, file); + VectorStoreFileAssociation association = addOperation.Value; Validate(association); Assert.Multiple(() => { @@ -266,27 +278,25 @@ public void Pagination_CanRehydrateFileAssociationCollection() public void CanUseBatchIngestion() { VectorStoreClient client = GetTestClient(); - VectorStore vectorStore = client.CreateVectorStore(); + CreateVectorStoreOperation createOperation = client.CreateVectorStore(waitUntilCompleted: true); + VectorStore vectorStore = createOperation.Value; Validate(vectorStore); IReadOnlyList testFiles = GetNewTestFiles(5); - VectorStoreBatchFileJob batchJob = client.CreateBatchFileJob(vectorStore, testFiles); - Validate(batchJob); + CreateBatchFileJobOperation batchOperation = client.CreateBatchFileJob(waitUntilCompleted: false, vectorStore, testFiles); + Validate(batchOperation); Assert.Multiple(() => { - Assert.That(batchJob.BatchId, Is.Not.Null); - Assert.That(batchJob.VectorStoreId, Is.EqualTo(vectorStore.Id)); - Assert.That(batchJob.Status, Is.EqualTo(VectorStoreBatchFileJobStatus.InProgress)); + Assert.That(batchOperation.BatchId, Is.Not.Null); + Assert.That(batchOperation.VectorStoreId, Is.EqualTo(vectorStore.Id)); + Assert.That(batchOperation.Status, Is.EqualTo(VectorStoreBatchFileJobStatus.InProgress)); }); - for (int i = 0; i < 10 && client.GetBatchFileJob(batchJob).Value.Status != VectorStoreBatchFileJobStatus.Completed; i++) - { - Thread.Sleep(500); - } + batchOperation.WaitForCompletion(); - foreach (VectorStoreFileAssociation association in client.GetFileAssociations(batchJob).GetAllValues()) + foreach (VectorStoreFileAssociation association in batchOperation.GetFilesInBatch().GetAllValues()) { Assert.Multiple(() => { @@ -300,6 +310,46 @@ public void CanUseBatchIngestion() } } + [Test] + public void CanRehydrateBatchFileJob() + { + VectorStoreClient client = GetTestClient(); + CreateVectorStoreOperation createOperation = client.CreateVectorStore(waitUntilCompleted: true); + VectorStore vectorStore = createOperation.Value; + Validate(vectorStore); + + IReadOnlyList testFiles = GetNewTestFiles(5); + + CreateBatchFileJobOperation batchOperation = client.CreateBatchFileJob(waitUntilCompleted: false, vectorStore, testFiles); + Validate(batchOperation); + + // Simulate rehydration of the operation + BinaryData rehydrationBytes = batchOperation.RehydrationToken.ToBytes(); + ContinuationToken rehydrationToken = ContinuationToken.FromBytes(rehydrationBytes); + + CreateBatchFileJobOperation rehydratedOperation = CreateBatchFileJobOperation.Rehydrate(client, rehydrationToken); + Validate(rehydratedOperation); + + Assert.Multiple(() => + { + Assert.That(batchOperation.BatchId, Is.Not.Null); + Assert.That(batchOperation.VectorStoreId, Is.EqualTo(vectorStore.Id)); + Assert.That(batchOperation.Status, Is.EqualTo(VectorStoreBatchFileJobStatus.InProgress)); + + Assert.That(rehydratedOperation.BatchId, Is.EqualTo(batchOperation.BatchId)); + Assert.That(rehydratedOperation.VectorStoreId, Is.EqualTo(vectorStore.Id)); + Assert.That(rehydratedOperation.Status, Is.EqualTo(VectorStoreBatchFileJobStatus.InProgress)); + }); + + Task.WaitAll( + Task.Run(() => batchOperation.WaitForCompletion()), + Task.Run(() => rehydratedOperation.WaitForCompletion())); + + Assert.IsTrue(batchOperation.IsCompleted); + Assert.IsTrue(rehydratedOperation.IsCompleted); + Assert.AreEqual(batchOperation.Status, rehydratedOperation.Status); + } + public enum ChunkingStrategyKind { Auto, Static } [Test] @@ -324,11 +374,12 @@ public async Task CanApplyChunkingStrategy(ChunkingStrategyKind strategyKind) Assert.That(inputStaticStrategy.OverlappingTokenCount, Is.EqualTo(250)); } - VectorStore vectorStore = await client.CreateVectorStoreAsync(new VectorStoreCreationOptions() + CreateVectorStoreOperation createOperation = await client.CreateVectorStoreAsync(waitUntilCompleted: true, new VectorStoreCreationOptions() { FileIds = testFiles.Select(file => file.Id).ToList(), ChunkingStrategy = chunkingStrategy, }); + VectorStore vectorStore = createOperation.Value; Validate(vectorStore); Assert.That(vectorStore.FileCounts.Total, Is.EqualTo(5)); @@ -382,9 +433,9 @@ protected void Cleanup() { ErrorOptions = ClientErrorBehaviors.NoThrow, }; - foreach (VectorStoreBatchFileJob job in _jobsToCancel) + foreach (CreateBatchFileJobOperation job in _jobsToCancel) { - ClientResult protocolResult = vectorStoreClient.CancelBatchFileJob(job.VectorStoreId, job.BatchId, requestOptions); + ClientResult protocolResult = job.CancelFileBatch(requestOptions); Console.WriteLine($"Cleanup: {job.BatchId} => {protocolResult?.GetRawResponse()?.Status}"); } foreach (VectorStoreFileAssociation association in _associationsToRemove) @@ -413,7 +464,7 @@ protected void Cleanup() /// The provided instance type isn't supported. private void Validate(T target) { - if (target is VectorStoreBatchFileJob job) + if (target is CreateBatchFileJobOperation job) { Assert.That(job.BatchId, Is.Not.Null); _jobsToCancel.Add(job); @@ -440,7 +491,7 @@ private void Validate(T target) } } - private readonly List _jobsToCancel = []; + private readonly List _jobsToCancel = []; private readonly List _associationsToRemove = []; private readonly List _filesToDelete = []; private readonly List _vectorStoresToDelete = []; diff --git a/tests/Batch/BatchTests.cs b/tests/Batch/BatchTests.cs index 16b7c5b4..de8b8c2a 100644 --- a/tests/Batch/BatchTests.cs +++ b/tests/Batch/BatchTests.cs @@ -54,7 +54,7 @@ public async Task ListBatchesProtocol() public async Task CreateGetAndCancelBatchProtocol() { using MemoryStream testFileStream = new(); - using StreamWriter streamWriter = new (testFileStream); + using StreamWriter streamWriter = new(testFileStream); string input = @"{""custom_id"": ""request-1"", ""method"": ""POST"", ""url"": ""/v1/chat/completions"", ""body"": {""model"": ""gpt-4o-mini"", ""messages"": [{""role"": ""system"", ""content"": ""You are a helpful assistant.""}, {""role"": ""user"", ""content"": ""What is 2+2?""}]}}"; streamWriter.WriteLine(input); streamWriter.Flush(); @@ -75,11 +75,11 @@ public async Task CreateGetAndCancelBatchProtocol() testMetadataKey = "test metadata value", }, })); - ClientResult batchResult = IsAsync - ? await client.CreateBatchAsync(content) - : client.CreateBatch(content); + CreateBatchOperation batchOperation = IsAsync + ? await client.CreateBatchAsync(waitUntilCompleted: false, content) + : client.CreateBatch(waitUntilCompleted: false, content); - BinaryData response = batchResult.GetRawResponse().Content; + BinaryData response = batchOperation.GetRawResponse().Content; JsonDocument jsonDocument = JsonDocument.Parse(response); JsonElement idElement = jsonDocument.RootElement.GetProperty("id"); @@ -100,18 +100,14 @@ public async Task CreateGetAndCancelBatchProtocol() Assert.That(status, Is.EqualTo("validating")); Assert.That(testMetadataKey, Is.EqualTo("test metadata value")); - batchResult = IsAsync - ? await client.GetBatchAsync(id, options: null) - : client.GetBatch(id, options: null); - JsonElement endpointElement = jsonDocument.RootElement.GetProperty("endpoint"); string endpoint = endpointElement.GetString(); Assert.That(endpoint, Is.EqualTo("/v1/chat/completions")); - batchResult = IsAsync - ? await client.CancelBatchAsync(id, options: null) - : client.CancelBatch(id, options: null); + ClientResult clientResult = IsAsync + ? await batchOperation.CancelBatchAsync(options: null) + : batchOperation.CancelBatch(options: null); statusElement = jsonDocument.RootElement.GetProperty("status"); status = statusElement.GetString(); @@ -131,5 +127,79 @@ public async Task CreateGetAndCancelBatchProtocol() //Assert.That(newBatchDynamic.status, Is.EqualTo("cancelling")); } + [Test] + public async Task CanRehydrateBatchOperation() + { + using MemoryStream testFileStream = new(); + using StreamWriter streamWriter = new(testFileStream); + string input = @"{""custom_id"": ""request-1"", ""method"": ""POST"", ""url"": ""/v1/chat/completions"", ""body"": {""model"": ""gpt-4o-mini"", ""messages"": [{""role"": ""system"", ""content"": ""You are a helpful assistant.""}, {""role"": ""user"", ""content"": ""What is 2+2?""}]}}"; + streamWriter.WriteLine(input); + streamWriter.Flush(); + testFileStream.Position = 0; + + FileClient fileClient = new(); + OpenAIFileInfo inputFile = await fileClient.UploadFileAsync(testFileStream, "test-batch-file", FileUploadPurpose.Batch); + Assert.That(inputFile.Id, Is.Not.Null.And.Not.Empty); + + BatchClient client = GetTestClient(); + BinaryContent content = BinaryContent.Create(BinaryData.FromObjectAsJson(new + { + input_file_id = inputFile.Id, + endpoint = "/v1/chat/completions", + completion_window = "24h", + metadata = new + { + testMetadataKey = "test metadata value", + }, + })); + CreateBatchOperation batchOperation = IsAsync + ? await client.CreateBatchAsync(waitUntilCompleted: false, content) + : client.CreateBatch(waitUntilCompleted: false, content); + + // Simulate rehydration of the operation + BinaryData rehydrationBytes = batchOperation.RehydrationToken.ToBytes(); + ContinuationToken rehydrationToken = ContinuationToken.FromBytes(rehydrationBytes); + + CreateBatchOperation rehydratedOperation = IsAsync ? + await CreateBatchOperation.RehydrateAsync(client, rehydrationToken) : + CreateBatchOperation.Rehydrate(client, rehydrationToken); + + static bool Validate(CreateBatchOperation operation) + { + BinaryData response = operation.GetRawResponse().Content; + JsonDocument jsonDocument = JsonDocument.Parse(response); + + JsonElement idElement = jsonDocument.RootElement.GetProperty("id"); + JsonElement createdAtElement = jsonDocument.RootElement.GetProperty("created_at"); + JsonElement statusElement = jsonDocument.RootElement.GetProperty("status"); + JsonElement metadataElement = jsonDocument.RootElement.GetProperty("metadata"); + JsonElement testMetadataKeyElement = metadataElement.GetProperty("testMetadataKey"); + + string id = idElement.GetString(); + long createdAt = createdAtElement.GetInt64(); + string status = statusElement.GetString(); + string testMetadataKey = testMetadataKeyElement.GetString(); + + long unixTime2024 = (new DateTimeOffset(2024, 01, 01, 0, 0, 0, TimeSpan.Zero)).ToUnixTimeSeconds(); + + Assert.That(id, Is.Not.Null.And.Not.Empty); + Assert.That(createdAt, Is.GreaterThan(unixTime2024)); + Assert.That(status, Is.EqualTo("validating")); + Assert.That(testMetadataKey, Is.EqualTo("test metadata value")); + + return true; + } + + Assert.IsTrue(Validate(batchOperation)); + Assert.IsTrue(Validate(rehydratedOperation)); + + Task.WaitAll( + IsAsync ? batchOperation.WaitForCompletionAsync() : Task.Run(() => batchOperation.WaitForCompletion()), + IsAsync ? rehydratedOperation.WaitForCompletionAsync() : Task.Run(() => rehydratedOperation.WaitForCompletion())); + + Assert.IsTrue(batchOperation.IsCompleted); + Assert.IsTrue(rehydratedOperation.IsCompleted); + } + private static BatchClient GetTestClient() => GetTestClient(TestScenario.Batch); } \ No newline at end of file diff --git a/tests/FineTuning/FineTuningTests.cs b/tests/FineTuning/FineTuningTests.cs new file mode 100644 index 00000000..93ed3e98 --- /dev/null +++ b/tests/FineTuning/FineTuningTests.cs @@ -0,0 +1,86 @@ +using NUnit.Framework; +using OpenAI.Files; +using OpenAI.FineTuning; +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using static OpenAI.Tests.TestHelpers; + +namespace OpenAI.Tests.FineTuning; + +[Parallelizable(ParallelScope.Fixtures)] +[Category("FineTuning")] +public partial class FineTuningTests +{ + [Test] + public void BasicFineTuningJobOperationsWork() + { + // Upload training file first + FileClient fileClient = GetTestClient(TestScenario.Files); + string filename = "toy_chat.jsonl"; + BinaryData fileContent = BinaryData.FromString(""" + {"messages": [{"role": "user", "content": "I lost my book today."}, {"role": "assistant", "content": "You can read everything on ebooks these days!"}]} + {"messages": [{"role": "system", "content": "You are a happy assistant that puts a positive spin on everything."}, {"role": "assistant", "content": "You're great!"}]} + """); + OpenAIFileInfo uploadedFile = fileClient.UploadFile(fileContent, filename, FileUploadPurpose.FineTune); + Assert.That(uploadedFile?.Filename, Is.EqualTo(filename)); + + // Submit fine-tuning job + FineTuningClient client = GetTestClient(); + + string json = $"{{\"training_file\":\"{uploadedFile.Id}\",\"model\":\"gpt-3.5-turbo\"}}"; + BinaryData input = BinaryData.FromString(json); + using BinaryContent content = BinaryContent.Create(input); + + CreateJobOperation operation = client.CreateJob(waitUntilCompleted: false, content); + } + + [OneTimeTearDown] + protected void Cleanup() + { + // Skip cleanup if there is no API key (e.g., if we are not running live tests). + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OPEN_API_KEY"))) + { + return; + } + + FileClient fileClient = new(); + RequestOptions requestOptions = new() + { + ErrorOptions = ClientErrorBehaviors.NoThrow, + }; + foreach (OpenAIFileInfo file in _filesToDelete) + { + Console.WriteLine($"Cleanup: {file.Id} -> {fileClient.DeleteFile(file.Id, requestOptions)?.GetRawResponse().Status}"); + } + _filesToDelete.Clear(); + } + + /// + /// Performs basic, invariant validation of a target that was just instantiated from its corresponding origination + /// mechanism. If applicable, the instance is recorded into the test run for cleanup of persistent resources. + /// + /// Instance type being validated. + /// The instance to validate. + /// The provided instance type isn't supported. + private void Validate(T target) + { + if (target is OpenAIFileInfo file) + { + Assert.That(file?.Id, Is.Not.Null); + _filesToDelete.Add(file); + } + else + { + throw new NotImplementedException($"{nameof(Validate)} helper not implemented for: {typeof(T)}"); + } + } + + private readonly List _filesToDelete = []; + + private static FineTuningClient GetTestClient() => GetTestClient(TestScenario.FineTuning); + + private static readonly DateTimeOffset s_2024 = new(2024, 1, 1, 0, 0, 0, TimeSpan.Zero); + private static readonly string s_cleanupMetadataKey = $"test_metadata_cleanup_eligible"; +} diff --git a/tests/Utility/TestHelpers.cs b/tests/Utility/TestHelpers.cs index 9231231e..6ff1237a 100644 --- a/tests/Utility/TestHelpers.cs +++ b/tests/Utility/TestHelpers.cs @@ -5,6 +5,7 @@ using OpenAI.Chat; using OpenAI.Embeddings; using OpenAI.Files; +using OpenAI.FineTuning; using OpenAI.Images; using OpenAI.VectorStores; using System; @@ -54,6 +55,7 @@ public static T GetTestClient(TestScenario scenario, string overrideModel = n TestScenario.Chat => new ChatClient(overrideModel ?? "gpt-4o-mini", options), TestScenario.Embeddings => new EmbeddingClient(overrideModel ?? "text-embedding-3-small", options), TestScenario.Files => new FileClient(options), + TestScenario.FineTuning => new FineTuningClient(options), TestScenario.Images => new ImageClient(overrideModel ?? "dall-e-3", options), #pragma warning disable OPENAI001 TestScenario.VectorStores => new VectorStoreClient(options),