diff --git a/README.zh-Hans.md b/README.zh-Hans.md index 65b1b82..d64ef04 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -1173,13 +1173,46 @@ Usage: in(31)/out(34)/reasoning()/total(65) 尽管 QWen-Long 支持直接传入字符串,但还是推荐先将文件上传后再通过 FileId 的形式传入 `message` 数组中。 -上传文件,使用 `UploadFileAsync()` 方法传入文件(注意不是 `UploadTemporaryFileAsync`, 后者是用于上传媒体文件的): +上传文件,使用 `OpenAiCompatibleUploadFileAsync()` 方法传入文件: ```csharp -var file1 = await client.UploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); +var file1 = await client.OpenAiCompatibleUploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); ``` -然后将文件作为 `system` 消息传入消息数组中,注意第一条 `system` 消息不能省略,否则模型可能会将文件里的内容当作 System prompt 。 +如果文件比较大,服务端可能需要几秒的时间进行解析。根据返回的 `file.Status` 属性是否为 `processed` 可以判断是否解析完成。未解析完成的文件无法被模型使用,需要等待解析完成。以下是一个示例方法,自动等待文档解析完毕或超时抛出异常: + +```csharp +private static async Task EnsureFileProcessedAsync( + IDashScopeClient client, + DashScopeFileId id, + int timeoutInSeconds = 5) +{ + var timeout = Task.Delay(TimeSpan.FromSeconds(timeoutInSeconds)); + while (timeout.IsCompleted == false) + { + var file = await client.GetFileAsync(id); + if (file.Status == "processed") + { + return; + } + + await Task.Delay(1000); + } + + throw new InvalidOperationException($"File not processed within timeout, fileId: {id}"); +} +``` + +调用方式: + +```csharp +if (file.Status != "processed") +{ + await EnsureFileProcessedAsync(client, file.Id, 3); // 最多等待 3 秒 +} +``` + +待文档解析完成后,将文件作为 `system` 消息传入消息数组中,注意第一条 `system` 消息不能省略,否则模型可能会将文件里的内容当作 `System prompt` 。 ```csharp var messages = new List(); @@ -1211,10 +1244,10 @@ var completion = client.GetTextCompletionStreamAsync( }); ``` -最后可以通过 `DeleteFileAsync()` 方法删除上传的文件 +最后可以通过 `OpenAiCompatibleDeleteFileAsync()` 方法删除上传的文件。 ```csharp -var result = await client.DeleteFileAsync(file1.Id); +var result = await client.OpenAiCompatibleDeleteFileAsync(file1.Id); Console.WriteLine(result.Deleted ? "Success" : "Failed"); ``` @@ -1222,11 +1255,14 @@ Console.WriteLine(result.Deleted ? "Success" : "Failed"); ```csharp Console.WriteLine("Uploading file1..."); -var file1 = await client.UploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); +var file1 = await client.OpenAiCompatibleUploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); Console.WriteLine("Uploading file2..."); -var file2 = await client.UploadFileAsync(File.OpenRead("1024-2.txt"), "file2.txt"); +var file2 = await client.OpenAiCompatibleUploadFileAsync(File.OpenRead("1024-2.txt"), "file2.txt"); Console.WriteLine($"Uploaded, file1 id: {file1.Id.ToUrl()}, file2 id: {file2.Id.ToUrl()}"); +await EnsureFileProcessedAsync(client, file1); +await EnsureFileProcessedAsync(client, file2); + var messages = new List(); messages.Add(TextChatMessage.System("You are a helpful assistant")); messages.Add(TextChatMessage.File(file1.Id)); @@ -1292,6 +1328,31 @@ Console.Write("Deleting file2..."); result = await client.DeleteFileAsync(file2.Id); Console.WriteLine(result.Deleted ? "Success" : "Failed"); +private static async Task EnsureFileProcessedAsync( + IDashScopeClient client, + DashScopeFile file, + int timeoutInSeconds = 5) +{ + if (file.Status == "processed") + { + return; + } + + var timeout = Task.Delay(TimeSpan.FromSeconds(timeoutInSeconds)); + while (timeout.IsCompleted == false) + { + var realtime = await client.GetFileAsync(file.Id); + if (realtime.Status == "processed") + { + return; + } + + await Task.Delay(1000); + } + + throw new InvalidOperationException($"File not processed within timeout, fileId: {file.Id}"); +} + /* Uploading file1... Uploading file2... @@ -1324,6 +1385,14 @@ Deleting file2...Success */ ``` +**注意及时删除上传的文件,这个接口有文件总数(1万)和文件总量(100GB)限制。** + +你可以使用 `ListFileAsync` 获取完整的文件列表并删除不再需要使用的文件 + +示例: + + + ### 翻译能力(Qwen-MT) 翻译能力主要通过 `Parameters` 里的 `TranslationOptions` 进行配置。 @@ -1507,10 +1576,10 @@ Usage: in(147)/out(130)/total(277) ### 数据挖掘(Qwen-doc-turbo) -上传文件,使用 `UploadFileAsync()` 方法传入文件(注意不是 `UploadTemporaryFileAsync`, 后者是用于上传媒体文件的): +上传文件,使用 `OpenAiCompatibleUploadFileAsync()` 方法传入文件(注意不是 `UploadTemporaryFileAsync`, 后者是用于上传媒体文件的): ```csharp -var file1 = await client.UploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); +var file1 = await client.OpenAiCompatibleUploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); ``` 然后将文件作为 `system` 消息传入消息数组中,注意第一条 `system` 消息不能省略,否则模型可能会将文件里的内容当作 System prompt 。 @@ -1549,7 +1618,7 @@ var completion = client.GetTextCompletionStreamAsync( ````csharp Console.WriteLine("Uploading file1..."); -var file1 = await client.UploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); +var file1 = await client.OpenAiCompatibleUploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); var messages = new List(); messages.Add(TextChatMessage.System("You are a helpful assistant")); messages.Add(TextChatMessage.File(file1.Id)); @@ -1593,7 +1662,7 @@ if (usage != null) // Deleting files Console.Write("Deleting file1..."); -var result = await client.DeleteFileAsync(file1.Id); +var result = await client.OpenAiCompatibleDeleteFileAsync(file1.Id); Console.WriteLine(result.Deleted ? "Success" : "Failed"); /* diff --git a/sample/Cnblogs.DashScope.Sample/Files/FileUploadSample.cs b/sample/Cnblogs.DashScope.Sample/Files/FileUploadSample.cs new file mode 100644 index 0000000..cc3298a --- /dev/null +++ b/sample/Cnblogs.DashScope.Sample/Files/FileUploadSample.cs @@ -0,0 +1,37 @@ +using System.Text.Json; +using Cnblogs.DashScope.Core; + +namespace Cnblogs.DashScope.Sample.Files; + +public class FileUploadSample : ISample +{ + /// + public string Description => "Upload File Sample"; + + /// + public async Task RunAsync(IDashScopeClient client) + { + var json = new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true }; + var file = new FileInfo("Lenna.jpg"); + Console.WriteLine("Uploading file..."); + var response = await client.UploadFilesAsync( + "file-extract", + [new DashScopeUploadFileInput(file.OpenRead(), file.Name, "Lenna")]); + Console.WriteLine($"File uploaded, fileId: {response.Data.UploadedFiles[0].FileId}"); + + await Task.Delay(1000); + Console.WriteLine("Get file info..."); + var fileInfo = await client.GetFileAsync(response.Data.UploadedFiles[0].FileId); + Console.WriteLine(JsonSerializer.Serialize(fileInfo.Data, json)); + + await Task.Delay(1000); + Console.WriteLine("List files..."); + var list = await client.ListFilesAsync(1, 2); + Console.WriteLine(JsonSerializer.Serialize(list.Data.Files, json)); + + await Task.Delay(1000); + Console.Write("Delete file..."); + await client.DeleteFileAsync(response.Data.UploadedFiles[0].FileId); + Console.WriteLine("Success"); + } +} diff --git a/sample/Cnblogs.DashScope.Sample/Text/DataMiningSample.cs b/sample/Cnblogs.DashScope.Sample/Text/DataMiningSample.cs index 3dff9a5..d8d2f60 100644 --- a/sample/Cnblogs.DashScope.Sample/Text/DataMiningSample.cs +++ b/sample/Cnblogs.DashScope.Sample/Text/DataMiningSample.cs @@ -12,11 +12,13 @@ public class DataMiningSample : ISample public async Task RunAsync(IDashScopeClient client) { Console.WriteLine("Uploading file1..."); - var file1 = await client.UploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); - var messages = new List(); - messages.Add(TextChatMessage.System("You are a helpful assistant")); - messages.Add(TextChatMessage.File(file1.Id)); - messages.Add(TextChatMessage.User("这篇文章讲了什么,整理成一个 JSON,需要包含标题(title)和摘要(description)")); + var file1 = await client.OpenAiCompatibleUploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); + var messages = new List + { + TextChatMessage.System("You are a helpful assistant"), + TextChatMessage.File(file1.Id), + TextChatMessage.User("这篇文章讲了什么,整理成一个 JSON,需要包含标题(title)和摘要(description)") + }; messages.ForEach(m => Console.WriteLine($"{m.Role} > {m.Content}")); var completion = client.GetTextCompletionStreamAsync( new ModelRequest() @@ -56,7 +58,7 @@ public async Task RunAsync(IDashScopeClient client) // Deleting files Console.Write("Deleting file1..."); - var result = await client.DeleteFileAsync(file1.Id); + var result = await client.OpenAiCompatibleDeleteFileAsync(file1.Id); Console.WriteLine(result.Deleted ? "Success" : "Failed"); } } diff --git a/sample/Cnblogs.DashScope.Sample/Text/LongContextSample.cs b/sample/Cnblogs.DashScope.Sample/Text/LongContextSample.cs index 2701673..d89fe4a 100644 --- a/sample/Cnblogs.DashScope.Sample/Text/LongContextSample.cs +++ b/sample/Cnblogs.DashScope.Sample/Text/LongContextSample.cs @@ -12,16 +12,21 @@ public class LongContextSample : ISample public async Task RunAsync(IDashScopeClient client) { Console.WriteLine("Uploading file1..."); - var file1 = await client.UploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); + var file1 = await client.OpenAiCompatibleUploadFileAsync(File.OpenRead("1024-1.txt"), "file1.txt"); Console.WriteLine("Uploading file2..."); - var file2 = await client.UploadFileAsync(File.OpenRead("1024-2.txt"), "file2.txt"); + var file2 = await client.OpenAiCompatibleUploadFileAsync(File.OpenRead("1024-2.txt"), "file2.txt"); Console.WriteLine($"Uploaded, file1 id: {file1.Id.ToUrl()}, file2 id: {file2.Id.ToUrl()}"); - var messages = new List(); - messages.Add(TextChatMessage.System("You are a helpful assistant")); - messages.Add(TextChatMessage.File(file1.Id)); - messages.Add(TextChatMessage.File(file2.Id)); - messages.Add(TextChatMessage.User("这两篇文章分别讲了什么?")); + await EnsureFileProcessedAsync(client, file1); + await EnsureFileProcessedAsync(client, file2); + + var messages = new List + { + TextChatMessage.System("You are a helpful assistant"), + TextChatMessage.File(file1.Id), + TextChatMessage.File(file2.Id), + TextChatMessage.User("这两篇文章分别讲了什么?") + }; messages.ForEach(m => Console.WriteLine($"{m.Role} > {m.Content}")); var completion = client.GetTextCompletionStreamAsync( @@ -72,12 +77,37 @@ public async Task RunAsync(IDashScopeClient client) // Deleting files Console.Write("Deleting file1..."); - var result = await client.DeleteFileAsync(file1.Id); + var result = await client.OpenAiCompatibleDeleteFileAsync(file1.Id); Console.WriteLine(result.Deleted ? "Success" : "Failed"); Console.Write("Deleting file2..."); - result = await client.DeleteFileAsync(file2.Id); + result = await client.OpenAiCompatibleDeleteFileAsync(file2.Id); Console.WriteLine(result.Deleted ? "Success" : "Failed"); } + + private static async Task EnsureFileProcessedAsync( + IDashScopeClient client, + DashScopeFile file, + int timeoutInSeconds = 5) + { + if (file.Status == "processed") + { + return; + } + + var timeout = Task.Delay(TimeSpan.FromSeconds(timeoutInSeconds)); + while (timeout.IsCompleted == false) + { + var realtime = await client.OpenAiCompatibleGetFileAsync(file.Id); + if (realtime.Status == "processed") + { + return; + } + + await Task.Delay(1000); + } + + throw new InvalidOperationException($"File not processed within timeout, fileId: {file.Id}"); + } } /* diff --git a/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs b/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs index 8bded6e..bc2eb3a 100644 --- a/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs +++ b/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs @@ -242,7 +242,7 @@ public async Task - public async Task UploadFileAsync( + public async Task OpenAiCompatibleUploadFileAsync( Stream file, string filename, string purpose = "file-extract", @@ -251,30 +251,44 @@ public async Task UploadFileAsync( var form = new MultipartFormDataContent(); form.Add(new StreamContent(file), "file", filename); form.Add(new StringContent(purpose), nameof(purpose)); - var request = new HttpRequestMessage(HttpMethod.Post, ApiLinks.Files) { Content = form }; + var request = new HttpRequestMessage(HttpMethod.Post, ApiLinks.FilesCompatible) { Content = form }; return (await SendCompatibleAsync(request, cancellationToken))!; } /// - public async Task GetFileAsync(DashScopeFileId id, CancellationToken cancellationToken = default) + public async Task OpenAiCompatibleGetFileAsync( + DashScopeFileId id, + CancellationToken cancellationToken = default) { - var request = BuildRequest(HttpMethod.Get, ApiLinks.Files + $"/{id}"); + var request = BuildRequest(HttpMethod.Get, ApiLinks.FilesCompatible + $"/{id}"); return (await SendCompatibleAsync(request, cancellationToken))!; } /// - public async Task ListFilesAsync(CancellationToken cancellationToken = default) + public async Task OpenAiCompatibleListFilesAsync( + string? after = null, + int? limit = null, + string? createAfter = null, + string? createBefore = null, + string? purpose = null, + CancellationToken cancellationToken = default) { - var request = BuildRequest(HttpMethod.Get, ApiLinks.Files); - return (await SendCompatibleAsync(request, cancellationToken))!; + var queryString = new QueryStringBuilder() + .Add(after) + .Add(limit) + .Add(createAfter, "create_after") + .Add(createBefore, "create_before") + .Add(purpose); + var request = BuildRequest(HttpMethod.Get, ApiLinks.FilesCompatible + queryString.Build()); + return (await SendCompatibleAsync(request, cancellationToken))!; } /// - public async Task DeleteFileAsync( + public async Task OpenAiCompatibleDeleteFileAsync( DashScopeFileId id, CancellationToken cancellationToken = default) { - var request = BuildRequest(HttpMethod.Delete, ApiLinks.Files + $"/{id}"); + var request = BuildRequest(HttpMethod.Delete, ApiLinks.FilesCompatible + $"/{id}"); return (await SendCompatibleAsync(request, cancellationToken))!; } @@ -332,7 +346,9 @@ public async Task UploadTemporaryFileAsync( form.Add(GetFormDataStringContent(policy.Data.XOssForbidOverwrite, "x-oss-forbid-overwrite")); var file = new StreamContent(fileStream); file.Headers.ContentType = null; - file.Headers.TryAddWithoutValidation("Content-Disposition", $"form-data; name=\"file\"; filename=\"{filename}\""); + file.Headers.TryAddWithoutValidation( + "Content-Disposition", + $"form-data; name=\"file\"; filename=\"{filename}\""); file.Headers.TryAddWithoutValidation("Content-Type", "application/octet-stream"); form.Add(file); var response = await _httpClient.PostAsync(policy.Data.UploadHost, form); @@ -348,6 +364,94 @@ public async Task UploadTemporaryFileAsync( await response.Content.ReadAsStringAsync()); } + /// + public async Task> UploadFilesAsync( + string purpose, + IEnumerable files, + bool leaveStreamOpen = false) + { + var form = DashScopeMultipartContent.Create(); + form.Add(GetFormDataStringContent(purpose, nameof(purpose))); + + List? toClose = null; + foreach (var fileData in files) + { + var file = new StreamContent(fileData.FileStream); + file.Headers.ContentType = null; + file.Headers.TryAddWithoutValidation( + "Content-Disposition", + $"form-data; name=\"file\"; filename=\"{fileData.FileName}\""); + form.Add(file); + if (string.IsNullOrWhiteSpace(fileData.Description) == false) + { + form.Add(GetFormDataStringContent(fileData.Description, "description")); + } + + if (!leaveStreamOpen) + { + toClose ??= new List(); + toClose.Add(fileData.FileStream); + } + } + + var response = await _httpClient.PostAsync(ApiLinks.Files(), form); + if (toClose?.Count > 0) + { + toClose.ForEach(x => x.Dispose()); + } + + return (await response.Content.ReadFromJsonAsync>( + DashScopeDefaults.SerializationOptions))!; + } + + /// + public async Task> ListFilesAsync( + int pageNo, + int pageSize, + CancellationToken cancellationToken = default) + { + var query = new QueryStringBuilder().Add(pageNo, "page_no").Add(pageSize, "page_size"); + var request = BuildRequest(HttpMethod.Get, ApiLinks.Files() + query.Build()); + var response = await SendAsync>(request, cancellationToken); + return response!; + } + + /// + public Task> GetFileAsync( + DashScopeFileId fileId, + CancellationToken cancellationToken = default) + { + return GetFileAsync(fileId.Value, cancellationToken); + } + + /// + public async Task> GetFileAsync( + string fileId, + CancellationToken cancellationToken = default) + { + var request = BuildRequest(HttpMethod.Get, ApiLinks.Files(fileId)); + var response = await SendAsync>(request, cancellationToken); + return response!; + } + + /// + public Task DeleteFileAsync( + DashScopeFileId fileId, + CancellationToken cancellationToken = default) + { + return DeleteFileAsync(fileId.Value, cancellationToken); + } + + /// + public async Task DeleteFileAsync( + string fileId, + CancellationToken cancellationToken = default) + { + var request = BuildRequest(HttpMethod.Delete, ApiLinks.Files(fileId)); + var response = await SendAsync(request, cancellationToken); + return response!; + } + private static StringContent GetFormDataStringContent(string value, string key) { var content = new StringContent(value); diff --git a/src/Cnblogs.DashScope.Core/DashScopeFailedUploadRecord.cs b/src/Cnblogs.DashScope.Core/DashScopeFailedUploadRecord.cs new file mode 100644 index 0000000..7def05a --- /dev/null +++ b/src/Cnblogs.DashScope.Core/DashScopeFailedUploadRecord.cs @@ -0,0 +1,9 @@ +namespace Cnblogs.DashScope.Core; + +/// +/// Represents one failed upload record. +/// +/// File name. +/// Error code. +/// Error message. +public record DashScopeFailedUploadRecord(string Name, string Code, string Message); diff --git a/src/Cnblogs.DashScope.Core/DashScopeFile.cs b/src/Cnblogs.DashScope.Core/DashScopeFile.cs index a544a2c..eb7c302 100644 --- a/src/Cnblogs.DashScope.Core/DashScopeFile.cs +++ b/src/Cnblogs.DashScope.Core/DashScopeFile.cs @@ -9,10 +9,12 @@ /// Unix timestamp(in seconds) of file create time. /// Name of the file. /// Purpose of the file. +/// Status of the file. public record DashScopeFile( DashScopeFileId Id, string Object, int Bytes, int CreatedAt, string Filename, - string? Purpose); + string? Purpose, + string Status); diff --git a/src/Cnblogs.DashScope.Core/DashScopeFileDetail.cs b/src/Cnblogs.DashScope.Core/DashScopeFileDetail.cs new file mode 100644 index 0000000..774b8bc --- /dev/null +++ b/src/Cnblogs.DashScope.Core/DashScopeFileDetail.cs @@ -0,0 +1,28 @@ +namespace Cnblogs.DashScope.Core; + +/// +/// Detail info of a DashScope file. +/// +/// ID of the file, can be used in model request. +/// Name of the file. +/// Description of the file. +/// File size in byte. +/// File's MD5. +/// Upload time. +/// Download link of the file. +/// Uploader's ID. +/// Region of the file. +/// ID of the api key which uploaded this file. +/// Internal ID of the file. +public record DashScopeFileDetail( + DashScopeFileId FileId, + string Name, + string Description, + int Size, + string Md5, + string GmtCreate, + string Url, + string? UserId, + string? Region, + string? ApiKeyId, + int? Id); diff --git a/src/Cnblogs.DashScope.Core/DashScopeFileResponse.cs b/src/Cnblogs.DashScope.Core/DashScopeFileResponse.cs new file mode 100644 index 0000000..64d3f41 --- /dev/null +++ b/src/Cnblogs.DashScope.Core/DashScopeFileResponse.cs @@ -0,0 +1,15 @@ +namespace Cnblogs.DashScope.Core; + +/// +/// Represent a file api response. +/// +/// Unique ID of the request. +public record DashScopeFileResponse(string RequestId); + +/// +/// Represent a file api response. +/// +/// Unique ID of the request. +/// Data of the response. +/// Type of the data. +public record DashScopeFileResponse(string RequestId, TData Data) : DashScopeFileResponse(RequestId); diff --git a/src/Cnblogs.DashScope.Core/DashScopeListFilesData.cs b/src/Cnblogs.DashScope.Core/DashScopeListFilesData.cs new file mode 100644 index 0000000..921c9e9 --- /dev/null +++ b/src/Cnblogs.DashScope.Core/DashScopeListFilesData.cs @@ -0,0 +1,10 @@ +namespace Cnblogs.DashScope.Core; + +/// +/// Represent one page of file list. +/// +/// Total count of files. +/// Page index. +/// Page size. +/// File item list. +public record DashScopeListFilesData(int Total, int PageNo, int PageSize, List Files); diff --git a/src/Cnblogs.DashScope.Core/DashScopeFileList.cs b/src/Cnblogs.DashScope.Core/DashScopeOpenAiCompatibleFileList.cs similarity index 72% rename from src/Cnblogs.DashScope.Core/DashScopeFileList.cs rename to src/Cnblogs.DashScope.Core/DashScopeOpenAiCompatibleFileList.cs index 447bc23..18874e1 100644 --- a/src/Cnblogs.DashScope.Core/DashScopeFileList.cs +++ b/src/Cnblogs.DashScope.Core/DashScopeOpenAiCompatibleFileList.cs @@ -6,4 +6,4 @@ /// Always be "list". /// True if not reached last page. /// Items of current page. -public record DashScopeFileList(string Object, bool HasMore, List Data); +public record DashScopeOpenAiCompatibleFileList(string Object, bool HasMore, List Data); diff --git a/src/Cnblogs.DashScope.Core/DashScopeUploadFileData.cs b/src/Cnblogs.DashScope.Core/DashScopeUploadFileData.cs new file mode 100644 index 0000000..c0d9f73 --- /dev/null +++ b/src/Cnblogs.DashScope.Core/DashScopeUploadFileData.cs @@ -0,0 +1,10 @@ +namespace Cnblogs.DashScope.Core; + +/// +/// Represents upload file result data. +/// +/// Successful uploads. +/// Failed uploads. +public record DashScopeUploadFileData( + List UploadedFiles, + List FailedUploads); diff --git a/src/Cnblogs.DashScope.Core/DashScopeUploadFileInput.cs b/src/Cnblogs.DashScope.Core/DashScopeUploadFileInput.cs new file mode 100644 index 0000000..baa6854 --- /dev/null +++ b/src/Cnblogs.DashScope.Core/DashScopeUploadFileInput.cs @@ -0,0 +1,9 @@ +namespace Cnblogs.DashScope.Core; + +/// +/// Represents one file input. +/// +/// File data. +/// Name of the file. +/// Description of the file. +public record DashScopeUploadFileInput(Stream FileStream, string FileName, string? Description = null); diff --git a/src/Cnblogs.DashScope.Core/DashScopeUploadedFileRecord.cs b/src/Cnblogs.DashScope.Core/DashScopeUploadedFileRecord.cs new file mode 100644 index 0000000..32916d6 --- /dev/null +++ b/src/Cnblogs.DashScope.Core/DashScopeUploadedFileRecord.cs @@ -0,0 +1,8 @@ +namespace Cnblogs.DashScope.Core; + +/// +/// Represents one uploaded file. +/// +/// File Id. +/// File name. +public record DashScopeUploadedFileRecord(DashScopeFileId FileId, string Name); diff --git a/src/Cnblogs.DashScope.Core/IDashScopeClient.cs b/src/Cnblogs.DashScope.Core/IDashScopeClient.cs index 679477d..c206916 100644 --- a/src/Cnblogs.DashScope.Core/IDashScopeClient.cs +++ b/src/Cnblogs.DashScope.Core/IDashScopeClient.cs @@ -202,7 +202,7 @@ Task> CreateImageGene /// The input of the task. /// The cancellation token to use. /// - public Task> + Task> CreateBackgroundGenerationTaskAsync( ModelRequest input, CancellationToken cancellationToken = default); @@ -215,7 +215,7 @@ public Task /// Purpose of the file, use "file-extract" to allow model access the file. Use "batch" for uploading batch operations .jsonl file. /// The cancellation token to use. /// - public Task UploadFileAsync( + Task OpenAiCompatibleUploadFileAsync( Stream file, string filename, string purpose = "file-extract", @@ -224,18 +224,29 @@ public Task UploadFileAsync( /// /// Get DashScope file by id. /// - /// Id of the file. + /// ID of the file. /// The cancellation token to use. /// /// Throws when file not exists, Status will be 404 in this case. - public Task GetFileAsync(DashScopeFileId id, CancellationToken cancellationToken = default); + Task OpenAiCompatibleGetFileAsync(DashScopeFileId id, CancellationToken cancellationToken = default); /// /// List DashScope files. /// + /// Fetch items after given file id. + /// Limit item count per page. + /// Filter files that created after given time. e.g. 20250306123000 + /// Filter files that created before given time. e.g. 20250306123000 + /// Filter files with given purpose. e.g. file-extract or batch. /// The cancellation token to use. /// - public Task ListFilesAsync(CancellationToken cancellationToken = default); + Task OpenAiCompatibleListFilesAsync( + string? after = null, + int? limit = null, + string? createAfter = null, + string? createBefore = null, + string? purpose = null, + CancellationToken cancellationToken = default); /// /// Delete DashScope file. @@ -244,7 +255,7 @@ public Task UploadFileAsync( /// The cancellation token to use. /// /// Throws when file not exists, Status would be 404. - public Task DeleteFileAsync( + Task OpenAiCompatibleDeleteFileAsync( DashScopeFileId id, CancellationToken cancellationToken = default); @@ -254,7 +265,7 @@ public Task DeleteFileAsync( /// The model to use. /// Cancellation token. /// - public Task CreateSpeechSynthesizerSocketSessionAsync( + Task CreateSpeechSynthesizerSocketSessionAsync( string modelId, CancellationToken cancellationToken = default); @@ -264,7 +275,7 @@ public Task CreateSpeechSynthesizerSocketSession /// The name of the model. /// /// - public Task GetTemporaryUploadPolicyAsync( + Task GetTemporaryUploadPolicyAsync( string modelId, CancellationToken cancellationToken = default); @@ -277,7 +288,7 @@ public Task CreateSpeechSynthesizerSocketSession /// /// Oss url of the file. /// Throws if response code is not 200. - public Task UploadTemporaryFileAsync( + Task UploadTemporaryFileAsync( string modelId, Stream fileStream, string filename, @@ -291,8 +302,70 @@ public Task UploadTemporaryFileAsync( /// The grant info. /// /// Throws if response code is not 200. - public Task UploadTemporaryFileAsync( + Task UploadTemporaryFileAsync( Stream fileStream, string filename, DashScopeTemporaryUploadPolicy policy); + + /// + /// Upload multiple files. + /// + /// Purpose of the file, can be 'file-extract', 'fine-tune', 'batch'. + /// The files to upload. + /// Do not dispose streams after request sent. + /// + Task> UploadFilesAsync( + string purpose, + IEnumerable files, + bool leaveStreamOpen = false); + + /// + /// List uploaded files. + /// + /// Page index. + /// Page size. + /// The to use. + /// + Task> ListFilesAsync( + int pageNo, + int pageSize, + CancellationToken cancellationToken = default); + + /// + /// Get uploaded file detail. + /// + /// The id of the file. + /// The to use. + /// + Task> GetFileAsync( + DashScopeFileId fileId, + CancellationToken cancellationToken = default); + + /// + /// Get uploaded file detail. + /// + /// The ID of the file. + /// The to use. + /// + Task> GetFileAsync( + string fileId, + CancellationToken cancellationToken = default); + + /// + /// Delete uploaded file. + /// + /// The ID of the file. + /// The to use. + /// + Task DeleteFileAsync( + DashScopeFileId fileId, + CancellationToken cancellationToken = default); + + /// + /// Delete uploaded file. + /// + /// The ID of the file. + /// The to use. + /// + Task DeleteFileAsync(string fileId, CancellationToken cancellationToken = default); } diff --git a/src/Cnblogs.DashScope.Core/Internals/ApiLinks.cs b/src/Cnblogs.DashScope.Core/Internals/ApiLinks.cs index aebeb4f..5cc65ca 100644 --- a/src/Cnblogs.DashScope.Core/Internals/ApiLinks.cs +++ b/src/Cnblogs.DashScope.Core/Internals/ApiLinks.cs @@ -11,6 +11,7 @@ internal static class ApiLinks public const string Tasks = "tasks/"; public const string Uploads = "uploads/"; public const string Tokenizer = "tokenizer"; - public const string Files = "/compatible-mode/v1/files"; + public static string Files(string? id = null) => string.IsNullOrWhiteSpace(id) ? "files" : $"files/{id}"; + public const string FilesCompatible = "/compatible-mode/v1/files"; public static string Application(string applicationId) => $"apps/{applicationId}/completion"; } diff --git a/src/Cnblogs.DashScope.Core/Internals/QueryStringBuilder.cs b/src/Cnblogs.DashScope.Core/Internals/QueryStringBuilder.cs new file mode 100644 index 0000000..2f28dd3 --- /dev/null +++ b/src/Cnblogs.DashScope.Core/Internals/QueryStringBuilder.cs @@ -0,0 +1,42 @@ +using System.Runtime.CompilerServices; +using System.Web; + +namespace Cnblogs.DashScope.Core.Internals; + +internal class QueryStringBuilder +{ + private readonly List> _items = new(); + + public QueryStringBuilder Add(T? value, [CallerArgumentExpression("value")] string? key = null) + => value switch + { + null => Add(key, null), + Enum e => Add(key, e.ToString("D")), + _ => Add(key, value.ToString()) + }; + + private QueryStringBuilder Add(string? parameterName, string? value) + { + ArgumentNullException.ThrowIfNull(parameterName); + if (string.IsNullOrEmpty(value)) + { + return this; + } + + _items.Add(new KeyValuePair(parameterName, value)); + return this; + } + + public string Build() + { + if (_items.Count == 0) + { + return string.Empty; + } + + var partial = string.Join( + '&', + _items.Select(x => $"{HttpUtility.UrlEncode(x.Key)}={HttpUtility.UrlEncode(x.Value)}")); + return $"?{partial}"; + } +} diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs index 3f3d9b5..afb4ce6 100644 --- a/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs +++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs @@ -92,9 +92,9 @@ public async Task Error_OpenAiCompatibleError_ExceptionAsync() var (client, _) = await Sut.GetTestClientAsync(false, testCase); // Act - var act = async () => await client.UploadFileAsync( - Snapshots.File.TestFile.OpenRead(), - Snapshots.File.TestFile.Name, + var act = async () => await client.OpenAiCompatibleUploadFileAsync( + Snapshots.OpenAiCompatibleFile.TestFile.OpenRead(), + Snapshots.OpenAiCompatibleFile.TestFile.Name, "other"); // Assert diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs index 5a9dac0..5f420a2 100644 --- a/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs +++ b/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs @@ -1,5 +1,6 @@ -using Cnblogs.DashScope.Tests.Shared.Utils; - +using Cnblogs.DashScope.Core; +using Cnblogs.DashScope.Core.Internals; +using Cnblogs.DashScope.Tests.Shared.Utils; using NSubstitute; namespace Cnblogs.DashScope.Sdk.UnitTests; @@ -15,11 +16,19 @@ public async Task File_Upload_SuccessAsync() var (client, handler) = await Sut.GetTestClientAsync(sse, testCase); // Act - var task = await client.UploadFileAsync(Snapshots.File.TestFile.OpenRead(), Snapshots.File.TestFile.Name); + var task = await client.UploadFilesAsync( + "file-extract", + new[] + { + new DashScopeUploadFileInput(Snapshots.File.TestFile.OpenRead(), Snapshots.File.TestFile.Name), + new DashScopeUploadFileInput(Snapshots.File.TestImage.OpenRead(), Snapshots.File.TestImage.Name) + }); // Assert handler.Received().MockSend( - Arg.Is(r => r.RequestUri!.AbsolutePath == "/compatible-mode/v1/files"), + Arg.Is(r + => r.Method == testCase.GetRequestMethod(sse) + && ("/api/v1" + r.RequestUri!.PathAndQuery) == testCase.GetRequestPathAndQuery(sse)), Arg.Any()); Assert.Equivalent(testCase.ResponseModel, task); } @@ -33,12 +42,13 @@ public async Task File_Get_SuccessAsync() var (client, handler) = await Sut.GetTestClientAsync(sse, testCase); // Act - var task = await client.GetFileAsync(testCase.ResponseModel.Id); + var task = await client.GetFileAsync(testCase.ResponseModel.Data.FileId); // Assert handler.Received().MockSend( - Arg.Is( - r => r.RequestUri!.AbsolutePath == "/compatible-mode/v1/files/" + testCase.ResponseModel.Id.Value), + Arg.Is(r + => r.Method == testCase.GetRequestMethod(sse) + && ("/api/v1" + r.RequestUri!.PathAndQuery) == testCase.GetRequestPathAndQuery(sse)), Arg.Any()); Assert.Equivalent(testCase.ResponseModel, task); } @@ -48,13 +58,18 @@ public async Task File_List_SuccessAsync() { // Arrange const bool sse = false; - var testCase = Snapshots.File.ListFileNoSse; - var (client, _) = await Sut.GetTestClientAsync(sse, testCase); + var testCase = Snapshots.File.ListFilesNoSse; + var (client, handler) = await Sut.GetTestClientAsync(sse, testCase); // Act - var list = await client.ListFilesAsync(); + var list = await client.ListFilesAsync(1, 2); // Assert + handler.Received().MockSend( + Arg.Is(r + => r.Method == testCase.GetRequestMethod(sse) + && ("/api/v1" + r.RequestUri!.PathAndQuery) == testCase.GetRequestPathAndQuery(sse)), + Arg.Any()); Assert.Equivalent(testCase.ResponseModel, list); } @@ -67,12 +82,13 @@ public async Task File_Delete_SuccessAsync() var (client, handler) = await Sut.GetTestClientAsync(sse, testCase); // Act - var task = await client.DeleteFileAsync(testCase.ResponseModel.Id); + var task = await client.DeleteFileAsync("file-fe-5d5eb068893f4b5e8551ada4"); // Assert handler.Received().MockSend( - Arg.Is( - r => r.RequestUri!.AbsolutePath == "/compatible-mode/v1/files/" + testCase.ResponseModel.Id.Value), + Arg.Is(r + => r.Method == testCase.GetRequestMethod(sse) + && ("/api/v1" + r.RequestUri!.PathAndQuery) == testCase.GetRequestPathAndQuery(sse)), Arg.Any()); Assert.Equivalent(testCase.ResponseModel, task); } diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/OpenAiCompatibleFileSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/OpenAiCompatibleFileSerializationTests.cs new file mode 100644 index 0000000..95439e8 --- /dev/null +++ b/test/Cnblogs.DashScope.Sdk.UnitTests/OpenAiCompatibleFileSerializationTests.cs @@ -0,0 +1,94 @@ +using Cnblogs.DashScope.Tests.Shared.Utils; +using NSubstitute; + +namespace Cnblogs.DashScope.Sdk.UnitTests; + +public class OpenAiCompatibleFileSerializationTests +{ + [Fact] + public async Task OpenAiFile_Upload_SuccessAsync() + { + // Arrange + const bool sse = false; + var testCase = Snapshots.OpenAiCompatibleFile.UploadFileCompatibleNoSse; + var (client, handler) = await Sut.GetTestClientAsync(sse, testCase); + + // Act + var task = await client.OpenAiCompatibleUploadFileAsync( + Snapshots.OpenAiCompatibleFile.TestFile.OpenRead(), + Snapshots.OpenAiCompatibleFile.TestFile.Name); + + // Assert + handler.Received().MockSend( + Arg.Is(r + => r.Method == testCase.GetRequestMethod(sse) + && r.RequestUri!.PathAndQuery == testCase.GetRequestPathAndQuery(sse)), + Arg.Any()); + Assert.Equivalent(testCase.ResponseModel, task); + } + + [Fact] + public async Task OpenAiFile_Get_SuccessAsync() + { + // Arrange + const bool sse = false; + var testCase = Snapshots.OpenAiCompatibleFile.GetFileCompatibleNoSse; + var (client, handler) = await Sut.GetTestClientAsync(sse, testCase); + + // Act + var task = await client.OpenAiCompatibleGetFileAsync(testCase.ResponseModel.Id); + + // Assert + handler.Received().MockSend( + Arg.Is(r + => r.Method == testCase.GetRequestMethod(sse) + && r.RequestUri!.PathAndQuery == testCase.GetRequestPathAndQuery(sse)), + Arg.Any()); + Assert.Equivalent(testCase.ResponseModel, task); + } + + [Fact] + public async Task OpenAiFile_List_SuccessAsync() + { + // Arrange + const bool sse = false; + var testCase = Snapshots.OpenAiCompatibleFile.ListFileCompatibleNoSse; + var (client, handler) = await Sut.GetTestClientAsync(sse, testCase); + + // Act + var list = await client.OpenAiCompatibleListFilesAsync( + "file-fe-e457d4773c3f4c9fbfadffaf", + 3, + "20250101", + "20251101", + "file-extract"); + + // Assert + handler.Received().MockSend( + Arg.Is(r + => r.Method == testCase.GetRequestMethod(sse) + && r.RequestUri!.PathAndQuery == testCase.GetRequestPathAndQuery(sse)), + Arg.Any()); + Assert.Equivalent(testCase.ResponseModel, list); + } + + [Fact] + public async Task OpenAiFile_Delete_SuccessAsync() + { + // Arrange + const bool sse = false; + var testCase = Snapshots.OpenAiCompatibleFile.DeleteFileCompatibleNoSse; + var (client, handler) = await Sut.GetTestClientAsync(sse, testCase); + + // Act + var task = await client.OpenAiCompatibleDeleteFileAsync(testCase.ResponseModel.Id); + + // Assert + handler.Received().MockSend( + Arg.Is(r + => r.Method == testCase.GetRequestMethod(sse) + && r.RequestUri!.PathAndQuery == testCase.GetRequestPathAndQuery(sse)), + Arg.Any()); + Assert.Equivalent(testCase.ResponseModel, task); + } +} diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/UploadSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/UploadSerializationTests.cs index b53b069..7f77974 100644 --- a/test/Cnblogs.DashScope.Sdk.UnitTests/UploadSerializationTests.cs +++ b/test/Cnblogs.DashScope.Sdk.UnitTests/UploadSerializationTests.cs @@ -29,7 +29,7 @@ public async Task Upload_GetPolicy_SuccessAsync() public async Task Upload_SubmitFileForm_SuccessAsync() { // Arrange - var file = Snapshots.File.TestImage; + var file = Snapshots.OpenAiCompatibleFile.TestImage; var policy = Snapshots.Upload.GetPolicyNoSse.ResponseModel; var testCase = Snapshots.Upload.UploadTemporaryFileNoSse; var (client, handler) = await Sut.GetTestClientAsync(new HttpResponseMessage(HttpStatusCode.NoContent)); @@ -52,7 +52,7 @@ public async Task Upload_GetOssLinkDirectly_SuccessAsync() { // Arrange const bool sse = false; - var file = Snapshots.File.TestImage; + var file = Snapshots.OpenAiCompatibleFile.TestImage; var policyCase = Snapshots.Upload.GetPolicyNoSse; var testCase = Snapshots.Upload.UploadTemporaryFileNoSse; var (client, handler) = await Sut.GetTestClientAsync(sse, policyCase); @@ -74,7 +74,7 @@ public async Task Upload_GetOssLinkDirectly_SuccessAsync() public async Task Upload_GetPolicyFailed_ThrowsAsync() { // Arrange - var file = Snapshots.File.TestImage; + var file = Snapshots.OpenAiCompatibleFile.TestImage; var (client, _) = await Sut.GetTestClientAsync( new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("null") }); diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-compatible-nosse.request.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-compatible-nosse.request.header.txt new file mode 100644 index 0000000..0d3b51c --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-compatible-nosse.request.header.txt @@ -0,0 +1,6 @@ +DELETE /compatible-mode/v1/files/file-fe-d5c0ea9110bd47afb0505f43 HTTP/1.1 +Accept: */* +Cache-Control: no-cache +Host: dashscope.aliyuncs.com +Accept-Encoding: gzip, deflate, br +Connection: keep-alive diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-compatible-nosse.response.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-compatible-nosse.response.body.txt new file mode 100644 index 0000000..8623bcd --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-compatible-nosse.response.body.txt @@ -0,0 +1 @@ +{"id":"file-fe-d5c0ea9110bd47afb0505f43","object":"file","deleted":true} diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-compatible-nosse.response.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-compatible-nosse.response.header.txt new file mode 100644 index 0000000..46d225f --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-compatible-nosse.response.header.txt @@ -0,0 +1,10 @@ +HTTP/1.1 200 OK +x-request-id: 49bfe5eb-b1e3-43fe-8689-d930a0bd2427 +content-type: application/json +content-length: 72 +req-cost-time: 549 +req-arrive-time: 1764499740121 +resp-start-time: 1764499740670 +x-envoy-upstream-service-time: 549 +date: Sun, 30 Nov 2025 10:49:00 GMT +server: istio-envoy diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.request.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.request.header.txt index 4f671b2..92043fc 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.request.header.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.request.header.txt @@ -1,4 +1,4 @@ -DELETE /api/v1/files/a4f34423-d413-4530-b167-b5180394f2ce HTTP/1.1 +DELETE /api/v1/files/file-fe-5d5eb068893f4b5e8551ada4 HTTP/1.1 Accept: */* Cache-Control: no-cache Host: dashscope.aliyuncs.com diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.response.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.response.body.txt index f9cb602..146e441 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.response.body.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.response.body.txt @@ -1 +1 @@ -{"object":"file","deleted":true,"id":"file-fe-qBKjZKfTx64R9oYmwyovNHBH"} +{"request_id":"df35151c-0df6-4cad-9912-83ebf8c633a4"} diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.response.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.response.header.txt index 134a7f3..2ec839d 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.response.header.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/delete-file-nosse.response.header.txt @@ -1,12 +1,9 @@ HTTP/1.1 200 OK -x-request-id: 3817b00a-0e6d-90cd-ada4-14a468185a59 -content-type: application/json;charset=UTF-8 -date: Wed, 10 Jul 2024 04:29:31 GMT -req-cost-time: 566 -req-arrive-time: 1720585771814 -resp-start-time: 1720585772380 -x-envoy-upstream-service-time: 565 -content-encoding: gzip -vary: Accept-Encoding +content-type: application/json +content-length: 53 +req-cost-time: 843 +req-arrive-time: 1764501039838 +resp-start-time: 1764501040681 +x-envoy-upstream-service-time: 834 +date: Sun, 30 Nov 2025 11:10:40 GMT server: istio-envoy -transfer-encoding: chunked diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-compatible-nosse.request.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-compatible-nosse.request.header.txt new file mode 100644 index 0000000..4580970 --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-compatible-nosse.request.header.txt @@ -0,0 +1,6 @@ +GET /compatible-mode/v1/files/file-fe-d5c0ea9110bd47afb0505f43 HTTP/1.1 +Accept: */* +Cache-Control: no-cache +Host: dashscope.aliyuncs.com +Accept-Encoding: gzip, deflate, br +Connection: keep-alive diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-compatible-nosse.response.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-compatible-nosse.response.body.txt new file mode 100644 index 0000000..ba9d7a6 --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-compatible-nosse.response.body.txt @@ -0,0 +1 @@ +{"id":"file-fe-d5c0ea9110bd47afb0505f43","object":"file","bytes":1314,"filename":"file1.txt","purpose":"file-extract","status":"processed","created_at":1761480070} diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-compatible-nosse.response.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-compatible-nosse.response.header.txt new file mode 100644 index 0000000..6ba197b --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-compatible-nosse.response.header.txt @@ -0,0 +1,12 @@ +HTTP/1.1 200 OK +x-request-id: a5c36afb-16a5-4f01-81a9-4001b2e0f206 +content-type: application/json +req-cost-time: 94 +req-arrive-time: 1764499203790 +resp-start-time: 1764499203885 +x-envoy-upstream-service-time: 93 +content-encoding: gzip +vary: Accept-Encoding +date: Sun, 30 Nov 2025 10:40:03 GMT +server: istio-envoy +transfer-encoding: chunked diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.request.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.request.header.txt index 88f0217..1073e2b 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.request.header.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.request.header.txt @@ -1,4 +1,4 @@ -GET /api/v1/files/6f87e744-aaff-409c-b596-1b851554bd6d HTTP/1.1 +GET /api/v1/files/file-fe-5d5eb068893f4b5e8551ada4 HTTP/1.1 Accept: */* Cache-Control: no-cache Host: dashscope.aliyuncs.com diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.response.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.response.body.txt index 8fdf9c4..5a5c625 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.response.body.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.response.body.txt @@ -1 +1 @@ -{"id":"file-fe-qBKjZKfTx64R9oYmwyovNHBH","object":"file","bytes":6,"created_at":1720582024,"filename":"test1.txt","purpose":"file-extract","status":"processed"} +{"data":{"id":193306194,"name":"test2.txt","description":"","purpose":"file-extract","size":7,"md5":"Td5oBnUd0kfuUGMd0zd5Ig==","region":"cn-beijing","file_id":"file-fe-5d5eb068893f4b5e8551ada4","url":"http://dashscope-file-mgr.oss-cn-beijing.aliyuncs.com/api-fs/1493478651020171/67516/2f2e1a67-e94c-4a00-8e86-6d51d94a8f75/test2.txt?Expires=1764587166&OSSAccessKeyId=STS.NZN1pTGUrUxPQhPveYcaRAYMg&Signature=k7llO9KgcPkeuQicUgwaGR2w%2F4A%3D&security-token=CAIS1AJ1q6Ft5B2yfSjIr5n7esrgqopT4rq7U07hkmUMb%2B5%2BrpzmhTz2IHhMdHFqBOwasfQ1nWxY7P0Ylrp6SJtIXleCZtF94oxN9h2gb4fb4wUfE3vB08%2FLI3OaLjKm9u2wCryLYbGwU%2FOpbE%2B%2B5U0X6LDmdDKkckW4OJmS8%2FBOZcgWWQ%2FKBlgvRq0hRG1YpdQdKGHaONu0LxfumRCwNkdzvRdmgm4NgsbWgO%2Fks0SD0gall7ZO%2FNiqfcL%2FMvMBZskvD42Hu8VtbbfE3SJq7BxHybx7lqQs%2B02c5onNWwMMv0nZY7CNro01d1VjFqQhXqBFqPW5jvBipO3YmsHv0RFBeOZOSDQE1i1TRm1UcgnAGaHaFd6TUxylurgEBon2HKn5z1gvlRKYWhvQG45hiCYmPtXwQEGpNl5k7MlN5QbLfi8Yf1QXq3esyb6gQz4rK%2F1R8LZDUvdUGoABihW5mNOnIIV1zRuddC7OZ50nQwm%2F23uV3Y8WICHL1WbbOqhjkgUlqZdQhBVk4pNlL2QoziUCeSwPJa6o2mvoch%2BIVx5OA48YB9pBa2KYLl%2BAkVNzQVU%2FLtOR2bQQWQwIwtKwJEP107ZOFwwQgLOapGkrawOc7PEdg0Brr71x%2BrEgAA%3D%3D","user_id":"1493478651020171","api_key_id":"67516","gmt_create":"2025-11-30 18:42:45"},"request_id":"1f05e2cb-de12-46f1-872c-ab70aa15e87f"} diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.response.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.response.header.txt index 8cf0266..457bd0e 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.response.header.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-file-nosse.response.header.txt @@ -1,12 +1,11 @@ HTTP/1.1 200 OK -x-request-id: 7564ffca-ae8f-9b85-908b-c33ab076a364 -content-type: application/json;charset=UTF-8 -date: Wed, 10 Jul 2024 04:12:14 GMT -req-cost-time: 138 -req-arrive-time: 1720584734533 -resp-start-time: 1720584734671 -x-envoy-upstream-service-time: 137 +content-type: application/json +req-cost-time: 50 +req-arrive-time: 1764500766449 +resp-start-time: 1764500766499 +x-envoy-upstream-service-time: 44 content-encoding: gzip vary: Accept-Encoding +date: Sun, 30 Nov 2025 11:06:06 GMT server: istio-envoy transfer-encoding: chunked diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-upload-policy-nosse.response.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-upload-policy-nosse.response.body.txt index fec31a7..82c0f7b 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-upload-policy-nosse.response.body.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/get-upload-policy-nosse.response.body.txt @@ -1 +1 @@ -{"data":{"policy":"eyJleHBpcmF0aW9uIjoiMjAyNS0wNy0xMlQxMjoxMDoyNC40ODhaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA3Mzc0MTgyNF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCJkYXNoc2NvcGUtaW5zdGFudFwvNTJhZmUwNzdmYjQ4MjVjNmQ3NDQxMTc1OGNiMWFiOThcLzIwMjUtMDctMTJcL2I3NDRmNGY4LTFhOWMtOWM2Yi05NTBkLTBkMzI3ZTMzMWYyZiJdLHsiYnVja2V0IjoiZGFzaHNjb3BlLWZpbGUtbWdyIn0seyJ4LW9zcy1vYmplY3QtYWNsIjoicHJpdmF0ZSJ9LHsieC1vc3MtZm9yYmlkLW92ZXJ3cml0ZSI6InRydWUifV19","signature":"n3dNX/aD3+WAly0QgzsURfiIk00=","upload_dir":"dashscope-instant/52afe077fb4825c6d74411758cb1ab98/2025-07-12/b744f4f8-1a9c-9c6b-950d-0d327e331f2f","upload_host":"https://dashscope-file-mgr.oss-cn-beijing.aliyuncs.com","expire_in_seconds":300,"max_file_size_mb":1024,"capacity_limit_mb":999999999,"oss_access_key_id":"LTAI5tG7vL6zZFFbuNrkCjdo","x_oss_object_acl":"private","x_oss_forbid_overwrite":"true"},"request_id":"b744f4f8-1a9c-9c6b-950d-0d327e331f2f"} +{"data":{"policy":"eyJleHBpcmF0aW9uIjoiMjAyNS0wNy0xMlQxMjoxMDoyNC40ODhaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA3Mzc0MTgyNF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCJkYXNoc2NvcGUtaW5zdGFudFwvNTJhZmUwNzdmYjQ4MjVjNmQ3NDQxMTc1OGNiMWFiOThcLzIwMjUtMDctMTJcL2I3NDRmNGY4LTFhOWMtOWM2Yi05NTBkLTBkMzI3ZTMzMWYyZiJdLHsiYnVja2V0IjoiZGFzaHNjb3BlLWZpbGUtbWdyIn0seyJ4LW9zcy1vYmplY3QtYWNsIjoicHJpdmF0ZSJ9LHsieC1vc3MtZm9yYmlkLW92ZXJ3cml0ZSI6InRydWUifV19","signature":"n3dNX/aD3+WAly0QgzsURfiIk00=","upload_dir":"dashscope-instant/52afe077fb4825c6d74411758cb1ab98/2025-07-12/b744f4f8-1a9c-9c6b-950d-0d327e331f2f","upload_host":"https://dashscope-file-mgr.oss-cn-beijing.aliyuncs.com","expire_in_seconds":300,"max_file_size_mb":1024,"capacity_limit_mb":999999999,"oss_access_key_id":"accessKeyId","x_oss_object_acl":"private","x_oss_forbid_overwrite":"true"},"request_id":"b744f4f8-1a9c-9c6b-950d-0d327e331f2f"} diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-compatible-nosse.request.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-compatible-nosse.request.header.txt new file mode 100644 index 0000000..61b8dd2 --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-compatible-nosse.request.header.txt @@ -0,0 +1,6 @@ +GET /compatible-mode/v1/files?after=file-fe-e457d4773c3f4c9fbfadffaf&limit=3&create_after=20250101&create_before=20251101&purpose=file-extract HTTP/1.1 +Accept: */* +Cache-Control: no-cache +Host: dashscope.aliyuncs.com +Accept-Encoding: gzip, deflate, br +Connection: keep-alive diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-compatible-nosse.response.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-compatible-nosse.response.body.txt new file mode 100644 index 0000000..5075987 --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-compatible-nosse.response.body.txt @@ -0,0 +1 @@ +{"object":"list","data":[{"id":"file-fe-d5c0ea9110bd47afb0505f43","object":"file","bytes":1314,"filename":"file1.txt","purpose":"file-extract","status":"processed","created_at":1761480070}],"has_more":false} diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-compatible-nosse.response.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-compatible-nosse.response.header.txt new file mode 100644 index 0000000..071972e --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-compatible-nosse.response.header.txt @@ -0,0 +1,11 @@ +HTTP/1.1 200 OK +x-request-id: 5f796ece-c45b-4cda-94fb-b291ef925bc9 +content-type: application/json +req-cost-time: 200 +req-arrive-time: 1764485805115 +resp-start-time: 1764485805315 +x-envoy-upstream-service-time: 199 +content-encoding: gzip +vary: Accept-Encoding +date: Sun, 30 Nov 2025 06:56:44 GMT +server: istio-envoy diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.request.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.request.header.txt index 83d65b4..55d1d9b 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.request.header.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.request.header.txt @@ -1,4 +1,4 @@ -GET /api/v1/files?page_no=1&page_size=1 HTTP/1.1 +GET /api/v1/files?page_no=1&page_size=2 HTTP/1.1 Accept: */* Cache-Control: no-cache Host: dashscope.aliyuncs.com diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.response.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.response.body.txt index cc2b25e..3e7d550 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.response.body.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.response.body.txt @@ -1 +1 @@ -{"object":"list","has_more":false,"data":[{"id":"file-fe-qBKjZKfTx64R9oYmwyovNHBH","object":"file","bytes":6,"created_at":1720582024,"filename":"test1.txt","purpose":"file-extract","status":"processed"},{"id":"file-fe-WTTG89tIUTd4ByqP3K48R3bn","object":"file","bytes":6,"created_at":1720535665,"filename":"test1.txt","purpose":"file-extract","status":"processed"}]} +{"data":{"total":15,"files":[{"id":193320717,"name":"test2.txt","description":"test2","size":7,"md5":"Td5oBnUd0kfuUGMd0zd5Ig==","region":"cn-beijing","file_id":"311d3340-1f9b-487c-bd35-481cd5a23855","url":"http://dashscope-file-mgr.oss-cn-beijing.aliyuncs.com/api-fs/1493478651020171/67516/311d3340-1f9b-487c-bd35-481cd5a23855/test2.txt?Expires=1764589060&OSSAccessKeyId=STS.NYUDr5WyfdYrXTGudSB7jFWoH&Signature=xa4nsjmZTcHaOfTXvv3cR7C7vNE%3D&security-token=CAIS1AJ1q6Ft5B2yfSjIr5rgD8iBuqZH05uZWnL2kWQGTrhGqZLEqjz2IHhMdHFqBOwasfQ1nWxY7P0Ylrp6SJtIXleCZtF94oxN9h2gb4fb40tLcHrB08%2FLI3OaLjKm9u2wCryLYbGwU%2FOpbE%2B%2B5U0X6LDmdDKkckW4OJmS8%2FBOZcgWWQ%2FKBlgvRq0hRG1YpdQdKGHaONu0LxfumRCwNkdzvRdmgm4NgsbWgO%2Fks0SD0gall7ZO%2FNiqfcL%2FMvMBZskvD42Hu8VtbbfE3SJq7BxHybx7lqQs%2B02c5onNWwMMv0nZY7CNro01d1VjFqQhXqBFqPW5jvBipO3YmsHv0RFBeOZOSDQE1i1TRm1UcgnAGaHaFd6TUxylurgEJk7zIan5z1gvlRKYWhvQG45hiCYmPtXwQEGpNl5k7MlN5QbLfi8Yf1QXq3esyb6gQz4rK2zRlCpDUvdUGoABOb8SEKtMfDU%2FVSF1VJ7hLFCQxC%2F7xuFKTrsxyK2UMGsgt18A%2Fe8Tq%2BtiBoH94pv%2B1hhJ34C6qRwLdhbhIN31TmYd%2FV9xPc9QFJYZuh49xUXFeapxnuaM8KxoXVc94FBlIy9ccDi%2FG%2BOyNbrtISjtNhJXehcJQ66tMTRxYIVeUA4gAA%3D%3D","user_id":"1493478651020171","api_key_id":"67516","gmt_create":"2025-11-30 19:37:33"},{"id":193320718,"name":"test1.txt","description":"test1","size":6,"md5":"2wbHjR4kz3CKFM6BybYX7A==","region":"cn-beijing","file_id":"8a95f76a-8d79-4d8d-b372-7d0a72493680","url":"http://dashscope-file-mgr.oss-cn-beijing.aliyuncs.com/api-fs/1493478651020171/67516/8a95f76a-8d79-4d8d-b372-7d0a72493680/test1.txt?Expires=1764589060&OSSAccessKeyId=STS.NYUDr5WyfdYrXTGudSB7jFWoH&Signature=1Gi%2FeChAGNPeFQj2oLszyd7M3ng%3D&security-token=CAIS1AJ1q6Ft5B2yfSjIr5rgD8iBuqZH05uZWnL2kWQGTrhGqZLEqjz2IHhMdHFqBOwasfQ1nWxY7P0Ylrp6SJtIXleCZtF94oxN9h2gb4fb40tLcHrB08%2FLI3OaLjKm9u2wCryLYbGwU%2FOpbE%2B%2B5U0X6LDmdDKkckW4OJmS8%2FBOZcgWWQ%2FKBlgvRq0hRG1YpdQdKGHaONu0LxfumRCwNkdzvRdmgm4NgsbWgO%2Fks0SD0gall7ZO%2FNiqfcL%2FMvMBZskvD42Hu8VtbbfE3SJq7BxHybx7lqQs%2B02c5onNWwMMv0nZY7CNro01d1VjFqQhXqBFqPW5jvBipO3YmsHv0RFBeOZOSDQE1i1TRm1UcgnAGaHaFd6TUxylurgEJk7zIan5z1gvlRKYWhvQG45hiCYmPtXwQEGpNl5k7MlN5QbLfi8Yf1QXq3esyb6gQz4rK2zRlCpDUvdUGoABOb8SEKtMfDU%2FVSF1VJ7hLFCQxC%2F7xuFKTrsxyK2UMGsgt18A%2Fe8Tq%2BtiBoH94pv%2B1hhJ34C6qRwLdhbhIN31TmYd%2FV9xPc9QFJYZuh49xUXFeapxnuaM8KxoXVc94FBlIy9ccDi%2FG%2BOyNbrtISjtNhJXehcJQ66tMTRxYIVeUA4gAA%3D%3D","user_id":"1493478651020171","api_key_id":"67516","gmt_create":"2025-11-30 19:37:33"}],"page_size":2,"page_no":1},"request_id":"d4bdceb3-0a87-4025-b226-afb13c5a3442"} diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.response.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.response.header.txt index 4c0de6c..15ea150 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.response.header.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/list-files-nosse.response.header.txt @@ -1,12 +1,11 @@ HTTP/1.1 200 OK -x-request-id: 7b920d81-2d97-965e-835d-f3ca7ee25ae4 -content-type: application/json;charset=UTF-8 -date: Wed, 10 Jul 2024 04:24:18 GMT -req-cost-time: 159 -req-arrive-time: 1720585458676 -resp-start-time: 1720585458835 -x-envoy-upstream-service-time: 158 +content-type: application/json +req-cost-time: 148 +req-arrive-time: 1764502659968 +resp-start-time: 1764502660116 +x-envoy-upstream-service-time: 139 content-encoding: gzip vary: Accept-Encoding +date: Sun, 30 Nov 2025 11:37:39 GMT server: istio-envoy transfer-encoding: chunked diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.request.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.request.body.txt new file mode 100644 index 0000000..640df95 --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.request.body.txt @@ -0,0 +1,7 @@ +----------------------------124769516956446222138824 +Content-Disposition: form-data; name="file"; filename="1ef3db24-f499-44b0-a5df-1db496e7f22e" +<1ef3db24-f499-44b0-a5df-1db496e7f22e> +----------------------------124769516956446222138824 +Content-Disposition: form-data; name="purpose" +file-extract +----------------------------124769516956446222138824-- diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.request.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.request.header.txt new file mode 100644 index 0000000..318b370 --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.request.header.txt @@ -0,0 +1,8 @@ +POST /compatible-mode/v1/files HTTP/1.1 +Accept: */* +Cache-Control: no-cache +Host: dashscope.aliyuncs.com +Accept-Encoding: gzip, deflate, br +Connection: keep-alive +Content-Type: multipart/form-data; boundary=--------------------------124769516956446222138824 +Content-Length: 332 diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.response.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.response.body.txt new file mode 100644 index 0000000..4b469fd --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.response.body.txt @@ -0,0 +1 @@ +{"id":"file-fe-5d5eb068893f4b5e8551ada4","object":"file","bytes":7,"filename":"test2.txt","purpose":"file-extract","status":"processed","created_at":1764499365} diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.response.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.response.header.txt new file mode 100644 index 0000000..e0f7fa9 --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-compatible-nosse.response.header.txt @@ -0,0 +1,12 @@ +HTTP/1.1 200 OK +x-request-id: 2f2e1a67-e94c-4a00-8e86-6d51d94a8f75 +content-type: application/json +req-cost-time: 574 +req-arrive-time: 1764499364785 +resp-start-time: 1764499365360 +x-envoy-upstream-service-time: 574 +content-encoding: gzip +vary: Accept-Encoding +date: Sun, 30 Nov 2025 10:42:45 GMT +server: istio-envoy +transfer-encoding: chunked diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.request.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.request.header.txt index 40d8b49..86093e8 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.request.header.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.request.header.txt @@ -4,4 +4,5 @@ Cache-Control: no-cache Host: dashscope.aliyuncs.com Accept-Encoding: gzip, deflate, br Connection: keep-alive -Content-Type: multipart/form-data; +Content-Type: multipart/form-data; boundary=--------------------------077180496463466976744602 +Content-Length: 599 diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.response.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.response.body.txt index 8fdf9c4..ac9098d 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.response.body.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.response.body.txt @@ -1 +1 @@ -{"id":"file-fe-qBKjZKfTx64R9oYmwyovNHBH","object":"file","bytes":6,"created_at":1720582024,"filename":"test1.txt","purpose":"file-extract","status":"processed"} +{"data":{"uploaded_files":[{"name":"test2.txt","file_id":"ed5a313b-3fdf-4cc9-a0b0-66664af692e1"}],"failed_uploads":[{"name":"test1.txt","code":"BadRequest.TooMany","message":"Out of number, <10> of <10> files has been uploaded."}]},"request_id":"982c4dc2-95a0-4fa5-982e-2732f5f9c011"} diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.response.header.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.response.header.txt index 99d5d08..fbe7475 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.response.header.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-file-nosse.response.header.txt @@ -1,12 +1,11 @@ HTTP/1.1 200 OK -x-request-id: 8ef1f38f-fc7b-90f9-bb09-1926c2675299 -content-type: application/json;charset=UTF-8 -date: Wed, 10 Jul 2024 03:27:04 GMT -req-cost-time: 346 -req-arrive-time: 1720582024232 -resp-start-time: 1720582024578 -x-envoy-upstream-service-time: 344 +content-type: application/json +req-cost-time: 392 +req-arrive-time: 1764502036427 +resp-start-time: 1764502036820 +x-envoy-upstream-service-time: 386 content-encoding: gzip vary: Accept-Encoding +date: Sun, 30 Nov 2025 11:27:16 GMT server: istio-envoy transfer-encoding: chunked diff --git a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-temporary-file-nosse.request.body.txt b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-temporary-file-nosse.request.body.txt index 11a3297..16d66ab 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-temporary-file-nosse.request.body.txt +++ b/test/Cnblogs.DashScope.Tests.Shared/RawHttpData/upload-temporary-file-nosse.request.body.txt @@ -1,7 +1,7 @@ --5aa22a67-eae4-4c54-8f62-c486fefd11a5 Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=OSSAccessKeyId -LTAI5tG7vL6zZFFbuNrkCjdo +accessKeyId --5aa22a67-eae4-4c54-8f62-c486fefd11a5 Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=policy diff --git a/test/Cnblogs.DashScope.Tests.Shared/Utils/RequestSnapshot.cs b/test/Cnblogs.DashScope.Tests.Shared/Utils/RequestSnapshot.cs index cdd3ed8..63cbf5e 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/Utils/RequestSnapshot.cs +++ b/test/Cnblogs.DashScope.Tests.Shared/Utils/RequestSnapshot.cs @@ -54,6 +54,18 @@ public string GetRequestBody(bool sse, string ext = "txt") { return File.ReadAllText(Path.Combine("RawHttpData", $"{GetSnapshotCaseName(sse)}.request.body.{ext}")); } + + public HttpMethod GetRequestMethod(bool sse) + { + var firstLine = File.ReadAllLines(Path.Combine("RawHttpData", $"{GetSnapshotCaseName(sse)}.request.header.txt"))[0]; + return new HttpMethod(firstLine.Split(' ')[0]); + } + + public string GetRequestPathAndQuery(bool sse) + { + var firstLine = File.ReadAllLines(Path.Combine("RawHttpData", $"{GetSnapshotCaseName(sse)}.request.header.txt"))[0]; + return firstLine.Split(' ')[1]; + } } public record RequestSnapshot(string Name, TResponse ResponseModel) : RequestSnapshot(Name) diff --git a/test/Cnblogs.DashScope.Tests.Shared/Utils/Snapshots.OpenAiCompatibleFile.cs b/test/Cnblogs.DashScope.Tests.Shared/Utils/Snapshots.OpenAiCompatibleFile.cs new file mode 100644 index 0000000..711d11f --- /dev/null +++ b/test/Cnblogs.DashScope.Tests.Shared/Utils/Snapshots.OpenAiCompatibleFile.cs @@ -0,0 +1,134 @@ +using Cnblogs.DashScope.Core; + +namespace Cnblogs.DashScope.Tests.Shared.Utils; + +public static partial class Snapshots +{ + public static class OpenAiCompatibleFile + { + public static readonly FileInfo TestFile = new("RawHttpData/test2.txt"); + public static readonly FileInfo TestImage = new("RawHttpData/Lenna.jpg"); + + public static readonly RequestSnapshot UploadFileCompatibleNoSse = new( + "upload-file-compatible", + new DashScopeFile( + "file-fe-5d5eb068893f4b5e8551ada4", + "file", + 7, + 1764499365, + "test2.txt", + "file-extract", + "processed")); + + public static readonly RequestSnapshot GetFileCompatibleNoSse = new( + "get-file-compatible", + new DashScopeFile( + "file-fe-d5c0ea9110bd47afb0505f43", + "file", + 1314, + 1761480070, + "file1.txt", + "file-extract", + "processed")); + + public static readonly RequestSnapshot ListFileCompatibleNoSse = new( + "list-files-compatible", + new DashScopeOpenAiCompatibleFileList( + "list", + false, + new List + { + new( + "file-fe-d5c0ea9110bd47afb0505f43", + "file", + 1314, + 1761480070, + "file1.txt", + "file-extract", + "processed"), + })); + + public static readonly RequestSnapshot DeleteFileCompatibleNoSse = new( + "delete-file-compatible", + new DashScopeDeleteFileResult("file", true, "file-fe-d5c0ea9110bd47afb0505f43")); + } + + public static class File + { + public static readonly FileInfo TestFile = new("RawHttpData/test2.txt"); + public static readonly FileInfo TestImage = new("RawHttpData/Lenna.jpg"); + + public static readonly RequestSnapshot> ListFilesNoSse = new( + "list-files", + new DashScopeFileResponse( + "d4bdceb3-0a87-4025-b226-afb13c5a3442", + new DashScopeListFilesData( + 15, + 1, + 2, + new List + { + new( + "311d3340-1f9b-487c-bd35-481cd5a23855", + "test2.txt", + "test2", + 7, + "Td5oBnUd0kfuUGMd0zd5Ig==", + "2025-11-30 19:37:33", + "http://dashscope-file-mgr.oss-cn-beijing.aliyuncs.com/api-fs/1493478651020171/67516/311d3340-1f9b-487c-bd35-481cd5a23855/test2.txt?Expires=1764589060&OSSAccessKeyId=STS.NYUDr5WyfdYrXTGudSB7jFWoH&Signature=xa4nsjmZTcHaOfTXvv3cR7C7vNE%3D&security-token=CAIS1AJ1q6Ft5B2yfSjIr5rgD8iBuqZH05uZWnL2kWQGTrhGqZLEqjz2IHhMdHFqBOwasfQ1nWxY7P0Ylrp6SJtIXleCZtF94oxN9h2gb4fb40tLcHrB08%2FLI3OaLjKm9u2wCryLYbGwU%2FOpbE%2B%2B5U0X6LDmdDKkckW4OJmS8%2FBOZcgWWQ%2FKBlgvRq0hRG1YpdQdKGHaONu0LxfumRCwNkdzvRdmgm4NgsbWgO%2Fks0SD0gall7ZO%2FNiqfcL%2FMvMBZskvD42Hu8VtbbfE3SJq7BxHybx7lqQs%2B02c5onNWwMMv0nZY7CNro01d1VjFqQhXqBFqPW5jvBipO3YmsHv0RFBeOZOSDQE1i1TRm1UcgnAGaHaFd6TUxylurgEJk7zIan5z1gvlRKYWhvQG45hiCYmPtXwQEGpNl5k7MlN5QbLfi8Yf1QXq3esyb6gQz4rK2zRlCpDUvdUGoABOb8SEKtMfDU%2FVSF1VJ7hLFCQxC%2F7xuFKTrsxyK2UMGsgt18A%2Fe8Tq%2BtiBoH94pv%2B1hhJ34C6qRwLdhbhIN31TmYd%2FV9xPc9QFJYZuh49xUXFeapxnuaM8KxoXVc94FBlIy9ccDi%2FG%2BOyNbrtISjtNhJXehcJQ66tMTRxYIVeUA4gAA%3D%3D", + "1493478651020171", + "cn-beijing", + "67516", + 193320717), + new( + "8a95f76a-8d79-4d8d-b372-7d0a72493680", + "test1.txt", + "test1", + 6, + "2wbHjR4kz3CKFM6BybYX7A==", + "2025-11-30 19:37:33", + "http://dashscope-file-mgr.oss-cn-beijing.aliyuncs.com/api-fs/1493478651020171/67516/8a95f76a-8d79-4d8d-b372-7d0a72493680/test1.txt?Expires=1764589060&OSSAccessKeyId=STS.NYUDr5WyfdYrXTGudSB7jFWoH&Signature=1Gi%2FeChAGNPeFQj2oLszyd7M3ng%3D&security-token=CAIS1AJ1q6Ft5B2yfSjIr5rgD8iBuqZH05uZWnL2kWQGTrhGqZLEqjz2IHhMdHFqBOwasfQ1nWxY7P0Ylrp6SJtIXleCZtF94oxN9h2gb4fb40tLcHrB08%2FLI3OaLjKm9u2wCryLYbGwU%2FOpbE%2B%2B5U0X6LDmdDKkckW4OJmS8%2FBOZcgWWQ%2FKBlgvRq0hRG1YpdQdKGHaONu0LxfumRCwNkdzvRdmgm4NgsbWgO%2Fks0SD0gall7ZO%2FNiqfcL%2FMvMBZskvD42Hu8VtbbfE3SJq7BxHybx7lqQs%2B02c5onNWwMMv0nZY7CNro01d1VjFqQhXqBFqPW5jvBipO3YmsHv0RFBeOZOSDQE1i1TRm1UcgnAGaHaFd6TUxylurgEJk7zIan5z1gvlRKYWhvQG45hiCYmPtXwQEGpNl5k7MlN5QbLfi8Yf1QXq3esyb6gQz4rK2zRlCpDUvdUGoABOb8SEKtMfDU%2FVSF1VJ7hLFCQxC%2F7xuFKTrsxyK2UMGsgt18A%2Fe8Tq%2BtiBoH94pv%2B1hhJ34C6qRwLdhbhIN31TmYd%2FV9xPc9QFJYZuh49xUXFeapxnuaM8KxoXVc94FBlIy9ccDi%2FG%2BOyNbrtISjtNhJXehcJQ66tMTRxYIVeUA4gAA%3D%3D", + "1493478651020171", + "cn-beijing", + "67516", + 193320718) + }))); + + public static readonly RequestSnapshot> GetFileNoSse = new( + "get-file", + new DashScopeFileResponse( + "1f05e2cb-de12-46f1-872c-ab70aa15e87f", + new DashScopeFileDetail( + "file-fe-5d5eb068893f4b5e8551ada4", + "test2.txt", + string.Empty, + 7, + "Td5oBnUd0kfuUGMd0zd5Ig==", + "2025-11-30 18:42:45", + "http://dashscope-file-mgr.oss-cn-beijing.aliyuncs.com/api-fs/1493478651020171/67516/2f2e1a67-e94c-4a00-8e86-6d51d94a8f75/test2.txt?Expires=1764587166&OSSAccessKeyId=STS.NZN1pTGUrUxPQhPveYcaRAYMg&Signature=k7llO9KgcPkeuQicUgwaGR2w%2F4A%3D&security-token=CAIS1AJ1q6Ft5B2yfSjIr5n7esrgqopT4rq7U07hkmUMb%2B5%2BrpzmhTz2IHhMdHFqBOwasfQ1nWxY7P0Ylrp6SJtIXleCZtF94oxN9h2gb4fb4wUfE3vB08%2FLI3OaLjKm9u2wCryLYbGwU%2FOpbE%2B%2B5U0X6LDmdDKkckW4OJmS8%2FBOZcgWWQ%2FKBlgvRq0hRG1YpdQdKGHaONu0LxfumRCwNkdzvRdmgm4NgsbWgO%2Fks0SD0gall7ZO%2FNiqfcL%2FMvMBZskvD42Hu8VtbbfE3SJq7BxHybx7lqQs%2B02c5onNWwMMv0nZY7CNro01d1VjFqQhXqBFqPW5jvBipO3YmsHv0RFBeOZOSDQE1i1TRm1UcgnAGaHaFd6TUxylurgEBon2HKn5z1gvlRKYWhvQG45hiCYmPtXwQEGpNl5k7MlN5QbLfi8Yf1QXq3esyb6gQz4rK%2F1R8LZDUvdUGoABihW5mNOnIIV1zRuddC7OZ50nQwm%2F23uV3Y8WICHL1WbbOqhjkgUlqZdQhBVk4pNlL2QoziUCeSwPJa6o2mvoch%2BIVx5OA48YB9pBa2KYLl%2BAkVNzQVU%2FLtOR2bQQWQwIwtKwJEP107ZOFwwQgLOapGkrawOc7PEdg0Brr71x%2BrEgAA%3D%3D", + "1493478651020171", + "cn-beijing", + "67516", + 193306194))); + + public static readonly RequestSnapshot DeleteFileNoSse = + new("delete-file", new("df35151c-0df6-4cad-9912-83ebf8c633a4")); + + public static readonly RequestSnapshot> UploadFileNoSse = new( + "upload-file", + new DashScopeFileResponse( + "982c4dc2-95a0-4fa5-982e-2732f5f9c011", + new DashScopeUploadFileData( + new List() + { + new("ed5a313b-3fdf-4cc9-a0b0-66664af692e1", "test2.txt") + }, + new List() + { + new( + "test1.txt", + "BadRequest.TooMany", + "Out of number, <10> of <10> files has been uploaded.") + }))); + } +} diff --git a/test/Cnblogs.DashScope.Tests.Shared/Utils/Snapshots.cs b/test/Cnblogs.DashScope.Tests.Shared/Utils/Snapshots.cs index e78f08b..7e22bf6 100644 --- a/test/Cnblogs.DashScope.Tests.Shared/Utils/Snapshots.cs +++ b/test/Cnblogs.DashScope.Tests.Shared/Utils/Snapshots.cs @@ -133,47 +133,6 @@ public static readonly }); } - public static class File - { - public static readonly FileInfo TestFile = new("RawHttpData/test1.txt"); - public static readonly FileInfo TestImage = new("RawHttpData/Lenna.jpg"); - - public static readonly RequestSnapshot UploadFileNoSse = new( - "upload-file", - new DashScopeFile("file-fe-qBKjZKfTx64R9oYmwyovNHBH", "file", 6, 1720582024, "test1.txt", "file-extract")); - - public static readonly RequestSnapshot GetFileNoSse = new( - "get-file", - new DashScopeFile("file-fe-qBKjZKfTx64R9oYmwyovNHBH", "file", 6, 1720582024, "test1.txt", "file-extract")); - - public static readonly RequestSnapshot ListFileNoSse = new( - "list-files", - new DashScopeFileList( - "list", - false, - new List - { - new( - "file-fe-qBKjZKfTx64R9oYmwyovNHBH", - "file", - 6, - 1720582024, - "test1.txt", - "file-extract"), - new( - "file-fe-WTTG89tIUTd4ByqP3K48R3bn", - "file", - 6, - 1720535665, - "test1.txt", - "file-extract") - })); - - public static readonly RequestSnapshot DeleteFileNoSse = new( - "delete-file", - new DashScopeDeleteFileResult("file", true, "file-fe-qBKjZKfTx64R9oYmwyovNHBH")); - } - public static class Upload { public static readonly RequestSnapshot GetPolicyNoSse = new( @@ -188,7 +147,7 @@ public static class Upload 300, 1024, 999999999, - "LTAI5tG7vL6zZFFbuNrkCjdo", + "accessKeyId", "private", "true")));