diff --git a/.editorconfig b/.editorconfig
index 6b17d25..644e447 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -384,5 +384,5 @@ dotnet_naming_style.s_camelcase.capitalization = camel_case
indent_size = 2
tab_width = 2
-[*.{props,targets,config,nuspec,json}]
+[*.{props,targets,config,nuspec,json,yml}]
indent_size = 2
diff --git "a/.github/ISSUE_TEMPLATE/api-\347\274\272\345\244\261.md" "b/.github/ISSUE_TEMPLATE/api-\347\274\272\345\244\261.md"
new file mode 100644
index 0000000..9cadf76
--- /dev/null
+++ "b/.github/ISSUE_TEMPLATE/api-\347\274\272\345\244\261.md"
@@ -0,0 +1,16 @@
+---
+name: API 缺失
+about: 百炼文档中提到了某个 API/参数/返回值,但 SDK 中没有
+title: "[API Sync]"
+labels: bug, untriaged
+assignees: ikesnowy
+
+---
+
+**百炼文档链接**
+提供百炼对应的文档链接,以及希望添加的内容
+
+**SDK 对应的方法(如果有)**
+
+**其他信息**
+例如,希望 SDK 通过 Enum 提供代码提示友好的接入
diff --git "a/.github/ISSUE_TEMPLATE/bug-\345\217\215\351\246\210.md" "b/.github/ISSUE_TEMPLATE/bug-\345\217\215\351\246\210.md"
new file mode 100644
index 0000000..c9d377f
--- /dev/null
+++ "b/.github/ISSUE_TEMPLATE/bug-\345\217\215\351\246\210.md"
@@ -0,0 +1,30 @@
+---
+name: Bug 反馈
+about: 代码不起作用/抛出异常/结果不正确
+title: "[BUG]"
+labels: bug, untriaged
+assignees: ikesnowy
+
+---
+
+**环境信息**
+安装的包及版本(例如 `Cnblogs.DashScope.Sdk/v0.7.6`):
+`.NET` 版本(例如 `net8.0)`:
+使用的模型名称(例如 `qwen-max`):
+
+**复现流程**
+
+进行如下调用时触发了 bug:
+
+```csharp
+// 输入参数
+
+// 调用的方法
+
+```
+
+**预期结果**
+按预期,以上调用应当返回 xxx 结果
+
+**其他相关信息**
+例如初步 debug 截图/部署方法(IIS, docker,k8s,控制台等)/日志信息/使用场景/访问规模等有关 BUG 出现场景的信息。
diff --git "a/.github/ISSUE_TEMPLATE/sdk-\344\275\277\347\224\250\344\275\223\351\252\214.md" "b/.github/ISSUE_TEMPLATE/sdk-\344\275\277\347\224\250\344\275\223\351\252\214.md"
new file mode 100644
index 0000000..1fb6156
--- /dev/null
+++ "b/.github/ISSUE_TEMPLATE/sdk-\344\275\277\347\224\250\344\275\223\351\252\214.md"
@@ -0,0 +1,24 @@
+---
+name: SDK 相关
+about: SDK 本身的改进,例如升级依赖包版本/添加方法重载/提供额外能力等
+title: "[SDK]"
+labels: untriaged
+assignees: ikesnowy
+
+---
+
+**希望更改的 SDK 内容**
+
+受影响的 SDK API
+```csharp
+// 提供 SDK 的方法名或者调用方法
+```
+
+希望做出的改进
+```csharp
+// 希望以这种方式调用
+```
+
+**其他信息**
+
+使用场景/项目背景/限制条件(例如:无法依赖 xx 包),以及可接受的其他方式
diff --git "a/.github/ISSUE_TEMPLATE/\346\226\207\346\241\243\347\233\270\345\205\263.md" "b/.github/ISSUE_TEMPLATE/\346\226\207\346\241\243\347\233\270\345\205\263.md"
new file mode 100644
index 0000000..90e2b60
--- /dev/null
+++ "b/.github/ISSUE_TEMPLATE/\346\226\207\346\241\243\347\233\270\345\205\263.md"
@@ -0,0 +1,12 @@
+---
+name: 文档相关
+about: SDK 在文档上需要做出的改进
+title: "[DOC]"
+labels: documentation, untriaged
+assignees: ikesnowy
+
+---
+
+**文档文件位置(如果有)**
+
+**希望做出的改进**
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1ab7c15..0af6bf5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,15 +7,25 @@ on:
branches: [ "main" ]
jobs:
- test:
+ test-net6:
runs-on: ubuntu-latest
- container: mcr.microsoft.com/dotnet/sdk:8.0
+ container: mcr.microsoft.com/dotnet/sdk:6.0
steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Build
- run: dotnet build -c Release
- - name: Test
- run: dotnet test -c Release
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Build
+ run: dotnet build src/Cnblogs.DashScope.AspNetCore -c Release
+ - name: Test
+ run: dotnet test test/Cnblogs.DashScope.Sdk.UnitTests -c Release
+ test-net8:
+ runs-on: ubuntu-latest
+ container: mcr.microsoft.com/dotnet/sdk:8.0
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Build
+ run: dotnet build src/Cnblogs.DashScope.AI -c Release
+ - name: Test
+ run: dotnet test test/Cnblogs.DashScope.AI.UnitTests -c Release
diff --git a/Cnblogs.DashScope.Sdk.sln b/Cnblogs.DashScope.Sdk.sln
index 26d0161..2cb77fe 100644
--- a/Cnblogs.DashScope.Sdk.sln
+++ b/Cnblogs.DashScope.Sdk.sln
@@ -16,10 +16,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.DashScope.AspNetCor
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.DashScope.Core", "src\Cnblogs.DashScope.Core\Cnblogs.DashScope.Core.csproj", "{CC389455-A3EA-4F09-B524-4DC351A1E1AA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.DashScope.Sdk.SnapshotGenerator", "test\Cnblogs.DashScope.Sdk.SnapshotGenerator\Cnblogs.DashScope.Sdk.SnapshotGenerator.csproj", "{5088DE77-1CE3-46FB-B9D0-27A6C9A5EED1}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.DashScope.AI", "src\Cnblogs.DashScope.AI\Cnblogs.DashScope.AI.csproj", "{5D5AD75A-8084-4738-AC56-B8A23E649452}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.DashScope.AI.UnitTests", "test\Cnblogs.DashScope.AI.UnitTests\Cnblogs.DashScope.AI.UnitTests.csproj", "{25EE79E1-147B-42FD-AFEA-E1550EDD1D36}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.DashScope.Tests.Shared", "test\Cnblogs.DashScope.Tests.Shared\Cnblogs.DashScope.Tests.Shared.csproj", "{06F0AF23-445B-4C6F-9E19-570DA9B7435D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -31,8 +33,9 @@ Global
{8885149A-78F0-4C8E-B9AA-87A46EA69219} = {2E15D1EC-4A07-416E-8BE6-D907F509FD35}
{C910495B-87AB-4AC1-989C-B6720695A139} = {008988ED-0A3B-4272-BCC3-7B4110699345}
{CC389455-A3EA-4F09-B524-4DC351A1E1AA} = {008988ED-0A3B-4272-BCC3-7B4110699345}
- {5088DE77-1CE3-46FB-B9D0-27A6C9A5EED1} = {CFC8ECB3-5248-46CD-A56C-EC088F2A3804}
{5D5AD75A-8084-4738-AC56-B8A23E649452} = {008988ED-0A3B-4272-BCC3-7B4110699345}
+ {25EE79E1-147B-42FD-AFEA-E1550EDD1D36} = {CFC8ECB3-5248-46CD-A56C-EC088F2A3804}
+ {06F0AF23-445B-4C6F-9E19-570DA9B7435D} = {CFC8ECB3-5248-46CD-A56C-EC088F2A3804}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FA6A118A-8D26-4B7A-9952-8504B8A0025B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -55,13 +58,17 @@ Global
{CC389455-A3EA-4F09-B524-4DC351A1E1AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC389455-A3EA-4F09-B524-4DC351A1E1AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC389455-A3EA-4F09-B524-4DC351A1E1AA}.Release|Any CPU.Build.0 = Release|Any CPU
- {5088DE77-1CE3-46FB-B9D0-27A6C9A5EED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5088DE77-1CE3-46FB-B9D0-27A6C9A5EED1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5088DE77-1CE3-46FB-B9D0-27A6C9A5EED1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5088DE77-1CE3-46FB-B9D0-27A6C9A5EED1}.Release|Any CPU.Build.0 = Release|Any CPU
{5D5AD75A-8084-4738-AC56-B8A23E649452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D5AD75A-8084-4738-AC56-B8A23E649452}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D5AD75A-8084-4738-AC56-B8A23E649452}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D5AD75A-8084-4738-AC56-B8A23E649452}.Release|Any CPU.Build.0 = Release|Any CPU
+ {25EE79E1-147B-42FD-AFEA-E1550EDD1D36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {25EE79E1-147B-42FD-AFEA-E1550EDD1D36}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {25EE79E1-147B-42FD-AFEA-E1550EDD1D36}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {25EE79E1-147B-42FD-AFEA-E1550EDD1D36}.Release|Any CPU.Build.0 = Release|Any CPU
+ {06F0AF23-445B-4C6F-9E19-570DA9B7435D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {06F0AF23-445B-4C6F-9E19-570DA9B7435D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {06F0AF23-445B-4C6F-9E19-570DA9B7435D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {06F0AF23-445B-4C6F-9E19-570DA9B7435D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/Cnblogs.DashScope.Sdk.sln.DotSettings b/Cnblogs.DashScope.Sdk.sln.DotSettings
index 3ad7f9e..9866f59 100644
--- a/Cnblogs.DashScope.Sdk.sln.DotSettings
+++ b/Cnblogs.DashScope.Sdk.sln.DotSettings
@@ -1,4 +1,8 @@
+ True
True
True
- True
\ No newline at end of file
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index f39a7b6..3eaabd9 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,6 @@
- net8.0
+ net6.0
enable
enable
Cnblogs
diff --git a/README.md b/README.md
index 02f3ff3..18049e2 100644
--- a/README.md
+++ b/README.md
@@ -1,42 +1,40 @@
English | [简体中文](https://github.com/cnblogs/dashscope-sdk/blob/main/README.zh-Hans.md)
+# Cnblogs.DashScopeSDK
+
[](https://www.nuget.org/packages/Cnblogs.DashScope.AI)
[](https://www.nuget.org/packages/Cnblogs.DashScope.Sdk)
[](https://www.nuget.org/packages/Cnblogs.DashScope.AspNetCore)
-# DashScope SDK for .NET
-
-An unofficial DashScope SDK maintained by Cnblogs.
+A non-official DashScope (Bailian) service SDK maintained by Cnblogs.
-**Warning**: this project is under active development, **Breaking Changes** may introduced without notice or major version change. Make sure you read the Release Notes before upgrading.
+**Note:** This project is actively under development. Breaking changes may occur even in minor versions. Please review the Release Notes before upgrading.
-# Quick Start
+## Quick Start
-## Using `Microsoft.Extensions.AI`
-
-Install `Cnblogs.DashScope.AI` Package
+### Using `Microsoft.Extensions.AI` Interface
+Install NuGet package `Cnblogs.DashScope.AI`
```csharp
var client = new DashScopeClient("your-api-key").AsChatClient("qwen-max");
var completion = await client.CompleteAsync("hello");
Console.WriteLine(completion)
```
-## Console App
-
-Install `Cnblogs.DashScope.Sdk` package.
+### Console Application
+Install NuGet package `Cnblogs.DashScope.Sdk`
```csharp
var client = new DashScopeClient("your-api-key");
var completion = await client.GetQWenCompletionAsync(QWenLlm.QWenMax, prompt);
-// or pass the model name string directly.
+// Or use model name string
// var completion = await client.GetQWenCompletionAsync("qwen-max", prompt);
Console.WriteLine(completion.Output.Text);
```
-## ASP.NET Core
+### ASP.NET Core Application
-Install the Cnblogs.DashScope.AspNetCore package.
+Install NuGet package `Cnblogs.DashScope.AspNetCore`
`Program.cs`
```csharp
@@ -44,99 +42,42 @@ builder.AddDashScopeClient(builder.Configuration);
```
`appsettings.json`
+
```json
{
"DashScope": {
- "ApiKey": "your-api-key",
+ "ApiKey": "your-api-key"
}
}
```
-`Usage`
+Application class:
+
```csharp
public class YourService(IDashScopeClient client)
{
public async Task CompletePromptAsync(string prompt)
{
- var completion = await client.GetQWenCompletionAsync(QWenLlm.QWenMax, prompt);
- return completion.Output.Text;
+ var completion = await client.GetQWenCompletionAsync(QWenLlm.QWenMax, prompt);
+ return completion.Output.Text;
}
}
```
+## Supported APIs
+- [Chat](#Chat) - QWen3, DeepSeek, etc. Supports reasoning, tool calling, web search, translation
+- [Multimodal](#multimodal) - QWen-VL, QVQ, etc. Supports reasoning, visual understanding, OCR, audio understanding
+- [Text-to-Speech (TTS)](#Text-to-Speech) - CosyVoice, Sambert
+- [Image Generation](#image-generation) - Wanx2.1 (text-to-image, portrait style transfer)
+- [Application Call](#application-call)
+- [Text Vectorization](#text-vectorization)
-# Supported APIs
-
-- Text Embedding API - `GetTextEmbeddingsAsync()`
-- Text Generation API(qwen-turbo, qwen-max, etc.) - `GetQWenCompletionAsync()` and `GetQWenCompletionStreamAsync()`
-- DeepSeek Models - `GetDeepSeekCompletionAsync()` and `GetDeepSeekCompletionStreamAsync()`
-- BaiChuan Models - Use `GetBaiChuanTextCompletionAsync()`
-- LLaMa2 Models - `GetLlama2TextCompletionAsync()`
-- Multimodal Generation API(qwen-vl-max, etc.) - `GetQWenMultimodalCompletionAsync()` and `GetQWenMultimodalCompletionStreamAsync()`
-- Wanx Models(Image generation, background generation, etc)
- - Image Synthesis - `CreateWanxImageSynthesisTaskAsync()` and `GetWanxImageSynthesisTaskAsync()`
- - Image Generation - `CreateWanxImageGenerationTaskAsync()` and `GetWanxImageGenerationTaskAsync()`
- - Background Image Generation - `CreateWanxBackgroundGenerationTaskAsync()` and `GetWanxBackgroundGenerationTaskAsync()`
-- File API that used by Qwen-Long - `UploadFileAsync()` and `DeleteFileAsync`
-- Application call - `GetApplicationResponseAsync()` and `GetApplicationResponseStreamAsync()`
-
-# Examples
-
-Visit [snapshots](./test/Cnblogs.DashScope.Sdk.UnitTests/Utils/Snapshots.cs) for calling samples.
-
-Visit [tests](./test/Cnblogs.DashScope.Sdk.UnitTests) for more usage of each api.
-
-## General Text Completion API
-
-Use `client.GetTextCompletionAsync` and `client.GetTextCompletionStreamAsync` to access text generation api directly.
-
-```csharp
-var completion = await dashScopeClient.GetTextCompletionAsync(
- new ModelRequest
- {
- Model = "your-model-name",
- Input = new TextGenerationInput { Prompt = prompt },
- Parameters = new TextGenerationParameters()
- {
- // control parameters as you wish.
- EnableSearch = true
- }
- });
-var completions = dashScopeClient.GetTextCompletionStreamAsync(
- new ModelRequest
- {
- Model = "your-model-name",
- Input = new TextGenerationInput { Messages = [TextChatMessage.System("you are a helpful assistant"), TextChatMessage.User("How are you?")] },
- Parameters = new TextGenerationParameters()
- {
- // control parameters as you wish.
- EnableSearch = true,
- IncreamentalOutput = true
- }
- });
-```
-
-## Single Text Completion
-```csharp
-var prompt = "hello"
-var completion = await client.GetQWenCompletionAsync(QWenLlm.QWenMax, prompt);
-Console.WriteLine(completion.Output.Text);
-```
-
-## Reasoning
+### Chat
-Use `completion.Output.Choices![0].Message.ReasoningContent` to access the reasoning content from model.
-
-```csharp
-var history = new List
-{
- ChatMessage.User("Calculate 1+1")
-};
-var completion = await client.GetDeepSeekChatCompletionAsync(DeepSeekLlm.DeepSeekR1, history);
-Console.WriteLine(completion.Output.Choices[0]!.Message.ReasoningContent);
-```
+Use `GetTextCompletionAsync`/`GetTextCompletionStreamAsync` for direct text generation.
+For QWen and DeepSeek, use shortcuts: `GetQWenChatCompletionAsync`/`GetDeepSeekChatCompletionAsync`
-## Multi-round chat
+[Official Documentation](https://help.aliyun.com/zh/model-studio/user-guide/text-generation/)
```csharp
var history = new List
@@ -153,115 +94,191 @@ var completion = await client.GetQWenChatCompletionAsync(QWenLlm.QWenMax, histor
Console.WriteLine(completion.Output.Choices[0].Message.Content); // The number is 42
```
-## Function Call
+#### Reasoning
-Creates a function with parameters
+Access model thoughts via `ReasoningContent` property
+```csharp
+var history = new List
+{
+ TextChatMessage.User("Calculate 1+1")
+};
+var completion = await client.GetDeepSeekChatCompletionAsync(DeepSeekLlm.DeepSeekR1, history);
+Console.WriteLine(completion.Output.Choices[0]!.Message.ReasoningContent);
+```
+For QWen3 models, enable reasoning with `TextGenerationParameters.EnableThinking`
+```csharp
+var stream = dashScopeClient
+ .GetQWenChatStreamAsync(
+ QWenLlm.QWenPlusLatest,
+ history,
+ new TextGenerationParameters
+ {
+ IncrementalOutput = true,
+ ResultFormat = ResultFormats.Message,
+ EnableThinking = true
+ });
+```
+#### Tool Calling
+Define a function for model to use:
```csharp
string GetCurrentWeather(GetCurrentWeatherParameters parameters)
{
- // actual implementation should be different.
- return "Sunny, 14" + parameters.Unit switch
- {
- TemperatureUnit.Celsius => "℃",
- TemperatureUnit.Fahrenheit => "℉"
- };
+ return "Sunny";
}
-
public record GetCurrentWeatherParameters(
[property: Required]
- [property: Description("The city and state, e.g. San Francisco, CA")]
+ [property: Description("City and state, e.g. San Francisco, CA")]
string Location,
[property: JsonConverter(typeof(EnumStringConverter))]
TemperatureUnit Unit = TemperatureUnit.Celsius);
-
-public enum TemperatureUnit
-{
- Celsius,
- Fahrenheit
-}
+public enum TemperatureUnit { Celsius, Fahrenheit }
```
-
-Append tool information to chat messages.
-
+Invoke with tool definitions. We using `JsonSchema.Net` for example, you could use any other library to generate JSON schema)
```csharp
-var tools = new List()
+var tools = new List
{
new(
ToolTypes.Function,
new FunctionDefinition(
nameof(GetCurrentWeather),
- "Get the weather abount given location",
+ "Get current weather",
new JsonSchemaBuilder().FromType().Build()))
};
+var history = new List { ChatMessage.User("What's the weather in CA?") };
+var parameters = new TextGenerationParameters { ResultFormat = ResultFormats.Message, Tools = tools };
-var history = new List
-{
- ChatMessage.User("What is the weather today in C.A?")
-};
-
-var parameters = new TextGenerationParamters()
-{
- ResultFormat = ResultFormats.Message,
- Tools = tools
-};
-
-// send question with available tools.
+// request model
var completion = await client.GetQWenChatCompletionAsync(QWenLlm.QWenMax, history, parameters);
-history.Add(completion.Output.Choice[0].Message);
-
-// model responding with tool calls.
Console.WriteLine(completion.Output.Choice[0].Message.ToolCalls[0].Function.Name); // GetCurrentWeather
+history.Add(completion.Output.Choice[0].Message);
-// calling tool that model requests and append result into history.
-var result = GetCurrentWeather(JsonSerializer.Deserialize(completion.Output.Choice[0].Message.ToolCalls[0].Function.Arguments));
-history.Add(ChatMessage.Tool(result, nameof(GetCurrentWeather)));
+// calls tool
+var result = GetCurrentWeather(new() { Location = "CA" });
+history.Add(new("tool", result, nameof(GetCurrentWeather)));
-// get back answers.
+// Get final answer
completion = await client.GetQWenChatCompletionAsync(QWenLlm.QWenMax, history, parameters);
-Console.WriteLine(completion.Output.Choice[0].Message.Content);
+Console.WriteLine(completion.Output.Choices[0].Message.Content); // "Current weather in California: Sunny"
```
+#### File Upload (Long Context Models)
+For Qwen-Long models:
+```csharp
+var file = new FileInfo("test.txt");
+var uploadedFile = await dashScopeClient.UploadFileAsync(file.OpenRead(), file.Name);
+var history = new List { ChatMessage.File(uploadedFile.Id) };
+var completion = await client.GetQWenChatCompletionAsync(QWenLlm.QWenLong, history);
+Console.WriteLine(completion.Output.Choices[0].Message.Content);
+// Cleanup
+await dashScopeClient.DeleteFileAsync(uploadedFile.Id);
+```
+### Multimodal
+Use `GetMultimodalGenerationAsync`/`GetMultimodalGenerationStreamAsync`
+[Official Documentation](https://help.aliyun.com/zh/model-studio/multimodal)
-Append the tool calling result with `tool` role, then model will generate answers based on tool calling result.
+```csharp
+var image = await File.ReadAllBytesAsync("Lenna.jpg");
+var response = dashScopeClient.GetMultimodalGenerationStreamAsync(
+ new ModelRequest()
+ {
+ Model = "qvq-plus",
+ Input = new MultimodalInput()
+ {
+ Messages =
+ [
+ MultimodalMessage.User(
+ [
+ MultimodalMessageContent.ImageContent(image, "image/jpeg"),
+ MultimodalMessageContent.TextContent("她是谁?")
+ ])
+ ]
+ },
+ Parameters = new MultimodalParameters { IncrementalOutput = true, VlHighResolutionImages = false }
+ });
+
+// output
+var reasoning = false;
+await foreach (var modelResponse in response)
+{
+ var choice = modelResponse.Output.Choices.FirstOrDefault();
+ if (choice != null)
+ {
+ if (choice.FinishReason != "null")
+ {
+ break;
+ }
+ if (string.IsNullOrEmpty(choice.Message.ReasoningContent) == false)
+ {
+ if (reasoning == false)
+ {
+ reasoning = true;
+ Console.WriteLine("");
+ }
-## QWen-Long with files
+ Console.Write(choice.Message.ReasoningContent);
+ continue;
+ }
-Upload file first.
+ if (reasoning)
+ {
+ reasoning = false;
+ Console.WriteLine("");
+ }
-```csharp
-var file = new FileInfo("test.txt");
-var uploadedFile = await dashScopeClient.UploadFileAsync(file.OpenRead(), file.Name);
+ Console.Write(choice.Message.Content[0].Text);
+ }
+}
```
+### Text-to-Speech
+
+Create a speech synthesis session using `dashScopeClient.CreateSpeechSynthesizerSocketSessionAsync()`.
-Using uploaded file id in messages.
+Note: Use the using statement to automatically dispose the session, or manually call Dispose() to release resources. Avoid reusing sessions.
+Create a synthesis session:
```csharp
-var history = new List
+using var tts = await dashScopeClient.CreateSpeechSynthesizerSocketSessionAsync("cosyvoice-v2");
+var taskId = await tts.RunTaskAsync(new SpeechSynthesizerParameters { Voice = "longxiaochun_v2", Format = "mp3" });
+await tts.ContinueTaskAsync(taskId, "Cnblogs");
+await tts.ContinueTaskAsync(taskId, "Code changes the world");
+await tts.FinishTaskAsync(taskId);
+var file = new FileInfo("tts.mp3");
+using var stream = file.OpenWrite();
+await foreach (var b in tts.GetAudioAsync())
{
- ChatMessage.File(uploadedFile.Id), // use array for multiple files, e.g. [file1.Id, file2.Id]
- ChatMessage.User("Summarize the content of file.")
+ stream.WriteByte(b);
}
-var parameters = new TextGenerationParameters()
-{
- ResultFormat = ResultFormats.Message
-};
-var completion = await client.GetQWenChatCompletionAsync(QWenLlm.QWenLong, history, parameters);
-Console.WriteLine(completion.Output.Choices[0].Message.Content);
+Console.WriteLine($"Audio saved to {file.FullName}");
```
-
-Delete file if needed
-
+### Image Generation
+#### Text-to-Image
+Use shortcuts for Wanx models:
```csharp
-var deletionResult = await dashScopeClient.DeleteFileAsync(uploadedFile.Id);
+var task = await dashScopeClient.CreateWanxImageSynthesisTaskAsync(
+ WanxModel.WanxV21Turbo,
+ "A futuristic cityscape at sunset",
+ new ImageSynthesisParameters { Style = ImageStyles.OilPainting });
+// Pull status
+while (true)
+{
+ var result = await dashScopeClient.GetWanxImageSynthesisTaskAsync(task.TaskId);
+ if (result.Output.TaskStatus == DashScopeTaskStatus.Succeeded)
+ {
+ Console.WriteLine($"Image URL: {result.Output.Results[0].Url}");
+ break;
+ }
+ await Task.Delay(500);
+}
```
+#### Portrait Style Transfer
+Use `CreateWanxImageGenerationTaskAsync` and `GetWanxImageGenerationTaskAsync`
-## Application call
+#### Background Generation
-Use `GetApplicationResponseAsync` to call an application.
-
-Use `GetApplicationResponseStreamAsync` for streaming output.
+Use `CreateWanxBackgroundGenerationTaskAsync` and `GetWanxBackgroundGenerationTaskAsync`
+### Application Call
```csharp
var request =
new ApplicationRequest()
@@ -283,8 +300,7 @@ var request =
var response = await client.GetApplicationResponseAsync("your-application-id", request);
Console.WriteLine(response.Output.Text);
```
-
-`ApplicationRequest` use an `Dictionary` as `BizParams` by default.
+`ApplicationRequest` uses `Dictionary` as the default type for `BizParams`.
```csharp
var request =
@@ -302,14 +318,13 @@ var request =
var response = await client.GetApplicationResponseAsync("your-application-id", request);
Console.WriteLine(response.Output.Text);
```
-
-You can use the generic version `ApplicationRequest` for strong-typed `BizParams`. But keep in mind that client use `snake_case` by default when doing json serialization, you may need to use `[JsonPropertyName("camelCase")]` for other type of naming policy.
+For strong typing support, you can use the generic class `ApplicationRequest`.
+Note that the SDK uses `snake_case` for JSON serialization. If your application uses different naming conventions, manually specify the serialized property names using `[JsonPropertyName("camelCase")]`.
```csharp
public record TestApplicationBizParam(
[property: JsonPropertyName("sourceCode")]
string SourceCode);
-
var request =
new ApplicationRequest()
{
@@ -323,3 +338,18 @@ var response = await client.GetApplicationResponseAsync("your-application-id", r
Console.WriteLine(response.Output.Text);
```
+### Text Vectorization
+
+```csharp
+var text = "Sample text for embedding";
+var response = await dashScopeClient.GetTextEmbeddingsAsync(
+ TextEmbeddingModel.TextEmbeddingV4,
+ [text],
+ new TextEmbeddingParameters { Dimension = 512 });
+var embedding = response.Output.Embeddings.First().Embedding;
+Console.WriteLine($"Embedding vector length: {embedding.Length}");
+```
+
+See [Snapshot Files](./test/Cnblogs.DashScope.Tests.Shared/Utils/Snapshots.cs) for API parameter examples.
+
+Review [Tests](./test) for comprehensive usage examples.
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index f275d76..aea5642 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -1,18 +1,18 @@
[English](https://github.com/cnblogs/dashscope-sdk/blob/main/README.md) | 简体中文
+# Cnblogs.DashScopeSDK
+
[](https://www.nuget.org/packages/Cnblogs.DashScope.AI)
[](https://www.nuget.org/packages/Cnblogs.DashScope.Sdk)
[](https://www.nuget.org/packages/Cnblogs.DashScope.AspNetCore)
-# Cnblogs.DashScopeSDK
-
由博客园维护并使用的非官方灵积(百炼)服务 SDK。
使用前注意:当前项目正在积极开发中,小版本也可能包含破坏性更改,升级前请查看对应版本 Release Note 进行迁移。
-# 快速开始
+## 快速开始
-## 使用 `Microsoft.Extensions.AI` 接口
+### 使用 `Microsoft.Extensions.AI` 接口
安装 NuGet 包 `Cnblogs.DashScope.AI`
@@ -22,7 +22,7 @@ var completion = await client.CompleteAsync("hello");
Console.WriteLine(completion)
```
-## 控制台应用
+### 控制台应用
安装 NuGet 包 `Cnblogs.DashScope.Sdk`。
@@ -34,7 +34,7 @@ var completion = await client.GetQWenCompletionAsync(QWenLlm.QWenMax, prompt);
Console.WriteLine(completion.Output.Text);
```
-## ASP.NET Core 应用
+### ASP.NET Core 应用
安装 NuGet 包 `Cnblogs.DashScope.AspNetCore`。
@@ -64,70 +64,22 @@ public class YourService(IDashScopeClient client)
}
```
-# 支持的 API
+## 支持的 API
-- 通用文本向量 - `GetTextEmbeddingsAsync()`
-- 通义千问(`qwen-turbo`, `qwen-max` 等) - `GetQWenCompletionAsync()` 和 `GetQWenCompletionStreamAsync()`
-- DeepSeek 系列模型(`deepseek-r1`,`deepseek-v3` 等) - `GetDeepSeekChatCompletionAsync()` 和 `GetDeepSeekChatCompletionStreamAsync()`
-- 百川开源大模型 - `GetBaiChuanTextCompletionAsync()`
-- LLaMa2 大语言模型 - `GetLlama2TextCompletionAsync()`
-- 通义千问 VL 和通义千问 Audio(`qwen-vl-max`, `qwen-audio`) - `GetQWenMultimodalCompletionAsync()` 和 `GetQWenMultimodalCompletionStreamAsync()`
-- 通义万相系列
- - 文生图 - `CreateWanxImageSynthesisTaskAsync()` 和 `GetWanxImageSynthesisTaskAsync()`
- - 人像风格重绘 - `CreateWanxImageGenerationTaskAsync()` 和 `GetWanxImageGenerationTaskAsync()`
- - 图像背景生成 - `CreateWanxBackgroundGenerationTaskAsync()` 和 `GetWanxBackgroundGenerationTaskAsync()`
-- 适用于 QWen-Long 的文件 API `UploadFileAsync()` 和 `DeleteFileAsync`
-- 应用调用 `GetApplicationResponseAsync` 和 `GetApplicationResponseStreamAsync()`
-- 其他使用相同 Endpoint 的模型
+- [对话](#对话) - QWen3, DeepSeek 等,支持推理/工具调用/网络搜索/翻译等场景
+- [多模态](#多模态) - QWen-VL,QVQ 等,支持推理/视觉理解/OCR/音频理解等场景
+- [语音合成](#语音合成) - CosyVoice,Sambert 等,支持 TTS 等应用场景
+- [图像生成](#图像生成) - wanx2.1 等,支持文生图,人像风格重绘等应用场景
+- [应用调用](#应用调用)
+- [文本向量](#文本向量)
-# 示例
-
-查看 [Snapshots.cs](./test/Cnblogs.DashScope.Sdk.UnitTests/Utils/Snapshots.cs) 获得 API 调用参数示例.
-
-查看 [测试](./test) 获得更多 API 使用示例。
-
-## 文本生成
+### 对话
使用 `dashScopeClient.GetTextCompletionAsync` 和 `dashScopeClient.GetTextCompletionStreamAsync` 来直接访问文本生成接口。
-相关文档:https://help.aliyun.com/zh/model-studio/user-guide/text-generation/
-
-```csharp
-var completion = await dashScopeClient.GetTextCompletionAsync(
- new ModelRequest
- {
- Model = "your-model-name",
- Input = new TextGenerationInput { Prompt = prompt },
- Parameters = new TextGenerationParameters()
- {
- // control parameters as you wish.
- EnableSearch = true
- }
- });
-
-var completions = dashScopeClient.GetTextCompletionStreamAsync(
- new ModelRequest
- {
- Model = "your-model-name",
- Input = new TextGenerationInput { Messages = [TextChatMessage.System("you are a helpful assistant"), TextChatMessage.User("How are you?")] },
- Parameters = new TextGenerationParameters()
- {
- // control parameters as you wish.
- EnableSearch = true,
- IncreamentalOutput = true
- }
- });
-```
+针对通义千问和 DeekSeek,我们提供了快捷方法进行调用: `GetQWenChatCompletionAsync` /`GetDeepSeekChatCompletionAsync`
-## 单轮对话
-
-```csharp
-var prompt = "你好"
-var completion = await client.GetQWenCompletionAsync(QWenLlm.QWenMax, prompt);
-Console.WriteLine(completion.Output.Text);
-```
-
-## 多轮对话
+相关文档:https://help.aliyun.com/zh/model-studio/user-guide/text-generation/
```csharp
var history = new List
@@ -144,7 +96,7 @@ var completion = await client.GetQWenChatCompletionAsync(QWenLlm.QWenMax, histor
Console.WriteLine(completion.Output.Choices[0].Message.Content); // The number is 42
```
-## 推理
+#### 推理
使用推理模型时,模型的思考过程可以通过 `ReasoningContent` 属性获取。
@@ -157,7 +109,22 @@ var completion = await client.GetDeepSeekChatCompletionAsync(DeepSeekLlm.DeepSee
Console.WriteLine(completion.Output.Choices[0]!.Message.ReasoningContent);
```
-## 工具调用
+对于支持的模型(例如 qwen3),可以使用 `TextGenerationParameters.EnableThinking` 决定是否使用模型的推理能力。
+
+```csharp
+var stream = dashScopeClient
+ .GetQWenChatStreamAsync(
+ QWenLlm.QWenPlusLatest,
+ history,
+ new TextGenerationParameters
+ {
+ IncrementalOutput = true,
+ ResultFormat = ResultFormats.Message,
+ EnableThinking = true
+ });
+```
+
+#### 工具调用
创建一个可供模型使用的方法。
@@ -182,7 +149,7 @@ public enum TemperatureUnit
}
```
-对话时带上方法的名称、描述和参数列表,参数列表以 JSON Schema 的形式提供。
+对话时带上方法的名称、描述和参数列表,参数列表以 JSON Schema 的形式提供(这里使用 `JsonSchema.Net` 库,您也可以使用其它具有类似功能的库)。
```csharp
var tools = new List()
@@ -224,9 +191,9 @@ Console.WriteLine(completion.Output.Choice[0].Message.Content) // 现在浙江
当模型认为应当调用工具时,返回消息中 `ToolCalls` 会提供调用的详情,本地在调用完成后可以把结果以 `tool` 角色返回。
-## 上传文件(QWen-Long)
+#### 上传文件(qwen-long)
-需要先提前将文件上传到 DashScope 来获得 Id。
+使用长上下文模型时,需要先提前将文件上传到 DashScope 来获得 Id。
```csharp
var file = new FileInfo("test.txt");
@@ -255,7 +222,162 @@ Console.WriteLine(completion.Output.Choices[0].Message.Content);
var deletionResult = await dashScopeClient.DeleteFileAsync(uploadedFile.Id);
```
-## 应用调用
+### 多模态
+
+使用 `dashScopeClient.GetMultimodalGenerationAsync` 和 `dashScopeClient.GetMultimodalGenerationStreamAsync` 来访问多模态文本生成接口。
+
+相关文档:[多模态_大模型服务平台百炼(Model Studio)-阿里云帮助中心](https://help.aliyun.com/zh/model-studio/multimodal)
+
+#### 视觉理解/推理
+
+使用 `MultimodalMessage.User()` 可以快速创建对应角色的消息。
+
+媒体内容可以通过公网 URL 或者 `byte[]` 传入。
+
+```csharp
+var image = await File.ReadAllBytesAsync("Lenna.jpg");
+var response = dashScopeClient.GetMultimodalGenerationStreamAsync(
+ new ModelRequest()
+ {
+ Model = "qvq-plus",
+ Input = new MultimodalInput()
+ {
+ Messages =
+ [
+ MultimodalMessage.User(
+ [
+ MultimodalMessageContent.ImageContent(image, "image/jpeg"),
+ MultimodalMessageContent.TextContent("她是谁?")
+ ])
+ ]
+ },
+ Parameters = new MultimodalParameters { IncrementalOutput = true, VlHighResolutionImages = false }
+ });
+
+// output
+var reasoning = false;
+await foreach (var modelResponse in response)
+{
+ var choice = modelResponse.Output.Choices.FirstOrDefault();
+ if (choice != null)
+ {
+ if (choice.FinishReason != "null")
+ {
+ break;
+ }
+
+ if (string.IsNullOrEmpty(choice.Message.ReasoningContent) == false)
+ {
+ if (reasoning == false)
+ {
+ reasoning = true;
+ Console.WriteLine("");
+ }
+
+ Console.Write(choice.Message.ReasoningContent);
+ continue;
+ }
+
+ if (reasoning)
+ {
+ reasoning = false;
+ Console.WriteLine("");
+ }
+
+ Console.Write(choice.Message.Content[0].Text);
+ }
+}
+```
+
+### 语音合成
+
+通过 `dashScopeClient.CreateSpeechSynthesizerSocketSessionAsync()` 来创建一个语音合成会话。
+
+**注意:使用 using 语句来自动释放会话,或者手动 Dispose 会话,尽量不要重用会话。**
+
+相关文档:[语音合成-CosyVoice_大模型服务平台百炼(Model Studio)-阿里云帮助中心](https://help.aliyun.com/zh/model-studio/cosyvoice-large-model-for-speech-synthesis)
+
+```csharp
+using var tts = await dashScopeClient.CreateSpeechSynthesizerSocketSessionAsync("cosyvoice-v2");
+var taskId = await tts.RunTaskAsync(
+ new SpeechSynthesizerParameters { Voice = "longxiaochun_v2", Format = "mp3" });
+await tts.ContinueTaskAsync(taskId, "博客园");
+await tts.ContinueTaskAsync(taskId, "代码改变世界");
+await tts.FinishTaskAsync(taskId);
+var file = new FileInfo("tts.mp3");
+using var stream = file.OpenWrite();
+await foreach (var b in tts.GetAudioAsync())
+{
+ stream.WriteByte(b);
+}
+
+stream.Close();
+
+var tokenUsage = 0;
+await foreach (var message in tts.GetMessagesAsync())
+{
+ if (message.Payload.Usage?.Characters > tokenUsage)
+ {
+ tokenUsage = message.Payload.Usage.Characters;
+ }
+}
+
+Console.WriteLine($"audio saved to {file.FullName}, token usage: {tokenUsage}");
+break;
+```
+
+### 图像生成
+
+#### 文生图
+
+我们针对通义万相提供了快捷 API `dashScopeClient.CreateWanxImageSynthesisTaskAsync()` 和 `GetWanxImageSynthesisTaskAsync()`。
+
+图片生成需要数秒到数十秒不等,对于 HTTP 请求来说太长,需要通过任务方式生成。
+
+先使用 `CreateWanxImageSynthesisTaskAsync()` 创建任务,再轮询 `GetWanxImageSynthesisTaskAsync()` 检查任务完成状态。
+
+相关文档:[通义万相2.1文生图V2版API参考_大模型服务平台百炼(Model Studio)-阿里云帮助中心](https://help.aliyun.com/zh/model-studio/text-to-image-v2-api-reference)
+
+```csharp
+var prompt = Console.ReadLine();
+var task = await dashScopeClient.CreateWanxImageSynthesisTaskAsync(
+ WanxModel.WanxV21Turbo,
+ prompt,
+ null,
+ new ImageSynthesisParameters { Style = ImageStyles.OilPainting });
+Console.WriteLine($"Task({task.TaskId}) submitted, checking status...");
+var watch = Stopwatch.StartNew();
+while (watch.Elapsed.TotalSeconds < 120)
+{
+ var result = await dashScopeClient.GetWanxImageSynthesisTaskAsync(task.TaskId);
+ Console.WriteLine($"{watch.ElapsedMilliseconds}ms - Status: {result.Output.TaskStatus}");
+ if (result.Output.TaskStatus == DashScopeTaskStatus.Succeeded)
+ {
+ Console.WriteLine($"Image generation finished, URL: {result.Output.Results![0].Url}");
+ return;
+ }
+
+ if (result.Output.TaskStatus == DashScopeTaskStatus.Failed)
+ {
+ Console.WriteLine($"Image generation failed, error message: {result.Output.Message}");
+ return;
+ }
+
+ await Task.Delay(500);
+}
+
+Console.WriteLine($"Task timout, taskId: {task.TaskId}");
+```
+
+#### 人像风格重绘和图像背景生成
+
+与文生图类似,先创建任务,再轮询状态。
+
+人像风格重绘 - `CreateWanxImageGenerationTaskAsync` 和 `GetWanxImageGenerationTaskAsync`
+
+图像背景生成 - `CreateWanxBackgroundGenerationTaskAsync` 和 `GetWanxBackgroundGenerationTaskAsync`
+
+### 应用调用
`GetApplicationResponseAsync` 用于进行应用调用。
@@ -322,3 +444,25 @@ var request =
var response = await client.GetApplicationResponseAsync("your-application-id", request);
Console.WriteLine(response.Output.Text);
```
+
+### 文本向量
+
+使用 `GetTextEmbeddingsAsync` 来调用文本向量接口。
+
+相关文档:[通用文本向量同步接口API详情_大模型服务平台百炼(Model Studio)-阿里云帮助中心](https://help.aliyun.com/zh/model-studio/text-embedding-synchronous-api)
+
+```csharp
+var text = Console.ReadLine();
+var response = await dashScopeClient.GetTextEmbeddingsAsync(
+ TextEmbeddingModel.TextEmbeddingV4,
+ [text],
+ new TextEmbeddingParameters() { Dimension = 512, });
+var array = response.Output.Embeddings.First().Embedding;
+Console.WriteLine("Embedding");
+Console.WriteLine(string.Join('\n', array));
+Console.WriteLine($"Token usage: {response.Usage?.TotalTokens}");
+```
+
+查看 [快照文件](./test/Cnblogs.DashScope.Tests.Shared/Utils/Snapshots.cs) 获得 API 调用参数示例.
+
+查看 [测试](./test) 获得更多 API 使用示例。
diff --git a/sample/Cnblogs.DashScope.Sample/Cnblogs.DashScope.Sample.csproj b/sample/Cnblogs.DashScope.Sample/Cnblogs.DashScope.Sample.csproj
index 6fcdad9..ef379a0 100644
--- a/sample/Cnblogs.DashScope.Sample/Cnblogs.DashScope.Sample.csproj
+++ b/sample/Cnblogs.DashScope.Sample/Cnblogs.DashScope.Sample.csproj
@@ -1,26 +1,29 @@
-
- Exe
- net8.0
- enable
- enable
- false
-
+
+ Exe
+ net8.0
+ enable
+ enable
+ false
+
-
-
-
-
+
+
+
+
-
-
- Always
-
-
+
+
+ Always
+
+
+ PreserveNewest
+
+
-
-
-
+
+
+
diff --git a/sample/Cnblogs.DashScope.Sample/Lenna.jpg b/sample/Cnblogs.DashScope.Sample/Lenna.jpg
new file mode 100644
index 0000000..4030eed
Binary files /dev/null and b/sample/Cnblogs.DashScope.Sample/Lenna.jpg differ
diff --git a/sample/Cnblogs.DashScope.Sample/Program.cs b/sample/Cnblogs.DashScope.Sample/Program.cs
index 8841af4..a0aa669 100644
--- a/sample/Cnblogs.DashScope.Sample/Program.cs
+++ b/sample/Cnblogs.DashScope.Sample/Program.cs
@@ -1,15 +1,19 @@
-using System.Text;
+using System.Diagnostics;
+using System.Text;
using System.Text.Json;
using Cnblogs.DashScope.Core;
using Cnblogs.DashScope.Sample;
using Cnblogs.DashScope.Sdk;
using Cnblogs.DashScope.Sdk.QWen;
+using Cnblogs.DashScope.Sdk.TextEmbedding;
+using Cnblogs.DashScope.Sdk.Wanx;
using Json.Schema;
using Json.Schema.Generation;
using Microsoft.Extensions.AI;
Console.WriteLine("Reading key from environment variable DASHSCOPE_KEY");
-var apiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY");
+var apiKey = Environment.GetEnvironmentVariable("DASHSCOPE_KEY", EnvironmentVariableTarget.Process)
+ ?? Environment.GetEnvironmentVariable("DASHSCOPE_KEY", EnvironmentVariableTarget.User);
if (string.IsNullOrEmpty(apiKey))
{
Console.Write("ApiKey > ");
@@ -32,12 +36,12 @@
switch (type)
{
case SampleType.TextCompletion:
- Console.WriteLine("Prompt > ");
+ Console.Write("Prompt > ");
userInput = Console.ReadLine()!;
await TextCompletionAsync(userInput);
break;
case SampleType.TextCompletionSse:
- Console.WriteLine("Prompt > ");
+ Console.Write("Prompt > ");
userInput = Console.ReadLine()!;
await TextCompletionStreamAsync(userInput);
break;
@@ -47,9 +51,15 @@
case SampleType.ChatCompletionWithTool:
await ChatWithToolsAsync();
break;
+ case SampleType.MultimodalCompletion:
+ await ChatWithImageAsync();
+ break;
case SampleType.ChatCompletionWithFiles:
await ChatWithFilesAsync();
break;
+ case SampleType.Text2Image:
+ await Text2ImageAsync();
+ break;
case SampleType.MicrosoftExtensionsAi:
await ChatWithMicrosoftExtensions();
break;
@@ -63,6 +73,54 @@
userInput = Console.ReadLine()!;
await ApplicationCallAsync(applicationId, userInput);
break;
+ case SampleType.TextToSpeech:
+ {
+ using var tts = await dashScopeClient.CreateSpeechSynthesizerSocketSessionAsync("cosyvoice-v2");
+ var taskId = await tts.RunTaskAsync(
+ new SpeechSynthesizerParameters { Voice = "longxiaochun_v2", Format = "mp3" });
+ await tts.ContinueTaskAsync(taskId, "博客园");
+ await tts.ContinueTaskAsync(taskId, "代码改变世界");
+ await tts.FinishTaskAsync(taskId);
+ var file = new FileInfo("tts.mp3");
+ await using var stream = file.OpenWrite();
+ await foreach (var b in tts.GetAudioAsync())
+ {
+ stream.WriteByte(b);
+ }
+
+ stream.Close();
+
+ var tokenUsage = 0;
+ await foreach (var message in tts.GetMessagesAsync())
+ {
+ if (message.Payload.Usage?.Characters > tokenUsage)
+ {
+ tokenUsage = message.Payload.Usage.Characters;
+ }
+ }
+
+ Console.WriteLine($"audio saved to {file.FullName}, token usage: {tokenUsage}");
+ break;
+ }
+
+ case SampleType.TextEmbedding:
+ Console.Write("text> ");
+ var text = Console.ReadLine();
+ if (string.IsNullOrEmpty(text))
+ {
+ text = "Coding changes world";
+ Console.WriteLine($"using default text: {text}");
+ }
+
+ var response = await dashScopeClient.GetTextEmbeddingsAsync(
+ TextEmbeddingModel.TextEmbeddingV3,
+ [text],
+ new TextEmbeddingParameters() { Dimension = 512, });
+ var array = response.Output.Embeddings.First().Embedding;
+ Console.WriteLine("Embedding");
+ Console.WriteLine(string.Join('\n', array));
+ Console.WriteLine($"Token usage: {response.Usage?.TotalTokens}");
+ break;
}
return;
@@ -97,9 +155,14 @@ async Task ChatStreamAsync()
history.Add(TextChatMessage.User(input));
var stream = dashScopeClient
.GetQWenChatStreamAsync(
- QWenLlm.QWenMax,
+ QWenLlm.QWenPlusLatest,
history,
- new TextGenerationParameters { IncrementalOutput = true, ResultFormat = ResultFormats.Message });
+ new TextGenerationParameters
+ {
+ IncrementalOutput = true,
+ ResultFormat = ResultFormats.Message,
+ EnableThinking = true
+ });
var role = string.Empty;
var message = new StringBuilder();
await foreach (var modelResponse in stream)
@@ -112,7 +175,10 @@ async Task ChatStreamAsync()
}
message.Append(chunk.Message.Content);
- Console.Write(chunk.Message.Content);
+ var write = string.IsNullOrEmpty(chunk.Message.ReasoningContent)
+ ? chunk.Message.Content
+ : chunk.Message.ReasoningContent;
+ Console.Write(write);
}
Console.WriteLine();
@@ -122,6 +188,60 @@ async Task ChatStreamAsync()
// ReSharper disable once FunctionNeverReturns
}
+async Task ChatWithImageAsync()
+{
+ var image = await File.ReadAllBytesAsync("Lenna.jpg");
+ var response = dashScopeClient.GetMultimodalGenerationStreamAsync(
+ new ModelRequest()
+ {
+ Model = "qvq-plus",
+ Input = new MultimodalInput()
+ {
+ Messages =
+ [
+ MultimodalMessage.User(
+ [
+ MultimodalMessageContent.ImageContent(image, "image/jpeg"),
+ MultimodalMessageContent.TextContent("她是谁?")
+ ])
+ ]
+ },
+ Parameters = new MultimodalParameters { IncrementalOutput = true, VlHighResolutionImages = false }
+ });
+ var reasoning = false;
+ await foreach (var modelResponse in response)
+ {
+ var choice = modelResponse.Output.Choices.FirstOrDefault();
+ if (choice != null)
+ {
+ if (choice.FinishReason != "null")
+ {
+ break;
+ }
+
+ if (string.IsNullOrEmpty(choice.Message.ReasoningContent) == false)
+ {
+ if (reasoning == false)
+ {
+ reasoning = true;
+ Console.WriteLine("");
+ }
+
+ Console.Write(choice.Message.ReasoningContent);
+ continue;
+ }
+
+ if (reasoning)
+ {
+ reasoning = false;
+ Console.WriteLine("");
+ }
+
+ Console.Write(choice.Message.Content[0].Text);
+ }
+ }
+}
+
async Task ChatWithFilesAsync()
{
var history = new List();
@@ -177,7 +297,7 @@ async Task ChatWithToolsAsync()
"获得当前天气",
new JsonSchemaBuilder().FromType().Build()))
};
- var chatParameters = new TextGenerationParameters() { ResultFormat = ResultFormats.Message, Tools = tools };
+ var chatParameters = new TextGenerationParameters { ResultFormat = ResultFormats.Message, Tools = tools };
var question = TextChatMessage.User("请问现在杭州的天气如何?");
history.Add(question);
Console.WriteLine($"{question.Role} > {question.Content}");
@@ -214,18 +334,54 @@ async Task ChatWithMicrosoftExtensions()
Console.WriteLine("Requesting model...");
var chatClient = dashScopeClient.AsChatClient("qwen-max");
List conversation =
- [
- new(ChatRole.System, "You are a helpful AI assistant"),
- new(ChatRole.User, "What is AI?")
- ];
+ new() { new(ChatRole.System, "You are a helpful AI assistant"), new(ChatRole.User, "What is AI?") };
var response = await chatClient.GetResponseAsync(conversation);
var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true };
Console.WriteLine(JsonSerializer.Serialize(response, serializerOptions));
}
+async Task Text2ImageAsync()
+{
+ Console.Write("Prompt> ");
+ var prompt = Console.ReadLine();
+ if (string.IsNullOrEmpty(prompt))
+ {
+ Console.WriteLine("Using sample prompt");
+ prompt = "A fluffy cat";
+ }
+
+ var task = await dashScopeClient.CreateWanxImageSynthesisTaskAsync(
+ WanxModel.WanxV21Turbo,
+ prompt,
+ null,
+ new ImageSynthesisParameters { Style = ImageStyles.OilPainting });
+ Console.WriteLine($"Task({task.TaskId}) submitted, checking status...");
+ var watch = Stopwatch.StartNew();
+ while (watch.Elapsed.TotalSeconds < 120)
+ {
+ var result = await dashScopeClient.GetWanxImageSynthesisTaskAsync(task.TaskId);
+ Console.WriteLine($"{watch.ElapsedMilliseconds}ms - Status: {result.Output.TaskStatus}");
+ if (result.Output.TaskStatus == DashScopeTaskStatus.Succeeded)
+ {
+ Console.WriteLine($"Image generation finished, URL: {result.Output.Results![0].Url}");
+ return;
+ }
+
+ if (result.Output.TaskStatus == DashScopeTaskStatus.Failed)
+ {
+ Console.WriteLine($"Image generation failed, error message: {result.Output.Message}");
+ return;
+ }
+
+ await Task.Delay(500);
+ }
+
+ Console.WriteLine($"Task timout, taskId: {task.TaskId}");
+}
+
async Task ApplicationCallAsync(string applicationId, string prompt)
{
- var request = new ApplicationRequest() { Input = new ApplicationInput() { Prompt = prompt } };
+ var request = new ApplicationRequest { Input = new ApplicationInput { Prompt = prompt } };
var response = await dashScopeClient.GetApplicationResponseAsync(applicationId, request);
Console.WriteLine(response.Output.Text);
}
diff --git a/sample/Cnblogs.DashScope.Sample/SampleType.cs b/sample/Cnblogs.DashScope.Sample/SampleType.cs
index feddf79..c78d94a 100644
--- a/sample/Cnblogs.DashScope.Sample/SampleType.cs
+++ b/sample/Cnblogs.DashScope.Sample/SampleType.cs
@@ -12,9 +12,17 @@ public enum SampleType
ChatCompletionWithFiles,
+ MultimodalCompletion,
+
+ Text2Image,
+
MicrosoftExtensionsAi,
MicrosoftExtensionsAiToolCall,
- ApplicationCall
+ ApplicationCall,
+
+ TextToSpeech,
+
+ TextEmbedding
}
diff --git a/sample/Cnblogs.DashScope.Sample/SampleTypeDescriptor.cs b/sample/Cnblogs.DashScope.Sample/SampleTypeDescriptor.cs
index 26988a5..2cd398a 100644
--- a/sample/Cnblogs.DashScope.Sample/SampleTypeDescriptor.cs
+++ b/sample/Cnblogs.DashScope.Sample/SampleTypeDescriptor.cs
@@ -11,9 +11,13 @@ public static string GetDescription(this SampleType sampleType)
SampleType.ChatCompletion => "Conversation between user and assistant",
SampleType.ChatCompletionWithTool => "Function call sample",
SampleType.ChatCompletionWithFiles => "File upload sample using qwen-long",
+ SampleType.MultimodalCompletion => "Multimodal completion",
+ SampleType.Text2Image => "Text to Image generation",
SampleType.MicrosoftExtensionsAi => "Use with Microsoft.Extensions.AI",
SampleType.MicrosoftExtensionsAiToolCall => "Use tool call with Microsoft.Extensions.AI interfaces",
SampleType.ApplicationCall => "Call pre-defined application",
+ SampleType.TextToSpeech => "TTS task",
+ SampleType.TextEmbedding => "Get text embedding",
_ => throw new ArgumentOutOfRangeException(nameof(sampleType), sampleType, "Unsupported sample option")
};
}
diff --git a/sample/Cnblogs.DashScope.Sample/ToolCallWithExtensions.cs b/sample/Cnblogs.DashScope.Sample/ToolCallWithExtensions.cs
index 0c30cd4..72ccc1e 100644
--- a/sample/Cnblogs.DashScope.Sample/ToolCallWithExtensions.cs
+++ b/sample/Cnblogs.DashScope.Sample/ToolCallWithExtensions.cs
@@ -12,7 +12,7 @@ public static async Task ToolCallWithExtensionAsync(this IDashScopeClient dashSc
[Description("Gets the weather")]
string GetWeather(string location) => Random.Shared.NextDouble() > 0.5 ? "It's sunny" : "It's raining";
- var chatOptions = new ChatOptions { Tools = [AIFunctionFactory.Create(GetWeather)] };
+ var chatOptions = new ChatOptions { Tools = new List { AIFunctionFactory.Create(GetWeather) } };
var client = dashScopeClient.AsChatClient("qwen-max").AsBuilder().UseFunctionInvocation().Build();
await foreach (var message in client.GetStreamingResponseAsync("What is weather of LA today?", chatOptions))
diff --git a/src/Cnblogs.DashScope.AI/Cnblogs.DashScope.AI.csproj b/src/Cnblogs.DashScope.AI/Cnblogs.DashScope.AI.csproj
index a92e8fc..5e45397 100644
--- a/src/Cnblogs.DashScope.AI/Cnblogs.DashScope.AI.csproj
+++ b/src/Cnblogs.DashScope.AI/Cnblogs.DashScope.AI.csproj
@@ -1,5 +1,6 @@
+ net8.0
Cnblogs.DashScope.AI
true
Cnblogs;Dashscope;Microsoft.Extensions.AI;Sdk;Embedding;
@@ -10,7 +11,8 @@
-
+
+
diff --git a/src/Cnblogs.DashScope.AI/DashScopeChatClient.cs b/src/Cnblogs.DashScope.AI/DashScopeChatClient.cs
index 11b86af..c8a0d0f 100644
--- a/src/Cnblogs.DashScope.AI/DashScopeChatClient.cs
+++ b/src/Cnblogs.DashScope.AI/DashScopeChatClient.cs
@@ -1,4 +1,4 @@
-using System.Runtime.CompilerServices;
+using System.Runtime.CompilerServices;
using System.Text.Json;
using Cnblogs.DashScope.Core;
using Cnblogs.DashScope.Sdk;
@@ -17,7 +17,7 @@ public sealed class DashScopeChatClient : IChatClient
private readonly string _modelId;
private static readonly JsonSchema EmptyObjectSchema =
- JsonSchema.FromText("""{"type":"object","required":[],"properties":{}}""");
+ JsonSchema.FromText("{\"type\":\"object\",\"required\":[],\"properties\":{}}");
private static readonly TextGenerationParameters
DefaultTextGenerationParameter = new() { ResultFormat = "message" };
@@ -55,7 +55,7 @@ public async Task GetResponseAsync(
if (useVl)
{
var response = await _dashScopeClient.GetMultimodalGenerationAsync(
- new ModelRequest()
+ new ModelRequest
{
Input = new MultimodalInput { Messages = ToMultimodalMessages(chatMessages) },
Parameters = ToMultimodalParameters(options),
@@ -63,7 +63,7 @@ public async Task GetResponseAsync(
},
cancellationToken);
- var returnMessage = new ChatMessage()
+ var returnMessage = new ChatMessage
{
RawRepresentation = response, Role = ToChatRole(response.Output.Choices[0].Message.Role),
};
@@ -80,7 +80,7 @@ public async Task GetResponseAsync(
if (response.Usage != null)
{
- completion.Usage = new UsageDetails()
+ completion.Usage = new UsageDetails
{
InputTokenCount = response.Usage.InputTokens, OutputTokenCount = response.Usage.OutputTokens,
};
@@ -92,7 +92,7 @@ public async Task GetResponseAsync(
{
var parameters = ToTextGenerationParameters(options) ?? DefaultTextGenerationParameter;
var response = await _dashScopeClient.GetTextCompletionAsync(
- new ModelRequest()
+ new ModelRequest
{
Input = new TextGenerationInput
{
@@ -116,7 +116,7 @@ public async Task GetResponseAsync(
if (response.Usage != null)
{
- completion.Usage = new UsageDetails()
+ completion.Usage = new UsageDetails
{
InputTokenCount = response.Usage.InputTokens,
OutputTokenCount = response.Usage.OutputTokens,
@@ -147,7 +147,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync(
var parameter = ToMultimodalParameters(options);
parameter.IncrementalOutput = true;
var stream = _dashScopeClient.GetMultimodalGenerationStreamAsync(
- new ModelRequest()
+ new ModelRequest
{
Input = new MultimodalInput { Messages = ToMultimodalMessages(chatMessages) },
Parameters = parameter,
@@ -164,7 +164,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync(
: ToFinishReason(response.Output.Choices[0].FinishReason);
completionId ??= response.RequestId;
- var update = new ChatResponseUpdate()
+ var update = new ChatResponseUpdate
{
ResponseId = completionId,
CreatedAt = DateTimeOffset.Now,
@@ -199,7 +199,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync(
{
// qwen does not support streaming with function call, fallback to non-streaming
var completion = await GetResponseAsync(chatMessages, options, cancellationToken);
- yield return new ChatResponseUpdate()
+ yield return new ChatResponseUpdate
{
ResponseId = completion.ResponseId,
Role = completion.Messages[0].Role,
@@ -216,7 +216,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync(
var parameters = ToTextGenerationParameters(options) ?? DefaultTextGenerationParameter;
parameters.IncrementalOutput = true;
var stream = _dashScopeClient.GetTextCompletionStreamAsync(
- new ModelRequest()
+ new ModelRequest
{
Input = new TextGenerationInput
{
@@ -238,7 +238,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync(
: ToFinishReason(response.Output.Choices[0].FinishReason);
completionId ??= response.RequestId;
- var update = new ChatResponseUpdate()
+ var update = new ChatResponseUpdate
{
ResponseId = completionId,
CreatedAt = DateTimeOffset.Now,
@@ -257,7 +257,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync(
{
update.Contents.Add(
new UsageContent(
- new UsageDetails()
+ new UsageDetails
{
InputTokenCount = response.Usage.InputTokens,
OutputTokenCount = response.Usage.OutputTokens,
@@ -299,7 +299,7 @@ public void Dispose()
private static ChatMessage ToChatMessage(TextChatMessage message)
{
- var returnMessage = new ChatMessage()
+ var returnMessage = new ChatMessage
{
RawRepresentation = message, Role = ToChatRole(message.Role),
};
@@ -485,7 +485,7 @@ private IEnumerable ToTextChatMessages(
format = "json_object";
}
- return new TextGenerationParameters()
+ return new TextGenerationParameters
{
ResultFormat = format,
Temperature = options.Temperature,
@@ -503,7 +503,8 @@ private IEnumerable ToTextChatMessages(
RequiredChatToolMode required when string.IsNullOrEmpty(required.RequiredFunctionName) == false =>
ToolChoice.FunctionChoice(required.RequiredFunctionName),
_ => ToolChoice.AutoChoice
- }
+ },
+ ParallelToolCalls = options.AllowMultipleToolCalls,
};
}
diff --git a/src/Cnblogs.DashScope.AI/DashScopeTextEmbeddingGenerator.cs b/src/Cnblogs.DashScope.AI/DashScopeTextEmbeddingGenerator.cs
index 06c28d7..f735844 100644
--- a/src/Cnblogs.DashScope.AI/DashScopeTextEmbeddingGenerator.cs
+++ b/src/Cnblogs.DashScope.AI/DashScopeTextEmbeddingGenerator.cs
@@ -44,7 +44,7 @@ public async Task>> GenerateAsync(
e => new Embedding(e.Embedding) { ModelId = _modelId, CreatedAt = DateTimeOffset.Now });
var rawUsage = rawResponse.Usage;
var usage = rawUsage != null
- ? new UsageDetails() { InputTokenCount = rawUsage.TotalTokens, TotalTokenCount = rawUsage.TotalTokens }
+ ? new UsageDetails { InputTokenCount = rawUsage.TotalTokens, TotalTokenCount = rawUsage.TotalTokens }
: null;
return new GeneratedEmbeddings>(embeddings)
{
diff --git a/src/Cnblogs.DashScope.AspNetCore/Assembly.cs b/src/Cnblogs.DashScope.AspNetCore/Assembly.cs
new file mode 100644
index 0000000..b79c4ed
--- /dev/null
+++ b/src/Cnblogs.DashScope.AspNetCore/Assembly.cs
@@ -0,0 +1,4 @@
+using System.Runtime.CompilerServices;
+
+[assembly:InternalsVisibleTo("Cnblogs.DashScope.Sdk.UnitTests")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
diff --git a/src/Cnblogs.DashScope.AspNetCore/Cnblogs.DashScope.AspNetCore.csproj b/src/Cnblogs.DashScope.AspNetCore/Cnblogs.DashScope.AspNetCore.csproj
index 1ab06a5..ba30d5f 100644
--- a/src/Cnblogs.DashScope.AspNetCore/Cnblogs.DashScope.AspNetCore.csproj
+++ b/src/Cnblogs.DashScope.AspNetCore/Cnblogs.DashScope.AspNetCore.csproj
@@ -3,7 +3,8 @@
Cnblogs.DashScopeSDK
true
- Cnblogs;Dashscope;AI;Sdk;Embedding;AspNetCore
+ Cnblogs;Dashscope;AI;Sdk;Embedding;AspNetCore;Bailian
+ Cnblogs.DashScope.AspNetCore
diff --git a/src/Cnblogs.DashScope.AspNetCore/DashScopeAspNetCoreDefaults.cs b/src/Cnblogs.DashScope.AspNetCore/DashScopeAspNetCoreDefaults.cs
new file mode 100644
index 0000000..1cdbb63
--- /dev/null
+++ b/src/Cnblogs.DashScope.AspNetCore/DashScopeAspNetCoreDefaults.cs
@@ -0,0 +1,6 @@
+namespace Cnblogs.DashScope.AspNetCore;
+
+internal static class DashScopeAspNetCoreDefaults
+{
+ public const string DefaultHttpClientName = "Cnblogs.DashScope.Http";
+}
diff --git a/src/Cnblogs.DashScope.AspNetCore/DashScopeClientAspNetCore.cs b/src/Cnblogs.DashScope.AspNetCore/DashScopeClientAspNetCore.cs
new file mode 100644
index 0000000..be4a685
--- /dev/null
+++ b/src/Cnblogs.DashScope.AspNetCore/DashScopeClientAspNetCore.cs
@@ -0,0 +1,20 @@
+using Cnblogs.DashScope.Core;
+
+namespace Cnblogs.DashScope.AspNetCore;
+
+///
+/// The with DI and options pattern support.
+///
+public class DashScopeClientAspNetCore
+ : DashScopeClientCore
+{
+ ///
+ /// The with DI and options pattern support.
+ ///
+ /// The factory to create .
+ /// The socket pool for WebSocket API calls.
+ public DashScopeClientAspNetCore(IHttpClientFactory factory, DashScopeClientWebSocketPool pool)
+ : base(factory.CreateClient(DashScopeAspNetCoreDefaults.DefaultHttpClientName), pool)
+ {
+ }
+}
diff --git a/src/Cnblogs.DashScope.AspNetCore/ServiceCollectionInjector.cs b/src/Cnblogs.DashScope.AspNetCore/ServiceCollectionInjector.cs
index ee6b00a..8b521f0 100644
--- a/src/Cnblogs.DashScope.AspNetCore/ServiceCollectionInjector.cs
+++ b/src/Cnblogs.DashScope.AspNetCore/ServiceCollectionInjector.cs
@@ -1,6 +1,9 @@
using System.Net.Http.Headers;
+using Cnblogs.DashScope.AspNetCore;
using Cnblogs.DashScope.Core;
+using Cnblogs.DashScope.Core.Internals;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;
@@ -37,9 +40,10 @@ public static IHttpClientBuilder AddDashScopeClient(this IServiceCollection serv
{
var apiKey = section["apiKey"]
?? throw new InvalidOperationException("There is no apiKey provided in given section");
- var baseAddress = section["baseAddress"];
+ var baseAddress = section["baseAddress"] ?? DashScopeDefaults.HttpApiBaseAddress;
var workspaceId = section["workspaceId"];
- return services.AddDashScopeClient(apiKey, baseAddress, workspaceId);
+ services.Configure(section);
+ return services.AddDashScopeHttpClient(apiKey, baseAddress, workspaceId);
}
///
@@ -48,16 +52,49 @@ public static IHttpClientBuilder AddDashScopeClient(this IServiceCollection serv
/// The service collection to add service to.
/// The DashScope api key.
/// The DashScope api base address, you may change this value if you are using proxy.
+ /// The DashScope websocket base address, you may want to change this value if use are using proxy.
/// Default workspace id to use.
///
public static IHttpClientBuilder AddDashScopeClient(
this IServiceCollection services,
string apiKey,
string? baseAddress = null,
+ string? baseWebsocketAddress = null,
string? workspaceId = null)
{
- baseAddress ??= "https://dashscope.aliyuncs.com/api/v1/";
- return services.AddHttpClient(
+ services.Configure(o =>
+ {
+ o.ApiKey = apiKey;
+ if (baseAddress != null)
+ {
+ o.BaseAddress = baseAddress;
+ }
+
+ if (baseWebsocketAddress != null)
+ {
+ o.WebsocketBaseAddress = baseWebsocketAddress;
+ }
+
+ o.WorkspaceId = workspaceId;
+ });
+
+ return services.AddDashScopeHttpClient(apiKey, baseAddress, workspaceId);
+ }
+
+ private static IHttpClientBuilder AddDashScopeHttpClient(
+ this IServiceCollection services,
+ string apiKey,
+ string? baseAddress,
+ string? workspaceId)
+ {
+ services.AddSingleton();
+ services.AddSingleton(sp
+ => new DashScopeClientWebSocketPool(
+ sp.GetRequiredService(),
+ sp.GetRequiredService>().Value));
+ services.AddScoped();
+ return services.AddHttpClient(
+ DashScopeAspNetCoreDefaults.DefaultHttpClientName,
h =>
{
h.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
@@ -66,7 +103,7 @@ public static IHttpClientBuilder AddDashScopeClient(
h.DefaultRequestHeaders.Add("X-DashScope-WorkSpace", workspaceId);
}
- h.BaseAddress = new Uri(baseAddress);
+ h.BaseAddress = new Uri(baseAddress ?? DashScopeDefaults.HttpApiBaseAddress);
});
}
}
diff --git a/src/Cnblogs.DashScope.Core/ApplicationInput.cs b/src/Cnblogs.DashScope.Core/ApplicationInput.cs
index 965124b..fc537e6 100644
--- a/src/Cnblogs.DashScope.Core/ApplicationInput.cs
+++ b/src/Cnblogs.DashScope.Core/ApplicationInput.cs
@@ -44,4 +44,6 @@ public class ApplicationInput
///
/// Inputs for application call.
///
-public class ApplicationInput : ApplicationInput>;
+public class ApplicationInput : ApplicationInput>
+{
+}
diff --git a/src/Cnblogs.DashScope.Core/ApplicationRequest.cs b/src/Cnblogs.DashScope.Core/ApplicationRequest.cs
index bdafef5..c1980e7 100644
--- a/src/Cnblogs.DashScope.Core/ApplicationRequest.cs
+++ b/src/Cnblogs.DashScope.Core/ApplicationRequest.cs
@@ -13,7 +13,7 @@ public class ApplicationRequest : IDashScopeWorkspaceConfig
///
/// Content of this call.
///
- public required ApplicationInput Input { get; set; }
+ public ApplicationInput Input { get; set; } = new();
///
/// Optional configurations.
@@ -30,4 +30,6 @@ public class ApplicationRequest : IDashScopeWorkspaceConfig
///
/// Request body for an application call with dictionary biz_content.
///
-public class ApplicationRequest : ApplicationRequest>;
+public class ApplicationRequest : ApplicationRequest>
+{
+}
diff --git a/src/Cnblogs.DashScope.Core/BackgroundGenerationInput.cs b/src/Cnblogs.DashScope.Core/BackgroundGenerationInput.cs
index bd820dc..dd5b2b3 100644
--- a/src/Cnblogs.DashScope.Core/BackgroundGenerationInput.cs
+++ b/src/Cnblogs.DashScope.Core/BackgroundGenerationInput.cs
@@ -8,7 +8,7 @@ public class BackgroundGenerationInput
///
/// The image url to generation background on.
///
- public required string BaseImageUrl { get; set; }
+ public string BaseImageUrl { get; set; } = string.Empty;
///
/// The reference image url for.
diff --git a/src/Cnblogs.DashScope.Core/BatchGetEmbeddingsInput.cs b/src/Cnblogs.DashScope.Core/BatchGetEmbeddingsInput.cs
index b9ab6e0..36b04cd 100644
--- a/src/Cnblogs.DashScope.Core/BatchGetEmbeddingsInput.cs
+++ b/src/Cnblogs.DashScope.Core/BatchGetEmbeddingsInput.cs
@@ -8,5 +8,5 @@ public class BatchGetEmbeddingsInput
///
/// The url of text file to compute embeddings from.
///
- public required string Url { get; set; }
+ public string Url { get; set; } = string.Empty;
}
diff --git a/src/Cnblogs.DashScope.Core/Cnblogs.DashScope.Core.csproj b/src/Cnblogs.DashScope.Core/Cnblogs.DashScope.Core.csproj
index 791029d..f337ae6 100644
--- a/src/Cnblogs.DashScope.Core/Cnblogs.DashScope.Core.csproj
+++ b/src/Cnblogs.DashScope.Core/Cnblogs.DashScope.Core.csproj
@@ -3,16 +3,17 @@
Cnblogs.DashScopeSDK
true
- Cnblogs;Dashscope;AI;Sdk;Embedding;
+ Cnblogs;Dashscope;AI;Sdk;Embedding;Bailian;
Provide pure api access to DashScope without extra references. Cnblogs.DashScope.Sdk should be used for general purpose.
-
+
-
+
+
-
+
diff --git a/src/Cnblogs.DashScope.Core/DashScopeClient.cs b/src/Cnblogs.DashScope.Core/DashScopeClient.cs
index dea20f9..1d1b0ca 100644
--- a/src/Cnblogs.DashScope.Core/DashScopeClient.cs
+++ b/src/Cnblogs.DashScope.Core/DashScopeClient.cs
@@ -9,14 +9,17 @@ namespace Cnblogs.DashScope.Core;
public class DashScopeClient : DashScopeClientCore
{
private static readonly Dictionary ClientPools = new();
+ private static readonly Dictionary SocketPools = new();
///
/// Creates a DashScopeClient for further api call.
///
/// The DashScope api key.
/// The timeout for internal http client, defaults to 2 minute.
- /// The base address for dashscope api call.
+ /// The base address for DashScope api call.
+ /// The base address for DashScope websocket api call.
/// The workspace id.
+ /// Maximum size of socket pool.
///
/// The underlying httpclient is cached by constructor parameter list.
/// Client created with same parameter value will share same underlying instance.
@@ -24,10 +27,42 @@ public class DashScopeClient : DashScopeClientCore
public DashScopeClient(
string apiKey,
TimeSpan? timeout = null,
- string? baseAddress = null,
+ string baseAddress = DashScopeDefaults.HttpApiBaseAddress,
+ string baseWebsocketAddress = DashScopeDefaults.WebsocketApiBaseAddress,
+ string? workspaceId = null,
+ int socketPoolSize = 32)
+ : base(
+ GetConfiguredClient(apiKey, timeout, baseAddress, workspaceId),
+ GetConfiguredSocketPool(apiKey, baseWebsocketAddress, socketPoolSize, workspaceId))
+ {
+ }
+
+ private static DashScopeClientWebSocketPool GetConfiguredSocketPool(
+ string apiKey,
+ string baseAddress,
+ int socketPoolSize,
string? workspaceId = null)
- : base(GetConfiguredClient(apiKey, timeout, baseAddress, workspaceId))
{
+ var key = GetCacheKey();
+
+ var pool = SocketPools.GetValueOrDefault(key);
+ if (pool is null)
+ {
+ pool = new DashScopeClientWebSocketPool(
+ new DashScopeClientWebSocketFactory(),
+ new DashScopeOptions
+ {
+ ApiKey = apiKey,
+ WebsocketBaseAddress = baseAddress,
+ SocketPoolSize = socketPoolSize,
+ WorkspaceId = workspaceId
+ });
+ SocketPools.Add(key, pool);
+ }
+
+ return pool;
+
+ string GetCacheKey() => $"{apiKey}-{socketPoolSize}-{baseAddress}-{workspaceId}";
}
private static HttpClient GetConfiguredClient(
@@ -41,7 +76,7 @@ private static HttpClient GetConfiguredClient(
{
client = new HttpClient
{
- BaseAddress = new Uri(baseAddress ?? DashScopeDefaults.DashScopeApiBaseAddress),
+ BaseAddress = new Uri(baseAddress ?? DashScopeDefaults.HttpApiBaseAddress),
Timeout = timeout ?? TimeSpan.FromMinutes(2)
};
diff --git a/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs b/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs
index 7cca41f..997ef63 100644
--- a/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs
+++ b/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs
@@ -4,7 +4,6 @@
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
-using System.Text.Json.Serialization;
using Cnblogs.DashScope.Core.Internals;
namespace Cnblogs.DashScope.Core;
@@ -14,22 +13,18 @@ namespace Cnblogs.DashScope.Core;
///
public class DashScopeClientCore : IDashScopeClient
{
- private static readonly JsonSerializerOptions SerializationOptions =
- new()
- {
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- };
-
private readonly HttpClient _httpClient;
+ private readonly DashScopeClientWebSocketPool _socketPool;
///
/// For DI container to inject pre-configured httpclient.
///
/// Pre-configured httpclient.
- public DashScopeClientCore(HttpClient httpClient)
+ /// Websocket pool.
+ public DashScopeClientCore(HttpClient httpClient, DashScopeClientWebSocketPool pool)
{
_httpClient = httpClient;
+ _socketPool = pool;
}
///
@@ -283,6 +278,15 @@ public async Task DeleteFileAsync(
return (await SendCompatibleAsync(request, cancellationToken))!;
}
+ ///
+ public async Task CreateSpeechSynthesizerSocketSessionAsync(
+ string modelId,
+ CancellationToken cancellationToken = default)
+ {
+ var socket = await _socketPool.RentSocketAsync(cancellationToken);
+ return new SpeechSynthesizerSocketSession(socket, modelId);
+ }
+
private static HttpRequestMessage BuildSseRequest(HttpMethod method, string url, TPayload payload)
where TPayload : class
{
@@ -304,7 +308,9 @@ private static HttpRequestMessage BuildRequest(
{
var message = new HttpRequestMessage(method, url)
{
- Content = payload != null ? JsonContent.Create(payload, options: SerializationOptions) : null
+ Content = payload != null
+ ? JsonContent.Create(payload, options: DashScopeDefaults.SerializationOptions)
+ : null
};
if (sse)
@@ -332,7 +338,7 @@ private static HttpRequestMessage BuildRequest(
{
var response = await GetSuccessResponseAsync(
message,
- r => new DashScopeError()
+ r => new DashScopeError
{
Code = r.Error.Type,
Message = r.Error.Message,
@@ -340,7 +346,9 @@ private static HttpRequestMessage BuildRequest(
},
HttpCompletionOption.ResponseContentRead,
cancellationToken);
- return await response.Content.ReadFromJsonAsync(SerializationOptions, cancellationToken);
+ return await response.Content.ReadFromJsonAsync(
+ DashScopeDefaults.SerializationOptions,
+ cancellationToken);
}
private async Task SendAsync(HttpRequestMessage message, CancellationToken cancellationToken)
@@ -350,7 +358,9 @@ private static HttpRequestMessage BuildRequest(
message,
HttpCompletionOption.ResponseContentRead,
cancellationToken);
- return await response.Content.ReadFromJsonAsync(SerializationOptions, cancellationToken);
+ return await response.Content.ReadFromJsonAsync(
+ DashScopeDefaults.SerializationOptions,
+ cancellationToken);
}
private async IAsyncEnumerable StreamAsync(
@@ -367,13 +377,14 @@ private async IAsyncEnumerable StreamAsync(
if (cancellationToken.IsCancellationRequested)
throw new TaskCanceledException();
- var line = await reader.ReadLineAsync(cancellationToken);
+ var line = await reader.ReadLineAsync();
if (line != null && line.StartsWith("data:"))
{
var data = line["data:".Length..];
if (data.StartsWith("{\"code\":"))
{
- var error = JsonSerializer.Deserialize(data, SerializationOptions)!;
+ var error =
+ JsonSerializer.Deserialize(data, DashScopeDefaults.SerializationOptions)!;
throw new DashScopeException(
message.RequestUri?.ToString(),
(int)response.StatusCode,
@@ -381,7 +392,7 @@ private async IAsyncEnumerable StreamAsync(
error.Message);
}
- yield return JsonSerializer.Deserialize(data, SerializationOptions)!;
+ yield return JsonSerializer.Deserialize(data, DashScopeDefaults.SerializationOptions)!;
}
}
}
@@ -418,7 +429,9 @@ private async Task GetSuccessResponseAsync(
DashScopeError? error = null;
try
{
- var r = await response.Content.ReadFromJsonAsync(SerializationOptions, cancellationToken);
+ var r = await response.Content.ReadFromJsonAsync(
+ DashScopeDefaults.SerializationOptions,
+ cancellationToken);
error = r == null ? null : errorMapper.Invoke(r);
}
catch (Exception)
diff --git a/src/Cnblogs.DashScope.Core/DashScopeClientWebSocket.cs b/src/Cnblogs.DashScope.Core/DashScopeClientWebSocket.cs
new file mode 100644
index 0000000..464aa4c
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeClientWebSocket.cs
@@ -0,0 +1,262 @@
+using System.Net.WebSockets;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Channels;
+using Cnblogs.DashScope.Core.Internals;
+
+namespace Cnblogs.DashScope.Core;
+
+///
+/// A websocket client for DashScope websocket API.
+///
+public sealed class DashScopeClientWebSocket : IDisposable
+{
+ private static readonly UnboundedChannelOptions UnboundedChannelOptions =
+ new()
+ {
+ SingleWriter = true,
+ SingleReader = true,
+ AllowSynchronousContinuations = true
+ };
+
+ private readonly IClientWebSocket _socket;
+ private Task? _receiveTask;
+ private TaskCompletionSource _taskStartedSignal = new();
+ private Channel? _binaryOutput;
+ private Channel>? _jsonOutput;
+
+ ///
+ /// Unique id of this socket.
+ ///
+ internal Guid Id { get; } = Guid.NewGuid();
+
+ ///
+ /// The binary output.
+ ///
+ public ChannelReader BinaryOutput
+ => _binaryOutput?.Reader
+ ?? throw new InvalidOperationException("Please call ResetOutput() before accessing output");
+
+ ///
+ /// The json output.
+ ///
+ /// Throws when ResetOutput is not called.
+ public ChannelReader> JsonOutput
+ => _jsonOutput?.Reader
+ ?? throw new InvalidOperationException("Please call ResetOutput() before accessing output");
+
+ ///
+ /// A task that completed when received task-started event.
+ ///
+ public Task TaskStarted => _taskStartedSignal.Task;
+
+ ///
+ /// Current state for this websocket.
+ ///
+ public DashScopeWebSocketState State { get; private set; }
+
+ ///
+ /// Initialize a configured web socket client.
+ ///
+ /// The api key to use.
+ /// Optional workspace id.
+ public DashScopeClientWebSocket(string apiKey, string? workspaceId = null)
+ {
+ _socket = new ClientWebSocketWrapper(new ClientWebSocket());
+ _socket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable");
+ _socket.Options.SetRequestHeader("Authorization", $"bearer {apiKey}");
+ if (string.IsNullOrEmpty(workspaceId) == false)
+ {
+ _socket.Options.SetRequestHeader("X-DashScope-WorkspaceId", workspaceId);
+ }
+ }
+
+ ///
+ /// Initiate a with a pre-configured .
+ ///
+ /// Pre-configured .
+ internal DashScopeClientWebSocket(IClientWebSocket socket)
+ {
+ _socket = socket;
+ }
+
+ ///
+ /// Start a websocket connection.
+ ///
+ /// Websocket API uri.
+ /// The cancellation token to use.
+ ///
+ /// When was request.
+ public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken = default)
+ {
+ await _socket.ConnectAsync(uri, cancellationToken);
+ _receiveTask = ReceiveMessagesAsync(cancellationToken);
+ State = DashScopeWebSocketState.Ready;
+ }
+
+ ///
+ /// Reset binary output.
+ ///
+ public void ResetOutput()
+ {
+ _binaryOutput?.Writer.TryComplete();
+ _binaryOutput = Channel.CreateUnbounded(UnboundedChannelOptions);
+ _jsonOutput?.Writer.TryComplete();
+ _jsonOutput = Channel.CreateUnbounded>(UnboundedChannelOptions);
+ _taskStartedSignal.TrySetResult(false);
+ _taskStartedSignal = new TaskCompletionSource();
+ }
+
+ ///
+ /// Send message to server.
+ ///
+ /// Request to send.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ ///
+ /// Type of the input.
+ /// Type of the parameter.
+ /// The is requested.
+ /// Websocket is not connected or already closed.
+ /// The underlying websocket has already been closed.
+ public Task SendMessageAsync(
+ DashScopeWebSocketRequest request,
+ CancellationToken cancellationToken = default)
+ where TInput : class, new()
+ where TParameter : class
+ {
+ if (State == DashScopeWebSocketState.Closed)
+ {
+ throw new InvalidOperationException("Socket is already closed.");
+ }
+
+ var json = JsonSerializer.Serialize(request, DashScopeDefaults.SerializationOptions);
+ return _socket.SendAsync(
+ new ArraySegment(Encoding.UTF8.GetBytes(json)),
+ WebSocketMessageType.Text,
+ true,
+ cancellationToken);
+ }
+
+ private async Task?> ReceiveMessageAsync(
+ CancellationToken cancellationToken = default)
+ {
+ var buffer = new byte[1024 * 4];
+ var segment = new ArraySegment(buffer);
+
+ try
+ {
+ var result = await _socket.ReceiveAsync(segment, cancellationToken);
+ if (result.MessageType == WebSocketMessageType.Close)
+ {
+ await CloseAsync(cancellationToken);
+ return null;
+ }
+
+ if (result.MessageType == WebSocketMessageType.Binary)
+ {
+ for (var i = 0; i < result.Count; i++)
+ {
+ await _binaryOutput!.Writer.WriteAsync(buffer[i], cancellationToken);
+ }
+
+ return null;
+ }
+
+ var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
+ var jsonResponse =
+ JsonSerializer.Deserialize>(
+ message,
+ DashScopeDefaults.SerializationOptions);
+ return jsonResponse;
+ }
+ catch
+ {
+ // close socket when exception happens.
+ await CloseAsync(cancellationToken);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Wait for server response.
+ ///
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ /// The task was failed.
+ private async Task ReceiveMessagesAsync(CancellationToken cancellationToken = default)
+ {
+ while (State != DashScopeWebSocketState.Closed && _socket.CloseStatus == null)
+ {
+ var json = await ReceiveMessageAsync(cancellationToken);
+ if (json == null)
+ {
+ continue;
+ }
+
+ if (_jsonOutput is not null)
+ {
+ await _jsonOutput.Writer.WriteAsync(json, cancellationToken);
+ }
+
+ var eventStr = json.Header.Event;
+ switch (eventStr)
+ {
+ case "task-started":
+ State = DashScopeWebSocketState.RunningTask;
+ _taskStartedSignal.TrySetResult(true);
+ break;
+ case "task-finished":
+ State = DashScopeWebSocketState.Ready;
+ _binaryOutput?.Writer.Complete();
+ _jsonOutput?.Writer.Complete();
+ break;
+ case "task-failed":
+ await CloseAsync(cancellationToken);
+ _binaryOutput?.Writer.Complete();
+ _jsonOutput?.Writer.Complete();
+ throw new DashScopeException(
+ null,
+ 400,
+ new DashScopeError
+ {
+ Code = json.Header.ErrorCode ?? string.Empty,
+ Message = json.Header.ErrorMessage ?? string.Empty,
+ RequestId = json.Header.Attributes.RequestUuid ?? string.Empty
+ },
+ json.Header.ErrorMessage ?? "The task was failed");
+ }
+ }
+
+ await CloseAsync(cancellationToken);
+ }
+
+ ///
+ /// Close the underlying websocket.
+ ///
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ ///
+ public async Task CloseAsync(CancellationToken cancellationToken = default)
+ {
+ await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
+ State = DashScopeWebSocketState.Closed;
+ _binaryOutput?.Writer.TryComplete();
+ _jsonOutput?.Writer.TryComplete();
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // Dispose managed resources.
+ _socket.Dispose();
+ _binaryOutput?.Writer.TryComplete();
+ _jsonOutput?.Writer.TryComplete();
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeClientWebSocketFactory.cs b/src/Cnblogs.DashScope.Core/DashScopeClientWebSocketFactory.cs
new file mode 100644
index 0000000..06edecb
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeClientWebSocketFactory.cs
@@ -0,0 +1,13 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Default implementation for .
+///
+public class DashScopeClientWebSocketFactory : IDashScopeClientWebSocketFactory
+{
+ ///
+ public DashScopeClientWebSocket GetClientWebSocket(string apiKey, string? workspaceId = null)
+ {
+ return new DashScopeClientWebSocket(apiKey, workspaceId);
+ }
+}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeClientWebSocketPool.cs b/src/Cnblogs.DashScope.Core/DashScopeClientWebSocketPool.cs
new file mode 100644
index 0000000..57cbed5
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeClientWebSocketPool.cs
@@ -0,0 +1,140 @@
+using System.Collections.Concurrent;
+
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Socket pool for DashScope API.
+///
+public sealed class DashScopeClientWebSocketPool : IDisposable
+{
+ private readonly ConcurrentBag _available = new();
+ private readonly ConcurrentDictionary _active = new();
+ private readonly DashScopeOptions _options;
+ private readonly IDashScopeClientWebSocketFactory _dashScopeClientWebSocketFactory;
+
+ ///
+ /// Socket pool for DashScope API.
+ ///
+ ///
+ /// Options for DashScope sdk.
+ public DashScopeClientWebSocketPool(
+ IDashScopeClientWebSocketFactory dashScopeClientWebSocketFactory,
+ DashScopeOptions options)
+ {
+ _dashScopeClientWebSocketFactory = dashScopeClientWebSocketFactory;
+ _options = options;
+ }
+
+ ///
+ /// Get available connection count.
+ ///
+ internal int AvailableSocketCount => _available.Count;
+
+ ///
+ /// Get active connection count.
+ ///
+ internal int ActiveSocketCount => _active.Count;
+
+ internal DashScopeClientWebSocketPool(
+ IEnumerable sockets,
+ IDashScopeClientWebSocketFactory dashScopeClientWebSocketFactory)
+ {
+ _options = new DashScopeOptions();
+ foreach (var socket in sockets)
+ {
+ _available.Add(socket);
+ }
+
+ _dashScopeClientWebSocketFactory = dashScopeClientWebSocketFactory;
+ }
+
+ internal void ReturnSocket(DashScopeClientWebSocket socket)
+ {
+ _active.Remove(socket.Id, out _);
+
+ if (socket.State != DashScopeWebSocketState.Ready)
+ {
+ // not returnable, disposing.
+ socket.Dispose();
+ return;
+ }
+
+ _available.Add(socket);
+ }
+
+ ///
+ /// Rent or create a socket connection from pool.
+ ///
+ ///
+ ///
+ public async Task RentSocketAsync(CancellationToken cancellationToken = default)
+ {
+ var found = false;
+ DashScopeClientWebSocket? socket = null;
+ while (found == false)
+ {
+ if (_available.IsEmpty == false)
+ {
+ found = _available.TryTake(out socket);
+ if (socket?.State != DashScopeWebSocketState.Ready)
+ {
+ // expired
+ found = false;
+ socket?.Dispose();
+ }
+ }
+ else
+ {
+ socket = await InitializeNewSocketAsync(_options.WebsocketBaseAddress, cancellationToken);
+ found = true;
+ }
+ }
+
+ return ActivateSocket(socket!);
+ }
+
+ private DashScopeClientWebSocketWrapper ActivateSocket(DashScopeClientWebSocket socket)
+ {
+ _active.TryAdd(socket.Id, socket);
+ return new DashScopeClientWebSocketWrapper(socket, this);
+ }
+
+ private async Task InitializeNewSocketAsync(
+ string url,
+ CancellationToken cancellationToken = default)
+ {
+ if (_available.Count + _active.Count >= _options.SocketPoolSize)
+ {
+ throw new InvalidOperationException("[DashScopeSDK] Socket pool is full");
+ }
+
+ var socket = _dashScopeClientWebSocketFactory.GetClientWebSocket(_options.ApiKey, _options.WorkspaceId);
+ await socket.ConnectAsync(new Uri(url), cancellationToken);
+ return socket;
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // Dispose managed resources.
+ while (_available.IsEmpty == false)
+ {
+ _available.TryTake(out var socket);
+ socket?.Dispose();
+ }
+
+ var activeSockets = _active.Values;
+ foreach (var activeSocket in activeSockets)
+ {
+ activeSocket.Dispose();
+ }
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeClientWebSocketWrapper.cs b/src/Cnblogs.DashScope.Core/DashScopeClientWebSocketWrapper.cs
new file mode 100644
index 0000000..696f304
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeClientWebSocketWrapper.cs
@@ -0,0 +1,56 @@
+using System.Text.Json;
+
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Represents a transient wrapper for rented websocket, should be transient.
+///
+/// The rented websocket
+/// The pool to return the socket to.
+public sealed record DashScopeClientWebSocketWrapper(DashScopeClientWebSocket Socket, DashScopeClientWebSocketPool Pool)
+ : IDisposable
+{
+ ///
+ /// The binary output.
+ ///
+ public IAsyncEnumerable BinaryOutput => Socket.BinaryOutput.ReadAllAsync();
+
+ ///
+ /// The json message output.
+ ///
+ public IAsyncEnumerable> JsonOutput => Socket.JsonOutput.ReadAllAsync();
+
+ ///
+ /// Reset task signal and output cannel.
+ ///
+ public void ResetTask() => Socket.ResetOutput();
+
+ ///
+ /// The task that completes when received task-started event from server.
+ ///
+ public Task TaskStarted => Socket.TaskStarted;
+
+ ///
+ /// Send message to server.
+ ///
+ /// Request to send.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ ///
+ /// Type of the input.
+ /// Type of the parameter.
+ /// The is requested.
+ /// Websocket is not connected.
+ /// The underlying websocket has already been closed.
+ public Task SendMessageAsync(
+ DashScopeWebSocketRequest request,
+ CancellationToken cancellationToken = default)
+ where TInput : class, new()
+ where TParameter : class
+ => Socket.SendMessageAsync(request, cancellationToken);
+
+ ///
+ public void Dispose()
+ {
+ Pool.ReturnSocket(Socket);
+ }
+}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeException.cs b/src/Cnblogs.DashScope.Core/DashScopeException.cs
index d4aba49..3826f87 100644
--- a/src/Cnblogs.DashScope.Core/DashScopeException.cs
+++ b/src/Cnblogs.DashScope.Core/DashScopeException.cs
@@ -3,24 +3,35 @@
///
/// Represents error detail for DashScope API calls.
///
-/// The requested api url.
-/// The status code of response. Would be 0 if no response is received.
-/// The error detail returned by server.
-/// The error message.
-public class DashScopeException(string? apiUrl, int status, DashScopeError? error, string message) : Exception(message)
+public class DashScopeException : Exception
{
+ ///
+ /// Represents error detail for DashScope API calls.
+ ///
+ /// The requested api url.
+ /// The status code of response. Would be 0 if no response is received.
+ /// The error detail returned by server.
+ /// The error message.
+ public DashScopeException(string? apiUrl, int status, DashScopeError? error, string message)
+ : base(message)
+ {
+ ApiUrl = apiUrl;
+ Error = error;
+ Status = status;
+ }
+
///
/// The requested api url.
///
- public string? ApiUrl { get; } = apiUrl;
+ public string? ApiUrl { get; }
///
/// The error detail returned by server.
///
- public DashScopeError? Error { get; } = error;
+ public DashScopeError? Error { get; }
///
/// The status code of response. Would be 0 if no response is received.
///
- public int Status { get; } = status;
+ public int Status { get; }
}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeOptions.cs b/src/Cnblogs.DashScope.Core/DashScopeOptions.cs
new file mode 100644
index 0000000..d68d1bb
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeOptions.cs
@@ -0,0 +1,34 @@
+using Cnblogs.DashScope.Core.Internals;
+
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Options for DashScope client.
+///
+public class DashScopeOptions
+{
+ ///
+ /// The api key used to access DashScope api
+ ///
+ public string ApiKey { get; set; } = string.Empty;
+
+ ///
+ /// Base address for DashScope HTTP API.
+ ///
+ public string BaseAddress { get; set; } = DashScopeDefaults.HttpApiBaseAddress;
+
+ ///
+ /// Base address for DashScope websocket API.
+ ///
+ public string WebsocketBaseAddress { get; set; } = DashScopeDefaults.WebsocketApiBaseAddress;
+
+ ///
+ /// Default workspace Id.
+ ///
+ public string? WorkspaceId { get; set; }
+
+ ///
+ /// Default socket pool size.
+ ///
+ public int SocketPoolSize { get; set; } = 32;
+}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeTaskOutput.cs b/src/Cnblogs.DashScope.Core/DashScopeTaskOutput.cs
index 8ca43f9..7a8bee7 100644
--- a/src/Cnblogs.DashScope.Core/DashScopeTaskOutput.cs
+++ b/src/Cnblogs.DashScope.Core/DashScopeTaskOutput.cs
@@ -11,7 +11,7 @@ public abstract record DashScopeTaskOutput
///
/// The unique id of this task.
///
- public required string TaskId { get; set; }
+ public string TaskId { get; set; } = string.Empty;
///
/// The status of this task.
diff --git a/src/Cnblogs.DashScope.Core/DashScopeTaskStatus.cs b/src/Cnblogs.DashScope.Core/DashScopeTaskStatus.cs
index 83a3509..3b87579 100644
--- a/src/Cnblogs.DashScope.Core/DashScopeTaskStatus.cs
+++ b/src/Cnblogs.DashScope.Core/DashScopeTaskStatus.cs
@@ -5,7 +5,7 @@ namespace Cnblogs.DashScope.Core;
///
/// Represents status of DashScope task.
///
-[JsonConverter(typeof(JsonStringEnumConverter))]
+[JsonConverter(typeof(JsonStringEnumConverter))]
public enum DashScopeTaskStatus
{
///
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketRequest.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketRequest.cs
new file mode 100644
index 0000000..b1845e0
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketRequest.cs
@@ -0,0 +1,21 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Represents a websocket request to DashScope.
+///
+/// Type of the input.
+/// Type of the parameter.
+public class DashScopeWebSocketRequest
+ where TInput : class, new()
+ where TParameter : class
+{
+ ///
+ /// Metadata of the request.
+ ///
+ public DashScopeWebSocketRequestHeader Header { get; set; } = new();
+
+ ///
+ /// Payload of the request.
+ ///
+ public DashScopeWebSocketRequestPayload Payload { get; set; } = new();
+}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketRequestHeader.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketRequestHeader.cs
new file mode 100644
index 0000000..a54c9c0
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketRequestHeader.cs
@@ -0,0 +1,22 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Metadata for websocket request.
+///
+public class DashScopeWebSocketRequestHeader
+{
+ ///
+ /// Action name.
+ ///
+ public string Action { get; set; } = string.Empty;
+
+ ///
+ /// UUID for task.
+ ///
+ public string TaskId { get; set; } = string.Empty;
+
+ ///
+ /// Streaming type.
+ ///
+ public string? Streaming { get; set; } = "duplex";
+}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketRequestPayload.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketRequestPayload.cs
new file mode 100644
index 0000000..e994239
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketRequestPayload.cs
@@ -0,0 +1,41 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Payload for websocket request.
+///
+/// Type of the input.
+/// Type of the parameter.
+public class DashScopeWebSocketRequestPayload
+ where TInput : class, new() // Input's default value must be empty object(not null or omitted).
+ where TParameter : class
+{
+ ///
+ /// Group name of task.
+ ///
+ public string? TaskGroup { get; set; }
+
+ ///
+ /// Requesting task name.
+ ///
+ public string? Task { get; set; }
+
+ ///
+ /// Requesting function name.
+ ///
+ public string? Function { get; set; }
+
+ ///
+ /// Model id.
+ ///
+ public string? Model { get; set; }
+
+ ///
+ /// Optional parameters.
+ ///
+ public TParameter? Parameters { get; set; }
+
+ ///
+ /// The input of the request.
+ ///
+ public TInput Input { get; set; } = new();
+}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponse.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponse.cs
new file mode 100644
index 0000000..bffc68b
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponse.cs
@@ -0,0 +1,11 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Response from websocket API.
+///
+/// Response metadatas.
+/// Response body.
+/// Output type of the response.
+public record DashScopeWebSocketResponse(
+ DashScopeWebSocketResponseHeader Header,
+ DashScopeWebSocketResponsePayload Payload);
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseHeader.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseHeader.cs
new file mode 100644
index 0000000..26bbb9f
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseHeader.cs
@@ -0,0 +1,16 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Metadata of the websocket response.
+///
+/// TaskId of the task.
+/// Event name.
+/// Error code when is task-failed.
+/// Error message when is task-failed.
+/// Optional attributes
+public record DashScopeWebSocketResponseHeader(
+ string TaskId,
+ string Event,
+ string? ErrorCode,
+ string? ErrorMessage,
+ DashScopeWebSocketResponseHeaderAttributes Attributes);
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseHeaderAttributes.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseHeaderAttributes.cs
new file mode 100644
index 0000000..1ee663e
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseHeaderAttributes.cs
@@ -0,0 +1,7 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Attributes field in websocket response header.
+///
+/// UUID for current request.
+public record DashScopeWebSocketResponseHeaderAttributes(string? RequestUuid);
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseMapper.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseMapper.cs
new file mode 100644
index 0000000..3dea7d0
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseMapper.cs
@@ -0,0 +1,27 @@
+using System.Text.Json;
+using Cnblogs.DashScope.Core.Internals;
+
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Mapper class for
+///
+internal static class DashScopeWebSocketResponseMapper
+{
+ public static DashScopeWebSocketResponse DeserializeOutput(this DashScopeWebSocketResponse source)
+ where TOutput : class
+ {
+ var output = source.Payload.Output;
+ if (output.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
+ {
+ return new DashScopeWebSocketResponse(
+ source.Header,
+ new DashScopeWebSocketResponsePayload(null, source.Payload.Usage));
+ }
+
+ var mapped = output.Deserialize(DashScopeDefaults.SerializationOptions);
+ return new DashScopeWebSocketResponse(
+ source.Header,
+ new DashScopeWebSocketResponsePayload(mapped, source.Payload.Usage));
+ }
+}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponsePayload.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponsePayload.cs
new file mode 100644
index 0000000..cce48aa
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponsePayload.cs
@@ -0,0 +1,9 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Payload field of websocket API response.
+///
+/// Content of the response.
+/// Task usage info.
+/// Type of the response content.
+public record DashScopeWebSocketResponsePayload(TOutput? Output, DashScopeWebSocketResponseUsage? Usage);
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseUsage.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseUsage.cs
new file mode 100644
index 0000000..7e7f734
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketResponseUsage.cs
@@ -0,0 +1,7 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Usage info of websocket task.
+///
+/// Current character usage count.
+public record DashScopeWebSocketResponseUsage(int Characters);
diff --git a/src/Cnblogs.DashScope.Core/DashScopeWebSocketState.cs b/src/Cnblogs.DashScope.Core/DashScopeWebSocketState.cs
new file mode 100644
index 0000000..2cb73b1
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/DashScopeWebSocketState.cs
@@ -0,0 +1,27 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// The state of .
+///
+public enum DashScopeWebSocketState
+{
+ ///
+ /// The socket has been created but not connected yet.
+ ///
+ Created,
+
+ ///
+ /// The socket has been connected and ready.
+ ///
+ Ready,
+
+ ///
+ /// The socket has a running task waiting to be finished.
+ ///
+ RunningTask,
+
+ ///
+ /// The socket has been closed.
+ ///
+ Closed
+}
diff --git a/src/Cnblogs.DashScope.Core/IBatchGetEmbeddingsParameters.cs b/src/Cnblogs.DashScope.Core/IBatchGetEmbeddingsParameters.cs
index d2640e0..04a28d3 100644
--- a/src/Cnblogs.DashScope.Core/IBatchGetEmbeddingsParameters.cs
+++ b/src/Cnblogs.DashScope.Core/IBatchGetEmbeddingsParameters.cs
@@ -3,4 +3,6 @@
///
/// Optional parameter of batch get embeddings request.
///
-public interface IBatchGetEmbeddingsParameters : ITextEmbeddingParameters;
+public interface IBatchGetEmbeddingsParameters : ITextEmbeddingParameters
+{
+}
diff --git a/src/Cnblogs.DashScope.Core/IDashScopeClient.cs b/src/Cnblogs.DashScope.Core/IDashScopeClient.cs
index a123050..cb61eb5 100644
--- a/src/Cnblogs.DashScope.Core/IDashScopeClient.cs
+++ b/src/Cnblogs.DashScope.Core/IDashScopeClient.cs
@@ -247,4 +247,14 @@ public Task UploadFileAsync(
public Task DeleteFileAsync(
DashScopeFileId id,
CancellationToken cancellationToken = default);
+
+ ///
+ /// Start a speech synthesizer session. Related model: cosyvoice
+ ///
+ /// The model to use.
+ /// Cancellation token.
+ ///
+ public Task CreateSpeechSynthesizerSocketSessionAsync(
+ string modelId,
+ CancellationToken cancellationToken = default);
}
diff --git a/src/Cnblogs.DashScope.Core/IDashScopeClientWebSocketFactory.cs b/src/Cnblogs.DashScope.Core/IDashScopeClientWebSocketFactory.cs
new file mode 100644
index 0000000..0cf7359
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/IDashScopeClientWebSocketFactory.cs
@@ -0,0 +1,15 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// A factory abstraction for a component that can create DashScopeClientWebSocket instance.
+///
+public interface IDashScopeClientWebSocketFactory
+{
+ ///
+ /// Creates a new .
+ ///
+ /// The api key.
+ /// Optional workspace id.
+ ///
+ DashScopeClientWebSocket GetClientWebSocket(string apiKey, string? workspaceId = null);
+}
diff --git a/src/Cnblogs.DashScope.Core/IImageSynthesisParameters.cs b/src/Cnblogs.DashScope.Core/IImageSynthesisParameters.cs
index 5e241c6..70a3b47 100644
--- a/src/Cnblogs.DashScope.Core/IImageSynthesisParameters.cs
+++ b/src/Cnblogs.DashScope.Core/IImageSynthesisParameters.cs
@@ -24,4 +24,14 @@ public interface IImageSynthesisParameters
/// Seed for randomizer, max at 4294967290. Once set, generated image will use seed, seed+1, seed+2, seed+3 depends on .
///
public uint? Seed { get; }
+
+ ///
+ /// Let LLM to rewrite your positive prompt, Defaults to true.
+ ///
+ public bool? PromptExtend { get; }
+
+ ///
+ /// Adds AI-Generated watermark on bottom right corner.
+ ///
+ public bool? Watermark { get; }
}
diff --git a/src/Cnblogs.DashScope.Core/ITextGenerationParameters.cs b/src/Cnblogs.DashScope.Core/ITextGenerationParameters.cs
index c32bb72..9c3b763 100644
--- a/src/Cnblogs.DashScope.Core/ITextGenerationParameters.cs
+++ b/src/Cnblogs.DashScope.Core/ITextGenerationParameters.cs
@@ -1,10 +1,11 @@
-namespace Cnblogs.DashScope.Core;
+namespace Cnblogs.DashScope.Core;
///
/// The text generation options.
///
public interface ITextGenerationParameters
- : IIncrementalOutputParameter, ISeedParameter, IProbabilityParameter, IPenaltyParameter, IMaxTokenParameter, IStopTokenParameter
+ : IIncrementalOutputParameter, ISeedParameter, IProbabilityParameter, IPenaltyParameter, IMaxTokenParameter,
+ IStopTokenParameter
{
///
/// The format of the result, must be text or message.
@@ -40,6 +41,31 @@ public interface ITextGenerationParameters
///
public bool? EnableSearch { get; }
+ ///
+ /// Search options. should set to true.
+ ///
+ public TextGenerationSearchOptions? SearchOptions { get; set; }
+
+ ///
+ /// Thinking option. Valid for supported models.(e.g. qwen3)
+ ///
+ public bool? EnableThinking { get; }
+
+ ///
+ /// Maximum length of thinking content. Valid for supported models.(e.g. qwen3)
+ ///
+ public int? ThinkingBudget { get; set; }
+
+ ///
+ /// Include log possibilities in response.
+ ///
+ public bool? Logprobs { get; set; }
+
+ ///
+ /// How many choices should be returned. Range: [0, 5]
+ ///
+ public int? TopLogprobs { get; set; }
+
///
/// Available tools for model to call.
///
@@ -49,4 +75,14 @@ public interface ITextGenerationParameters
/// Behavior when choosing tools.
///
public ToolChoice? ToolChoice { get; }
+
+ ///
+ /// Whether to enable parallel tool calling
+ ///
+ public bool? ParallelToolCalls { get; }
+
+ ///
+ /// Options when using QWen-MT models.
+ ///
+ public TextGenerationTranslationOptions? TranslationOptions { get; set; }
}
diff --git a/src/Cnblogs.DashScope.Core/ImageGenerationInput.cs b/src/Cnblogs.DashScope.Core/ImageGenerationInput.cs
index d21dca1..4ea9876 100644
--- a/src/Cnblogs.DashScope.Core/ImageGenerationInput.cs
+++ b/src/Cnblogs.DashScope.Core/ImageGenerationInput.cs
@@ -8,10 +8,10 @@ public class ImageGenerationInput
///
/// The image url to generation new image from.
///
- public required string ImageUrl { get; set; }
+ public string ImageUrl { get; set; } = string.Empty;
///
/// The style the new image should use, checkout docs for sample image of different indexes: https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-wanxiang-style-repaint
///
- public required int StyleIndex { get; set; }
+ public int StyleIndex { get; set; }
}
diff --git a/src/Cnblogs.DashScope.Core/ImageSynthesisInput.cs b/src/Cnblogs.DashScope.Core/ImageSynthesisInput.cs
index 9c78118..21c0dcc 100644
--- a/src/Cnblogs.DashScope.Core/ImageSynthesisInput.cs
+++ b/src/Cnblogs.DashScope.Core/ImageSynthesisInput.cs
@@ -8,7 +8,7 @@ public class ImageSynthesisInput
///
/// The prompt to generate image from. This will be chopped at max length of 500 characters.
///
- public required string Prompt { get; set; }
+ public string Prompt { get; set; } = string.Empty;
///
/// The negative prompt to generate image from. This will be chopped at max length of 500 characters.
diff --git a/src/Cnblogs.DashScope.Core/ImageSynthesisParameters.cs b/src/Cnblogs.DashScope.Core/ImageSynthesisParameters.cs
index 58497fe..8ca30bd 100644
--- a/src/Cnblogs.DashScope.Core/ImageSynthesisParameters.cs
+++ b/src/Cnblogs.DashScope.Core/ImageSynthesisParameters.cs
@@ -16,4 +16,10 @@ public class ImageSynthesisParameters : IImageSynthesisParameters
///
public uint? Seed { get; set; }
+
+ ///
+ public bool? PromptExtend { get; set; }
+
+ ///
+ public bool? Watermark { get; set; }
}
diff --git a/src/Cnblogs.DashScope.Core/Internals/Assembly.cs b/src/Cnblogs.DashScope.Core/Internals/Assembly.cs
new file mode 100644
index 0000000..e628d36
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/Internals/Assembly.cs
@@ -0,0 +1,5 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Cnblogs.DashScope.Sdk.UnitTests")]
+[assembly: InternalsVisibleTo("Cnblogs.DashScope.Tests.Shared")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
diff --git a/src/Cnblogs.DashScope.Core/Internals/ByteArrayLiteralConvertor.cs b/src/Cnblogs.DashScope.Core/Internals/ByteArrayLiteralConvertor.cs
new file mode 100644
index 0000000..509e4c1
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/Internals/ByteArrayLiteralConvertor.cs
@@ -0,0 +1,43 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Cnblogs.DashScope.Core.Internals;
+
+internal class ByteArrayLiteralConvertor : JsonConverter
+{
+ ///
+ public override byte[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.StartArray)
+ {
+ reader.Read(); // read out start of array
+ var list = new List(8); // should fit most tokens
+ while (reader.TokenType != JsonTokenType.EndArray)
+ {
+ list.Add(reader.GetByte());
+ reader.Read();
+ }
+
+ return list.ToArray();
+ }
+
+ if (reader.TokenType == JsonTokenType.Null)
+ {
+ return null;
+ }
+
+ return reader.GetBytesFromBase64();
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
+ {
+ writer.WriteStartArray();
+ foreach (var b in value)
+ {
+ writer.WriteNumberValue(b);
+ }
+
+ writer.WriteEndArray();
+ }
+}
diff --git a/src/Cnblogs.DashScope.Core/Internals/ClientWebSocketWrapper.cs b/src/Cnblogs.DashScope.Core/Internals/ClientWebSocketWrapper.cs
new file mode 100644
index 0000000..71ebddb
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/Internals/ClientWebSocketWrapper.cs
@@ -0,0 +1,47 @@
+using System.Net.WebSockets;
+
+namespace Cnblogs.DashScope.Core.Internals;
+
+internal sealed class ClientWebSocketWrapper : IClientWebSocket
+{
+ private readonly ClientWebSocket _socket;
+
+ public ClientWebSocketWrapper(ClientWebSocket socket)
+ {
+ _socket = socket;
+ }
+
+ ///
+ public void Dispose()
+ {
+ _socket.Dispose();
+ }
+
+ ///
+ public ClientWebSocketOptions Options => _socket.Options;
+
+ ///
+ public WebSocketCloseStatus? CloseStatus => _socket.CloseStatus;
+
+ ///
+ public Task ConnectAsync(Uri uri, CancellationToken cancellation) => _socket.ConnectAsync(uri, cancellation);
+
+ ///
+ public Task SendAsync(
+ ArraySegment buffer,
+ WebSocketMessageType messageType,
+ bool endOfMessage,
+ CancellationToken cancellationToken)
+ => _socket.SendAsync(buffer, messageType, endOfMessage, cancellationToken);
+
+ ///
+ public Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken)
+ => _socket.ReceiveAsync(buffer, cancellationToken);
+
+ ///
+ public Task CloseAsync(
+ WebSocketCloseStatus closeStatus,
+ string? statusDescription,
+ CancellationToken cancellationToken)
+ => _socket.CloseAsync(closeStatus, statusDescription, cancellationToken);
+}
diff --git a/src/Cnblogs.DashScope.Core/Internals/DashScopeDateTimeConvertor.cs b/src/Cnblogs.DashScope.Core/Internals/DashScopeDateTimeConvertor.cs
index c4c9987..45fc52f 100644
--- a/src/Cnblogs.DashScope.Core/Internals/DashScopeDateTimeConvertor.cs
+++ b/src/Cnblogs.DashScope.Core/Internals/DashScopeDateTimeConvertor.cs
@@ -6,11 +6,7 @@ namespace Cnblogs.DashScope.Core.Internals;
internal class DashScopeDateTimeConvertor : JsonConverter
{
- private static readonly string[] DateTimeFormats =
- [
- "yyyy-MM-dd HH:mm:ss.fff",
- "yyyy-MM-dd HH:mm:ss.FFF"
- ];
+ private static readonly string[] DateTimeFormats = { "yyyy-MM-dd HH:mm:ss.fff", "yyyy-MM-dd HH:mm:ss.FFF" };
///
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
diff --git a/src/Cnblogs.DashScope.Core/Internals/DashScopeDefaults.cs b/src/Cnblogs.DashScope.Core/Internals/DashScopeDefaults.cs
index 5c1b537..7623c1b 100644
--- a/src/Cnblogs.DashScope.Core/Internals/DashScopeDefaults.cs
+++ b/src/Cnblogs.DashScope.Core/Internals/DashScopeDefaults.cs
@@ -1,6 +1,30 @@
-namespace Cnblogs.DashScope.Core.Internals;
+using System.Text.Json;
+using System.Text.Json.Serialization;
-internal static class DashScopeDefaults
+namespace Cnblogs.DashScope.Core.Internals;
+
+///
+/// Default values for DashScope client.
+///
+public static class DashScopeDefaults
{
- public const string DashScopeApiBaseAddress = "https://dashscope.aliyuncs.com/api/v1/";
+ ///
+ /// Base address for HTTP API.
+ ///
+ public const string HttpApiBaseAddress = "https://dashscope.aliyuncs.com/api/v1/";
+
+ ///
+ /// Base address for websocket API.
+ ///
+ public const string WebsocketApiBaseAddress = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/";
+
+ ///
+ /// Default json serializer options.
+ ///
+ public static readonly JsonSerializerOptions SerializationOptions =
+ new()
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+ };
}
diff --git a/src/Cnblogs.DashScope.Core/Internals/IClientWebSocket.cs b/src/Cnblogs.DashScope.Core/Internals/IClientWebSocket.cs
new file mode 100644
index 0000000..b4a3cd7
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/Internals/IClientWebSocket.cs
@@ -0,0 +1,25 @@
+using System.Net.WebSockets;
+
+namespace Cnblogs.DashScope.Core.Internals;
+
+///
+/// Extract for testing purpose.
+///
+internal interface IClientWebSocket : IDisposable
+{
+ public ClientWebSocketOptions Options { get; }
+
+ public WebSocketCloseStatus? CloseStatus { get; }
+
+ public Task ConnectAsync(Uri uri, CancellationToken cancellation);
+
+ public Task SendAsync(
+ ArraySegment buffer,
+ WebSocketMessageType messageType,
+ bool endOfMessage,
+ CancellationToken cancellationToken);
+
+ Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken);
+
+ Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken);
+}
diff --git a/src/Cnblogs.DashScope.Core/Internals/TextGenerationStopConvertor.cs b/src/Cnblogs.DashScope.Core/Internals/TextGenerationStopConvertor.cs
index 1efd6b5..5f704a8 100644
--- a/src/Cnblogs.DashScope.Core/Internals/TextGenerationStopConvertor.cs
+++ b/src/Cnblogs.DashScope.Core/Internals/TextGenerationStopConvertor.cs
@@ -77,21 +77,21 @@ private static TextGenerationStop ReadArray(ref Utf8JsonReader reader)
case JsonTokenType.EndArray:
return type switch
{
- DeserializationArrayType.Strings => stringList ?? [],
- DeserializationArrayType.Token => intList?.ToArray() ?? [],
- DeserializationArrayType.Tokens => tokenList ?? [],
+ DeserializationArrayType.Strings => stringList ?? new List(),
+ DeserializationArrayType.Token => intList?.ToArray() ?? Array.Empty(),
+ DeserializationArrayType.Tokens => tokenList ?? new List(),
_ => throw new JsonException("Impossible deserialization type")
};
case JsonTokenType.StartArray when type is DeserializationArrayType.Tokens:
- tokenList ??= [];
+ tokenList ??= new List();
tokenList.Add(ReadTokenId(ref reader));
break;
case JsonTokenType.Number when type is DeserializationArrayType.Token:
- intList ??= [];
+ intList ??= new List();
intList.Add(reader.GetInt32());
break;
case JsonTokenType.String when type is DeserializationArrayType.Strings:
- stringList ??= [];
+ stringList ??= new List();
stringList.Add(reader.GetString()!);
break;
default:
diff --git a/src/Cnblogs.DashScope.Core/ModelRequest.cs b/src/Cnblogs.DashScope.Core/ModelRequest.cs
index 69ad50c..93fbfff 100644
--- a/src/Cnblogs.DashScope.Core/ModelRequest.cs
+++ b/src/Cnblogs.DashScope.Core/ModelRequest.cs
@@ -10,12 +10,12 @@ public class ModelRequest
///
/// The model to use.
///
- public required string Model { get; init; }
+ public string Model { get; init; } = string.Empty;
///
/// Input of this request.
///
- public required TInput Input { get; init; }
+ public TInput Input { get; init; } = null!;
}
///
diff --git a/src/Cnblogs.DashScope.Core/MultimodalInput.cs b/src/Cnblogs.DashScope.Core/MultimodalInput.cs
index 775b4d5..251e0ac 100644
--- a/src/Cnblogs.DashScope.Core/MultimodalInput.cs
+++ b/src/Cnblogs.DashScope.Core/MultimodalInput.cs
@@ -8,5 +8,5 @@ public class MultimodalInput
///
/// The messages of context, model will generate from last user message.
///
- public required IEnumerable Messages { get; set; }
+ public IEnumerable Messages { get; set; } = Array.Empty();
}
diff --git a/src/Cnblogs.DashScope.Core/MultimodalMessage.cs b/src/Cnblogs.DashScope.Core/MultimodalMessage.cs
index d1034b0..48bcc44 100644
--- a/src/Cnblogs.DashScope.Core/MultimodalMessage.cs
+++ b/src/Cnblogs.DashScope.Core/MultimodalMessage.cs
@@ -7,7 +7,11 @@ namespace Cnblogs.DashScope.Core;
///
/// The role associated with this message.
/// The contents of this message.
-public record MultimodalMessage(string Role, IReadOnlyList Content)
+/// Thoughts from the model.
+public record MultimodalMessage(
+ string Role,
+ IReadOnlyList Content,
+ string? ReasoningContent = null)
: IMessage>
{
///
@@ -34,9 +38,12 @@ public static MultimodalMessage System(IReadOnlyList c
/// Creates an assistant message.
///
/// Message contents.
+ /// Thoughts from the model.
///
- public static MultimodalMessage Assistant(IReadOnlyList contents)
+ public static MultimodalMessage Assistant(
+ IReadOnlyList contents,
+ string? reasoningContent = null)
{
- return new MultimodalMessage(DashScopeRoleNames.Assistant, contents);
+ return new MultimodalMessage(DashScopeRoleNames.Assistant, contents, reasoningContent);
}
}
diff --git a/src/Cnblogs.DashScope.Core/MultimodalMessageContent.cs b/src/Cnblogs.DashScope.Core/MultimodalMessageContent.cs
index 98fff19..aee7879 100644
--- a/src/Cnblogs.DashScope.Core/MultimodalMessageContent.cs
+++ b/src/Cnblogs.DashScope.Core/MultimodalMessageContent.cs
@@ -1,6 +1,4 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace Cnblogs.DashScope.Core;
+namespace Cnblogs.DashScope.Core;
///
/// Represents one content of a .
@@ -12,10 +10,8 @@ namespace Cnblogs.DashScope.Core;
/// For qwen-vl-ocr only. Minimal pixels for ocr task.
/// For qwen-vl-ocr only. Maximum pixels for ocr task.
public record MultimodalMessageContent(
- [StringSyntax(StringSyntaxAttribute.Uri)]
string? Image = null,
string? Text = null,
- [StringSyntax(StringSyntaxAttribute.Uri)]
string? Audio = null,
IEnumerable? Video = null,
int? MinPixels = null,
diff --git a/src/Cnblogs.DashScope.Core/QWenTokenizer.cs b/src/Cnblogs.DashScope.Core/QWenTokenizer.cs
index 3deec09..f114a14 100644
--- a/src/Cnblogs.DashScope.Core/QWenTokenizer.cs
+++ b/src/Cnblogs.DashScope.Core/QWenTokenizer.cs
@@ -1,48 +1,35 @@
-using System.Text.RegularExpressions;
-using Cnblogs.DashScope.Core.Internals;
-using Microsoft.ML.Tokenizers;
+using Cnblogs.DashScope.Core.Internals;
+using Microsoft.DeepDev;
namespace Cnblogs.DashScope.Core;
///
-/// Local implementation for QWen tokenizer
+/// Tokenizer using QWen
///
-public partial class QWenTokenizer
+public class QWenTokenizer
{
private static readonly Dictionary SpecialTokens =
- new List
- {
- "<|endoftext|>",
- "<|im_start|>",
- "<|im_end|>"
- }
+ new[] { "<|endoftext|>", "<|im_start|>", "<|im_end|>" }
.Concat(Enumerable.Range(0, 205).Select(x => $"<|extra_{x}|>"))
.Select((x, i) => new KeyValuePair(x, 151643 + i))
- .ToDictionary();
-
- [GeneratedRegex(
- @"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+",
- RegexOptions.Compiled,
- "zh-CN")]
- private static partial Regex Pattern();
+ .ToDictionary(x => x.Key, x => x.Value);
///
- /// Created tokenizer
+ /// Static tokenizer
///
- public static Tokenizer Tokenizer { get; } = TiktokenTokenizer.Create(
+ public static readonly ITokenizer Tokenizer = TokenizerBuilder.CreateTokenizer(
DashScopeEmbeddedResource.ReadBpeFile(),
- new RegexPreTokenizer(Pattern(), SpecialTokens),
- null,
- SpecialTokens);
+ SpecialTokens,
+ @"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+");
///
- /// Encode text to tokens.
+ /// Encode text.
///
- /// The text to encode.
+ /// The text to be encoded.
///
- public static IReadOnlyList Encode(string text)
+ public static List Encode(string text)
{
- return Tokenizer.EncodeToIds(text);
+ return Tokenizer.Encode(text, false);
}
///
@@ -56,25 +43,42 @@ public static string Decode(int[] tokens)
}
///
- /// Get token count for text.
+ /// Count tokens.
+ ///
+ /// Input text.
+ ///
+ public int CountTokens(string text)
+ {
+ return Tokenizer.Encode(text).Count;
+ }
+
+ ///
+ /// Split text to string tokens.
+ ///
+ /// Input text.
+ ///
+ public IReadOnlyList GetTokens(string text)
+ {
+ return Tokenizer.Encode(text).Select(x => Tokenizer.Decode(new[] { x })).ToList();
+ }
+
+ ///
+ /// Count tokens.
///
- /// The text to tokenize.
+ /// The text to be tokenized.
///
- public static int CountTokens(string text)
+ public static int CountTokensStatic(string text)
{
- return Tokenizer.CountTokens(text);
+ return Tokenizer.Encode(text).Count;
}
///
- /// Find the index of the maximum encoding capacity without surpassing the token limit.
+ /// Get tokens
///
- /// The input text.
- /// The maximum number of tokens to encode.
- /// If the tokenizer's normalization is enabled or is , this will be set to in its normalized form; otherwise, this value will be set to .
- /// The token count can be generated which should be smaller than the maximum token count.
+ /// The text to tokenizers.
///
- public static int GetIndexByTokenCount(string text, int maxTokenCount, out string? normalizedText, out int tokenCount)
+ public static IReadOnlyList GetTokensStatic(string text)
{
- return Tokenizer.GetIndexByTokenCount(text, maxTokenCount, out normalizedText, out tokenCount);
+ return Tokenizer.Encode(text).Select(x => Tokenizer.Decode(new[] { x })).ToList();
}
}
diff --git a/src/Cnblogs.DashScope.Core/SpeechSynthesizerInput.cs b/src/Cnblogs.DashScope.Core/SpeechSynthesizerInput.cs
new file mode 100644
index 0000000..8421fe2
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/SpeechSynthesizerInput.cs
@@ -0,0 +1,12 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Input for TTS task.
+///
+public class SpeechSynthesizerInput
+{
+ ///
+ /// Input text, can be null for run-task command.
+ ///
+ public string? Text { get; set; }
+}
diff --git a/src/Cnblogs.DashScope.Core/SpeechSynthesizerOutput.cs b/src/Cnblogs.DashScope.Core/SpeechSynthesizerOutput.cs
new file mode 100644
index 0000000..11a000d
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/SpeechSynthesizerOutput.cs
@@ -0,0 +1,7 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Output for TTS task.
+///
+/// The output sentences.
+public record SpeechSynthesizerOutput(SpeechSynthesizerOutputSentences? Sentence);
diff --git a/src/Cnblogs.DashScope.Core/SpeechSynthesizerOutputSentences.cs b/src/Cnblogs.DashScope.Core/SpeechSynthesizerOutputSentences.cs
new file mode 100644
index 0000000..0698aba
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/SpeechSynthesizerOutputSentences.cs
@@ -0,0 +1,7 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Sentences for TTS output.
+///
+/// Output words.
+public record SpeechSynthesizerOutputSentences(string[]? Words);
diff --git a/src/Cnblogs.DashScope.Core/SpeechSynthesizerParameters.cs b/src/Cnblogs.DashScope.Core/SpeechSynthesizerParameters.cs
new file mode 100644
index 0000000..8e59c19
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/SpeechSynthesizerParameters.cs
@@ -0,0 +1,47 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Parameters for TTS task.
+///
+public class SpeechSynthesizerParameters
+{
+ ///
+ /// Fixed to "PlainText"
+ ///
+ public string TextType { get; set; } = "PlainText";
+
+ ///
+ /// The voice to use.
+ ///
+ public string Voice { get; set; } = string.Empty;
+
+ ///
+ /// Output file format, can be pcm, wav or mp3.
+ ///
+ public string? Format { get; set; }
+
+ ///
+ /// Output audio sample rate.
+ ///
+ public int? SampleRate { get; set; }
+
+ ///
+ /// Output audio volume, range between 0-100, defaults to 50.
+ ///
+ public int? Volume { get; set; }
+
+ ///
+ /// Speech speed, range between 0.5~2.0, defaults to 1.0.
+ ///
+ public float? Rate { get; set; }
+
+ ///
+ /// Pitch of the voice, range between 0.5~2, defaults to 1.0.
+ ///
+ public float? Pitch { get; set; }
+
+ ///
+ /// Enable SSML, you can only send text once if enabled.
+ ///
+ public bool? EnableSsml { get; set; }
+}
diff --git a/src/Cnblogs.DashScope.Core/SpeechSynthesizerSocketSession.cs b/src/Cnblogs.DashScope.Core/SpeechSynthesizerSocketSession.cs
new file mode 100644
index 0000000..a3ed0e4
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/SpeechSynthesizerSocketSession.cs
@@ -0,0 +1,156 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Represents a socket-based TTS session.
+///
+public sealed class SpeechSynthesizerSocketSession
+ : IDisposable
+{
+ private readonly DashScopeClientWebSocketWrapper _socket;
+ private readonly string _modelId;
+
+ ///
+ /// Represents a socket-based TTS session.
+ ///
+ /// Underlying websocket.
+ /// Model name to use.
+ public SpeechSynthesizerSocketSession(DashScopeClientWebSocketWrapper socket, string modelId)
+ {
+ _socket = socket;
+ _modelId = modelId;
+ }
+
+ ///
+ /// Send a run-task command, use random GUID as taskId.
+ ///
+ /// Input parameters.
+ /// Input text.
+ /// The cancellation token to use.
+ /// The generated taskId.
+ public Task RunTaskAsync(
+ SpeechSynthesizerParameters parameters,
+ string? text = null,
+ CancellationToken cancellationToken = default)
+ {
+ return RunTaskAsync(Guid.NewGuid().ToString(), parameters, text, cancellationToken);
+ }
+
+ ///
+ /// Send a run-task command.
+ ///
+ /// Unique taskId.
+ /// Input parameters.
+ /// Input text.
+ /// The cancellation token to use.
+ /// .
+ public async Task RunTaskAsync(
+ string taskId,
+ SpeechSynthesizerParameters parameters,
+ string? text = null,
+ CancellationToken cancellationToken = default)
+ {
+ var command = new DashScopeWebSocketRequest
+ {
+ Header = new DashScopeWebSocketRequestHeader
+ {
+ Action = "run-task", TaskId = taskId,
+ },
+ Payload = new DashScopeWebSocketRequestPayload
+ {
+ Input = new SpeechSynthesizerInput { Text = text, },
+ TaskGroup = "audio",
+ Task = "tts",
+ Function = "SpeechSynthesizer",
+ Model = _modelId,
+ Parameters = parameters
+ }
+ };
+
+ _socket.ResetTask();
+ await _socket.SendMessageAsync(command, cancellationToken);
+ await _socket.TaskStarted;
+ return taskId;
+ }
+
+ ///
+ /// Append input text to task.
+ ///
+ /// TaskId to append.
+ /// Text to append.
+ /// Cancellation token to use.
+ public async Task ContinueTaskAsync(string taskId, string input, CancellationToken cancellationToken = default)
+ {
+ var command = new DashScopeWebSocketRequest
+ {
+ Header = new DashScopeWebSocketRequestHeader
+ {
+ Action = "continue-task",
+ TaskId = taskId,
+ Streaming = null
+ },
+ Payload = new DashScopeWebSocketRequestPayload
+ {
+ Input = new SpeechSynthesizerInput { Text = input }
+ }
+ };
+ await _socket.SendMessageAsync(command, cancellationToken);
+ }
+
+ ///
+ /// Send finish-task command.
+ ///
+ /// Unique id of the task.
+ /// The cancellation token to use.
+ public async Task FinishTaskAsync(string taskId, CancellationToken cancellationToken = default)
+ {
+ var command = new DashScopeWebSocketRequest
+ {
+ Header = new DashScopeWebSocketRequestHeader
+ {
+ TaskId = taskId,
+ Action = "finish-task",
+ Streaming = null
+ },
+ Payload = new DashScopeWebSocketRequestPayload
+ {
+ Input = new SpeechSynthesizerInput()
+ }
+ };
+ await _socket.SendMessageAsync(command, cancellationToken);
+ }
+
+ ///
+ /// Get the audio stream.
+ ///
+ ///
+ public IAsyncEnumerable GetAudioAsync()
+ {
+ return _socket.BinaryOutput;
+ }
+
+ ///
+ /// Get the message stream.
+ ///
+ ///
+ public async IAsyncEnumerable> GetMessagesAsync()
+ {
+ await foreach (var response in _socket.JsonOutput)
+ {
+ yield return response.DeserializeOutput();
+ }
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _socket.Dispose();
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+}
diff --git a/src/Cnblogs.DashScope.Core/TextChatMessage.cs b/src/Cnblogs.DashScope.Core/TextChatMessage.cs
index 7191191..3a3bfa2 100644
--- a/src/Cnblogs.DashScope.Core/TextChatMessage.cs
+++ b/src/Cnblogs.DashScope.Core/TextChatMessage.cs
@@ -6,20 +6,7 @@ namespace Cnblogs.DashScope.Core;
///
/// Represents a chat message between the user and the model.
///
-/// The role of this message.
-/// The content of this message.
-/// Used when role is tool, represents the function name of this message generated by.
-/// Notify model that next message should use this message as prefix.
-/// Reasoning content for reasoning model.
-/// Calls to the function.
-[method: JsonConstructor]
-public record TextChatMessage(
- string Role,
- string Content,
- string? Name = null,
- bool? Partial = null,
- string? ReasoningContent = null,
- List? ToolCalls = null) : IMessage
+public record TextChatMessage : IMessage
{
///
/// Create chat message from an uploaded DashScope file.
@@ -39,6 +26,50 @@ public TextChatMessage(IEnumerable fileIds)
{
}
+ ///
+ /// Represents a chat message between the user and the model.
+ ///
+ /// The role of this message.
+ /// The content of this message.
+ /// Used when role is tool, represents the function name of this message generated by.
+ /// Notify model that next message should use this message as prefix.
+ /// Reasoning content for reasoning model.
+ /// Calls to the function.
+ [JsonConstructor]
+ public TextChatMessage(
+ string role,
+ string content,
+ string? name = null,
+ bool? partial = null,
+ string? reasoningContent = null,
+ List? toolCalls = null)
+ {
+ Role = role;
+ Content = content;
+ Name = name;
+ Partial = partial;
+ ReasoningContent = reasoningContent;
+ ToolCalls = toolCalls;
+ }
+
+ /// The role of this message.
+ public string Role { get; init; }
+
+ /// The content of this message.
+ public string Content { get; init; }
+
+ /// Used when role is tool, represents the function name of this message generated by.
+ public string? Name { get; init; }
+
+ /// Notify model that next message should use this message as prefix.
+ public bool? Partial { get; init; }
+
+ /// Reasoning content for reasoning model.
+ public string? ReasoningContent { get; init; }
+
+ /// Calls to the function.
+ public List? ToolCalls { get; init; }
+
///
/// Creates a file message.
///
diff --git a/src/Cnblogs.DashScope.Core/TextEmbeddingInput.cs b/src/Cnblogs.DashScope.Core/TextEmbeddingInput.cs
index 41295eb..fb884a2 100644
--- a/src/Cnblogs.DashScope.Core/TextEmbeddingInput.cs
+++ b/src/Cnblogs.DashScope.Core/TextEmbeddingInput.cs
@@ -8,5 +8,5 @@ public class TextEmbeddingInput
///
/// The texts to be computed.
///
- public required IEnumerable Texts { get; set; }
+ public IEnumerable Texts { get; set; } = Array.Empty();
}
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationChoice.cs b/src/Cnblogs.DashScope.Core/TextGenerationChoice.cs
index 6f5d7ab..faca9b9 100644
--- a/src/Cnblogs.DashScope.Core/TextGenerationChoice.cs
+++ b/src/Cnblogs.DashScope.Core/TextGenerationChoice.cs
@@ -13,5 +13,10 @@ public class TextGenerationChoice
///
/// The generated message.
///
- public required TextChatMessage Message { get; set; }
+ public TextChatMessage Message { get; set; } = new(Array.Empty());
+
+ ///
+ /// Token array with log possibility info.
+ ///
+ public TextGenerationLogprobs? Logprobs { get; set; }
}
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationLogprobContent.cs b/src/Cnblogs.DashScope.Core/TextGenerationLogprobContent.cs
new file mode 100644
index 0000000..c8b8b7d
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TextGenerationLogprobContent.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+using Cnblogs.DashScope.Core.Internals;
+
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Represents a possible choice of token.
+///
+/// Token content.
+/// Token content in UTF-8 byte array.
+/// Possibility, null when it's too low.
+/// The most possible alternatives.
+public record TextGenerationLogprobContent(
+ string Token,
+ [property: JsonConverter(typeof(ByteArrayLiteralConvertor))]
+ byte[] Bytes,
+ float? Logprob,
+ List TopLogprobs);
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationLogprobs.cs b/src/Cnblogs.DashScope.Core/TextGenerationLogprobs.cs
new file mode 100644
index 0000000..ba4a107
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TextGenerationLogprobs.cs
@@ -0,0 +1,7 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Possibilities of token choices.
+///
+/// The choices with their possibility.
+public record TextGenerationLogprobs(List Content);
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationOutput.cs b/src/Cnblogs.DashScope.Core/TextGenerationOutput.cs
index 7288b8a..d0d43d8 100644
--- a/src/Cnblogs.DashScope.Core/TextGenerationOutput.cs
+++ b/src/Cnblogs.DashScope.Core/TextGenerationOutput.cs
@@ -19,4 +19,9 @@ public class TextGenerationOutput
/// Not null when . is "message".
///
public List? Choices { get; set; }
+
+ ///
+ /// Not null when . configured to show source.
+ ///
+ public TextGenerationWebSearchInfo? SearchInfo { get; set; }
}
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationOutputTokenDetails.cs b/src/Cnblogs.DashScope.Core/TextGenerationOutputTokenDetails.cs
new file mode 100644
index 0000000..8e3f49b
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TextGenerationOutputTokenDetails.cs
@@ -0,0 +1,7 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Output details for text generation api.
+///
+/// Token count of reasoning content.
+public record TextGenerationOutputTokenDetails(int ReasoningTokens);
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationParameters.cs b/src/Cnblogs.DashScope.Core/TextGenerationParameters.cs
index c5151f3..fc2fee9 100644
--- a/src/Cnblogs.DashScope.Core/TextGenerationParameters.cs
+++ b/src/Cnblogs.DashScope.Core/TextGenerationParameters.cs
@@ -1,4 +1,4 @@
-namespace Cnblogs.DashScope.Core;
+namespace Cnblogs.DashScope.Core;
///
/// The text generation options.
@@ -38,12 +38,33 @@ public class TextGenerationParameters : ITextGenerationParameters
///
public bool? EnableSearch { get; set; }
+ ///
+ public TextGenerationSearchOptions? SearchOptions { get; set; }
+
+ ///
+ public bool? EnableThinking { get; set; }
+
+ ///
+ public int? ThinkingBudget { get; set; }
+
+ ///
+ public bool? Logprobs { get; set; }
+
+ ///
+ public int? TopLogprobs { get; set; }
+
///
public IEnumerable? Tools { get; set; }
///
public ToolChoice? ToolChoice { get; set; }
+ ///
+ public bool? ParallelToolCalls { get; set; }
+
+ ///
+ public TextGenerationTranslationOptions? TranslationOptions { get; set; }
+
///
public bool? IncrementalOutput { get; set; }
}
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationPromptTokenDetails.cs b/src/Cnblogs.DashScope.Core/TextGenerationPromptTokenDetails.cs
new file mode 100644
index 0000000..0b4cef6
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TextGenerationPromptTokenDetails.cs
@@ -0,0 +1,7 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Token usage details.
+///
+/// Token count of cached input.
+public record TextGenerationPromptTokenDetails(int CachedTokens);
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationSearchOptions.cs b/src/Cnblogs.DashScope.Core/TextGenerationSearchOptions.cs
new file mode 100644
index 0000000..d0d8cb4
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TextGenerationSearchOptions.cs
@@ -0,0 +1,32 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Web search options
+///
+public class TextGenerationSearchOptions
+{
+ ///
+ /// Show search result in response. Defaults to false.
+ ///
+ public bool? EnableSource { get; set; }
+
+ ///
+ /// Include citation in output. Defaults to false.
+ ///
+ public bool? EnableCitation { get; set; }
+
+ ///
+ /// Citation format. Defaults to "[<number>]"
+ ///
+ public string? CitationFormat { get; set; }
+
+ ///
+ /// Force model to use web search. Defaults to false.
+ ///
+ public bool? ForcedSearch { get; set; }
+
+ ///
+ /// How many search records should be provided to model. "standard" - 5 records. "pro" - 10 records.
+ ///
+ public string? SearchStrategy { get; set; }
+}
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationTokenDetails.cs b/src/Cnblogs.DashScope.Core/TextGenerationTokenDetails.cs
deleted file mode 100644
index d8e5b18..0000000
--- a/src/Cnblogs.DashScope.Core/TextGenerationTokenDetails.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Cnblogs.DashScope.Core;
-
-///
-/// Token usage details.
-///
-/// Token count of cached input tokens
-public record TextGenerationTokenDetails(int CachedTokens);
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationTokenUsage.cs b/src/Cnblogs.DashScope.Core/TextGenerationTokenUsage.cs
index 7662d7d..c908e55 100644
--- a/src/Cnblogs.DashScope.Core/TextGenerationTokenUsage.cs
+++ b/src/Cnblogs.DashScope.Core/TextGenerationTokenUsage.cs
@@ -14,7 +14,12 @@ public class TextGenerationTokenUsage
///
/// Input token details.
///
- public TextGenerationTokenDetails? PromptTokensDetails { get; set; }
+ public TextGenerationPromptTokenDetails? PromptTokensDetails { get; set; }
+
+ ///
+ /// Output token details.
+ ///
+ public TextGenerationOutputTokenDetails? OutputTokensDetails { get; set; }
///
/// The number of output token.
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationTopLogprobContent.cs b/src/Cnblogs.DashScope.Core/TextGenerationTopLogprobContent.cs
new file mode 100644
index 0000000..9f19187
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TextGenerationTopLogprobContent.cs
@@ -0,0 +1,16 @@
+using System.Text.Json.Serialization;
+using Cnblogs.DashScope.Core.Internals;
+
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Represents one choice of most possibility alternative tokens.
+///
+/// The token content.
+/// The token content in UTF-8 byte array.
+/// Possibility, null when possibility is too low.
+public record TextGenerationTopLogprobContent(
+ string Token,
+ [property: JsonConverter(typeof(ByteArrayLiteralConvertor))]
+ byte[] Bytes,
+ float? Logprob);
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationTranslationOptions.cs b/src/Cnblogs.DashScope.Core/TextGenerationTranslationOptions.cs
new file mode 100644
index 0000000..d77f7c8
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TextGenerationTranslationOptions.cs
@@ -0,0 +1,32 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Configurations when using translation models.
+///
+public class TextGenerationTranslationOptions
+{
+ ///
+ /// The language name of the input text. Use 'auto' to enable auto-detection.
+ ///
+ public string SourceLang { get; set; } = "auto";
+
+ ///
+ /// The language name of the output text.
+ ///
+ public string TargetLang { get; set; } = string.Empty;
+
+ ///
+ /// Term list for translation.
+ ///
+ public IEnumerable? Terms { get; set; }
+
+ ///
+ /// Sample texts for translation
+ ///
+ public IEnumerable? TmList { get; set; }
+
+ ///
+ /// Domain info about the source text. Only supports English.
+ ///
+ public string? Domains { get; set; }
+}
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationWebSearchInfo.cs b/src/Cnblogs.DashScope.Core/TextGenerationWebSearchInfo.cs
new file mode 100644
index 0000000..27da418
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TextGenerationWebSearchInfo.cs
@@ -0,0 +1,7 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Web search information.
+///
+/// Web search results.
+public record TextGenerationWebSearchInfo(List SearchResults);
diff --git a/src/Cnblogs.DashScope.Core/TextGenerationWebSearchResult.cs b/src/Cnblogs.DashScope.Core/TextGenerationWebSearchResult.cs
new file mode 100644
index 0000000..ccdf085
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TextGenerationWebSearchResult.cs
@@ -0,0 +1,11 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Represents one web search record.
+///
+/// Source site name.
+/// Source site favicon url.
+/// Serial number of search records.
+/// Page title.
+/// Page url.
+public record TextGenerationWebSearchResult(string SiteName, string Icon, int Index, string Title, string Url);
diff --git a/src/Cnblogs.DashScope.Core/TranslationReference.cs b/src/Cnblogs.DashScope.Core/TranslationReference.cs
new file mode 100644
index 0000000..256056a
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/TranslationReference.cs
@@ -0,0 +1,8 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// A text pair that used for translation reference.
+///
+/// The text in source language.
+/// The text in target language.
+public record TranslationReference(string Source, string Target);
diff --git a/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuan2Llm.cs b/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuan2Llm.cs
deleted file mode 100644
index 8ee2af3..0000000
--- a/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuan2Llm.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Cnblogs.DashScope.Sdk.BaiChuan;
-
-///
-/// BaiChuan2 model, supports prompt and message format.
-///
-public enum BaiChuan2Llm
-{
- ///
- /// baichuan2-7b-chat-v1
- ///
- BaiChuan2_7BChatV1 = 1,
-
- ///
- /// baichuan2-13b-chat-v1
- ///
- BaiChuan2_13BChatV1 = 2
-}
diff --git a/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuanLlm.cs b/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuanLlm.cs
deleted file mode 100644
index 53c5d8a..0000000
--- a/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuanLlm.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Cnblogs.DashScope.Sdk.BaiChuan;
-
-///
-/// Supported baichuan model: https://help.aliyun.com/zh/dashscope/developer-reference/api-details-2
-///
-public enum BaiChuanLlm
-{
- ///
- /// baichuan-7b-v1
- ///
- BaiChuan7B = 1
-}
diff --git a/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuanLlmName.cs b/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuanLlmName.cs
deleted file mode 100644
index b5ffff1..0000000
--- a/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuanLlmName.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace Cnblogs.DashScope.Sdk.BaiChuan;
-
-internal static class BaiChuanLlmName
-{
- public static string GetModelName(this BaiChuanLlm llm)
- {
- return llm switch
- {
- BaiChuanLlm.BaiChuan7B => "baichuan-7b-v1",
- _ => ThrowHelper.UnknownModelName(nameof(llm), llm)
- };
- }
-
- public static string GetModelName(this BaiChuan2Llm llm)
- {
- return llm switch
- {
- BaiChuan2Llm.BaiChuan2_7BChatV1 => "baichuan2-7b-chat-v1",
- BaiChuan2Llm.BaiChuan2_13BChatV1 => "baichuan2-13b-chat-v1",
- _ => ThrowHelper.UnknownModelName(nameof(llm), llm)
- };
- }
-}
diff --git a/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuanTextGenerationApi.cs b/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuanTextGenerationApi.cs
deleted file mode 100644
index ef3ec60..0000000
--- a/src/Cnblogs.DashScope.Sdk/BaiChuan/BaiChuanTextGenerationApi.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using Cnblogs.DashScope.Core;
-
-namespace Cnblogs.DashScope.Sdk.BaiChuan;
-
-///
-/// BaiChuan LLM generation apis, doc: https://help.aliyun.com/zh/dashscope/developer-reference/api-details-2
-///
-public static class BaiChuanTextGenerationApi
-{
- ///
- /// Get text completion from baichuan model.
- ///
- /// The .
- /// The llm to use.
- /// The prompt to generate completion from.
- ///
- public static Task> GetBaiChuanTextCompletionAsync(
- this IDashScopeClient client,
- BaiChuanLlm llm,
- string prompt)
- {
- return client.GetBaiChuanTextCompletionAsync(llm.GetModelName(), prompt);
- }
-
- ///
- /// Get text completion from baichuan model.
- ///
- /// The .
- /// The llm to use.
- /// The prompt to generate completion from.
- ///
- public static Task> GetBaiChuanTextCompletionAsync(
- this IDashScopeClient client,
- string llm,
- string prompt)
- {
- return client.GetTextCompletionAsync(
- new ModelRequest
- {
- Model = llm,
- Input = new TextGenerationInput { Prompt = prompt },
- Parameters = null
- });
- }
-
- ///
- /// Get text completion from baichuan model.
- ///
- /// The .
- /// The model name.
- /// The context messages.
- /// Can be 'text' or 'message', defaults to 'text'. Call to get available options.
- ///
- public static Task> GetBaiChuanTextCompletionAsync(
- this IDashScopeClient client,
- BaiChuan2Llm llm,
- IEnumerable messages,
- string? resultFormat = null)
- {
- return client.GetBaiChuanTextCompletionAsync(llm.GetModelName(), messages, resultFormat);
- }
-
- ///
- /// Get text completion from baichuan model.
- ///
- /// The .
- /// The model name.
- /// The context messages.
- /// Can be 'text' or 'message', defaults to 'text'. Call to get available options.
- ///
- public static Task> GetBaiChuanTextCompletionAsync(
- this IDashScopeClient client,
- string llm,
- IEnumerable messages,
- string? resultFormat = null)
- {
- return client.GetTextCompletionAsync(
- new ModelRequest
- {
- Model = llm,
- Input = new TextGenerationInput { Messages = messages },
- Parameters = string.IsNullOrEmpty(resultFormat) == false
- ? new TextGenerationParameters { ResultFormat = resultFormat }
- : null
- });
- }
-}
diff --git a/src/Cnblogs.DashScope.Sdk/Cnblogs.DashScope.Sdk.csproj b/src/Cnblogs.DashScope.Sdk/Cnblogs.DashScope.Sdk.csproj
index 3480ef8..68ba835 100644
--- a/src/Cnblogs.DashScope.Sdk/Cnblogs.DashScope.Sdk.csproj
+++ b/src/Cnblogs.DashScope.Sdk/Cnblogs.DashScope.Sdk.csproj
@@ -4,9 +4,6 @@
true
Cnblogs;Dashscope;AI;Sdk;Embedding;
-
-
-
diff --git a/src/Cnblogs.DashScope.Sdk/FunctionDefinition.cs b/src/Cnblogs.DashScope.Sdk/FunctionDefinition.cs
index 1344496..042f5c5 100644
--- a/src/Cnblogs.DashScope.Sdk/FunctionDefinition.cs
+++ b/src/Cnblogs.DashScope.Sdk/FunctionDefinition.cs
@@ -1,5 +1,4 @@
using Cnblogs.DashScope.Core;
-using Json.Schema;
namespace Cnblogs.DashScope.Sdk;
@@ -13,8 +12,8 @@ public record FunctionDefinition : IFunctionDefinition
///
/// The name of the function.
/// Descriptions about this function for model to reference on.
- /// Parameter maps of this function.
- public FunctionDefinition(string name, string description, JsonSchema? parameters)
+ /// Parameter maps of this function, can be dictionary or JsonSchema.
+ public FunctionDefinition(string name, string description, object? parameters)
{
Name = name;
Description = description;
diff --git a/src/Cnblogs.DashScope.Sdk/Llama2/Llama2Model.cs b/src/Cnblogs.DashScope.Sdk/Llama2/Llama2Model.cs
deleted file mode 100644
index 827fd14..0000000
--- a/src/Cnblogs.DashScope.Sdk/Llama2/Llama2Model.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Cnblogs.DashScope.Sdk.Llama2;
-
-///
-/// Supported models for LLaMa2.
-///
-public enum Llama2Model
-{
- ///
- /// llama2-7b-chat-v2
- ///
- Chat7Bv2 = 1,
-
- ///
- /// llama2-13b-chat-v2
- ///
- Chat13Bv2 = 2
-}
diff --git a/src/Cnblogs.DashScope.Sdk/Llama2/Llama2ModelNames.cs b/src/Cnblogs.DashScope.Sdk/Llama2/Llama2ModelNames.cs
deleted file mode 100644
index 44357d3..0000000
--- a/src/Cnblogs.DashScope.Sdk/Llama2/Llama2ModelNames.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Cnblogs.DashScope.Sdk.Llama2;
-
-internal static class Llama2ModelNames
-{
- public static string GetModelName(this Llama2Model model)
- {
- return model switch
- {
- Llama2Model.Chat7Bv2 => "llama2-7b-chat-v2",
- Llama2Model.Chat13Bv2 => "llama2-13b-chat-v2",
- _ => ThrowHelper.UnknownModelName(nameof(model), model)
- };
- }
-}
diff --git a/src/Cnblogs.DashScope.Sdk/Llama2/Llama2TextGenerationApi.cs b/src/Cnblogs.DashScope.Sdk/Llama2/Llama2TextGenerationApi.cs
deleted file mode 100644
index 5fa9b45..0000000
--- a/src/Cnblogs.DashScope.Sdk/Llama2/Llama2TextGenerationApi.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using Cnblogs.DashScope.Core;
-
-namespace Cnblogs.DashScope.Sdk.Llama2;
-
-///
-/// Extensions for llama2 text generation, docs: https://help.aliyun.com/zh/dashscope/developer-reference/api-details-11
-///
-public static class Llama2TextGenerationApi
-{
- ///
- /// Get text completion from llama2 model.
- ///
- /// The .
- /// The model name.
- /// The context messages.
- /// Can be 'text' or 'message'. Call to get available options.
- ///
- public static async Task>
- GetLlama2TextCompletionAsync(
- this IDashScopeClient client,
- Llama2Model model,
- IEnumerable messages,
- string? resultFormat = null)
- {
- return await client.GetLlama2TextCompletionAsync(model.GetModelName(), messages, resultFormat);
- }
-
- ///
- /// Get text completion from llama2 model.
- ///
- /// The .
- /// The model name.
- /// The context messages.
- /// Can be 'text' or 'message'. Call to get available options.
- ///
- public static async Task>
- GetLlama2TextCompletionAsync(
- this IDashScopeClient client,
- string model,
- IEnumerable messages,
- string? resultFormat = null)
- {
- return await client.GetTextCompletionAsync(
- new ModelRequest
- {
- Model = model,
- Input = new TextGenerationInput { Messages = messages },
- Parameters = resultFormat != null
- ? new TextGenerationParameters { ResultFormat = resultFormat }
- : null
- });
- }
-}
diff --git a/src/Cnblogs.DashScope.Sdk/QWenMultimodal/QWenMultimodalModel.cs b/src/Cnblogs.DashScope.Sdk/QWenMultimodal/QWenMultimodalModel.cs
index fa65480..abeeade 100644
--- a/src/Cnblogs.DashScope.Sdk/QWenMultimodal/QWenMultimodalModel.cs
+++ b/src/Cnblogs.DashScope.Sdk/QWenMultimodal/QWenMultimodalModel.cs
@@ -58,5 +58,25 @@ public enum QWenMultimodalModel
///
/// qwen-audio-turbo-latest
///
- QWenAudioTurboLatest = 11
+ QWenAudioTurboLatest = 11,
+
+ ///
+ /// qvq-max
+ ///
+ QvQMax = 12,
+
+ ///
+ /// qvq-max-latest
+ ///
+ QvQMaxLatest = 13,
+
+ ///
+ /// qvq-plus
+ ///
+ QvQPlus = 14,
+
+ ///
+ /// qvq-plus-latest
+ ///
+ QvQPlusLatest = 15
}
diff --git a/src/Cnblogs.DashScope.Sdk/QWenMultimodal/QWenMultimodalModelNames.cs b/src/Cnblogs.DashScope.Sdk/QWenMultimodal/QWenMultimodalModelNames.cs
index b5bd02a..e22aeda 100644
--- a/src/Cnblogs.DashScope.Sdk/QWenMultimodal/QWenMultimodalModelNames.cs
+++ b/src/Cnblogs.DashScope.Sdk/QWenMultimodal/QWenMultimodalModelNames.cs
@@ -17,6 +17,10 @@ public static string GetModelName(this QWenMultimodalModel multimodalModel)
QWenMultimodalModel.QWenVlPlusLatest => "qwen-vl-plus-latest",
QWenMultimodalModel.QWenVlOcrLatest => "qwen-vl-ocr-latest",
QWenMultimodalModel.QWenAudioTurboLatest => "qwen-audio-turbo-latest",
+ QWenMultimodalModel.QvQMax => "qvq-max",
+ QWenMultimodalModel.QvQMaxLatest => "qvq-max-latest",
+ QWenMultimodalModel.QvQPlus => "qvq-plus",
+ QWenMultimodalModel.QvQPlusLatest => "qvq-plus-latest",
_ => ThrowHelper.UnknownModelName(nameof(multimodalModel), multimodalModel)
};
}
diff --git a/src/Cnblogs.DashScope.Sdk/TextEmbedding/TextEmbeddingModel.cs b/src/Cnblogs.DashScope.Sdk/TextEmbedding/TextEmbeddingModel.cs
index 808db24..72af726 100644
--- a/src/Cnblogs.DashScope.Sdk/TextEmbedding/TextEmbeddingModel.cs
+++ b/src/Cnblogs.DashScope.Sdk/TextEmbedding/TextEmbeddingModel.cs
@@ -19,4 +19,9 @@ public enum TextEmbeddingModel
/// text-embedding-v3
///
TextEmbeddingV3 = 3,
+
+ ///
+ /// text-embedding-v4
+ ///
+ TextEmbeddingV4 = 4
}
diff --git a/src/Cnblogs.DashScope.Sdk/TextEmbedding/TextEmbeddingModelNames.cs b/src/Cnblogs.DashScope.Sdk/TextEmbedding/TextEmbeddingModelNames.cs
index 410c33e..21d6b7d 100644
--- a/src/Cnblogs.DashScope.Sdk/TextEmbedding/TextEmbeddingModelNames.cs
+++ b/src/Cnblogs.DashScope.Sdk/TextEmbedding/TextEmbeddingModelNames.cs
@@ -9,6 +9,7 @@ public static string GetModelName(this TextEmbeddingModel model)
TextEmbeddingModel.TextEmbeddingV1 => "text-embedding-v1",
TextEmbeddingModel.TextEmbeddingV2 => "text-embedding-v2",
TextEmbeddingModel.TextEmbeddingV3 => "text-embedding-v3",
+ TextEmbeddingModel.TextEmbeddingV4 => "text-embedding-v4",
_ => ThrowHelper.UnknownModelName(nameof(model), model),
};
}
diff --git a/src/Cnblogs.DashScope.Sdk/Wanx/WanxModel.cs b/src/Cnblogs.DashScope.Sdk/Wanx/WanxModel.cs
index f278eeb..3d19fdd 100644
--- a/src/Cnblogs.DashScope.Sdk/Wanx/WanxModel.cs
+++ b/src/Cnblogs.DashScope.Sdk/Wanx/WanxModel.cs
@@ -8,5 +8,20 @@ public enum WanxModel
///
/// wanx-v1
///
- WanxV1 = 1
+ WanxV1 = 1,
+
+ ///
+ /// wanx2.1-t2i-plus
+ ///
+ WanxV21Plus = 2,
+
+ ///
+ /// wanx2.1-t2i-turbo
+ ///
+ WanxV21Turbo = 3,
+
+ ///
+ /// wanx2.0-t2i-turbo
+ ///
+ WanxV20Turbo = 4
}
diff --git a/src/Cnblogs.DashScope.Sdk/Wanx/WanxModelNames.cs b/src/Cnblogs.DashScope.Sdk/Wanx/WanxModelNames.cs
index ab04555..8e04751 100644
--- a/src/Cnblogs.DashScope.Sdk/Wanx/WanxModelNames.cs
+++ b/src/Cnblogs.DashScope.Sdk/Wanx/WanxModelNames.cs
@@ -7,6 +7,9 @@ public static string GetModelName(this WanxModel model)
return model switch
{
WanxModel.WanxV1 => "wanx-v1",
+ WanxModel.WanxV21Plus => "wanx2.1-t2i-plus",
+ WanxModel.WanxV21Turbo => "wanx2.1-t2i-turbo",
+ WanxModel.WanxV20Turbo => "wanx2.0-t2i-turbo",
_ => ThrowHelper.UnknownModelName(nameof(model), model)
};
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ChatClientTests.cs b/test/Cnblogs.DashScope.AI.UnitTests/ChatClientTests.cs
similarity index 87%
rename from test/Cnblogs.DashScope.Sdk.UnitTests/ChatClientTests.cs
rename to test/Cnblogs.DashScope.AI.UnitTests/ChatClientTests.cs
index c1f5096..8bd80fa 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ChatClientTests.cs
+++ b/test/Cnblogs.DashScope.AI.UnitTests/ChatClientTests.cs
@@ -1,12 +1,12 @@
using System.Text;
using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using FluentAssertions;
+using Cnblogs.DashScope.Tests.Shared.Utils;
+
using Microsoft.Extensions.AI;
using NSubstitute;
using NSubstitute.Extensions;
-namespace Cnblogs.DashScope.Sdk.UnitTests;
+namespace Cnblogs.DashScope.AI.UnitTests;
public class ChatClientTests
{
@@ -28,7 +28,7 @@ public async Task ChatClient_TextCompletion_SuccessAsync()
// Act
var response = await client.GetResponseAsync(
content,
- new ChatOptions()
+ new ChatOptions
{
FrequencyPenalty = parameter?.RepetitionPenalty,
PresencePenalty = parameter?.PresencePenalty,
@@ -46,7 +46,7 @@ public async Task ChatClient_TextCompletion_SuccessAsync()
Arg.Is>(
m => m.IsEquivalent(testCase.RequestModel)),
Arg.Any());
- response.Messages[0].Text.Should().Be(testCase.ResponseModel.Output.Choices?.First().Message.Content);
+ Assert.Equal(testCase.ResponseModel.Output.Choices![0].Message.Content, response.Messages[0].Text);
}
[Fact]
@@ -69,7 +69,7 @@ public async Task ChatClient_TextCompletionStream_SuccessAsync()
// Act
var response = client.GetStreamingResponseAsync(
content,
- new ChatOptions()
+ new ChatOptions
{
FrequencyPenalty = parameter?.RepetitionPenalty,
PresencePenalty = parameter?.PresencePenalty,
@@ -79,7 +79,7 @@ public async Task ChatClient_TextCompletionStream_SuccessAsync()
Temperature = parameter?.Temperature,
TopK = parameter?.TopK,
TopP = parameter?.TopP,
- StopSequences = ["你好"],
+ StopSequences = new List { "你好" },
ToolMode = ChatToolMode.Auto
});
var text = new StringBuilder();
@@ -93,7 +93,7 @@ public async Task ChatClient_TextCompletionStream_SuccessAsync()
Arg.Is>(
m => m.IsEquivalent(testCase.RequestModel)),
Arg.Any());
- text.ToString().Should().Be(testCase.ResponseModel.Output.Choices?.First().Message.Content);
+ Assert.Equal(testCase.ResponseModel.Output.Choices![0].Message.Content, text.ToString());
}
[Fact]
@@ -113,7 +113,10 @@ public async Task ChatClient_ImageRecognition_SuccessAsync()
{
new(
ChatRole.User,
- [new DataContent(contents[0].Image!, "image/png"), new TextContent(contents[1].Text)])
+ new List
+ {
+ new DataContent(contents[0].Image!, "image/png"), new TextContent(contents[1].Text)
+ }),
};
var parameter = testCase.RequestModel.Parameters;
@@ -136,8 +139,7 @@ public async Task ChatClient_ImageRecognition_SuccessAsync()
await dashScopeClient.Received().GetMultimodalGenerationAsync(
Arg.Is>(m => m.IsEquivalent(testCase.RequestModel)),
Arg.Any());
- response.Messages[0].Text.Should()
- .BeEquivalentTo(testCase.ResponseModel.Output.Choices[0].Message.Content[0].Text);
+ Assert.Equal(testCase.ResponseModel.Output.Choices[0].Message.Content[0].Text, response.Messages[0].Text);
}
[Fact]
@@ -157,14 +159,17 @@ public async Task ChatClient_ImageRecognitionStream_SuccessAsync()
{
new(
ChatRole.User,
- [new DataContent(contents[0].Image!, "image/png"), new TextContent(contents[1].Text)])
+ new List
+ {
+ new DataContent(contents[0].Image!, "image/png"), new TextContent(contents[1].Text)
+ })
};
var parameter = testCase.RequestModel.Parameters;
// Act
var response = client.GetStreamingResponseAsync(
messages,
- new ChatOptions()
+ new ChatOptions
{
FrequencyPenalty = parameter?.RepetitionPenalty,
PresencePenalty = parameter?.PresencePenalty,
@@ -185,6 +190,6 @@ public async Task ChatClient_ImageRecognitionStream_SuccessAsync()
_ = dashScopeClient.Received().GetMultimodalGenerationStreamAsync(
Arg.Is>(m => m.IsEquivalent(testCase.RequestModel)),
Arg.Any());
- text.ToString().Should().Be(testCase.ResponseModel.Output.Choices.First().Message.Content[0].Text);
+ Assert.Equal(testCase.ResponseModel.Output.Choices.First().Message.Content[0].Text, text.ToString());
}
}
diff --git a/test/Cnblogs.DashScope.AI.UnitTests/Cnblogs.DashScope.AI.UnitTests.csproj b/test/Cnblogs.DashScope.AI.UnitTests/Cnblogs.DashScope.AI.UnitTests.csproj
new file mode 100644
index 0000000..293114c
--- /dev/null
+++ b/test/Cnblogs.DashScope.AI.UnitTests/Cnblogs.DashScope.AI.UnitTests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+ false
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/EmbeddingClientTests.cs b/test/Cnblogs.DashScope.AI.UnitTests/EmbeddingClientTests.cs
similarity index 76%
rename from test/Cnblogs.DashScope.Sdk.UnitTests/EmbeddingClientTests.cs
rename to test/Cnblogs.DashScope.AI.UnitTests/EmbeddingClientTests.cs
index 96e21b4..b8c17e6 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/EmbeddingClientTests.cs
+++ b/test/Cnblogs.DashScope.AI.UnitTests/EmbeddingClientTests.cs
@@ -1,11 +1,10 @@
using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using FluentAssertions;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using Microsoft.Extensions.AI;
using NSubstitute;
using NSubstitute.Extensions;
-namespace Cnblogs.DashScope.Sdk.UnitTests;
+namespace Cnblogs.DashScope.AI.UnitTests;
public class EmbeddingClientTests
{
@@ -27,17 +26,18 @@ public async Task EmbeddingClient_Text_SuccessAsync()
// Act
var response = await client.GenerateAsync(
content,
- new EmbeddingGenerationOptions()
+ new EmbeddingGenerationOptions
{
ModelId = testCase.RequestModel.Model, Dimensions = parameter?.Dimension
});
// Assert
_ = dashScopeClient.Received().GetEmbeddingsAsync(
- Arg.Is>(
- m => m.IsEquivalent(testCase.RequestModel)),
+ Arg.Is>(m
+ => m.IsEquivalent(testCase.RequestModel)),
Arg.Any());
- response.Select(x => x.Vector.ToArray()).Should()
- .BeEquivalentTo(testCase.ResponseModel.Output.Embeddings.Select(x => x.Embedding));
+ Assert.Equivalent(
+ testCase.ResponseModel.Output.Embeddings.Select(x => x.Embedding),
+ response.Select(x => x.Vector.ToArray()));
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Cnblogs.DashScope.Sdk.SnapshotGenerator.csproj b/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Cnblogs.DashScope.Sdk.SnapshotGenerator.csproj
deleted file mode 100644
index 8c791ae..0000000
--- a/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Cnblogs.DashScope.Sdk.SnapshotGenerator.csproj
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- Exe
- false
-
-
-
-
-
-
-
diff --git a/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Program.cs b/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Program.cs
deleted file mode 100644
index 3f99f6b..0000000
--- a/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Program.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using System.Net;
-using System.Text;
-
-const string basePath = "../../../../Cnblogs.DashScope.Sdk.UnitTests/RawHttpData";
-var snapshots = new DirectoryInfo(basePath);
-Console.WriteLine("Reading key from environment variable DASHSCOPE_KEY");
-var apiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY");
-if (string.IsNullOrEmpty(apiKey))
-{
- Console.Write("ApiKey > ");
- apiKey = Console.ReadLine();
-}
-
-var handler = new SocketsHttpHandler() { AutomaticDecompression = DecompressionMethods.All, };
-var client = new HttpClient(handler) { BaseAddress = new Uri("https://dashscope.aliyuncs.com/api/v1/") };
-client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
-
-while (true)
-{
- Console.Write("Snapshot Name > ");
- var snapshotName = Console.ReadLine()?.Trim();
- if (string.IsNullOrEmpty(snapshotName))
- {
- continue;
- }
-
- var snapshot = snapshots.EnumerateFiles().Where(s => s.Name.StartsWith(snapshotName))
- .Select(s => s.Name.Split('.').First()).Distinct()
- .ToList();
- if (snapshot.Count == 0)
- {
- Console.WriteLine($"No snapshot was found with name: {snapshotName}");
- }
-
- Console.WriteLine($"Updating {snapshot.Count} snapshots ...");
- foreach (var name in snapshot)
- {
- Console.WriteLine($"Updating {name}");
- await UpdateSnapshotsAsync(client, name);
- Console.WriteLine($"{name} updated");
- }
-}
-
-static async Task UpdateSnapshotsAsync(HttpClient client, string name)
-{
- var requestHeader = await File.ReadAllLinesAsync(Path.Combine(basePath, $"{name}.request.header.txt"));
- var requestBodyFile = Path.Combine(basePath, $"{name}.request.body.json");
- var requestBody = File.Exists(requestBodyFile)
- ? await File.ReadAllTextAsync(Path.Combine(basePath, $"{name}.request.body.json"))
- : string.Empty;
- var firstLine = requestHeader[0].Split(' ');
- var method = HttpMethod.Parse(firstLine[0]);
- var request = new HttpRequestMessage(method, firstLine[1]);
- var contentType = "application/json";
- foreach (var header in requestHeader.Skip(1))
- {
- if (string.IsNullOrWhiteSpace(header))
- {
- continue;
- }
-
- var values = header.Split(':', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
- if (values[0] == "Content-Type")
- {
- contentType = values[1];
- continue;
- }
-
- if (values[0] == "Content-Length")
- {
- continue;
- }
-
- request.Headers.Add(values[0], values[1]);
- }
-
- if (string.IsNullOrWhiteSpace(requestBodyFile) == false)
- {
- request.Content = new StringContent(requestBody, Encoding.Default, contentType);
- }
-
- var response = await client.SendAsync(request);
- var responseBody = await response.Content.ReadAsStringAsync();
- var responseHeaderFile = new StringBuilder();
- responseHeaderFile.AppendLine($"HTTP/1.1 {(int)response.StatusCode} {response.StatusCode}");
- responseHeaderFile = response.Headers.Aggregate(
- responseHeaderFile,
- (sb, pair) => sb.AppendLine($"{pair.Key}: {string.Join(',', pair.Value)}"));
- await File.WriteAllTextAsync(Path.Combine(basePath, $"{name}.response.header.txt"), responseHeaderFile.ToString());
- await File.WriteAllTextAsync(Path.Combine(basePath, $"{name}.response.body.txt"), responseBody);
-}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ApplicationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ApplicationSerializationTests.cs
index a8021a0..6552149 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ApplicationSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ApplicationSerializationTests.cs
@@ -1,5 +1,4 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using FluentAssertions;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -21,7 +20,7 @@ public async Task SingleCompletion_TextNoSse_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
[Fact]
@@ -39,7 +38,7 @@ public async Task SingleCompletion_ThoughtNoSse_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
[Fact]
@@ -58,11 +57,17 @@ public async Task SingleCompletion_TextSse_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- outputs.SkipLast(1).Should().AllSatisfy(x => x.Output.FinishReason.Should().Be("null"));
- outputs.Last().Should().BeEquivalentTo(
- testCase.ResponseModel,
- o => o.Excluding(y => y.Output.Text).Excluding(x => x.Output.Thoughts));
- text.Should().Be(testCase.ResponseModel.Output.Text);
+ Assert.All(outputs.SkipLast(1), x => Assert.Equal("null", x.Output.FinishReason));
+ Assert.Equal(testCase.ResponseModel.Output.Text, text);
+ var last = outputs.Last();
+ last = last with
+ {
+ Output = last.Output with
+ {
+ Text = testCase.ResponseModel.Output.Text, Thoughts = testCase.ResponseModel.Output.Thoughts
+ }
+ };
+ Assert.Equivalent(testCase.ResponseModel, last);
}
[Fact]
@@ -80,7 +85,7 @@ public async Task ConversationCompletion_SessionIdNoSse_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
[Fact]
@@ -98,7 +103,7 @@ public async Task ConversationCompletion_MessageNoSse_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
[Fact]
@@ -116,7 +121,7 @@ public async Task SingleCompletion_MemoryNoSse_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
[Fact]
@@ -134,6 +139,6 @@ public async Task SingleCompletion_WorkflowNoSse_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/BackgroundGenerationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/BackgroundGenerationSerializationTests.cs
index 6754aa2..14022b2 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/BackgroundGenerationSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/BackgroundGenerationSerializationTests.cs
@@ -1,5 +1,5 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using FluentAssertions;
+using Cnblogs.DashScope.Tests.Shared.Utils;
+
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -21,6 +21,6 @@ public async Task BackgroundGeneration_CreateTask_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/BaiChuanApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/BaiChuanApiTests.cs
deleted file mode 100644
index 1184b1a..0000000
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/BaiChuanApiTests.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.BaiChuan;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using NSubstitute;
-
-namespace Cnblogs.DashScope.Sdk.UnitTests;
-
-public class BaiChuanApiTests
-{
- [Fact]
- public async Task BaiChuanTextGeneration_UseEnum_SuccessAsync()
- {
- // Arrange
- var client = Substitute.For();
-
- // Act
- _ = await client.GetBaiChuanTextCompletionAsync(BaiChuanLlm.BaiChuan7B, Cases.Prompt);
-
- // Assert
- _ = await client.Received().GetTextCompletionAsync(
- Arg.Is>(
- s => s.Model == "baichuan-7b-v1" && s.Input.Prompt == Cases.Prompt && s.Parameters == null));
- }
-
- [Fact]
- public async Task BaiChuanTextGeneration_CustomModel_SuccessAsync()
- {
- // Arrange
- var client = Substitute.For();
-
- // Act
- _ = await client.GetBaiChuanTextCompletionAsync(BaiChuanLlm.BaiChuan7B, Cases.Prompt);
-
- // Assert
- _ = await client.Received().GetTextCompletionAsync(
- Arg.Is>(
- s => s.Model == "baichuan-7b-v1" && s.Input.Prompt == Cases.Prompt && s.Parameters == null));
- }
-
- [Fact]
- public async Task BaiChuan2TextGeneration_UseEnum_SuccessAsync()
- {
- // Arrange
- var client = Substitute.For();
-
- // Act
- _ = await client.GetBaiChuanTextCompletionAsync(
- BaiChuan2Llm.BaiChuan2_13BChatV1,
- Cases.TextMessages,
- ResultFormats.Message);
-
- // Assert
- _ = await client.Received().GetTextCompletionAsync(
- Arg.Is>(
- s => s.Model == "baichuan2-13b-chat-v1"
- && s.Input.Messages == Cases.TextMessages
- && s.Parameters != null
- && s.Parameters.ResultFormat == ResultFormats.Message));
- }
-
- [Fact]
- public async Task BaiChuan2TextGeneration_CustomModel_SuccessAsync()
- {
- // Arrange
- var client = Substitute.For();
-
- // Act
- _ = await client.GetBaiChuanTextCompletionAsync(
- Cases.CustomModelName,
- Cases.TextMessages,
- ResultFormats.Message);
-
- // Assert
- _ = await client.Received().GetTextCompletionAsync(
- Arg.Is>(
- s => s.Model == Cases.CustomModelName
- && s.Input.Messages == Cases.TextMessages
- && s.Parameters != null
- && s.Parameters.ResultFormat == ResultFormats.Message));
- }
-}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/Cnblogs.DashScope.Sdk.UnitTests.csproj b/test/Cnblogs.DashScope.Sdk.UnitTests/Cnblogs.DashScope.Sdk.UnitTests.csproj
index 3f82c79..2fd5793 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/Cnblogs.DashScope.Sdk.UnitTests.csproj
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/Cnblogs.DashScope.Sdk.UnitTests.csproj
@@ -1,39 +1,37 @@
-
- false
- true
-
+
+ net6.0
+ false
+ true
+
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
-
-
-
+
+
+
-
-
- Always
-
-
+
+
+ Always
+
+
-
-
-
-
+
+
+
+
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientTests.cs
index 93f7d4d..a2317bd 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientTests.cs
@@ -1,12 +1,19 @@
using System.Net.Http.Headers;
using System.Reflection;
using Cnblogs.DashScope.Core;
-using FluentAssertions;
+using Xunit.Abstractions;
namespace Cnblogs.DashScope.Sdk.UnitTests;
public class DashScopeClientTests
{
+ private readonly ITestOutputHelper _output;
+
+ public DashScopeClientTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
[Fact]
public void DashScopeClient_Constructor_New()
{
@@ -14,10 +21,8 @@ public void DashScopeClient_Constructor_New()
const string apiKey = "apiKey";
// Act
- var act = () => new DashScopeClient(apiKey);
-
- // Assert
- act.Should().NotThrow();
+ var client = new DashScopeClient(apiKey);
+ _output.WriteLine("hash: " + client.GetHashCode()); // do something to avoid optimization
}
[Theory]
@@ -37,7 +42,7 @@ public void DashScopeClient_Constructor_NotCacheableParams(
var value2 = HttpClientAccessor.GetValue(client2);
// Assert
- value.Should().NotBe(value2);
+ Assert.NotSame(value2, value);
}
[Theory]
@@ -57,7 +62,7 @@ public void DashScopeClient_Constructor_CacheableParams(
var value2 = HttpClientAccessor.GetValue(client2);
// Assert
- value.Should().Be(value2);
+ Assert.Same(value2, value);
}
[Fact]
@@ -71,8 +76,7 @@ public void DashScopeClient_Constructor_WithApiKeyHeader()
var value = HttpClientAccessor.GetValue(client) as HttpClient;
// Assert
- value?.DefaultRequestHeaders.Authorization?.Should()
- .BeEquivalentTo(new AuthenticationHeaderValue("Bearer", apiKey));
+ Assert.Equivalent(new AuthenticationHeaderValue("Bearer", apiKey), value?.DefaultRequestHeaders.Authorization);
}
[Fact]
@@ -81,13 +85,13 @@ public void DashScopeClient_Constructor_WithWorkspaceId()
// Arrange
const string apiKey = "key";
const string workspaceId = "workspaceId";
- var client = new DashScopeClient(apiKey, null, null, workspaceId);
+ var client = new DashScopeClient(apiKey, workspaceId: workspaceId);
// Act
var value = HttpClientAccessor.GetValue(client) as HttpClient;
// Assert
- value?.DefaultRequestHeaders.GetValues("X-DashScope-WorkSpace").Should().BeEquivalentTo(workspaceId);
+ Assert.Equal(workspaceId, value?.DefaultRequestHeaders.GetValues("X-DashScope-WorkSpace").First());
}
[Fact]
@@ -102,7 +106,7 @@ public void DashScopeClient_Constructor_WithPrivateEndpoint()
var value = HttpClientAccessor.GetValue(client) as HttpClient;
// Assert
- value?.BaseAddress.Should().BeEquivalentTo(new Uri(privateEndpoint));
+ Assert.Equivalent(new Uri(privateEndpoint), value?.BaseAddress);
}
public static TheoryData ParamsShouldNotCache
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketFactoryTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketFactoryTests.cs
new file mode 100644
index 0000000..4d13328
--- /dev/null
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketFactoryTests.cs
@@ -0,0 +1,41 @@
+using System.Net;
+using System.Net.WebSockets;
+using System.Reflection;
+using Cnblogs.DashScope.Core;
+using Cnblogs.DashScope.Core.Internals;
+
+namespace Cnblogs.DashScope.Sdk.UnitTests;
+
+public class DashScopeClientWebSocketFactoryTests
+{
+ private static readonly FieldInfo ClientWebSocketWrapperGetter =
+ typeof(DashScopeClientWebSocket).GetField("_socket", BindingFlags.NonPublic | BindingFlags.Instance)!;
+
+ private static readonly FieldInfo ClientWebSocketGetter =
+ typeof(ClientWebSocketWrapper).GetField("_socket", BindingFlags.NonPublic | BindingFlags.Instance)!;
+
+ private static readonly PropertyInfo RequestHeaderGetter = typeof(ClientWebSocketOptions).GetProperty(
+ "RequestHeaders",
+ BindingFlags.NonPublic | BindingFlags.Instance)!;
+
+ [Fact]
+ public void CreateSocket_WithWorkspaceId_SetKeyAndSpaceIdProperly()
+ {
+ // Arrange
+ const string apiKey = "apikey";
+ const string workspaceId = "some-space";
+ var factory = new DashScopeClientWebSocketFactory();
+
+ // Act
+ var socket = factory.GetClientWebSocket(apiKey, workspaceId);
+ var socketWrapper = ClientWebSocketWrapperGetter.GetValue(socket) as ClientWebSocketWrapper;
+ var clientWebSocket = ClientWebSocketGetter.GetValue(socketWrapper) as ClientWebSocket;
+ var headers = RequestHeaderGetter.GetValue(clientWebSocket?.Options) as WebHeaderCollection;
+
+ // Assert
+ Assert.NotNull(socketWrapper);
+ Assert.NotNull(headers);
+ Assert.Equal("bearer " + apiKey, headers.Get("Authorization"));
+ Assert.Equal(workspaceId, headers.Get("X-DashScope-WorkspaceId"));
+ }
+}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketPoolTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketPoolTests.cs
new file mode 100644
index 0000000..e484586
--- /dev/null
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketPoolTests.cs
@@ -0,0 +1,160 @@
+using Cnblogs.DashScope.Core;
+using Cnblogs.DashScope.Tests.Shared.Utils;
+using NSubstitute;
+using NSubstitute.Extensions;
+
+namespace Cnblogs.DashScope.Sdk.UnitTests;
+
+public class DashScopeClientWebSocketPoolTests
+{
+ [Fact]
+ public async Task RentSocket_PoolIsEmpty_CreateAsync()
+ {
+ // Arrange
+ var option = new DashScopeOptions();
+ var factory = Substitute.For();
+ factory.Configure().GetClientWebSocket(Arg.Any(), Arg.Any())
+ .Returns(new DashScopeClientWebSocket(new FakeClientWebSocket()));
+ var pool = new DashScopeClientWebSocketPool(factory, option);
+
+ // Act
+ var socket = await pool.RentSocketAsync();
+
+ // Assert
+ Assert.Equal(DashScopeWebSocketState.Ready, socket.Socket.State);
+ }
+
+ [Fact]
+ public async Task RentSocket_HasAvailableSocket_ReturnAsync()
+ {
+ // Arrange
+ var option = new DashScopeOptions();
+ var factory = Substitute.For();
+ factory.Configure().GetClientWebSocket(Arg.Any(), Arg.Any())
+ .Returns(new DashScopeClientWebSocket(new FakeClientWebSocket()));
+ var readySocket = factory.GetClientWebSocket(string.Empty);
+ await readySocket.ConnectAsync(new Uri(option.WebsocketBaseAddress));
+ var pool = new DashScopeClientWebSocketPool(new[] { readySocket }, factory);
+
+ // Act
+ var socket = await pool.RentSocketAsync();
+
+ // Assert
+ Assert.Equal(DashScopeWebSocketState.Ready, socket.Socket.State);
+ Assert.StrictEqual(readySocket, socket.Socket);
+ }
+
+ [Fact]
+ public async Task RentSocket_SocketExpired_DisposeAndMoveNext()
+ {
+ // Arrange
+ var factory = Substitute.For();
+ factory.Configure().GetClientWebSocket(Arg.Any(), Arg.Any())
+ .Returns(_ => new DashScopeClientWebSocket(new FakeClientWebSocket()));
+
+ var fakeSocket = new FakeClientWebSocket();
+ var closedSocket = new DashScopeClientWebSocket(fakeSocket);
+ await closedSocket.CloseAsync();
+ var pool = new DashScopeClientWebSocketPool(new[] { closedSocket }, factory);
+
+ // Act
+ var socket = await pool.RentSocketAsync();
+
+ // Assert
+ Assert.Equal(DashScopeWebSocketState.Ready, socket.Socket.State);
+ Assert.NotStrictEqual(closedSocket, socket.Socket);
+ Assert.True(fakeSocket.DisposeCalled);
+ }
+
+ [Fact]
+ public async Task RentSocket_PoolStarvation_ThrowAsync()
+ {
+ // Arrange
+ var option = new DashScopeOptions { SocketPoolSize = 3 };
+ var factory = Substitute.For();
+ factory.Configure().GetClientWebSocket(Arg.Any(), Arg.Any())
+ .Returns(_ => new DashScopeClientWebSocket(new FakeClientWebSocket()));
+ var pool = new DashScopeClientWebSocketPool(factory, option);
+ await Task.WhenAll(Enumerable.Range(0, option.SocketPoolSize).Select(async _ => await pool.RentSocketAsync()));
+
+ // Act
+ var act = async () => await pool.RentSocketAsync();
+
+ // Assert
+ await Assert.ThrowsAsync(act);
+ }
+
+ [Fact]
+ public async Task Dispose_ManuallyDispose_DisposeAllPooledSocketsAsync()
+ {
+ // Arrange
+ var option = new DashScopeOptions();
+ var factory = Substitute.For();
+ factory.Configure().GetClientWebSocket(Arg.Any(), Arg.Any())
+ .Returns(_ => new DashScopeClientWebSocket(new FakeClientWebSocket()));
+ var fake1 = new FakeClientWebSocket();
+ var fake2 = new FakeClientWebSocket();
+ var s1 = new DashScopeClientWebSocket(fake1);
+ var s2 = new DashScopeClientWebSocket(fake2);
+ var sockets = new[] { s1, s2 };
+ foreach (var socket in sockets)
+ {
+ await socket.ConnectAsync(new Uri(option.WebsocketBaseAddress));
+ }
+
+ var pool = new DashScopeClientWebSocketPool(sockets, factory);
+
+ // Act
+ var active = await pool.RentSocketAsync();
+ pool.Dispose();
+
+ // Assert
+ Assert.NotNull(active);
+ Assert.True(fake1.DisposeCalled);
+ Assert.True(fake2.DisposeCalled);
+ }
+
+ [Fact]
+ public async Task ReturnSocket_SocketNotReady_DisposeAsync()
+ {
+ // Arrange
+ var option = new DashScopeOptions();
+ var fakeSocket = new FakeClientWebSocket();
+ var factory = Substitute.For();
+ factory.Configure().GetClientWebSocket(Arg.Any(), Arg.Any())
+ .Returns(new DashScopeClientWebSocket(fakeSocket));
+ var pool = new DashScopeClientWebSocketPool(factory, option);
+
+ // Act
+ var socket = await pool.RentSocketAsync();
+ await fakeSocket.WriteServerCloseAsync();
+ pool.ReturnSocket(socket.Socket);
+
+ // Assert
+ Assert.Equal(0, pool.ActiveSocketCount);
+ Assert.Equal(0, pool.AvailableSocketCount);
+ Assert.Equal(DashScopeWebSocketState.Closed, socket.Socket.State);
+ Assert.True(fakeSocket.DisposeCalled);
+ }
+
+ [Fact]
+ public async Task ReturnSocket_SocketReady_SaveAsync()
+ {
+ // Arrange
+ var option = new DashScopeOptions();
+ var fakeSocket = new FakeClientWebSocket();
+ var factory = Substitute.For();
+ factory.Configure().GetClientWebSocket(Arg.Any(), Arg.Any())
+ .Returns(new DashScopeClientWebSocket(fakeSocket));
+ var pool = new DashScopeClientWebSocketPool(factory, option);
+
+ // Act
+ var socket = await pool.RentSocketAsync();
+ pool.ReturnSocket(socket.Socket);
+
+ // Assert
+ Assert.Equal(1, pool.AvailableSocketCount);
+ Assert.Equal(0, pool.ActiveSocketCount);
+ Assert.False(fakeSocket.DisposeCalled);
+ }
+}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketTests.cs
new file mode 100644
index 0000000..5f789c2
--- /dev/null
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketTests.cs
@@ -0,0 +1,277 @@
+using System.Net;
+using System.Net.WebSockets;
+using System.Reflection;
+using Cnblogs.DashScope.Core;
+using Cnblogs.DashScope.Core.Internals;
+using Cnblogs.DashScope.Tests.Shared.Utils;
+using NSubstitute;
+
+namespace Cnblogs.DashScope.Sdk.UnitTests;
+
+[Collection(nameof(SocketTestsCollection))]
+public class DashScopeClientWebSocketTests
+{
+ private static readonly FieldInfo InnerSocketInfo =
+ typeof(DashScopeClientWebSocket).GetField("_socket", BindingFlags.NonPublic | BindingFlags.Instance)
+ ?? throw new InvalidOperationException(
+ $"Can not found {nameof(DashScopeClientWebSocket)}._client, please update this test after refactoring");
+
+ private static readonly PropertyInfo InnerRequestHeaderInfo =
+ typeof(ClientWebSocketOptions).GetProperty("RequestHeaders", BindingFlags.NonPublic | BindingFlags.Instance)
+ ?? throw new InvalidOperationException(
+ $"Can not found {nameof(ClientWebSocketOptions)}.RequestHeaders property, please update this test after framework change");
+
+ [Fact]
+ public void Constructor_UseApiKeyAndWorkspaceId_EnsureConfigured()
+ {
+ // Arrange
+ const string apiKey = "apiKey";
+ const string workspaceId = "workspaceId";
+
+ // Act
+ var client = new DashScopeClientWebSocket(apiKey, workspaceId);
+ var headers = ExtractHeaders(client);
+
+ // Assert
+ Assert.Equal($"bearer {apiKey}", headers.GetValues("Authorization")?.First());
+ Assert.Equal("enable", headers.GetValues("X-DashScope-DataInspection")?.First());
+ Assert.Equal(workspaceId, headers.GetValues("X-DashScope-WorkspaceId")?.First());
+ }
+
+ [Fact]
+ public void Constructor_UseApiKeyWithoutWorkspaceId_EnsureConfigured()
+ {
+ // Arrange
+ const string apiKey = "apiKey";
+
+ // Act
+ var client = new DashScopeClientWebSocket(apiKey);
+ var headers = ExtractHeaders(client);
+
+ // Assert
+ Assert.Equal($"bearer {apiKey}", headers.GetValues("Authorization")?.First());
+ Assert.Equal("enable", headers.GetValues("X-DashScope-DataInspection")?.First());
+ Assert.Null(headers.GetValues("X-DashScope-WorkspaceId"));
+ }
+
+ [Fact]
+ public void Constructor_UsePreconfiguredSocket_EnsureConfigured()
+ {
+ // Arrange
+ using var socket = new ClientWebSocketWrapper(new ClientWebSocket());
+
+ // Act
+ var client = new DashScopeClientWebSocket(socket);
+
+ // Assert
+ Assert.StrictEqual(socket, InnerSocketInfo.GetValue(client));
+ }
+
+ [Fact]
+ public async Task ConnectAsync_InitialConnect_ChangeStateAsync()
+ {
+ // Arrange
+ var socket = Substitute.For();
+ var client = new DashScopeClientWebSocket(socket);
+ var apiUri = new Uri("ws://test.com");
+
+ // Act
+ await client.ConnectAsync(apiUri);
+
+ // Assert
+ Assert.Equal(DashScopeWebSocketState.Ready, client.State);
+ await socket.Received(1).ConnectAsync(Arg.Is(apiUri), Arg.Any());
+ await socket.Received().ReceiveAsync(Arg.Any>(), Arg.Any());
+ }
+
+ [Fact]
+ public async Task ResetOutput_WithInitialOutput_CompleteThenCreateNewOutputAsync()
+ {
+ // Arrange
+ var socket = Substitute.For();
+ var client = new DashScopeClientWebSocket(socket);
+ client.ResetOutput();
+ var oldBinary = client.BinaryOutput;
+ var oldJson = client.JsonOutput;
+ var oldSignal = client.TaskStarted;
+
+ // Act
+ client.ResetOutput();
+
+ // Assert
+ Assert.False(await oldSignal);
+ Assert.True(oldBinary.Completion.IsCompletedSuccessfully);
+ Assert.NotSame(oldBinary, client.BinaryOutput);
+ Assert.True(oldJson.Completion.IsCompletedSuccessfully);
+ Assert.NotSame(oldJson, client.JsonOutput);
+ Assert.NotSame(oldSignal, client.TaskStarted);
+ }
+
+ [Fact]
+ public async Task SendMessageAsync_SocketClosed_ThrowAsync()
+ {
+ // Arrange
+ var socket = Substitute.For();
+ var client = new DashScopeClientWebSocket(socket);
+ var snapshot = Snapshots.SpeechSynthesizer.RunTask;
+ await client.CloseAsync();
+
+ // Act
+ var act = () => client.SendMessageAsync(snapshot.Message);
+
+ // Assert
+ await Assert.ThrowsAsync(act);
+ }
+
+ [Fact]
+ public async Task SendMessageAsync_Connected_SendAsync()
+ {
+ // Arrange
+ var socket = Substitute.For();
+ var client = new DashScopeClientWebSocket(socket);
+ var snapshot = Snapshots.SpeechSynthesizer.RunTask;
+
+ // Act
+ await client.ConnectAsync(new Uri(DashScopeDefaults.WebsocketApiBaseAddress));
+ await client.SendMessageAsync(snapshot.Message);
+
+ // Assert
+ await socket.Received().SendAsync(
+ Arg.Is>(s => Checkers.IsJsonEquivalent(s, snapshot.GetMessageJson())),
+ WebSocketMessageType.Text,
+ true,
+ Arg.Any());
+ }
+
+ [Fact]
+ public async Task ReceiveMessageAsync_ServerClosed_CloseAsync()
+ {
+ // Arrange
+ var (_, dashScopeClientWebSocket, server) = await Sut.GetSocketTestClientAsync();
+
+ // Act
+ await server.WriteServerCloseAsync();
+
+ // Assert
+ Assert.Equal(DashScopeWebSocketState.Closed, dashScopeClientWebSocket.State);
+ Assert.Equal(WebSocketCloseStatus.NormalClosure, server.CloseStatus);
+ }
+
+ [Fact]
+ public async Task ReceiveMessageAsync_TaskStarted_UpdateStateToRunningAsync()
+ {
+ // Arrange
+ var (_, clientWebSocket, server) = await Sut.GetSocketTestClientAsync();
+ var snapshot = Snapshots.SpeechSynthesizer.TaskStarted;
+ var taskStarted = clientWebSocket.TaskStarted;
+
+ // Act
+ await server.WriteServerMessageAsync(snapshot.GetMessageJson());
+ var timeout = Task.Delay(2000); // socket handles message in other thread, wait for it.
+ var any = await Task.WhenAny(timeout, taskStarted);
+
+ // Assert
+ Assert.Equal(any, taskStarted);
+ Assert.Equal(DashScopeWebSocketState.RunningTask, clientWebSocket.State);
+ }
+
+ [Fact]
+ public async Task ReceiveMessageAsync_TaskFinished_UpdateStateToReadyAsync()
+ {
+ // Arrange
+ var (_, clientWebSocket, server) = await Sut.GetSocketTestClientAsync();
+ await server.WriteServerMessageAsync(Snapshots.SpeechSynthesizer.TaskStarted.GetMessageJson());
+ await clientWebSocket.TaskStarted;
+ var snapshot = Snapshots.SpeechSynthesizer.TaskFinished;
+ var binaryOutput = clientWebSocket.BinaryOutput;
+ var jsonOutput = clientWebSocket.JsonOutput;
+
+ // Act
+ await server.WriteServerMessageAsync(snapshot.GetMessageJson());
+ var json = await jsonOutput.ReadAllAsync().ToListAsync();
+
+ // Assert
+ Assert.True(binaryOutput.Completion.IsCompleted);
+ Assert.True(jsonOutput.Completion.IsCompleted);
+ Assert.Equal(2, json.Count);
+ Assert.Equal(DashScopeWebSocketState.Ready, clientWebSocket.State);
+ }
+
+ [Fact]
+ public async Task ReceiveMessageAsync_TaskFailed_CloseAndThrowAsync()
+ {
+ // Arrange
+ var (_, clientWebSocket, server) = await Sut.GetSocketTestClientAsync();
+ await server.WriteServerMessageAsync(Snapshots.SpeechSynthesizer.TaskStarted.GetMessageJson());
+ await clientWebSocket.TaskStarted;
+ var taskFailed = Snapshots.SpeechSynthesizer.TaskFailed;
+ var binary = clientWebSocket.BinaryOutput;
+ var json = clientWebSocket.JsonOutput;
+
+ // Act
+ await server.WriteServerMessageAsync(taskFailed.GetMessageJson());
+ await server.WriteServerCloseAsync();
+ var messages = await json.ReadAllAsync().ToListAsync();
+
+ // Assert
+ Assert.True(binary.Completion.IsCompleted);
+ Assert.True(json.Completion.IsCompleted);
+ Assert.Equal(2, messages.Count);
+ Assert.Equal(DashScopeWebSocketState.Closed, clientWebSocket.State);
+ }
+
+ [Fact]
+ public async Task ReceiveMessageAsync_ReceiveBinary_WriteToBinaryOutputAsync()
+ {
+ // Arrange
+ var (_, clientWebSocket, server) = await Sut.GetSocketTestClientAsync();
+ await server.WriteServerMessageAsync(Snapshots.SpeechSynthesizer.TaskStarted.GetMessageJson());
+ await clientWebSocket.TaskStarted;
+ var expectedAudio = Snapshots.SpeechSynthesizer.AudioTts;
+ var output = clientWebSocket.BinaryOutput;
+ var audioTask = output.ReadAllAsync().ToArrayAsync();
+
+ // Act
+ await server.WriteServerMessageAsync(expectedAudio);
+ await server.WriteServerMessageAsync(Snapshots.SpeechSynthesizer.TaskFinished.GetMessageJson());
+ var audio = await audioTask;
+
+ // Assert
+ Assert.True(output.Completion.IsCompleted);
+ Assert.Equal(expectedAudio, audio);
+ Assert.Equal(DashScopeWebSocketState.Ready, clientWebSocket.State);
+ }
+
+ [Fact]
+ public async Task Dispose_ManuallyCalled_DisposeSocketAndOutputTogetherAsync()
+ {
+ // Arrange
+ var (_, clientWebSocket, server) = await Sut.GetSocketTestClientAsync();
+ var output = clientWebSocket.BinaryOutput;
+
+ // Act
+ clientWebSocket.Dispose();
+
+ // Assert
+ Assert.True(output.Completion.IsCompleted);
+ Assert.True(server.DisposeCalled);
+ }
+
+ private static WebHeaderCollection ExtractHeaders(DashScopeClientWebSocket socket)
+ {
+ var obj = InnerSocketInfo.GetValue(socket);
+ if (obj is not IClientWebSocket clientWebSocket)
+ {
+ throw new InvalidOperationException($"Get null when trying to fetch {InnerSocketInfo.Name}");
+ }
+
+ obj = InnerRequestHeaderInfo.GetValue(clientWebSocket.Options);
+ if (obj is not WebHeaderCollection headers)
+ {
+ throw new InvalidOperationException(
+ $"Wrong type or null when trying to fetch {InnerRequestHeaderInfo.Name}");
+ }
+
+ return headers;
+ }
+}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketWrapperTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketWrapperTests.cs
new file mode 100644
index 0000000..5d1ce82
--- /dev/null
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientWebSocketWrapperTests.cs
@@ -0,0 +1,31 @@
+using Cnblogs.DashScope.Core;
+using Cnblogs.DashScope.Tests.Shared.Utils;
+using NSubstitute;
+using NSubstitute.Extensions;
+using Xunit.Abstractions;
+
+namespace Cnblogs.DashScope.Sdk.UnitTests;
+
+public class DashScopeClientWebSocketWrapperTests
+{
+ [Fact]
+ public async Task Dispose_CallDispose_ReturnSocketToPoolAsync()
+ {
+ // Arrange
+ var option = new DashScopeOptions();
+ var fakeSocket = new FakeClientWebSocket();
+ var factory = Substitute.For();
+ factory.Configure().GetClientWebSocket(Arg.Any(), Arg.Any())
+ .Returns(new DashScopeClientWebSocket(fakeSocket));
+ var pool = new DashScopeClientWebSocketPool(factory, option);
+
+ // Act
+ var socket = await pool.RentSocketAsync();
+ socket.Dispose();
+
+ // Assert
+ Assert.Equal(1, pool.AvailableSocketCount);
+ Assert.Equal(0, pool.ActiveSocketCount);
+ Assert.False(fakeSocket.DisposeCalled);
+ }
+}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/DeepSeekTextGenerationApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/DeepSeekTextGenerationApiTests.cs
index 5bc1956..87643dd 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/DeepSeekTextGenerationApiTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/DeepSeekTextGenerationApiTests.cs
@@ -13,12 +13,29 @@ public async Task TextCompletion_UseEnum_SuccessAsync()
var client = Substitute.For();
// Act
- await client.GetDeepSeekChatCompletionAsync(DeepSeekLlm.DeepSeekR1, [TextChatMessage.User("你好")]);
+ await client.GetDeepSeekChatCompletionAsync(
+ DeepSeekLlm.DeepSeekR1,
+ new List { TextChatMessage.User("你好") }.AsReadOnly());
// Assert
await client.Received().GetTextCompletionAsync(
- Arg.Is>(
- x => x.Model == "deepseek-r1" && x.Input.Messages!.First().Content == "你好" && x.Parameters == null));
+ Arg.Is>(x
+ => x.Model == "deepseek-r1" && x.Input.Messages!.First().Content == "你好" && x.Parameters == null));
+ }
+
+ [Fact]
+ public async Task TextCompletion_UseInvalidEnum_SuccessAsync()
+ {
+ // Arrange
+ var client = Substitute.For();
+
+ // Act
+ var act = async () => await client.GetDeepSeekChatCompletionAsync(
+ (DeepSeekLlm)(-1),
+ new List { TextChatMessage.User("你好") }.AsReadOnly());
+
+ // Assert
+ await Assert.ThrowsAsync(act);
}
[Fact]
@@ -29,12 +46,14 @@ public async Task TextCompletion_UseCustomModel_SuccessAsync()
var client = Substitute.For();
// Act
- await client.GetDeepSeekChatCompletionAsync(customModel, [TextChatMessage.User("你好")]);
+ await client.GetDeepSeekChatCompletionAsync(
+ customModel,
+ new List { TextChatMessage.User("你好") }.AsReadOnly());
// Assert
await client.Received().GetTextCompletionAsync(
- Arg.Is>(
- x => x.Model == customModel && x.Input.Messages!.First().Content == "你好" && x.Parameters == null));
+ Arg.Is>(x
+ => x.Model == customModel && x.Input.Messages!.First().Content == "你好" && x.Parameters == null));
}
[Fact]
@@ -44,14 +63,15 @@ public void StreamCompletion_UseEnum_SuccessAsync()
var client = Substitute.For();
// Act
- _ = client.GetDeepSeekChatCompletionStreamAsync(DeepSeekLlm.DeepSeekV3, [TextChatMessage.User("你好")]);
+ _ = client.GetDeepSeekChatCompletionStreamAsync(
+ DeepSeekLlm.DeepSeekV3,
+ new List { TextChatMessage.User("你好") }.AsReadOnly());
// Assert
_ = client.Received().GetTextCompletionStreamAsync(
- Arg.Is>(
- x => x.Model == "deepseek-v3"
- && x.Input.Messages!.First().Content == "你好"
- && x.Parameters!.IncrementalOutput == true));
+ Arg.Is>(x => x.Model == "deepseek-v3"
+ && x.Input.Messages!.First().Content == "你好"
+ && x.Parameters!.IncrementalOutput == true));
}
[Fact]
@@ -62,13 +82,14 @@ public void StreamCompletion_CustomModel_SuccessAsync()
var client = Substitute.For();
// Act
- _ = client.GetDeepSeekChatCompletionStreamAsync(customModel, [TextChatMessage.User("你好")]);
+ _ = client.GetDeepSeekChatCompletionStreamAsync(
+ customModel,
+ new List { TextChatMessage.User("你好") }.AsReadOnly());
// Assert
_ = client.Received().GetTextCompletionStreamAsync(
- Arg.Is>(
- x => x.Model == customModel
- && x.Input.Messages!.First().Content == "你好"
- && x.Parameters!.IncrementalOutput == true));
+ Arg.Is>(x => x.Model == customModel
+ && x.Input.Messages!.First().Content == "你好"
+ && x.Parameters!.IncrementalOutput == true));
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs
index e660852..3f3d9b5 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs
@@ -1,6 +1,5 @@
using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using FluentAssertions;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using NSubstitute.Extensions;
@@ -21,7 +20,8 @@ public async Task Error_AuthError_ExceptionAsync()
var act = async () => await client.GetTextCompletionAsync(testCase.RequestModel);
// Assert
- (await act.Should().ThrowAsync()).And.Error.Should().BeEquivalentTo(testCase.ResponseModel);
+ var ex = await Assert.ThrowsAsync(act);
+ Assert.Equivalent(testCase.ResponseModel, ex.Error);
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
@@ -39,7 +39,8 @@ public async Task Error_ParameterErrorNoSse_ExceptionAsync()
var act = async () => await client.GetTextCompletionAsync(testCase.RequestModel);
// Assert
- (await act.Should().ThrowAsync()).And.Error.Should().BeEquivalentTo(testCase.ResponseModel);
+ var ex = await Assert.ThrowsAsync(act);
+ Assert.Equivalent(testCase.ResponseModel, ex.Error);
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
@@ -58,7 +59,8 @@ public async Task Error_ParameterErrorSse_ExceptionAsync()
var act = async () => await stream.LastAsync();
// Assert
- (await act.Should().ThrowAsync()).And.Error.Should().BeEquivalentTo(testCase.ResponseModel);
+ var ex = await Assert.ThrowsAsync(act);
+ Assert.Equivalent(testCase.ResponseModel, ex.Error);
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
@@ -71,13 +73,15 @@ public async Task Error_NetworkError_ExceptionAsync()
var (client, handler) = Sut.GetTestClient();
handler.Configure().MockSend(Arg.Any(), Arg.Any())
.Throws(new InvalidOperationException("Network error!"));
+ var testCase = Snapshots.TextGeneration.TextFormat.SinglePrompt;
// Act
var act = async ()
- => await client.GetTextCompletionAsync(Snapshots.TextGeneration.TextFormat.SinglePrompt.RequestModel);
+ => await client.GetTextCompletionAsync(testCase.RequestModel);
// Assert
- (await act.Should().ThrowAsync()).And.Error.Should().BeNull();
+ var ex = await Assert.ThrowsAsync(act);
+ Assert.Null(ex.Error);
}
[Fact]
@@ -94,6 +98,7 @@ public async Task Error_OpenAiCompatibleError_ExceptionAsync()
"other");
// Assert
- (await act.Should().ThrowAsync()).And.Error.Should().BeEquivalentTo(testCase.ResponseModel);
+ var ex = await Assert.ThrowsAsync(act);
+ Assert.Equivalent(testCase.ResponseModel, ex.Error);
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs
index 05faa58..5a9dac0 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs
@@ -1,5 +1,5 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using FluentAssertions;
+using Cnblogs.DashScope.Tests.Shared.Utils;
+
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -21,7 +21,7 @@ public async Task File_Upload_SuccessAsync()
handler.Received().MockSend(
Arg.Is(r => r.RequestUri!.AbsolutePath == "/compatible-mode/v1/files"),
Arg.Any());
- task.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, task);
}
[Fact]
@@ -40,7 +40,7 @@ public async Task File_Get_SuccessAsync()
Arg.Is(
r => r.RequestUri!.AbsolutePath == "/compatible-mode/v1/files/" + testCase.ResponseModel.Id.Value),
Arg.Any());
- task.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, task);
}
[Fact]
@@ -55,7 +55,7 @@ public async Task File_List_SuccessAsync()
var list = await client.ListFilesAsync();
// Assert
- list.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, list);
}
[Fact]
@@ -74,6 +74,6 @@ public async Task File_Delete_SuccessAsync()
Arg.Is(
r => r.RequestUri!.AbsolutePath == "/compatible-mode/v1/files/" + testCase.ResponseModel.Id.Value),
Arg.Any());
- task.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, task);
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ImageGenerationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ImageGenerationSerializationTests.cs
index d0d30b7..ac1b04b 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ImageGenerationSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ImageGenerationSerializationTests.cs
@@ -1,5 +1,5 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using FluentAssertions;
+using Cnblogs.DashScope.Tests.Shared.Utils;
+
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -21,6 +21,6 @@ public async Task ImageGeneration_CreateTask_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ImageSynthesisSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ImageSynthesisSerializationTests.cs
index 909c6bf..f6348ca 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ImageSynthesisSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ImageSynthesisSerializationTests.cs
@@ -1,5 +1,4 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using FluentAssertions;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -21,6 +20,6 @@ public async Task ImageSynThesis_CreateTask_SuccessAsync()
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/Llama2TextGenerationApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/Llama2TextGenerationApiTests.cs
deleted file mode 100644
index 29c1a8c..0000000
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/Llama2TextGenerationApiTests.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.Llama2;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using NSubstitute;
-
-namespace Cnblogs.DashScope.Sdk.UnitTests;
-
-public class Llama2TextGenerationApiTests
-{
- [Fact]
- public async Task Llama2_UseEnum_SuccessAsync()
- {
- // Arrange
- var client = Substitute.For();
-
- // Act
- _ = await client.GetLlama2TextCompletionAsync(Llama2Model.Chat13Bv2, Cases.TextMessages, ResultFormats.Message);
-
- // Assert
- _ = await client.Received().GetTextCompletionAsync(
- Arg.Is>(
- s => s.Input.Messages == Cases.TextMessages
- && s.Model == "llama2-13b-chat-v2"
- && s.Parameters != null
- && s.Parameters.ResultFormat == ResultFormats.Message));
- }
-
- [Fact]
- public async Task Llama2_CustomModel_SuccessAsync()
- {
- // Arrange
- var client = Substitute.For();
-
- // Act
- _ = await client.GetLlama2TextCompletionAsync(Cases.CustomModelName, Cases.TextMessages, ResultFormats.Message);
-
- // Assert
- _ = await client.Received().GetTextCompletionAsync(
- Arg.Is>(
- s => s.Input.Messages == Cases.TextMessages
- && s.Model == Cases.CustomModelName
- && s.Parameters != null
- && s.Parameters.ResultFormat == ResultFormats.Message));
- }
-}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/MultimodalGenerationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/MultimodalGenerationSerializationTests.cs
index 8889981..5825b14 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/MultimodalGenerationSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/MultimodalGenerationSerializationTests.cs
@@ -1,7 +1,6 @@
using System.Text;
using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
-using FluentAssertions;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -25,7 +24,7 @@ public async Task MultimodalGeneration_NoSse_SuccessAsync(
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- response.Should().BeEquivalentTo(testCase.ResponseModel);
+ Assert.Equivalent(testCase.ResponseModel, response);
}
[Theory]
@@ -41,18 +40,31 @@ public async Task MultimodalGeneration_Sse_SuccessAsync(
// Act
var message = new StringBuilder();
var outputs = await client.GetMultimodalGenerationStreamAsync(testCase.RequestModel).ToListAsync();
- outputs.ForEach(
- x => message.Append(x.Output.Choices[0].Message.Content.FirstOrDefault()?.Text ?? string.Empty));
+ outputs.ForEach(x
+ => message.Append(x.Output.Choices[0].Message.Content.FirstOrDefault()?.Text ?? string.Empty));
// Assert
handler.Received().MockSend(
Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
Arg.Any());
- outputs.SkipLast(1).Should().AllSatisfy(x => x.Output.Choices[0].FinishReason.Should().Be("null"));
- outputs.Last().Should().BeEquivalentTo(
- testCase.ResponseModel,
- o => o.Excluding(y => y.Output.Choices[0].Message.Content));
- message.ToString().Should().Be(testCase.ResponseModel.Output.Choices[0].Message.Content[0].Text);
+ Assert.All(outputs.SkipLast(1), x => Assert.Equal("null", x.Output.Choices[0].FinishReason));
+ Assert.Equal(testCase.ResponseModel.Output.Choices[0].Message.Content[0].Text, message.ToString());
+ var last = outputs.Last();
+ last = last with
+ {
+ Output = new MultimodalOutput(
+ new List
+ {
+ last.Output.Choices[0] with
+ {
+ Message = last.Output.Choices[0].Message with
+ {
+ Content = testCase.ResponseModel.Output.Choices[0].Message.Content
+ }
+ }
+ })
+ };
+ Assert.Equivalent(last, testCase.ResponseModel);
}
public static TheoryData,
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenMultimodalApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenMultimodalApiTests.cs
index e3b5424..8e7a553 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenMultimodalApiTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenMultimodalApiTests.cs
@@ -1,6 +1,6 @@
using Cnblogs.DashScope.Core;
using Cnblogs.DashScope.Sdk.QWenMultimodal;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -8,13 +8,15 @@ namespace Cnblogs.DashScope.Sdk.UnitTests;
public class QWenMultimodalApiTests
{
private static readonly List Messages =
- [
- MultimodalMessage.User(
- [
- MultimodalMessageContent.ImageContent("https://cdn.example.com/image.jpg"),
- MultimodalMessageContent.TextContent("说明一下这张图片的内容")
- ])
- ];
+ new()
+ {
+ MultimodalMessage.User(
+ new List
+ {
+ MultimodalMessageContent.ImageContent("https://cdn.example.com/image.jpg"),
+ MultimodalMessageContent.TextContent("说明一下这张图片的内容")
+ }.AsReadOnly())
+ };
[Fact]
public async Task Multimodal_UseEnum_SuccessAsync()
@@ -28,8 +30,23 @@ public async Task Multimodal_UseEnum_SuccessAsync()
// Assert
_ = client.Received().GetMultimodalGenerationAsync(
- Arg.Is>(
- s => s.Model == "qwen-vl-max" && s.Input.Messages == Messages && s.Parameters == parameters));
+ Arg.Is>(s
+ => s.Model == "qwen-vl-max" && s.Input.Messages == Messages && s.Parameters == parameters));
+ }
+
+ [Fact]
+ public async Task Multimodal_UseInvalidEnum_SuccessAsync()
+ {
+ // Arrange
+ var client = Substitute.For();
+ var parameters = new MultimodalParameters { Seed = 6666 };
+
+ // Act
+ var act = async ()
+ => await client.GetQWenMultimodalCompletionAsync((QWenMultimodalModel)(-1), Messages, parameters);
+
+ // Assert
+ await Assert.ThrowsAsync(act);
}
[Fact]
@@ -44,8 +61,8 @@ public async Task Multimodal_CustomModel_SuccessAsync()
// Assert
_ = client.Received().GetMultimodalGenerationAsync(
- Arg.Is>(
- s => s.Model == Cases.CustomModelName && s.Input.Messages == Messages && s.Parameters == parameters));
+ Arg.Is>(s
+ => s.Model == Cases.CustomModelName && s.Input.Messages == Messages && s.Parameters == parameters));
}
[Fact]
@@ -60,8 +77,8 @@ public void MultimodalStream_UseEnum_Success()
// Assert
_ = client.Received().GetMultimodalGenerationStreamAsync(
- Arg.Is>(
- s => s.Model == "qwen-vl-plus" && s.Input.Messages == Messages && s.Parameters == parameters));
+ Arg.Is>(s
+ => s.Model == "qwen-vl-plus" && s.Input.Messages == Messages && s.Parameters == parameters));
}
[Fact]
@@ -76,7 +93,7 @@ public void Multimodal_CustomModel_Success()
// Assert
_ = client.Received().GetMultimodalGenerationStreamAsync(
- Arg.Is>(
- s => s.Model == Cases.CustomModelName && s.Input.Messages == Messages && s.Parameters == parameters));
+ Arg.Is>(s
+ => s.Model == Cases.CustomModelName && s.Input.Messages == Messages && s.Parameters == parameters));
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTextGenerationApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTextGenerationApiTests.cs
index ad9d5fb..5fb1f6f 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTextGenerationApiTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTextGenerationApiTests.cs
@@ -1,6 +1,6 @@
using Cnblogs.DashScope.Core;
using Cnblogs.DashScope.Sdk.QWen;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -102,6 +102,20 @@ await client.Received().GetTextCompletionAsync(
s => s.Input.Messages == Cases.TextMessages && s.Parameters == parameters && s.Model == "qwen-max-1201"));
}
+ [Fact]
+ public async Task QWenChatCompletion_UseInvalidEnum_SuccessAsync()
+ {
+ // Arrange
+ var client = Substitute.For();
+ var parameters = new TextGenerationParameters { EnableSearch = true, ResultFormat = ResultFormats.Message };
+
+ // Act
+ var act = async () => await client.GetQWenChatCompletionAsync((QWenLlm)(-1), Cases.TextMessages, parameters);
+
+ // Assert
+ await Assert.ThrowsAsync(act);
+ }
+
[Fact]
public async Task QWenChatCompletion_CustomModel_SuccessAsync()
{
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTokenizerTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTokenizerTests.cs
index 8f91a35..ae7615d 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTokenizerTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTokenizerTests.cs
@@ -1,5 +1,4 @@
using Cnblogs.DashScope.Core;
-using FluentAssertions;
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -9,475 +8,42 @@ public class QWenTokenizerTests
"很不错的一款产品,送人的额,非常喜欢,很不错,好评哦,以后还会多多支持的\n已经吃了,感觉不错。第二次购买,这个价格很实惠,卖家还赠送了美容器和尺子,很愉悦的一次购物。\n衣服的质量杠杠的,很漂亮,不枉我等了这么久啊,喜欢,以后还来这里买,我一次买了四件不到200块钱,真好\n用了一段时间了,感觉比传统的风扇好用,广度大,档数多,静音效果也不错,关键是还很漂亮!\n这个热量低,比意面还低,关键是口感还好,挺好吃,多煮一会一级棒,我的减脂早餐就靠它了\n整体评价:好 使用效果:好 包装与外观:好 三好商品\n电视机很好,我给老爸买的,老人很喜欢,图像清晰,音质很好,老人喜欢我满意。\n宝贝很轻巧,打的时候网弹力很足,儿子非常喜欢,五分好评,还送了羽毛球噢!\n终于装上,风很大,热的还可以,排风声音不小,安装的师傅说不能直吹,安装的时候非常满意,最后还帮忙把棚顶给擦干净了,非常感谢。\n颜色很美。就是别人的盒子都是平整的,唯独我的盒子有很严重的压痕。本来想直接用这个盒子装胶带的..现在这个样子强迫症看着实在很糟心。压成这样要么包装有问题,要么本来产品就有问题。而卖家一直在甩锅快递。由于包裹是被家里人拆开的,所以我也不知道是什么样子的,不好跟卖家掰扯。但我看别的人都是纸盒装的,想来我这个也是纸盒装的。那么排除包装的原因的话,很可能就是寄过来的就是个瑕疵品。比起同样今天到货的另一家,有个小瑕疵立马给我补寄了一个,这家的处理方式真的..换货..或者直接道歉说疏忽我都接受的。甩锅真的不能忍。本来还给好几个人推了这家店...就挺失望的 以后应该不会来了。这坚定的甩锅态度我也不点退换了..请不要给我打电话改评,谢谢。";
private static readonly int[] Tokens =
- [
- 109517,
- 110659,
- 82700,
- 3837,
- 36605,
- 103947,
- 61191,
- 3837,
- 115171,
- 3837,
- 109517,
- 3837,
- 102959,
- 104170,
- 3837,
- 103934,
- 104342,
- 103235,
- 100143,
- 9370,
- 198,
- 99461,
- 105705,
- 3837,
- 100681,
- 100832,
- 1773,
- 106309,
- 103946,
- 3837,
- 99487,
- 97480,
- 99165,
- 109896,
- 3837,
- 108602,
- 97706,
- 109349,
- 34187,
- 105157,
- 31548,
- 33108,
- 101489,
- 44729,
- 3837,
- 99165,
- 111985,
- 99774,
- 32571,
- 102297,
- 8997,
- 102214,
- 108042,
- 103178,
- 103178,
- 9370,
- 3837,
- 117817,
- 3837,
- 16530,
- 119117,
- 35946,
- 49567,
- 34187,
- 113260,
- 103924,
- 3837,
- 99729,
- 3837,
- 103934,
- 97706,
- 115217,
- 99565,
- 3837,
- 35946,
- 99796,
- 105275,
- 63703,
- 14224,
- 99828,
- 17,
- 15,
- 15,
- 106734,
- 3837,
- 88051,
- 52801,
- 198,
- 11622,
- 99593,
- 101430,
- 34187,
- 3837,
- 100681,
- 56006,
- 105062,
- 117814,
- 52801,
- 11622,
- 3837,
- 80942,
- 26381,
- 26288,
- 3837,
- 100104,
- 8863,
- 42140,
- 3837,
- 99541,
- 78685,
- 101062,
- 116006,
- 3837,
- 111623,
- 97706,
- 117817,
- 6313,
- 198,
- 99487,
- 108461,
- 99285,
- 3837,
- 56006,
- 36589,
- 27091,
- 97706,
- 99285,
- 3837,
- 111623,
- 107816,
- 108432,
- 3837,
- 101174,
- 106678,
- 3837,
- 42140,
- 104783,
- 102947,
- 105072,
- 102321,
- 3837,
- 97611,
- 99536,
- 100553,
- 102589,
- 80158,
- 99904,
- 99652,
- 34187,
- 198,
- 101932,
- 103964,
- 5122,
- 52801,
- 85658,
- 101062,
- 5122,
- 52801,
- 94305,
- 227,
- 98641,
- 57218,
- 105502,
- 5122,
- 52801,
- 220,
- 44991,
- 52801,
- 45943,
- 198,
- 115281,
- 101243,
- 3837,
- 35946,
- 89012,
- 114198,
- 108850,
- 3837,
- 102032,
- 108295,
- 3837,
- 107553,
- 104542,
- 3837,
- 78685,
- 99178,
- 101243,
- 3837,
- 102032,
- 99729,
- 35946,
- 100545,
- 8997,
- 105882,
- 99165,
- 99578,
- 100084,
- 3837,
- 75437,
- 103920,
- 31139,
- 100080,
- 47534,
- 99165,
- 99336,
- 3837,
- 102067,
- 115171,
- 3837,
- 75108,
- 17177,
- 102959,
- 3837,
- 97706,
- 36605,
- 34187,
- 116140,
- 118401,
- 6313,
- 198,
- 104020,
- 98641,
- 17447,
- 3837,
- 99208,
- 101235,
- 3837,
- 99259,
- 9370,
- 104468,
- 3837,
- 59956,
- 99208,
- 102274,
- 112592,
- 3837,
- 103999,
- 9370,
- 105476,
- 36587,
- 53153,
- 73145,
- 102203,
- 3837,
- 103999,
- 103920,
- 99491,
- 100545,
- 3837,
- 100161,
- 97706,
- 106128,
- 99360,
- 103031,
- 99743,
- 89012,
- 101432,
- 102466,
- 34187,
- 3837,
- 99491,
- 104305,
- 8997,
- 102284,
- 99165,
- 57566,
- 1773,
- 99486,
- 107693,
- 110792,
- 100132,
- 112857,
- 9370,
- 3837,
- 100473,
- 99510,
- 97611,
- 110792,
- 18830,
- 99165,
- 105806,
- 99451,
- 101629,
- 1773,
- 102347,
- 99172,
- 101041,
- 11622,
- 99487,
- 110792,
- 98641,
- 100773,
- 99278,
- 9370,
- 496,
- 99601,
- 99487,
- 102481,
- 111057,
- 99769,
- 101952,
- 100538,
- 99165,
- 103336,
- 63109,
- 1773,
- 99451,
- 12857,
- 99654,
- 106016,
- 104184,
- 110686,
- 3837,
- 106016,
- 102347,
- 82700,
- 104435,
- 86119,
- 1773,
- 68536,
- 108602,
- 105078,
- 107001,
- 101240,
- 104655,
- 1773,
- 101887,
- 108232,
- 20412,
- 99250,
- 102078,
- 17340,
- 101348,
- 29767,
- 9370,
- 3837,
- 107020,
- 107066,
- 102021,
- 102481,
- 9370,
- 3837,
- 101132,
- 99557,
- 108602,
- 119059,
- 105948,
- 1773,
- 105984,
- 50930,
- 62922,
- 100623,
- 100132,
- 100050,
- 102307,
- 98641,
- 9370,
- 3837,
- 99172,
- 36407,
- 35946,
- 99487,
- 100000,
- 100050,
- 102307,
- 98641,
- 9370,
- 1773,
- 100624,
- 102945,
- 104184,
- 104249,
- 100363,
- 3837,
- 107093,
- 99486,
- 101400,
- 101180,
- 105729,
- 18947,
- 117219,
- 24442,
- 1773,
- 107957,
- 101165,
- 100644,
- 26939,
- 81668,
- 9370,
- 100266,
- 45629,
- 3837,
- 104627,
- 30709,
- 117219,
- 108628,
- 104169,
- 99622,
- 101400,
- 104059,
- 3837,
- 101610,
- 9370,
- 54542,
- 75768,
- 100672,
- 496,
- 71134,
- 81668,
- 496,
- 100631,
- 101041,
- 107961,
- 36587,
- 100900,
- 100310,
- 107108,
- 100669,
- 9370,
- 1773,
- 107001,
- 101240,
- 100672,
- 53153,
- 100292,
- 1773,
- 102347,
- 97706,
- 89012,
- 52801,
- 112285,
- 83751,
- 34187,
- 101610,
- 71416,
- 1112,
- 80158,
- 101174,
- 106586,
- 9370,
- 220,
- 103934,
- 99730,
- 99670,
- 101161,
- 1773,
- 43288,
- 102405,
- 9370,
- 107001,
- 101240,
- 102316,
- 35946,
- 99744,
- 27442,
- 55806,
- 110735,
- 496,
- 14880,
- 100148,
- 104169,
- 106202,
- 22418,
- 63379,
- 3837,
- 102570,
- 1773
- ];
+ new[]
+ {
+ 109517, 110659, 82700, 3837, 36605, 103947, 61191, 3837, 115171, 3837, 109517, 3837, 102959, 104170,
+ 3837, 103934, 104342, 103235, 100143, 9370, 198, 99461, 105705, 3837, 100681, 100832, 1773, 106309,
+ 103946, 3837, 99487, 97480, 99165, 109896, 3837, 108602, 97706, 109349, 34187, 105157, 31548, 33108,
+ 101489, 44729, 3837, 99165, 111985, 99774, 32571, 102297, 8997, 102214, 108042, 103178, 103178, 9370,
+ 3837, 117817, 3837, 16530, 119117, 35946, 49567, 34187, 113260, 103924, 3837, 99729, 3837, 103934,
+ 97706, 115217, 99565, 3837, 35946, 99796, 105275, 63703, 14224, 99828, 17, 15, 15, 106734, 3837, 88051,
+ 52801, 198, 11622, 99593, 101430, 34187, 3837, 100681, 56006, 105062, 117814, 52801, 11622, 3837, 80942,
+ 26381, 26288, 3837, 100104, 8863, 42140, 3837, 99541, 78685, 101062, 116006, 3837, 111623, 97706,
+ 117817, 6313, 198, 99487, 108461, 99285, 3837, 56006, 36589, 27091, 97706, 99285, 3837, 111623, 107816,
+ 108432, 3837, 101174, 106678, 3837, 42140, 104783, 102947, 105072, 102321, 3837, 97611, 99536, 100553,
+ 102589, 80158, 99904, 99652, 34187, 198, 101932, 103964, 5122, 52801, 85658, 101062, 5122, 52801, 94305,
+ 227, 98641, 57218, 105502, 5122, 52801, 220, 44991, 52801, 45943, 198, 115281, 101243, 3837, 35946,
+ 89012, 114198, 108850, 3837, 102032, 108295, 3837, 107553, 104542, 3837, 78685, 99178, 101243, 3837,
+ 102032, 99729, 35946, 100545, 8997, 105882, 99165, 99578, 100084, 3837, 75437, 103920, 31139, 100080,
+ 47534, 99165, 99336, 3837, 102067, 115171, 3837, 75108, 17177, 102959, 3837, 97706, 36605, 34187,
+ 116140, 118401, 6313, 198, 104020, 98641, 17447, 3837, 99208, 101235, 3837, 99259, 9370, 104468, 3837,
+ 59956, 99208, 102274, 112592, 3837, 103999, 9370, 105476, 36587, 53153, 73145, 102203, 3837, 103999,
+ 103920, 99491, 100545, 3837, 100161, 97706, 106128, 99360, 103031, 99743, 89012, 101432, 102466, 34187,
+ 3837, 99491, 104305, 8997, 102284, 99165, 57566, 1773, 99486, 107693, 110792, 100132, 112857, 9370,
+ 3837, 100473, 99510, 97611, 110792, 18830, 99165, 105806, 99451, 101629, 1773, 102347, 99172, 101041,
+ 11622, 99487, 110792, 98641, 100773, 99278, 9370, 496, 99601, 99487, 102481, 111057, 99769, 101952,
+ 100538, 99165, 103336, 63109, 1773, 99451, 12857, 99654, 106016, 104184, 110686, 3837, 106016, 102347,
+ 82700, 104435, 86119, 1773, 68536, 108602, 105078, 107001, 101240, 104655, 1773, 101887, 108232, 20412,
+ 99250, 102078, 17340, 101348, 29767, 9370, 3837, 107020, 107066, 102021, 102481, 9370, 3837, 101132,
+ 99557, 108602, 119059, 105948, 1773, 105984, 50930, 62922, 100623, 100132, 100050, 102307, 98641, 9370,
+ 3837, 99172, 36407, 35946, 99487, 100000, 100050, 102307, 98641, 9370, 1773, 100624, 102945, 104184,
+ 104249, 100363, 3837, 107093, 99486, 101400, 101180, 105729, 18947, 117219, 24442, 1773, 107957, 101165,
+ 100644, 26939, 81668, 9370, 100266, 45629, 3837, 104627, 30709, 117219, 108628, 104169, 99622, 101400,
+ 104059, 3837, 101610, 9370, 54542, 75768, 100672, 496, 71134, 81668, 496, 100631, 101041, 107961, 36587,
+ 100900, 100310, 107108, 100669, 9370, 1773, 107001, 101240, 100672, 53153, 100292, 1773, 102347, 97706,
+ 89012, 52801, 112285, 83751, 34187, 101610, 71416, 1112, 80158, 101174, 106586, 9370, 220, 103934,
+ 99730, 99670, 101161, 1773, 43288, 102405, 9370, 107001, 101240, 102316, 35946, 99744, 27442, 55806,
+ 110735, 496, 14880, 100148, 104169, 106202, 22418, 63379, 3837, 102570, 1773
+ };
[Fact]
public void QWenTokenizer_Encode_SuccessAsync()
@@ -486,7 +52,7 @@ public void QWenTokenizer_Encode_SuccessAsync()
var embeddings = QWenTokenizer.Encode(Text);
// Assert
- embeddings.Should().BeEquivalentTo(Tokens);
+ Assert.Equivalent(Tokens, embeddings);
}
[Fact]
@@ -496,27 +62,16 @@ public void QWenTokenizer_Decode_SuccessAsync()
var text = QWenTokenizer.Decode(Tokens);
// Assert
- text.Should().BeEquivalentTo(Text);
+ Assert.Equal(Text, text);
}
[Fact]
public void QWenTokenizer_EncodeToStrings_SuccessAsync()
{
// Act
- var tokens = QWenTokenizer.Tokenizer.EncodeToTokens(Text, out _);
+ var tokens = QWenTokenizer.Tokenizer.Encode(Text);
// Assert
- tokens.Select(t => t.Id).Should().BeEquivalentTo(Tokens);
- }
-
- [Fact]
- public void QWenTokenizer_CountIndex_SuccessAsync()
- {
- // Act
- var maxIndex = QWenTokenizer.GetIndexByTokenCount(Text, 100, out _, out var tokenCount);
-
- // Assert
- maxIndex.Should().Be(155);
- tokenCount.Should().BeLessThanOrEqualTo(100);
+ Assert.Equivalent(Tokens, tokens);
}
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/get-task-background-generation-success-nosse.response.body.txt b/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/get-task-background-generation-success-nosse.response.body.txt
deleted file mode 100644
index 75aba2c..0000000
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/get-task-background-generation-success-nosse.response.body.txt
+++ /dev/null
@@ -1 +0,0 @@
-{"request_id":"8b22164d-c784-9a31-bda3-3c26259d4213","output":{"task_id":"b2e98d78-c79b-431c-b2d7-c7bcd54465da","task_status":"SUCCEEDED","submit_time":"2024-03-04 10:08:57.333","scheduled_time":"2024-03-04 10:08:57.363","end_time":"2024-03-04 10:09:07.727","text_results":{"urls":[{"url":"https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/466b5214/20240304/100901_0_4645005c-713d-4e92-9629-b12cbe5f3671.png?Expires=1709604547&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=kmZGXc2s8P4uI%2BVrADITyrPz82U%3D"},{"url":"https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/466b5214/20240304/100901_1_b1979b75-c553-4d9b-9c9f-80f401a0d124.png?Expires=1709604547&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=cb1Qg%2FkIuZyI7XQqWHjP712N0ak%3D"}],"params":[{"sample_idx":0,"layers":[{"color":"#521b08","top":0,"left":0,"gradient":{"type":"linear","color_stops":[{"offset":0,"color":"#521b0800"},{"offset":1,"color":"#521b08ff"}],"gradient_units":"pixels","coords":{"y1":257,"x1":0,"y2":0,"x2":0}},"width":1024,"type":"text_mask","idx":0,"opacity":0.8,"radius":0,"height":257},{"font_weight":"Regular","font_size":67,"type":"text","content":"分享好时光","font_under_line":false,"line_height":1,"font_italic":false,"top":25,"sub_type":"Title","font_color":"#e6baa7","left":319,"text_stroke":"1px #fffffff0","width":385,"text_shadow":"1px 0px #80808080","font_family":"站酷文艺体","idx":1,"alignment":"center","opacity":1,"font_line_through":false,"direction":"horizontal","height":77},{"color":"#e6baa7","top":118,"left":395,"gradient":{"type":"linear","color_stops":[{"offset":0,"color":"#e6baa7ff"},{"offset":1,"color":"#e6baa7ff"}],"gradient_units":"pixels","coords":{"y1":0,"x1":0,"y2":50,"x2":0}},"width":233,"type":"text_mask","idx":2,"opacity":1,"radius":37,"box_shadow":"2px 1px #80808080","height":50},{"font_weight":"Medium","font_size":27,"type":"text","content":"只为不一样的你","font_under_line":false,"line_height":1,"font_italic":false,"top":118,"sub_type":"SubTitle","font_color":"#223629","left":395,"width":233,"text_shadow":0,"font_family":"阿里巴巴普惠体","idx":3,"alignment":"center","opacity":1,"font_line_through":false,"direction":"horizontal","height":50}]},{"sample_idx":1,"layers":[{"color":"#efeae4","top":0,"left":0,"gradient":{"type":"linear","color_stops":[{"offset":0,"color":"#efeae400"},{"offset":1,"color":"#efeae4ff"}],"gradient_units":"pixels","coords":{"y1":257,"x1":0,"y2":0,"x2":0}},"width":1024,"type":"text_mask","idx":0,"opacity":0.8,"radius":0,"height":257},{"font_weight":"Regular","font_size":67,"type":"text","content":"分享好时光","font_under_line":false,"line_height":1,"font_italic":false,"top":25,"sub_type":"Title","font_color":"#421f12","left":319,"text_stroke":"1px #fffffff0","width":385,"text_shadow":"0px 2px #80808080","font_family":"钉钉进步体","idx":1,"alignment":"center","opacity":1,"font_line_through":false,"direction":"horizontal","height":77},{"color":"#421f12","top":118,"left":395,"gradient":{"type":"linear","color_stops":[{"offset":0,"color":"#421f12ff"},{"offset":1,"color":"#421f12ff"}],"gradient_units":"pixels","coords":{"y1":0,"x1":0,"y2":50,"x2":0}},"width":233,"type":"text_mask","idx":2,"opacity":1,"radius":37,"box_shadow":"0px 0px #80808080","height":50},{"font_weight":"Regular","font_size":27,"type":"text","content":"只为不一样的你","font_under_line":false,"line_height":1,"font_italic":false,"top":118,"sub_type":"SubTitle","font_color":"#f1eeec","left":395,"width":233,"text_shadow":0,"font_family":"阿里巴巴普惠体","idx":3,"alignment":"center","opacity":1,"font_line_through":false,"direction":"horizontal","height":50}]}]},"results":[{"url":"https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/466b5214/20240304/100905_0_02dc0bba-8b1d-4648-8b95-eb2b92fe715d.png?Expires=1709604547&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=OYstgSxWOl%2FOxYTLa2Mx3bi2RWw%3D"},{"url":"https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/466b5214/20240304/100905_1_e1af86ec-152a-4ebe-b2a0-b40a592043b2.png?Expires=1709604547&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=p0UXTUdXfp0tFlt0K5tDsA%2Fxl1M%3D"}],"task_metrics":{"TOTAL":2,"SUCCEEDED":2,"FAILED":0}},"usage":{"image_count":2}}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/get-task-image-synthesis-success-nosse.response.body.txt b/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/get-task-image-synthesis-success-nosse.response.body.txt
deleted file mode 100644
index 43c0bb2..0000000
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/get-task-image-synthesis-success-nosse.response.body.txt
+++ /dev/null
@@ -1 +0,0 @@
-{"request_id":"6662e925-4846-9afe-a3af-0d131805d378","output":{"task_id":"9e2b6ef6-285d-4efa-8651-4dbda7d571fa","task_status":"SUCCEEDED","submit_time":"2024-03-01 17:38:24.817","scheduled_time":"2024-03-01 17:38:24.831","end_time":"2024-03-01 17:38:55.565","results":[{"url":"https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/1d/d4/20240301/8d820c8d/4c48fa53-2907-499b-b9ac-76477fe8d299-1.png?Expires=1709372333&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=bEfLmd%2BarXgZyhxcVYOWs%2BovJb8%3D"},{"url":"https://dashscope-result-sh.oss-cn-shanghai.aliyuncs.com/1d/79/20240301/3ab595ad/aa3e6d8d-884d-4431-b9c2-3684edeb072e-1.png?Expires=1709372333&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=fdPScmRkIXyH3TSaSaWwvVjxREQ%3D"},{"url":"https://dashscope-result-sh.oss-cn-shanghai.aliyuncs.com/1d/0f/20240301/3ab595ad/ecfe06b3-b91c-4950-a932-49ea1619a1f9-1.png?Expires=1709372333&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=gNuVAt8iy4X8Nl2l3K4Gu4f0ydw%3D"},{"url":"https://dashscope-result-sh.oss-cn-shanghai.aliyuncs.com/1d/3d/20240301/3ab595ad/3fca748e-d491-458a-bb72-73649af33209-1.png?Expires=1709372333&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=Mx5TueC9I9yfDno9rjzi48opHtM%3D"}],"task_metrics":{"TOTAL":4,"SUCCEEDED":4,"FAILED":0}},"usage":{"image_count":4}}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/single-generation-message-reasoning-sse.response.body.txt b/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/single-generation-message-reasoning-sse.response.body.txt
deleted file mode 100644
index 40e024f..0000000
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/single-generation-message-reasoning-sse.response.body.txt
+++ /dev/null
@@ -1,1975 +0,0 @@
-id:1
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"嗯","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":14,"input_tokens":11,"output_tokens":3},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:2
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":15,"input_tokens":11,"output_tokens":4},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:3
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"用户","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":16,"input_tokens":11,"output_tokens":5},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:4
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"问","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":17,"input_tokens":11,"output_tokens":6},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:5
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":18,"input_tokens":11,"output_tokens":7},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:6
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"加","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":19,"input_tokens":11,"output_tokens":8},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:7
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":20,"input_tokens":11,"output_tokens":9},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:8
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"等于","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":21,"input_tokens":11,"output_tokens":10},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:9
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"多少","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":22,"input_tokens":11,"output_tokens":11},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:10
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":23,"input_tokens":11,"output_tokens":12},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:11
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"这个问题","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":24,"input_tokens":11,"output_tokens":13},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:12
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"看起来","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":25,"input_tokens":11,"output_tokens":14},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:13
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"很简单","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":26,"input_tokens":11,"output_tokens":15},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:14
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":27,"input_tokens":11,"output_tokens":16},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:15
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"但其实","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":28,"input_tokens":11,"output_tokens":17},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:16
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":29,"input_tokens":11,"output_tokens":18},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:17
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"有很多","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":30,"input_tokens":11,"output_tokens":19},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:18
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"种","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":31,"input_tokens":11,"output_tokens":20},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:19
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"情况","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":32,"input_tokens":11,"output_tokens":21},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:20
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"需要考虑","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":33,"input_tokens":11,"output_tokens":22},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:21
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":34,"input_tokens":11,"output_tokens":23},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:22
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"首先","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":35,"input_tokens":11,"output_tokens":24},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:23
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":36,"input_tokens":11,"output_tokens":25},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:24
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"我得","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":37,"input_tokens":11,"output_tokens":26},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:25
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"确定","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":38,"input_tokens":11,"output_tokens":27},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:26
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"用户","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":39,"input_tokens":11,"output_tokens":28},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:27
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"是不是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":40,"input_tokens":11,"output_tokens":29},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:28
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"在","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":41,"input_tokens":11,"output_tokens":30},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:29
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"问","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":42,"input_tokens":11,"output_tokens":31},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:30
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"数学","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":43,"input_tokens":11,"output_tokens":32},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:31
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"上的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":44,"input_tokens":11,"output_tokens":33},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:32
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"基本","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":45,"input_tokens":11,"output_tokens":34},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:33
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"加法","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":46,"input_tokens":11,"output_tokens":35},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:34
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":47,"input_tokens":11,"output_tokens":36},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:35
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"通常","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":48,"input_tokens":11,"output_tokens":37},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:36
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"来说","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":49,"input_tokens":11,"output_tokens":38},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:37
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":50,"input_tokens":11,"output_tokens":39},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:38
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":51,"input_tokens":11,"output_tokens":40},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:39
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"加","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":52,"input_tokens":11,"output_tokens":41},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:40
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":53,"input_tokens":11,"output_tokens":42},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:41
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"等于","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":54,"input_tokens":11,"output_tokens":43},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:42
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"2","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":55,"input_tokens":11,"output_tokens":44},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:43
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":56,"input_tokens":11,"output_tokens":45},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:44
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"这是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":57,"input_tokens":11,"output_tokens":46},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:45
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"数学","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":58,"input_tokens":11,"output_tokens":47},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:46
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"里的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":59,"input_tokens":11,"output_tokens":48},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:47
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"基本","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":60,"input_tokens":11,"output_tokens":49},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:48
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"事实","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":61,"input_tokens":11,"output_tokens":50},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:49
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":62,"input_tokens":11,"output_tokens":51},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:50
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"根据","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":63,"input_tokens":11,"output_tokens":52},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:51
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"皮","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":64,"input_tokens":11,"output_tokens":53},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:52
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"亚","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":65,"input_tokens":11,"output_tokens":54},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:53
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"诺","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":66,"input_tokens":11,"output_tokens":55},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:54
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"公","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":67,"input_tokens":11,"output_tokens":56},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:55
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"理","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":68,"input_tokens":11,"output_tokens":57},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:56
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"或者","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":69,"input_tokens":11,"output_tokens":58},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:57
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"基本的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":70,"input_tokens":11,"output_tokens":59},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:58
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"算术","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":71,"input_tokens":11,"output_tokens":60},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:59
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"规则","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":72,"input_tokens":11,"output_tokens":61},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:60
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":73,"input_tokens":11,"output_tokens":62},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:61
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"不过","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":74,"input_tokens":11,"output_tokens":63},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:62
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":75,"input_tokens":11,"output_tokens":64},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:63
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"有时候","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":76,"input_tokens":11,"output_tokens":65},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:64
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"问题","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":77,"input_tokens":11,"output_tokens":66},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:65
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能有","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":78,"input_tokens":11,"output_tokens":67},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:66
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"隐藏","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":79,"input_tokens":11,"output_tokens":68},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:67
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"的含义","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":80,"input_tokens":11,"output_tokens":69},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:68
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":81,"input_tokens":11,"output_tokens":70},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:69
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"特别是在","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":82,"input_tokens":11,"output_tokens":71},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:70
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"不同的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":83,"input_tokens":11,"output_tokens":72},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:71
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"语境","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":84,"input_tokens":11,"output_tokens":73},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:72
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"下","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":85,"input_tokens":11,"output_tokens":74},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:73
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":86,"input_tokens":11,"output_tokens":75},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:74
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"答案","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":87,"input_tokens":11,"output_tokens":76},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:75
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能会","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":88,"input_tokens":11,"output_tokens":77},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:76
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"不同","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":89,"input_tokens":11,"output_tokens":78},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:77
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":90,"input_tokens":11,"output_tokens":79},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:78
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"比如","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":91,"input_tokens":11,"output_tokens":80},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:79
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"在","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":92,"input_tokens":11,"output_tokens":81},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:80
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"二进制","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":93,"input_tokens":11,"output_tokens":82},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:81
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"中","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":94,"input_tokens":11,"output_tokens":83},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:82
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":95,"input_tokens":11,"output_tokens":84},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:83
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":96,"input_tokens":11,"output_tokens":85},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:84
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"+","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":97,"input_tokens":11,"output_tokens":86},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:85
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":98,"input_tokens":11,"output_tokens":87},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:86
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"等于","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":99,"input_tokens":11,"output_tokens":88},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:87
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"10","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":100,"input_tokens":11,"output_tokens":89},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:88
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":101,"input_tokens":11,"output_tokens":90},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:89
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"或者在","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":102,"input_tokens":11,"output_tokens":91},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:90
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"布尔","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":103,"input_tokens":11,"output_tokens":92},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:91
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"代数","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":104,"input_tokens":11,"output_tokens":93},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:92
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"中","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":105,"input_tokens":11,"output_tokens":94},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:93
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":106,"input_tokens":11,"output_tokens":95},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:94
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":107,"input_tokens":11,"output_tokens":96},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:95
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"+","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":108,"input_tokens":11,"output_tokens":97},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:96
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":109,"input_tokens":11,"output_tokens":98},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:97
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":110,"input_tokens":11,"output_tokens":99},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:98
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"等于","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":111,"input_tokens":11,"output_tokens":100},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:99
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":112,"input_tokens":11,"output_tokens":101},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:100
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":113,"input_tokens":11,"output_tokens":102},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:101
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"如果是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":114,"input_tokens":11,"output_tokens":103},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:102
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"逻辑","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":115,"input_tokens":11,"output_tokens":104},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:103
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"或","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":116,"input_tokens":11,"output_tokens":105},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:104
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"运算","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":117,"input_tokens":11,"output_tokens":106},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:105
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"的话","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":118,"input_tokens":11,"output_tokens":107},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:106
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":119,"input_tokens":11,"output_tokens":108},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:107
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"不过","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":120,"input_tokens":11,"output_tokens":109},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:108
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"大部分","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":121,"input_tokens":11,"output_tokens":110},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:109
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"情况下","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":122,"input_tokens":11,"output_tokens":111},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:110
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":123,"input_tokens":11,"output_tokens":112},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:111
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"尤其是在","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":124,"input_tokens":11,"output_tokens":113},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:112
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"日常","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":125,"input_tokens":11,"output_tokens":114},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:113
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"交流","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":126,"input_tokens":11,"output_tokens":115},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:114
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"中","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":127,"input_tokens":11,"output_tokens":116},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:115
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":128,"input_tokens":11,"output_tokens":117},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:116
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"人们","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":129,"input_tokens":11,"output_tokens":118},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:117
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"提到","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":130,"input_tokens":11,"output_tokens":119},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:118
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":131,"input_tokens":11,"output_tokens":120},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:119
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"+","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":132,"input_tokens":11,"output_tokens":121},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:120
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":133,"input_tokens":11,"output_tokens":122},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:121
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"的时候","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":134,"input_tokens":11,"output_tokens":123},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:122
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"都是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":135,"input_tokens":11,"output_tokens":124},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:123
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"指","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":136,"input_tokens":11,"output_tokens":125},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:124
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"十进制","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":137,"input_tokens":11,"output_tokens":126},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:125
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"加法","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":138,"input_tokens":11,"output_tokens":127},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:126
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":139,"input_tokens":11,"output_tokens":128},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:127
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"结果","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":140,"input_tokens":11,"output_tokens":129},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:128
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"自然是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":141,"input_tokens":11,"output_tokens":130},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:129
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"2","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":142,"input_tokens":11,"output_tokens":131},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:130
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":143,"input_tokens":11,"output_tokens":132},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:131
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"不过","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":144,"input_tokens":11,"output_tokens":133},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:132
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"也有可能","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":145,"input_tokens":11,"output_tokens":134},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:133
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"用户","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":146,"input_tokens":11,"output_tokens":135},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:134
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"是在","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":147,"input_tokens":11,"output_tokens":136},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:135
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"测试","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":148,"input_tokens":11,"output_tokens":137},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:136
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"我的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":149,"input_tokens":11,"output_tokens":138},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:137
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"反应","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":150,"input_tokens":11,"output_tokens":139},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:138
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":151,"input_tokens":11,"output_tokens":140},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:139
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"或者","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":152,"input_tokens":11,"output_tokens":141},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:140
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"想","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":153,"input_tokens":11,"output_tokens":142},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:141
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"看看","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":154,"input_tokens":11,"output_tokens":143},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:142
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"我会","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":155,"input_tokens":11,"output_tokens":144},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:143
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"不会","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":156,"input_tokens":11,"output_tokens":145},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:144
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"考虑","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":157,"input_tokens":11,"output_tokens":146},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:145
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"其他","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":158,"input_tokens":11,"output_tokens":147},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:146
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能性","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":159,"input_tokens":11,"output_tokens":148},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:147
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":160,"input_tokens":11,"output_tokens":149},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:148
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"比如","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":161,"input_tokens":11,"output_tokens":150},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:149
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"在","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":162,"input_tokens":11,"output_tokens":151},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:150
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"特定的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":163,"input_tokens":11,"output_tokens":152},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:151
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"谜","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":164,"input_tokens":11,"output_tokens":153},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:152
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"语","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":165,"input_tokens":11,"output_tokens":154},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:153
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"或","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":166,"input_tokens":11,"output_tokens":155},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:154
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"笑话","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":167,"input_tokens":11,"output_tokens":156},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:155
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"中","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":168,"input_tokens":11,"output_tokens":157},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:156
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":169,"input_tokens":11,"output_tokens":158},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:157
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"答案","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":170,"input_tokens":11,"output_tokens":159},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:158
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":171,"input_tokens":11,"output_tokens":160},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:159
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"不是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":172,"input_tokens":11,"output_tokens":161},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:160
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"2","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":173,"input_tokens":11,"output_tokens":162},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:161
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":174,"input_tokens":11,"output_tokens":163},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:162
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"比如","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":175,"input_tokens":11,"output_tokens":164},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:163
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"“","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":176,"input_tokens":11,"output_tokens":165},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:164
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":177,"input_tokens":11,"output_tokens":166},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:165
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"滴水","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":178,"input_tokens":11,"output_tokens":167},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:166
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"加","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":179,"input_tokens":11,"output_tokens":168},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:167
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":180,"input_tokens":11,"output_tokens":169},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:168
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"滴水","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":181,"input_tokens":11,"output_tokens":170},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:169
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"还是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":182,"input_tokens":11,"output_tokens":171},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:170
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"1","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":183,"input_tokens":11,"output_tokens":172},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:171
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"滴水","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":184,"input_tokens":11,"output_tokens":173},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:172
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"”,","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":185,"input_tokens":11,"output_tokens":174},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:173
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"或者","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":186,"input_tokens":11,"output_tokens":175},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:174
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"类似的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":187,"input_tokens":11,"output_tokens":176},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:175
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"文字","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":188,"input_tokens":11,"output_tokens":177},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:176
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"游戏","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":189,"input_tokens":11,"output_tokens":178},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:177
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":190,"input_tokens":11,"output_tokens":179},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:178
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"但","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":191,"input_tokens":11,"output_tokens":180},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:179
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"根据","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":192,"input_tokens":11,"output_tokens":181},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:180
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"常规","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":193,"input_tokens":11,"output_tokens":182},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:181
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"问题","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":194,"input_tokens":11,"output_tokens":183},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:182
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":195,"input_tokens":11,"output_tokens":184},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:183
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"我应该","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":196,"input_tokens":11,"output_tokens":185},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:184
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"先","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":197,"input_tokens":11,"output_tokens":186},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:185
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"给出","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":198,"input_tokens":11,"output_tokens":187},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:186
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"正确的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":199,"input_tokens":11,"output_tokens":188},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:187
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"数学","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":200,"input_tokens":11,"output_tokens":189},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:188
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"答案","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":201,"input_tokens":11,"output_tokens":190},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:189
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":202,"input_tokens":11,"output_tokens":191},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:190
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"再","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":203,"input_tokens":11,"output_tokens":192},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:191
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"补充","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":204,"input_tokens":11,"output_tokens":193},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:192
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":205,"input_tokens":11,"output_tokens":194},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:193
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"其他","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":206,"input_tokens":11,"output_tokens":195},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:194
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"情况","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":207,"input_tokens":11,"output_tokens":196},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:195
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":208,"input_tokens":11,"output_tokens":197},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:196
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"这样","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":209,"input_tokens":11,"output_tokens":198},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:197
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"既","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":210,"input_tokens":11,"output_tokens":199},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:198
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"准确","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":211,"input_tokens":11,"output_tokens":200},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:199
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"又","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":212,"input_tokens":11,"output_tokens":201},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:200
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"全面","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":213,"input_tokens":11,"output_tokens":202},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:201
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":214,"input_tokens":11,"output_tokens":203},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:202
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"所以","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":215,"input_tokens":11,"output_tokens":204},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:203
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":216,"input_tokens":11,"output_tokens":205},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:204
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"先","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":217,"input_tokens":11,"output_tokens":206},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:205
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"回答","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":218,"input_tokens":11,"output_tokens":207},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:206
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"2","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":219,"input_tokens":11,"output_tokens":208},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:207
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":220,"input_tokens":11,"output_tokens":209},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:208
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"然后","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":221,"input_tokens":11,"output_tokens":210},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:209
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"解释","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":222,"input_tokens":11,"output_tokens":211},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:210
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"其他","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":223,"input_tokens":11,"output_tokens":212},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:211
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能性","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":224,"input_tokens":11,"output_tokens":213},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:212
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":225,"input_tokens":11,"output_tokens":214},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:213
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"不过","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":226,"input_tokens":11,"output_tokens":215},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:214
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"用户","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":227,"input_tokens":11,"output_tokens":216},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:215
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"的问题","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":228,"input_tokens":11,"output_tokens":217},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:216
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"看起来","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":229,"input_tokens":11,"output_tokens":218},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:217
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"直接","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":230,"input_tokens":11,"output_tokens":219},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:218
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":231,"input_tokens":11,"output_tokens":220},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:219
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":232,"input_tokens":11,"output_tokens":221},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:220
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"不需要","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":233,"input_tokens":11,"output_tokens":222},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:221
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"太","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":234,"input_tokens":11,"output_tokens":223},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:222
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"复杂的","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":235,"input_tokens":11,"output_tokens":224},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:223
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"解释","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":236,"input_tokens":11,"output_tokens":225},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:224
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":237,"input_tokens":11,"output_tokens":226},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:225
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"但","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":238,"input_tokens":11,"output_tokens":227},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:226
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"为了","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":239,"input_tokens":11,"output_tokens":228},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:227
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"保险","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":240,"input_tokens":11,"output_tokens":229},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:228
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"起见","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":241,"input_tokens":11,"output_tokens":230},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:229
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":242,"input_tokens":11,"output_tokens":231},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:230
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"还是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":243,"input_tokens":11,"output_tokens":232},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:231
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"确认","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":244,"input_tokens":11,"output_tokens":233},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:232
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"一下","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":245,"input_tokens":11,"output_tokens":234},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:233
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"是否有","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":246,"input_tokens":11,"output_tokens":235},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:234
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"其他","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":247,"input_tokens":11,"output_tokens":236},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:235
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"意图","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":248,"input_tokens":11,"output_tokens":237},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:236
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"比较好","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":249,"input_tokens":11,"output_tokens":238},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:237
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":250,"input_tokens":11,"output_tokens":239},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:238
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"或者","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":251,"input_tokens":11,"output_tokens":240},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:239
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"用户","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":252,"input_tokens":11,"output_tokens":241},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:240
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":253,"input_tokens":11,"output_tokens":242},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:241
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"只是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":254,"input_tokens":11,"output_tokens":243},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:242
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"单纯","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":255,"input_tokens":11,"output_tokens":244},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:243
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"想知道","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":256,"input_tokens":11,"output_tokens":245},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:244
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"答案","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":257,"input_tokens":11,"output_tokens":246},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:245
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":258,"input_tokens":11,"output_tokens":247},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:246
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"所以","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":259,"input_tokens":11,"output_tokens":248},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:247
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"直接","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":260,"input_tokens":11,"output_tokens":249},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:248
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"回答","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":261,"input_tokens":11,"output_tokens":250},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:249
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"2","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":262,"input_tokens":11,"output_tokens":251},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:250
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"即可","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":263,"input_tokens":11,"output_tokens":252},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:251
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":264,"input_tokens":11,"output_tokens":253},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:252
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"需要","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":265,"input_tokens":11,"output_tokens":254},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:253
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"平衡","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":266,"input_tokens":11,"output_tokens":255},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:254
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"简洁","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":267,"input_tokens":11,"output_tokens":256},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:255
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"和","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":268,"input_tokens":11,"output_tokens":257},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:256
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"全面","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":269,"input_tokens":11,"output_tokens":258},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:257
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"性","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":270,"input_tokens":11,"output_tokens":259},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:258
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":271,"input_tokens":11,"output_tokens":260},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:259
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"可能","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":272,"input_tokens":11,"output_tokens":261},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:260
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"先","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":273,"input_tokens":11,"output_tokens":262},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:261
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"给出","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":274,"input_tokens":11,"output_tokens":263},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:262
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"直接","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":275,"input_tokens":11,"output_tokens":264},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:263
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"答案","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":276,"input_tokens":11,"output_tokens":265},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:264
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":277,"input_tokens":11,"output_tokens":266},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:265
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"然后","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":278,"input_tokens":11,"output_tokens":267},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:266
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"简单","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":279,"input_tokens":11,"output_tokens":268},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:267
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"说明","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":280,"input_tokens":11,"output_tokens":269},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:268
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"其他","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":281,"input_tokens":11,"output_tokens":270},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:269
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"情况","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":282,"input_tokens":11,"output_tokens":271},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:270
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"的存在","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":283,"input_tokens":11,"output_tokens":272},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:271
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":284,"input_tokens":11,"output_tokens":273},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:272
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"这样","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":285,"input_tokens":11,"output_tokens":274},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:273
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"既","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":286,"input_tokens":11,"output_tokens":275},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:274
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"满足","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":287,"input_tokens":11,"output_tokens":276},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:275
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"需求","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":288,"input_tokens":11,"output_tokens":277},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:276
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":289,"input_tokens":11,"output_tokens":278},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:277
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"又","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":290,"input_tokens":11,"output_tokens":279},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:278
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"避免","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":291,"input_tokens":11,"output_tokens":280},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:279
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"信息","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":292,"input_tokens":11,"output_tokens":281},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:280
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"过","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":293,"input_tokens":11,"output_tokens":282},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:281
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"载","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":294,"input_tokens":11,"output_tokens":283},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:282
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":295,"input_tokens":11,"output_tokens":284},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:283
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"总之","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":296,"input_tokens":11,"output_tokens":285},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:284
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":297,"input_tokens":11,"output_tokens":286},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:285
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"核心","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":298,"input_tokens":11,"output_tokens":287},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:286
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"答案","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":299,"input_tokens":11,"output_tokens":288},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:287
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"应该是","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":300,"input_tokens":11,"output_tokens":289},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:288
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"2","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":301,"input_tokens":11,"output_tokens":290},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:289
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":",","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":302,"input_tokens":11,"output_tokens":291},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:290
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"但","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":303,"input_tokens":11,"output_tokens":292},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:291
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"根据","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":304,"input_tokens":11,"output_tokens":293},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:292
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"情况","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":305,"input_tokens":11,"output_tokens":294},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:293
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"适当","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":306,"input_tokens":11,"output_tokens":295},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:294
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"扩展","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":307,"input_tokens":11,"output_tokens":296},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:295
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"。","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":308,"input_tokens":11,"output_tokens":297},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:296
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":311,"input_tokens":11,"output_tokens":300},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:297
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"+","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":312,"input_tokens":11,"output_tokens":301},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:298
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":313,"input_tokens":11,"output_tokens":302},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:299
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" 在","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":315,"input_tokens":11,"output_tokens":304},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:300
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"基础","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":316,"input_tokens":11,"output_tokens":305},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:301
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"算术","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":317,"input_tokens":11,"output_tokens":306},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:302
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"中的","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":318,"input_tokens":11,"output_tokens":307},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:303
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"答案是","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":319,"input_tokens":11,"output_tokens":308},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:304
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" **","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":320,"input_tokens":11,"output_tokens":309},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:305
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"2","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":321,"input_tokens":11,"output_tokens":310},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:306
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"**","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":322,"input_tokens":11,"output_tokens":311},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:307
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"。","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":323,"input_tokens":11,"output_tokens":312},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:308
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"\n\n不过","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":325,"input_tokens":11,"output_tokens":314},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:309
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":",","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":326,"input_tokens":11,"output_tokens":315},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:310
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"根据","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":327,"input_tokens":11,"output_tokens":316},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:311
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"不同的","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":328,"input_tokens":11,"output_tokens":317},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:312
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"数学","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":329,"input_tokens":11,"output_tokens":318},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:313
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"或","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":330,"input_tokens":11,"output_tokens":319},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:314
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"逻辑","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":331,"input_tokens":11,"output_tokens":320},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:315
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"系统","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":332,"input_tokens":11,"output_tokens":321},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:316
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":",","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":333,"input_tokens":11,"output_tokens":322},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:317
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"结果","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":334,"input_tokens":11,"output_tokens":323},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:318
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"可能","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":335,"input_tokens":11,"output_tokens":324},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:319
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"不同","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":336,"input_tokens":11,"output_tokens":325},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:320
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":":","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":337,"input_tokens":11,"output_tokens":326},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:321
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"\n-","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":338,"input_tokens":11,"output_tokens":327},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:322
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" **","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":339,"input_tokens":11,"output_tokens":328},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:323
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"二进制","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":340,"input_tokens":11,"output_tokens":329},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:324
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"**","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":341,"input_tokens":11,"output_tokens":330},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:325
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":":","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":342,"input_tokens":11,"output_tokens":331},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:326
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":343,"input_tokens":11,"output_tokens":332},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:327
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"+","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":344,"input_tokens":11,"output_tokens":333},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:328
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":345,"input_tokens":11,"output_tokens":334},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:329
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" =","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":346,"input_tokens":11,"output_tokens":335},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:330
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" 10","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":348,"input_tokens":11,"output_tokens":337},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:331
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"(","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":349,"input_tokens":11,"output_tokens":338},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:332
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"读","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":350,"input_tokens":11,"output_tokens":339},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:333
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"作","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":351,"input_tokens":11,"output_tokens":340},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:334
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"“","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":352,"input_tokens":11,"output_tokens":341},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:335
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"一","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":353,"input_tokens":11,"output_tokens":342},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:336
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"零","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":354,"input_tokens":11,"output_tokens":343},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:337
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"”","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":355,"input_tokens":11,"output_tokens":344},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:338
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":")。","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":356,"input_tokens":11,"output_tokens":345},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:339
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"\n-","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":357,"input_tokens":11,"output_tokens":346},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:340
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" **","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":358,"input_tokens":11,"output_tokens":347},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:341
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"布尔","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":359,"input_tokens":11,"output_tokens":348},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:342
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"代数","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":360,"input_tokens":11,"output_tokens":349},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:343
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"**","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":361,"input_tokens":11,"output_tokens":350},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:344
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":":","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":362,"input_tokens":11,"output_tokens":351},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:345
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":363,"input_tokens":11,"output_tokens":352},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:346
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"+","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":364,"input_tokens":11,"output_tokens":353},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:347
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":365,"input_tokens":11,"output_tokens":354},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:348
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" =","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":366,"input_tokens":11,"output_tokens":355},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:349
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" 1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":368,"input_tokens":11,"output_tokens":357},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:350
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"(","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":369,"input_tokens":11,"output_tokens":358},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:351
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"逻辑","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":370,"input_tokens":11,"output_tokens":359},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:352
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"“","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":371,"input_tokens":11,"output_tokens":360},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:353
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"或","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":372,"input_tokens":11,"output_tokens":361},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:354
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"”","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":373,"input_tokens":11,"output_tokens":362},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:355
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"运算","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":374,"input_tokens":11,"output_tokens":363},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:356
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":")。","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":375,"input_tokens":11,"output_tokens":364},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:357
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"\n-","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":376,"input_tokens":11,"output_tokens":365},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:358
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" **","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":377,"input_tokens":11,"output_tokens":366},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:359
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"抽象","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":378,"input_tokens":11,"output_tokens":367},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:360
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"场景","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":379,"input_tokens":11,"output_tokens":368},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:361
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"**","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":380,"input_tokens":11,"output_tokens":369},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:362
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":":","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":381,"input_tokens":11,"output_tokens":370},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:363
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"如","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":382,"input_tokens":11,"output_tokens":371},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:364
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"“","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":383,"input_tokens":11,"output_tokens":372},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:365
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":384,"input_tokens":11,"output_tokens":373},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:366
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"滴水","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":385,"input_tokens":11,"output_tokens":374},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:367
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" +","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":386,"input_tokens":11,"output_tokens":375},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:368
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" 1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":388,"input_tokens":11,"output_tokens":377},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:369
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"滴水","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":389,"input_tokens":11,"output_tokens":378},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:370
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" =","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":390,"input_tokens":11,"output_tokens":379},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:371
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" 1","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":392,"input_tokens":11,"output_tokens":381},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:372
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"大","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":393,"input_tokens":11,"output_tokens":382},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:373
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"滴水","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":394,"input_tokens":11,"output_tokens":383},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:374
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"”(","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":395,"input_tokens":11,"output_tokens":384},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:375
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"非","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":396,"input_tokens":11,"output_tokens":385},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:376
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"数学","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":397,"input_tokens":11,"output_tokens":386},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:377
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"意义的","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":398,"input_tokens":11,"output_tokens":387},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:378
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"合并","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":399,"input_tokens":11,"output_tokens":388},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:379
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":")。","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":400,"input_tokens":11,"output_tokens":389},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:380
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"\n\n日常","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":401,"input_tokens":11,"output_tokens":390},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:381
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"问题","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":402,"input_tokens":11,"output_tokens":391},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:382
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"中","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":403,"input_tokens":11,"output_tokens":392},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:383
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"默认","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":404,"input_tokens":11,"output_tokens":393},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:384
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"使用","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":405,"input_tokens":11,"output_tokens":394},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:385
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"十进制","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":406,"input_tokens":11,"output_tokens":395},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:386
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"算术","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":407,"input_tokens":11,"output_tokens":396},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:387
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":",","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":408,"input_tokens":11,"output_tokens":397},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:388
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"因此","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":409,"input_tokens":11,"output_tokens":398},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:389
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"答案是","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":410,"input_tokens":11,"output_tokens":399},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:390
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" **","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":411,"input_tokens":11,"output_tokens":400},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:391
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"2","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":412,"input_tokens":11,"output_tokens":401},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:392
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"**","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":413,"input_tokens":11,"output_tokens":402},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:393
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":" 😊","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":416,"input_tokens":11,"output_tokens":405},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:394
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"。","reasoning_content":"","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":417,"input_tokens":11,"output_tokens":406},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
-id:395
-event:result
-:HTTP_STATUS/200
-data:{"output":{"choices":[{"message":{"content":"","reasoning_content":"","role":"assistant"},"finish_reason":"stop"}]},"usage":{"total_tokens":417,"input_tokens":11,"output_tokens":406},"request_id":"e4ad5d0f-8019-9716-adc6-eae1411d3c9a"}
-
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ServiceCollectionInjectorTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ServiceCollectionInjectorTests.cs
index 02230cc..54d6870 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ServiceCollectionInjectorTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ServiceCollectionInjectorTests.cs
@@ -1,6 +1,6 @@
using System.Net.Http.Headers;
+using Cnblogs.DashScope.AspNetCore;
using Cnblogs.DashScope.Core;
-using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -20,14 +20,17 @@ public void Parameter_Normal_Inject()
// Act
services.AddDashScopeClient(ApiKey);
var provider = services.BuildServiceProvider();
- var httpClient = provider.GetRequiredService().CreateClient(nameof(IDashScopeClient));
+ var httpClient = provider.GetRequiredService()
+ .CreateClient(DashScopeAspNetCoreDefaults.DefaultHttpClientName);
+ var client = provider.GetRequiredService();
// Assert
- provider.GetRequiredService().Should().NotBeNull().And
- .BeOfType