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/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..165038d 100644
--- a/Cnblogs.DashScope.Sdk.sln
+++ b/Cnblogs.DashScope.Sdk.sln
@@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.DashScope.Sdk.Snaps
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
@@ -33,6 +37,8 @@ Global
{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
@@ -63,5 +69,13 @@ Global
{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/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 efd74b1..8af1a64 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ builder.AddDashScopeClient(builder.Configuration);
```json
{
"DashScope": {
- "ApiKey": "your-api-key"
+ "ApiKey": "your-api-key",
}
}
```
@@ -66,20 +66,54 @@ public class YourService(IDashScopeClient client)
# Supported APIs
-- Text Embedding API - `dashScopeClient.GetTextEmbeddingsAsync()`
-- Text Generation API(qwen-turbo, qwen-max, etc.) - `dashScopeClient.GetQwenCompletionAsync()` and `dashScopeClient.GetQWenCompletionStreamAsync()`
-- BaiChuan Models - Use `dashScopeClient.GetBaiChuanTextCompletionAsync()`
-- LLaMa2 Models - `dashScopeClient.GetLlama2TextCompletionAsync()`
-- Multimodal Generation API(qwen-vl-max, etc.) - `dashScopeClient.GetQWenMultimodalCompletionAsync()` and `dashScopeClient.GetQWenMultimodalCompletionStreamAsync()`
+- 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 - `dashScopeClient.UploadFileAsync()` and `dashScopeClient.DeleteFileAsync`
+- File API that used by Qwen-Long - `UploadFileAsync()` and `DeleteFileAsync`
+- Application call - `GetApplicationResponseAsync()` and `GetApplicationResponseStreamAsync()`
# Examples
-Visit [tests](./test) for more usage of each api.
+Visit [snapshots](./test/Cnblogs.DashScope.Tests.Shared/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
@@ -106,6 +140,36 @@ var completion = await client.GetQWenChatCompletionAsync(QWenLlm.QWenMax, histor
Console.WriteLine(completion.Output.Choices[0].Message.Content); // The number is 42
```
+## Reasoning
+
+Use `completion.Output.Choices![0].Message.ReasoningContent` to access the thoughts from reasoning 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);
+```
+
+### QWen3
+
+Use `TextGenerationParameters.EnableThinking` to toggle reasoning.
+
+```csharp
+var stream = dashScopeClient
+ .GetQWenChatStreamAsync(
+ QWenLlm.QWenPlusLatest,
+ history,
+ new TextGenerationParameters
+ {
+ IncrementalOutput = true,
+ ResultFormat = ResultFormats.Message,
+ EnableThinking = true
+ });
+```
+
## Function Call
Creates a function with parameters
@@ -135,7 +199,7 @@ public enum TemperatureUnit
}
```
-Append tool information to chat messages.
+Append tool information to chat messages (Here we use `JsonSchema.NET` to generate JSON Schema).
```csharp
var tools = new List()
@@ -208,3 +272,71 @@ Delete file if needed
```csharp
var deletionResult = await dashScopeClient.DeleteFileAsync(uploadedFile.Id);
```
+
+## Application call
+
+Use `GetApplicationResponseAsync` to call an application.
+
+Use `GetApplicationResponseStreamAsync` for streaming output.
+
+```csharp
+var request =
+ new ApplicationRequest()
+ {
+ Input = new ApplicationInput() { Prompt = "Summarize this file." },
+ Parameters = new ApplicationParameters()
+ {
+ TopK = 100,
+ TopP = 0.8f,
+ Seed = 1234,
+ Temperature = 0.85f,
+ RagOptions = new ApplicationRagOptions()
+ {
+ PipelineIds = ["thie5bysoj"],
+ FileIds = ["file_d129d632800c45aa9e7421b30561f447_10207234"]
+ }
+ }
+ };
+var response = await client.GetApplicationResponseAsync("your-application-id", request);
+Console.WriteLine(response.Output.Text);
+```
+
+`ApplicationRequest` use an `Dictionary` as `BizParams` by default.
+
+```csharp
+var request =
+ new ApplicationRequest()
+ {
+ Input = new ApplicationInput()
+ {
+ Prompt = "Summarize this file.",
+ BizParams = new Dictionary()
+ {
+ { "customKey1", "custom-value" }
+ }
+ }
+ };
+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.
+
+```csharp
+public record TestApplicationBizParam(
+ [property: JsonPropertyName("sourceCode")]
+ string SourceCode);
+
+var request =
+ new ApplicationRequest()
+ {
+ Input = new ApplicationInput()
+ {
+ Prompt = "Summarize this file.",
+ BizParams = new TestApplicationBizParam("test")
+ }
+ };
+var response = await client.GetApplicationResponseAsync("your-application-id", request);
+Console.WriteLine(response.Output.Text);
+```
+
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index 9f81d6b..b2545c3 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -66,22 +66,59 @@ public class YourService(IDashScopeClient client)
# 支持的 API
-- 通用文本向量 - `dashScopeClient.GetTextEmbeddingsAsync()`
-- 通义千问(`qwen-turbo`, `qwen-max` 等) - `dashScopeClient.GetQwenCompletionAsync()` and `dashScopeClient.GetQWenCompletionStreamAsync()`
-- 百川开源大模型 - Use `dashScopeClient.GetBaiChuanTextCompletionAsync()`
-- LLaMa2 大语言模型 - `dashScopeClient.GetLlama2TextCompletionAsync()`
-- 通义千问 VL 和通义千问 Audio(`qwen-vl-max`, `qwen-audio`) - `dashScopeClient.GetQWenMultimodalCompletionAsync()` and `dashScopeClient.GetQWenMultimodalCompletionStreamAsync()`
+- 通用文本向量 - `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()` and `GetWanxImageSynthesisTaskAsync()`
- - 人像风格重绘 - `CreateWanxImageGenerationTaskAsync()` and `GetWanxImageGenerationTaskAsync()`
- - 图像背景生成 - `CreateWanxBackgroundGenerationTaskAsync()` and `GetWanxBackgroundGenerationTaskAsync()`
-- 适用于 QWen-Long 的文件 API `dashScopeClient.UploadFileAsync()` and `dashScopeClient.DeleteFileAsync`
+ - 文生图 - `CreateWanxImageSynthesisTaskAsync()` 和 `GetWanxImageSynthesisTaskAsync()`
+ - 人像风格重绘 - `CreateWanxImageGenerationTaskAsync()` 和 `GetWanxImageGenerationTaskAsync()`
+ - 图像背景生成 - `CreateWanxBackgroundGenerationTaskAsync()` 和 `GetWanxBackgroundGenerationTaskAsync()`
+- 适用于 QWen-Long 的文件 API `UploadFileAsync()` 和 `DeleteFileAsync`
+- 应用调用 `GetApplicationResponseAsync` 和 `GetApplicationResponseStreamAsync()`
- 其他使用相同 Endpoint 的模型
# 示例
+查看 [快照文件](./test/Cnblogs.DashScope.Tests.Shared/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
+ }
+ });
+```
+
## 单轮对话
```csharp
@@ -107,6 +144,36 @@ var completion = await client.GetQWenChatCompletionAsync(QWenLlm.QWenMax, histor
Console.WriteLine(completion.Output.Choices[0].Message.Content); // The number is 42
```
+## 推理
+
+使用推理模型时,模型的思考过程可以通过 `ReasoningContent` 属性获取。
+
+```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);
+```
+
+### QWen3
+
+使用 `TextGenerationParameters.EnableThinking` 决定是否使用模型的推理能力。
+
+```csharp
+var stream = dashScopeClient
+ .GetQWenChatStreamAsync(
+ QWenLlm.QWenPlusLatest,
+ history,
+ new TextGenerationParameters
+ {
+ IncrementalOutput = true,
+ ResultFormat = ResultFormats.Message,
+ EnableThinking = true
+ });
+```
+
## 工具调用
创建一个可供模型使用的方法。
@@ -132,7 +199,7 @@ public enum TemperatureUnit
}
```
-对话时带上方法的名称、描述和参数列表,参数列表以 JSON Schema 的形式提供。
+对话时带上方法的名称、描述和参数列表,参数列表以 JSON Schema 的形式提供(这里使用 `JsonSchema.Net` 库,您也可以使用其它具有类似功能的库)。
```csharp
var tools = new List()
@@ -204,3 +271,71 @@ Console.WriteLine(completion.Output.Choices[0].Message.Content);
```csharp
var deletionResult = await dashScopeClient.DeleteFileAsync(uploadedFile.Id);
```
+
+## 应用调用
+
+`GetApplicationResponseAsync` 用于进行应用调用。
+
+`GetApplicationResponseStreamAsync` 用于流式调用。
+
+```csharp
+var request =
+ new ApplicationRequest()
+ {
+ Input = new ApplicationInput() { Prompt = "Summarize this file." },
+ Parameters = new ApplicationParameters()
+ {
+ TopK = 100,
+ TopP = 0.8f,
+ Seed = 1234,
+ Temperature = 0.85f,
+ RagOptions = new ApplicationRagOptions()
+ {
+ PipelineIds = ["thie5bysoj"],
+ FileIds = ["file_d129d632800c45aa9e7421b30561f447_10207234"]
+ }
+ }
+ };
+var response = await client.GetApplicationResponseAsync("your-application-id", request);
+Console.WriteLine(response.Output.Text);
+```
+
+`ApplicationRequest` 默认使用 `Dictionary` 作为 `BizParams` 的类型。
+
+```csharp
+var request =
+ new ApplicationRequest()
+ {
+ Input = new ApplicationInput()
+ {
+ Prompt = "Summarize this file.",
+ BizParams = new Dictionary()
+ {
+ { "customKey1", "custom-value" }
+ }
+ }
+ };
+var response = await client.GetApplicationResponseAsync("your-application-id", request);
+Console.WriteLine(response.Output.Text);
+```
+
+如需强类型支持,可以使用泛型类 `ApplicationRequest`。
+注意 SDK 在 JSON 序列化时使用 `snake_case`。如果你的应用采用其他的命名规则,请使用 `[JsonPropertyName("camelCase")]` 来手动指定序列化时的属性名称。
+
+```csharp
+public record TestApplicationBizParam(
+ [property: JsonPropertyName("sourceCode")]
+ string SourceCode);
+
+var request =
+ new ApplicationRequest()
+ {
+ Input = new ApplicationInput()
+ {
+ Prompt = "Summarize this file.",
+ BizParams = new TestApplicationBizParam("test")
+ }
+ };
+var response = await client.GetApplicationResponseAsync("your-application-id", request);
+Console.WriteLine(response.Output.Text);
+```
diff --git a/sample/Cnblogs.DashScope.Sample/Cnblogs.DashScope.Sample.csproj b/sample/Cnblogs.DashScope.Sample/Cnblogs.DashScope.Sample.csproj
index a7ba2f0..ba80da7 100644
--- a/sample/Cnblogs.DashScope.Sample/Cnblogs.DashScope.Sample.csproj
+++ b/sample/Cnblogs.DashScope.Sample/Cnblogs.DashScope.Sample.csproj
@@ -20,7 +20,7 @@
-
+
diff --git a/sample/Cnblogs.DashScope.Sample/Program.cs b/sample/Cnblogs.DashScope.Sample/Program.cs
index c5958f3..87bfe69 100644
--- a/sample/Cnblogs.DashScope.Sample/Program.cs
+++ b/sample/Cnblogs.DashScope.Sample/Program.cs
@@ -32,12 +32,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;
@@ -56,6 +56,13 @@
case SampleType.MicrosoftExtensionsAiToolCall:
await dashScopeClient.ToolCallWithExtensionAsync();
break;
+ case SampleType.ApplicationCall:
+ Console.Write("Application Id > ");
+ var applicationId = Console.ReadLine()!;
+ Console.Write("Prompt > ");
+ userInput = Console.ReadLine()!;
+ await ApplicationCallAsync(applicationId, userInput);
+ break;
}
return;
@@ -90,9 +97,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)
@@ -105,7 +117,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();
@@ -170,7 +185,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}");
@@ -207,11 +222,15 @@ 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 ApplicationCallAsync(string applicationId, string 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 94d119d..feddf79 100644
--- a/sample/Cnblogs.DashScope.Sample/SampleType.cs
+++ b/sample/Cnblogs.DashScope.Sample/SampleType.cs
@@ -14,5 +14,7 @@ public enum SampleType
MicrosoftExtensionsAi,
- MicrosoftExtensionsAiToolCall
+ MicrosoftExtensionsAiToolCall,
+
+ ApplicationCall
}
diff --git a/sample/Cnblogs.DashScope.Sample/SampleTypeDescriptor.cs b/sample/Cnblogs.DashScope.Sample/SampleTypeDescriptor.cs
index e39f284..26988a5 100644
--- a/sample/Cnblogs.DashScope.Sample/SampleTypeDescriptor.cs
+++ b/sample/Cnblogs.DashScope.Sample/SampleTypeDescriptor.cs
@@ -13,6 +13,7 @@ public static string GetDescription(this SampleType sampleType)
SampleType.ChatCompletionWithFiles => "File upload sample using qwen-long",
SampleType.MicrosoftExtensionsAi => "Use with Microsoft.Extensions.AI",
SampleType.MicrosoftExtensionsAiToolCall => "Use tool call with Microsoft.Extensions.AI interfaces",
+ SampleType.ApplicationCall => "Call pre-defined application",
_ => 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 fe758a6..8de1ebd 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 f28456f..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" };
@@ -43,7 +43,7 @@ public DashScopeChatClient(IDashScopeClient dashScopeClient, string modelId)
///
public async Task GetResponseAsync(
- IList chatMessages,
+ IEnumerable chatMessages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
@@ -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,
@@ -130,7 +130,7 @@ public async Task GetResponseAsync(
///
public async IAsyncEnumerable GetStreamingResponseAsync(
- IList chatMessages,
+ IEnumerable chatMessages,
ChatOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
@@ -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,
@@ -183,7 +183,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync(
{
update.Contents.Add(
new UsageContent(
- new UsageDetails()
+ new UsageDetails
{
InputTokenCount = response.Usage.InputTokens,
OutputTokenCount = response.Usage.OutputTokens,
@@ -199,16 +199,16 @@ 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.Message.Role,
+ Role = completion.Messages[0].Role,
AdditionalProperties = completion.AdditionalProperties,
- Contents = completion.Message.Contents,
- RawRepresentation = completion.Message.RawRepresentation,
+ Contents = completion.Messages[0].Contents,
+ RawRepresentation = completion.Messages[0].RawRepresentation,
CreatedAt = completion.CreatedAt,
FinishReason = completion.FinishReason,
- ModelId = completion.ModelId,
+ ModelId = completion.ModelId
};
}
else
@@ -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),
};
@@ -392,11 +392,11 @@ private List ToMultimodalMessageContents(IList MultimodalMessageContent.TextContent(text.Text),
- DataContent { Data.Length: > 0 } data when data.MediaTypeStartsWith("image") =>
+ DataContent { Data.Length: > 0 } data when data.HasTopLevelMediaType("image") =>
MultimodalMessageContent.ImageContent(
- data.Data.Value.Span,
+ data.Data.Span,
data.MediaType ?? throw new InvalidOperationException("image media type should not be null")),
- DataContent { Uri: { } uri } data when data.MediaTypeStartsWith("image") =>
+ DataContent { Uri: { } uri } data when data.HasTopLevelMediaType("image") =>
MultimodalMessageContent.ImageContent(uri),
_ => null
};
@@ -422,7 +422,7 @@ private IEnumerable ToTextChatMessages(
{
yield return new TextChatMessage(
from.Role.Value,
- from.Text ?? string.Empty,
+ from.Text,
from.AuthorName);
}
else if (from.Role == ChatRole.Tool)
@@ -464,9 +464,10 @@ private IEnumerable ToTextChatMessages(
// <400> InternalError.Algo.InvalidParameter: Empty tool_calls is not supported in message
yield return new TextChatMessage(
from.Role.Value,
- from.Text ?? string.Empty,
+ from.Text,
from.AuthorName,
null,
+ null,
functionCall.Count > 0 ? functionCall : null);
}
}
@@ -484,7 +485,7 @@ private IEnumerable ToTextChatMessages(
format = "json_object";
}
- return new TextGenerationParameters()
+ return new TextGenerationParameters
{
ResultFormat = format,
Temperature = options.Temperature,
@@ -502,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 fba5e02..f735844 100644
--- a/src/Cnblogs.DashScope.AI/DashScopeTextEmbeddingGenerator.cs
+++ b/src/Cnblogs.DashScope.AI/DashScopeTextEmbeddingGenerator.cs
@@ -29,7 +29,6 @@ public DashScopeTextEmbeddingGenerator(IDashScopeClient dashScopeClient, string
_dashScopeClient = dashScopeClient;
_modelId = modelId;
_parameters = new TextEmbeddingParameters { Dimension = dimensions };
- Metadata = new EmbeddingGeneratorMetadata("dashscope", _dashScopeClient.BaseAddress, modelId, dimensions);
}
///
@@ -45,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)
{
@@ -88,7 +87,4 @@ public void Dispose()
options.AdditionalProperties?.GetValueOrDefault(nameof(TextEmbeddingParameters.TextType)) as string,
};
}
-
- ///
- public EmbeddingGeneratorMetadata Metadata { get; }
}
diff --git a/src/Cnblogs.DashScope.AspNetCore/ServiceCollectionInjector.cs b/src/Cnblogs.DashScope.AspNetCore/ServiceCollectionInjector.cs
index 6d91b83..ee6b00a 100644
--- a/src/Cnblogs.DashScope.AspNetCore/ServiceCollectionInjector.cs
+++ b/src/Cnblogs.DashScope.AspNetCore/ServiceCollectionInjector.cs
@@ -38,9 +38,8 @@ 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"];
- return string.IsNullOrEmpty(baseAddress)
- ? services.AddDashScopeClient(apiKey)
- : services.AddDashScopeClient(apiKey, baseAddress);
+ var workspaceId = section["workspaceId"];
+ return services.AddDashScopeClient(apiKey, baseAddress, workspaceId);
}
///
@@ -49,16 +48,24 @@ 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.
+ /// Default workspace id to use.
///
public static IHttpClientBuilder AddDashScopeClient(
this IServiceCollection services,
string apiKey,
- string baseAddress = "https://dashscope.aliyuncs.com/api/v1/")
+ string? baseAddress = null,
+ string? workspaceId = null)
{
+ baseAddress ??= "https://dashscope.aliyuncs.com/api/v1/";
return services.AddHttpClient(
h =>
{
h.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
+ if (string.IsNullOrWhiteSpace(workspaceId) == false)
+ {
+ h.DefaultRequestHeaders.Add("X-DashScope-WorkSpace", workspaceId);
+ }
+
h.BaseAddress = new Uri(baseAddress);
});
}
diff --git a/src/Cnblogs.DashScope.Core/ApplicationDocReference.cs b/src/Cnblogs.DashScope.Core/ApplicationDocReference.cs
new file mode 100644
index 0000000..83c48af
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationDocReference.cs
@@ -0,0 +1,20 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// One reference for application output.
+///
+/// The index id of the doc.
+/// Text slice title.
+/// Unique id of the doc been referenced.
+/// Name of the doc been referenced.
+/// Referenced content.
+/// Image URLs beed referenced.
+/// Page numbers of referenced content belongs to.
+public record ApplicationDocReference(
+ string IndexId,
+ string Title,
+ string DocId,
+ string DocName,
+ string Text,
+ List? Images,
+ List? PageNumber);
diff --git a/src/Cnblogs.DashScope.Core/ApplicationInput.cs b/src/Cnblogs.DashScope.Core/ApplicationInput.cs
new file mode 100644
index 0000000..fc537e6
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationInput.cs
@@ -0,0 +1,49 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Inputs for application call.
+///
+/// Type of the BizContent.
+public class ApplicationInput
+ where TBizParams : class
+{
+ ///
+ /// The prompt for model to generate response upon. Optional when has been set.
+ ///
+ ///
+ /// Prompt will be appended to when both set.
+ ///
+ public string? Prompt { get; set; }
+
+ ///
+ /// The session id for conversation history. This will be ignored if has been set.
+ ///
+ public string? SessionId { get; set; }
+
+ ///
+ /// The conversation history.
+ ///
+ public IEnumerable? Messages { get; set; }
+
+ ///
+ /// The id of memory when enabled.
+ ///
+ public string? MemoryId { get; set; }
+
+ ///
+ /// List of image urls for inputs.
+ ///
+ public IEnumerable? ImageList { get; set; }
+
+ ///
+ /// User defined content.
+ ///
+ public TBizParams? BizParams { get; set; } = null;
+}
+
+///
+/// Inputs for application call.
+///
+public class ApplicationInput : ApplicationInput>
+{
+}
diff --git a/src/Cnblogs.DashScope.Core/ApplicationMessage.cs b/src/Cnblogs.DashScope.Core/ApplicationMessage.cs
new file mode 100644
index 0000000..9edfdf5
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationMessage.cs
@@ -0,0 +1,30 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// A single message for application call.
+///
+/// The role of this message belongs to.
+/// The content of the message.
+public record ApplicationMessage(string Role, string Content)
+{
+ ///
+ /// Creates a user message.
+ ///
+ /// Content of the message.
+ ///
+ public static ApplicationMessage User(string content) => new("user", content);
+
+ ///
+ /// Creates a system message.
+ ///
+ /// Content of the message.
+ ///
+ public static ApplicationMessage System(string content) => new("system", content);
+
+ ///
+ /// Creates a assistant message.
+ ///
+ /// Content of the message.
+ ///
+ public static ApplicationMessage Assistant(string content) => new("assistant", content);
+}
diff --git a/src/Cnblogs.DashScope.Core/ApplicationModelUsage.cs b/src/Cnblogs.DashScope.Core/ApplicationModelUsage.cs
new file mode 100644
index 0000000..e0e838e
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationModelUsage.cs
@@ -0,0 +1,9 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Token usages for one model.
+///
+/// The id of the model.
+/// Total input tokens of this model.
+/// Total output tokens from this model.
+public record ApplicationModelUsage(string ModelId, int InputTokens, int OutputTokens);
diff --git a/src/Cnblogs.DashScope.Core/ApplicationOutput.cs b/src/Cnblogs.DashScope.Core/ApplicationOutput.cs
new file mode 100644
index 0000000..258bd60
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationOutput.cs
@@ -0,0 +1,16 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// The output of application call.
+///
+/// Output text from application.
+/// Finish reason from application.
+/// Unique id of current session.
+/// Thoughts from application.
+/// Doc references from application output.
+public record ApplicationOutput(
+ string Text,
+ string FinishReason,
+ string SessionId,
+ List? Thoughts,
+ List? DocReferences);
diff --git a/src/Cnblogs.DashScope.Core/ApplicationOutputThought.cs b/src/Cnblogs.DashScope.Core/ApplicationOutputThought.cs
new file mode 100644
index 0000000..b4bf55f
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationOutputThought.cs
@@ -0,0 +1,24 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// The model thought output.
+///
+/// The thought content of the model.
+/// Type of the action. e.g. agentRag, reasoning.
+/// The name of the action.
+/// The action been executed.
+/// The streaming result of action input.
+/// The input of the action.
+/// Lookup or plugin output.
+/// Reasoning output when using DeepSeek-R1.
+/// Arguments of the action.
+public record ApplicationOutputThought(
+ string? Thought,
+ string? ActionType,
+ string? ActionName,
+ string? Action,
+ string? ActionInputStream,
+ string? ActionInput,
+ string? Observation,
+ string? Response,
+ string? Arguments);
diff --git a/src/Cnblogs.DashScope.Core/ApplicationParameters.cs b/src/Cnblogs.DashScope.Core/ApplicationParameters.cs
new file mode 100644
index 0000000..86303b0
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationParameters.cs
@@ -0,0 +1,37 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Parameters for application call.
+///
+public class ApplicationParameters : IIncrementalOutputParameter, ISeedParameter, IProbabilityParameter
+{
+ ///
+ public bool? IncrementalOutput { get; set; }
+
+ ///
+ /// Output format for flow application. Can be full_thoughts or agent_format. Defaults to full_thoughts.
+ ///
+ public string? FlowStreamMode { get; set; }
+
+ ///
+ /// Options for RAG applications.
+ ///
+ public ApplicationRagOptions? RagOptions { get; set; }
+
+ ///
+ public ulong? Seed { get; set; }
+
+ ///
+ public float? TopP { get; set; }
+
+ ///
+ public int? TopK { get; set; }
+
+ ///
+ public float? Temperature { get; set; }
+
+ ///
+ /// Controls whether output contains think block.
+ ///
+ public bool? HasThoughts { get; set; }
+}
diff --git a/src/Cnblogs.DashScope.Core/ApplicationRagOptions.cs b/src/Cnblogs.DashScope.Core/ApplicationRagOptions.cs
new file mode 100644
index 0000000..38aff36
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationRagOptions.cs
@@ -0,0 +1,37 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Options for RAG application.
+///
+public class ApplicationRagOptions
+{
+ ///
+ /// The pipelines to search from.
+ ///
+ public IEnumerable? PipelineIds { get; set; }
+
+ ///
+ /// The ids of file to reference from.
+ ///
+ public IEnumerable? FileIds { get; set; }
+
+ ///
+ /// Metadata filter for non-structured files.
+ ///
+ public Dictionary? MetadataFilter { get; set; }
+
+ ///
+ /// Tag filter for non-structured files.
+ ///
+ public IEnumerable? Tags { get; set; }
+
+ ///
+ /// Filter for structured files.
+ ///
+ public Dictionary? StructuredFilter { get; set; }
+
+ ///
+ /// File ids for current session.
+ ///
+ public IEnumerable? SessionFileIds { get; set; }
+}
diff --git a/src/Cnblogs.DashScope.Core/ApplicationRequest.cs b/src/Cnblogs.DashScope.Core/ApplicationRequest.cs
new file mode 100644
index 0000000..c1980e7
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationRequest.cs
@@ -0,0 +1,35 @@
+using System.Text.Json.Serialization;
+using Cnblogs.DashScope.Core.Internals;
+
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Request body for an application all.
+///
+/// Type of the biz_content
+public class ApplicationRequest : IDashScopeWorkspaceConfig
+ where TBizParams : class
+{
+ ///
+ /// Content of this call.
+ ///
+ public ApplicationInput Input { get; set; } = new();
+
+ ///
+ /// Optional configurations.
+ ///
+ public ApplicationParameters? Parameters { get; set; }
+
+ ///
+ /// Optional workspace id.
+ ///
+ [JsonIgnore]
+ public string? WorkspaceId { get; set; }
+}
+
+///
+/// Request body for an application call with dictionary biz_content.
+///
+public class ApplicationRequest : ApplicationRequest>
+{
+}
diff --git a/src/Cnblogs.DashScope.Core/ApplicationResponse.cs b/src/Cnblogs.DashScope.Core/ApplicationResponse.cs
new file mode 100644
index 0000000..2089940
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationResponse.cs
@@ -0,0 +1,12 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Response of application call.
+///
+/// Unique id of this request.
+/// The output of application call.
+/// Token usage of this application call.
+public record ApplicationResponse(
+ string RequestId,
+ ApplicationOutput Output,
+ ApplicationUsage Usage);
diff --git a/src/Cnblogs.DashScope.Core/ApplicationUsage.cs b/src/Cnblogs.DashScope.Core/ApplicationUsage.cs
new file mode 100644
index 0000000..8a4ef98
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/ApplicationUsage.cs
@@ -0,0 +1,7 @@
+namespace Cnblogs.DashScope.Core;
+
+///
+/// Total token usages of this application call.
+///
+/// All models been used and their token usages. Can be null when workflow application without using any model.
+public record ApplicationUsage(List? Models);
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..b164ed0 100644
--- a/src/Cnblogs.DashScope.Core/Cnblogs.DashScope.Core.csproj
+++ b/src/Cnblogs.DashScope.Core/Cnblogs.DashScope.Core.csproj
@@ -8,11 +8,12 @@
-
+
-
+
+
diff --git a/src/Cnblogs.DashScope.Core/DashScopeClient.cs b/src/Cnblogs.DashScope.Core/DashScopeClient.cs
index 26f716f..dea20f9 100644
--- a/src/Cnblogs.DashScope.Core/DashScopeClient.cs
+++ b/src/Cnblogs.DashScope.Core/DashScopeClient.cs
@@ -15,32 +15,43 @@ public class DashScopeClient : DashScopeClientCore
///
/// The DashScope api key.
/// The timeout for internal http client, defaults to 2 minute.
+ /// The base address for dashscope api call.
+ /// The workspace id.
///
- /// The underlying httpclient is cached by apiKey and timeout.
- /// Client created with same apiKey and timeout value will share same underlying instance.
+ /// The underlying httpclient is cached by constructor parameter list.
+ /// Client created with same parameter value will share same underlying instance.
///
- public DashScopeClient(string apiKey, TimeSpan? timeout = null)
- : base(GetConfiguredClient(apiKey, timeout))
+ public DashScopeClient(
+ string apiKey,
+ TimeSpan? timeout = null,
+ string? baseAddress = null,
+ string? workspaceId = null)
+ : base(GetConfiguredClient(apiKey, timeout, baseAddress, workspaceId))
{
}
- private static HttpClient GetConfiguredClient(string apiKey, TimeSpan? timeout)
+ private static HttpClient GetConfiguredClient(
+ string apiKey,
+ TimeSpan? timeout = null,
+ string? baseAddress = null,
+ string? workspaceId = null)
{
var client = ClientPools.GetValueOrDefault(GetCacheKey());
if (client is null)
{
client = new HttpClient
{
- BaseAddress = new Uri(DashScopeDefaults.DashScopeApiBaseAddress),
+ BaseAddress = new Uri(baseAddress ?? DashScopeDefaults.DashScopeApiBaseAddress),
Timeout = timeout ?? TimeSpan.FromMinutes(2)
};
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
+ client.DefaultRequestHeaders.Add("X-DashScope-WorkSpace", workspaceId);
ClientPools.Add(GetCacheKey(), client);
}
return client;
- string GetCacheKey() => $"{apiKey}-{timeout?.TotalMilliseconds}";
+ string GetCacheKey() => $"{apiKey}-{timeout?.TotalMilliseconds}-{baseAddress}-{workspaceId}";
}
}
diff --git a/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs b/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs
index 9784516..e0f3159 100644
--- a/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs
+++ b/src/Cnblogs.DashScope.Core/DashScopeClientCore.cs
@@ -18,7 +18,7 @@ public class DashScopeClientCore : IDashScopeClient
new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
private readonly HttpClient _httpClient;
@@ -35,6 +35,46 @@ public DashScopeClientCore(HttpClient httpClient)
///
public Uri? BaseAddress => _httpClient.BaseAddress;
+ ///
+ public Task GetApplicationResponseAsync(
+ string applicationId,
+ ApplicationRequest input,
+ CancellationToken cancellationToken = default)
+ {
+ return GetApplicationResponseAsync>(applicationId, input, cancellationToken);
+ }
+
+ ///
+ public async Task GetApplicationResponseAsync(
+ string applicationId,
+ ApplicationRequest input,
+ CancellationToken cancellationToken = default)
+ where TBizContent : class
+ {
+ var request = BuildRequest(HttpMethod.Post, ApiLinks.Application(applicationId), input);
+ return (await SendAsync(request, cancellationToken))!;
+ }
+
+ ///
+ public IAsyncEnumerable GetApplicationResponseStreamAsync(
+ string applicationId,
+ ApplicationRequest input,
+ CancellationToken cancellationToken = default)
+ {
+ return GetApplicationResponseStreamAsync>(applicationId, input, cancellationToken);
+ }
+
+ ///
+ public IAsyncEnumerable GetApplicationResponseStreamAsync(
+ string applicationId,
+ ApplicationRequest input,
+ CancellationToken cancellationToken = default)
+ where TBizContent : class
+ {
+ var request = BuildSseRequest(HttpMethod.Post, ApiLinks.Application(applicationId), input);
+ return StreamAsync(request, cancellationToken);
+ }
+
///
public async Task> GetTextCompletionAsync(
ModelRequest input,
@@ -235,7 +275,9 @@ public async Task ListFilesAsync(CancellationToken cancellati
}
///
- public async Task DeleteFileAsync(DashScopeFileId id, CancellationToken cancellationToken = default)
+ public async Task DeleteFileAsync(
+ DashScopeFileId id,
+ CancellationToken cancellationToken = default)
{
var request = BuildRequest(HttpMethod.Delete, ApiLinks.Files + $"/{id}");
return (await SendCompatibleAsync(request, cancellationToken))!;
@@ -275,6 +317,11 @@ private static HttpRequestMessage BuildRequest(
message.Headers.Add("X-DashScope-Async", "enable");
}
+ if (payload is IDashScopeWorkspaceConfig config && string.IsNullOrWhiteSpace(config.WorkspaceId) == false)
+ {
+ message.Headers.Add("X-DashScope-WorkSpace", config.WorkspaceId);
+ }
+
return message;
}
@@ -285,7 +332,7 @@ private static HttpRequestMessage BuildRequest(
{
var response = await GetSuccessResponseAsync(
message,
- r => new DashScopeError()
+ r => new DashScopeError
{
Code = r.Error.Type,
Message = r.Error.Message,
@@ -320,7 +367,7 @@ 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..];
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/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/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 3c32291..a123050 100644
--- a/src/Cnblogs.DashScope.Core/IDashScopeClient.cs
+++ b/src/Cnblogs.DashScope.Core/IDashScopeClient.cs
@@ -10,6 +10,58 @@ public interface IDashScopeClient
///
Uri? BaseAddress { get; }
+ ///
+ /// Make a call to custom application.
+ ///
+ /// Name of the application.
+ /// The request body.
+ /// The cancellation token to use.
+ ///
+ Task GetApplicationResponseAsync(
+ string applicationId,
+ ApplicationRequest input,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Make a call to custom application.
+ ///
+ /// Name of the application.
+ /// The request body.
+ /// The cancellation token to use.
+ /// Type of the biz_content.
+ ///
+ Task GetApplicationResponseAsync(
+ string applicationId,
+ ApplicationRequest input,
+ CancellationToken cancellationToken = default)
+ where TBizParams : class;
+
+ ///
+ /// Make a call to custom application.
+ ///
+ /// Name of the application.
+ /// The request body.
+ /// The cancellation token to use.
+ ///
+ IAsyncEnumerable GetApplicationResponseStreamAsync(
+ string applicationId,
+ ApplicationRequest input,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Make a call to custom application.
+ ///
+ /// Name of the application.
+ /// The request body.
+ /// The cancellation token to use.
+ /// Type of the biz_content.
+ ///
+ IAsyncEnumerable GetApplicationResponseStreamAsync(
+ string applicationId,
+ ApplicationRequest input,
+ CancellationToken cancellationToken = default)
+ where TBizContent : class;
+
///
/// Return textual completions as configured for a given prompt.
///
diff --git a/src/Cnblogs.DashScope.Core/ITextGenerationParameters.cs b/src/Cnblogs.DashScope.Core/ITextGenerationParameters.cs
index c32bb72..37a3849 100644
--- a/src/Cnblogs.DashScope.Core/ITextGenerationParameters.cs
+++ b/src/Cnblogs.DashScope.Core/ITextGenerationParameters.cs
@@ -1,4 +1,4 @@
-namespace Cnblogs.DashScope.Core;
+namespace Cnblogs.DashScope.Core;
///
/// The text generation options.
@@ -40,6 +40,11 @@ public interface ITextGenerationParameters
///
public bool? EnableSearch { get; }
+ ///
+ /// Thinking option. Valid for supported models.(e.g. qwen3)
+ ///
+ public bool? EnableThinking { get; }
+
///
/// Available tools for model to call.
///
@@ -49,4 +54,9 @@ public interface ITextGenerationParameters
/// Behavior when choosing tools.
///
public ToolChoice? ToolChoice { get; }
+
+ ///
+ /// Whether to enable parallel tool calling
+ ///
+ public bool? ParallelToolCalls { get; }
}
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/Internals/ApiLinks.cs b/src/Cnblogs.DashScope.Core/Internals/ApiLinks.cs
index 22cc689..d098719 100644
--- a/src/Cnblogs.DashScope.Core/Internals/ApiLinks.cs
+++ b/src/Cnblogs.DashScope.Core/Internals/ApiLinks.cs
@@ -11,4 +11,5 @@ internal static class ApiLinks
public const string Tasks = "tasks/";
public const string Tokenizer = "tokenizer";
public const string Files = "/compatible-mode/v1/files";
+ public static string Application(string applicationId) => $"apps/{applicationId}/completion";
}
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/IDashScopeWorkspaceConfig.cs b/src/Cnblogs.DashScope.Core/Internals/IDashScopeWorkspaceConfig.cs
new file mode 100644
index 0000000..752056b
--- /dev/null
+++ b/src/Cnblogs.DashScope.Core/Internals/IDashScopeWorkspaceConfig.cs
@@ -0,0 +1,12 @@
+namespace Cnblogs.DashScope.Core.Internals;
+
+///
+/// Workspace configuration.
+///
+internal interface IDashScopeWorkspaceConfig
+{
+ ///
+ /// Unique id of workspace to use.
+ ///
+ public string? WorkspaceId { get; }
+}
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/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/TextChatMessage.cs b/src/Cnblogs.DashScope.Core/TextChatMessage.cs
index 71d6fd8..f61a033 100644
--- a/src/Cnblogs.DashScope.Core/TextChatMessage.cs
+++ b/src/Cnblogs.DashScope.Core/TextChatMessage.cs
@@ -6,18 +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.
-/// Calls to the function.
-[method: JsonConstructor]
-public record TextChatMessage(
- string Role,
- string Content,
- string? Name = null,
- bool? Partial = null,
- List? ToolCalls = null) : IMessage
+public record TextChatMessage : IMessage
{
///
/// Create chat message from an uploaded DashScope file.
@@ -37,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)
+ {
+ this.Role = role;
+ this.Content = content;
+ this.Name = name;
+ this.Partial = partial;
+ this.ReasoningContent = reasoningContent;
+ this.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.
///
@@ -84,11 +117,17 @@ public static TextChatMessage System(string content)
/// The content of the message.
/// When set to true, content of this message would be the prefix of next model output.
/// Author name.
+ /// Think content when reasoning.
/// Tool calls by model.
///
- public static TextChatMessage Assistant(string content, bool? partial = null, string? name = null, List? toolCalls = null)
+ public static TextChatMessage Assistant(
+ string content,
+ bool? partial = null,
+ string? name = null,
+ string? reasoningContent = null,
+ List? toolCalls = null)
{
- return new TextChatMessage(DashScopeRoleNames.Assistant, content, name, partial, toolCalls);
+ return new TextChatMessage(DashScopeRoleNames.Assistant, content, name, partial, reasoningContent, toolCalls);
}
///
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..51eb4ac 100644
--- a/src/Cnblogs.DashScope.Core/TextGenerationChoice.cs
+++ b/src/Cnblogs.DashScope.Core/TextGenerationChoice.cs
@@ -13,5 +13,5 @@ public class TextGenerationChoice
///
/// The generated message.
///
- public required TextChatMessage Message { get; set; }
+ public TextChatMessage Message { get; set; } = new(Array.Empty());
}
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..49800ed 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,18 @@ public class TextGenerationParameters : ITextGenerationParameters
///
public bool? EnableSearch { get; set; }
+ ///
+ public bool? EnableThinking { get; set; }
+
///
public IEnumerable? Tools { get; set; }
///
public ToolChoice? ToolChoice { get; set; }
+ ///
+ public bool? ParallelToolCalls { 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/TextGenerationTokenUsage.cs b/src/Cnblogs.DashScope.Core/TextGenerationTokenUsage.cs
index 8021388..c908e55 100644
--- a/src/Cnblogs.DashScope.Core/TextGenerationTokenUsage.cs
+++ b/src/Cnblogs.DashScope.Core/TextGenerationTokenUsage.cs
@@ -11,6 +11,16 @@ public class TextGenerationTokenUsage
/// This number may larger than input if internet search is enabled.
public int InputTokens { get; set; }
+ ///
+ /// Input token details.
+ ///
+ public TextGenerationPromptTokenDetails? PromptTokensDetails { get; set; }
+
+ ///
+ /// Output token details.
+ ///
+ public TextGenerationOutputTokenDetails? OutputTokensDetails { get; set; }
+
///
/// The number of output token.
///
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/DeepSeek/DeepSeekLlm.cs b/src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekLlm.cs
new file mode 100644
index 0000000..b5ce953
--- /dev/null
+++ b/src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekLlm.cs
@@ -0,0 +1,17 @@
+namespace Cnblogs.DashScope.Sdk.DeepSeek;
+
+///
+/// DeepSeek models.
+///
+public enum DeepSeekLlm
+{
+ ///
+ /// deepseek-v3 model.
+ ///
+ DeepSeekV3 = 1,
+
+ ///
+ /// deepseek-r1 model.
+ ///
+ DeepSeekR1 = 2
+}
diff --git a/src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekLlmName.cs b/src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekLlmName.cs
new file mode 100644
index 0000000..f0c8bfa
--- /dev/null
+++ b/src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekLlmName.cs
@@ -0,0 +1,14 @@
+namespace Cnblogs.DashScope.Sdk.DeepSeek;
+
+internal static class DeepSeekLlmName
+{
+ public static string GetModelName(this DeepSeekLlm model)
+ {
+ return model switch
+ {
+ DeepSeekLlm.DeepSeekR1 => "deepseek-r1",
+ DeepSeekLlm.DeepSeekV3 => "deepseek-v3",
+ _ => ThrowHelper.UnknownModelName(nameof(model), model)
+ };
+ }
+}
diff --git a/src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekTextGenerationApi.cs b/src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekTextGenerationApi.cs
new file mode 100644
index 0000000..ecc988e
--- /dev/null
+++ b/src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekTextGenerationApi.cs
@@ -0,0 +1,87 @@
+using Cnblogs.DashScope.Core;
+
+namespace Cnblogs.DashScope.Sdk.DeepSeek;
+
+///
+/// Extensions for calling DeepSeek models, see: https://help.aliyun.com/zh/model-studio/developer-reference/deepseek
+///
+public static class DeepSeekTextGenerationApi
+{
+ private static TextGenerationParameters StreamingParameters { get; } = new() { IncrementalOutput = true };
+
+ ///
+ /// Get text completion from deepseek model.
+ ///
+ /// The .
+ /// The model name.
+ /// The context messages.
+ ///
+ public static async Task>
+ GetDeepSeekChatCompletionAsync(
+ this IDashScopeClient client,
+ DeepSeekLlm model,
+ IEnumerable messages)
+ {
+ return await client.GetDeepSeekChatCompletionAsync(model.GetModelName(), messages);
+ }
+
+ ///
+ /// Get text completion from deepseek model.
+ ///
+ /// The .
+ /// The model name.
+ /// The context messages.
+ ///
+ public static async Task>
+ GetDeepSeekChatCompletionAsync(
+ this IDashScopeClient client,
+ string model,
+ IEnumerable messages)
+ {
+ return await client.GetTextCompletionAsync(
+ new ModelRequest
+ {
+ Model = model,
+ Input = new TextGenerationInput { Messages = messages },
+ Parameters = null
+ });
+ }
+
+ ///
+ /// Get streamed completion from deepseek model.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IAsyncEnumerable>
+ GetDeepSeekChatCompletionStreamAsync(
+ this IDashScopeClient client,
+ DeepSeekLlm model,
+ IEnumerable messages)
+ {
+ return client.GetDeepSeekChatCompletionStreamAsync(model.GetModelName(), messages);
+ }
+
+ ///
+ /// Get streamed completion from deepseek model.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IAsyncEnumerable>
+ GetDeepSeekChatCompletionStreamAsync(
+ this IDashScopeClient client,
+ string model,
+ IEnumerable messages)
+ {
+ return client.GetTextCompletionStreamAsync(
+ new ModelRequest
+ {
+ Model = model,
+ Input = new TextGenerationInput { Messages = messages },
+ Parameters = StreamingParameters
+ });
+ }
+}
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/QWen/QWenLlm.cs b/src/Cnblogs.DashScope.Sdk/QWen/QWenLlm.cs
index b8dbc98..99beadd 100644
--- a/src/Cnblogs.DashScope.Sdk/QWen/QWenLlm.cs
+++ b/src/Cnblogs.DashScope.Sdk/QWen/QWenLlm.cs
@@ -133,5 +133,10 @@ public enum QWenLlm
///
/// qvq-72b-preview
///
- QwQ72BPreview = 25
+ QvQ72BPreview = 25,
+
+ ///
+ /// qwq-32b
+ ///
+ QwQ32B = 26
}
diff --git a/src/Cnblogs.DashScope.Sdk/QWen/QWenLlmNames.cs b/src/Cnblogs.DashScope.Sdk/QWen/QWenLlmNames.cs
index 8bc299e..a433baf 100644
--- a/src/Cnblogs.DashScope.Sdk/QWen/QWenLlmNames.cs
+++ b/src/Cnblogs.DashScope.Sdk/QWen/QWenLlmNames.cs
@@ -30,7 +30,8 @@ public static string GetModelName(this QWenLlm llm)
QWenLlm.QWenPlusLatest => "qwen-plus-latest",
QWenLlm.QWenTurboLatest => "qwen-turbo-latest",
QWenLlm.QwQ32BPreview => "qwq-32b-preview",
- QWenLlm.QwQ72BPreview => "qwq-72b-preview",
+ QWenLlm.QvQ72BPreview => "qvq-72b-preview",
+ QWenLlm.QwQ32B => "qwq-32b",
_ => ThrowHelper.UnknownModelName(nameof(llm), llm)
};
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ChatClientTests.cs b/test/Cnblogs.DashScope.AI.UnitTests/ChatClientTests.cs
similarity index 90%
rename from test/Cnblogs.DashScope.Sdk.UnitTests/ChatClientTests.cs
rename to test/Cnblogs.DashScope.AI.UnitTests/ChatClientTests.cs
index 89ac79c..84f54c9 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 Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
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.Message.Text.Should().Be(testCase.ResponseModel.Output.Choices?.First().Message.Content);
+ response.Messages[0].Text.Should().Be(testCase.ResponseModel.Output.Choices?.First().Message.Content);
}
[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();
@@ -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,7 +139,7 @@ public async Task ChatClient_ImageRecognition_SuccessAsync()
await dashScopeClient.Received().GetMultimodalGenerationAsync(
Arg.Is>(m => m.IsEquivalent(testCase.RequestModel)),
Arg.Any());
- response.Choices[0].Text.Should()
+ response.Messages[0].Text.Should()
.BeEquivalentTo(testCase.ResponseModel.Output.Choices[0].Message.Content[0].Text);
}
@@ -157,14 +160,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,
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..4c729f0
--- /dev/null
+++ b/test/Cnblogs.DashScope.AI.UnitTests/Cnblogs.DashScope.AI.UnitTests.csproj
@@ -0,0 +1,32 @@
+
+
+
+ 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 91%
rename from test/Cnblogs.DashScope.Sdk.UnitTests/EmbeddingClientTests.cs
rename to test/Cnblogs.DashScope.AI.UnitTests/EmbeddingClientTests.cs
index 96e21b4..f8bad1b 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/EmbeddingClientTests.cs
+++ b/test/Cnblogs.DashScope.AI.UnitTests/EmbeddingClientTests.cs
@@ -1,11 +1,11 @@
using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using Microsoft.Extensions.AI;
using NSubstitute;
using NSubstitute.Extensions;
-namespace Cnblogs.DashScope.Sdk.UnitTests;
+namespace Cnblogs.DashScope.AI.UnitTests;
public class EmbeddingClientTests
{
@@ -27,7 +27,7 @@ public async Task EmbeddingClient_Text_SuccessAsync()
// Act
var response = await client.GenerateAsync(
content,
- new EmbeddingGenerationOptions()
+ new EmbeddingGenerationOptions
{
ModelId = testCase.RequestModel.Model, Dimensions = parameter?.Dimension
});
diff --git a/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Cnblogs.DashScope.Sdk.SnapshotGenerator.csproj b/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Cnblogs.DashScope.Sdk.SnapshotGenerator.csproj
index 8de488e..c23a53c 100644
--- a/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Cnblogs.DashScope.Sdk.SnapshotGenerator.csproj
+++ b/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Cnblogs.DashScope.Sdk.SnapshotGenerator.csproj
@@ -1,11 +1,13 @@
-
- Exe
-
+
+ net8.0
+ Exe
+ false
+
-
-
-
+
+
+
diff --git a/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Program.cs b/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Program.cs
index 3f99f6b..83ba9e2 100644
--- a/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Program.cs
+++ b/test/Cnblogs.DashScope.Sdk.SnapshotGenerator/Program.cs
@@ -11,7 +11,7 @@
apiKey = Console.ReadLine();
}
-var handler = new SocketsHttpHandler() { AutomaticDecompression = DecompressionMethods.All, };
+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}");
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ApplicationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ApplicationSerializationTests.cs
new file mode 100644
index 0000000..d6de7a6
--- /dev/null
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ApplicationSerializationTests.cs
@@ -0,0 +1,139 @@
+using Cnblogs.DashScope.Tests.Shared.Utils;
+using FluentAssertions;
+using NSubstitute;
+
+namespace Cnblogs.DashScope.Sdk.UnitTests;
+
+public class ApplicationSerializationTests
+{
+ [Fact]
+ public async Task SingleCompletion_TextNoSse_SuccessAsync()
+ {
+ // Arrange
+ const bool sse = false;
+ var testCase = Snapshots.Application.SinglePromptNoSse;
+ var (client, handler) = await Sut.GetTestClientAsync(sse, testCase);
+
+ // Act
+ var response = await client.GetApplicationResponseAsync("anyId", testCase.RequestModel);
+
+ // Assert
+ handler.Received().MockSend(
+ Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
+ Arg.Any());
+ response.Should().BeEquivalentTo(testCase.ResponseModel);
+ }
+
+ [Fact]
+ public async Task SingleCompletion_ThoughtNoSse_SuccessAsync()
+ {
+ // Arrange
+ const bool sse = false;
+ var testCase = Snapshots.Application.SinglePromptWithThoughtsNoSse;
+ var (client, handler) = await Sut.GetTestClientAsync(sse, testCase);
+
+ // Act
+ var response = await client.GetApplicationResponseAsync("anyId", testCase.RequestModel);
+
+ // Assert
+ handler.Received().MockSend(
+ Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
+ Arg.Any());
+ response.Should().BeEquivalentTo(testCase.ResponseModel);
+ }
+
+ [Fact]
+ public async Task SingleCompletion_TextSse_SuccessAsync()
+ {
+ // Arrange
+ const bool sse = true;
+ var testCase = Snapshots.Application.SinglePromptSse;
+ var (client, handler) = await Sut.GetTestClientAsync(sse, testCase);
+
+ // Act
+ var outputs = await client.GetApplicationResponseStreamAsync("anyId", testCase.RequestModel).ToListAsync();
+ var text = string.Join(string.Empty, outputs.Select(o => o.Output.Text));
+
+ // Assert
+ 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);
+ }
+
+ [Fact]
+ public async Task ConversationCompletion_SessionIdNoSse_SuccessAsync()
+ {
+ // Arrange
+ const bool sse = false;
+ var testCase = Snapshots.Application.ConversationSessionIdNoSse;
+ var (client, handler) = await Sut.GetTestClientAsync(sse, testCase);
+
+ // Act
+ var response = await client.GetApplicationResponseAsync("anyId", testCase.RequestModel);
+
+ // Assert
+ handler.Received().MockSend(
+ Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
+ Arg.Any());
+ response.Should().BeEquivalentTo(testCase.ResponseModel);
+ }
+
+ [Fact]
+ public async Task ConversationCompletion_MessageNoSse_SuccessAsync()
+ {
+ // Arrange
+ const bool sse = false;
+ var testCase = Snapshots.Application.ConversationMessageNoSse;
+ var (client, handler) = await Sut.GetTestClientAsync(sse, testCase);
+
+ // Act
+ var response = await client.GetApplicationResponseAsync("anyId", testCase.RequestModel);
+
+ // Assert
+ handler.Received().MockSend(
+ Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
+ Arg.Any());
+ response.Should().BeEquivalentTo(testCase.ResponseModel);
+ }
+
+ [Fact]
+ public async Task SingleCompletion_MemoryNoSse_SuccessAsync()
+ {
+ // Arrange
+ const bool sse = false;
+ var testCase = Snapshots.Application.SinglePromptWithMemoryNoSse;
+ var (client, handler) = await Sut.GetTestClientAsync(sse, testCase);
+
+ // Act
+ var response = await client.GetApplicationResponseAsync("anyId", testCase.RequestModel);
+
+ // Assert
+ handler.Received().MockSend(
+ Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
+ Arg.Any());
+ response.Should().BeEquivalentTo(testCase.ResponseModel);
+ }
+
+ [Fact]
+ public async Task SingleCompletion_WorkflowNoSse_SuccessAsync()
+ {
+ // Arrange
+ const bool sse = false;
+ var testCase = Snapshots.Application.WorkflowNoSse;
+ var (client, handler) = await Sut.GetTestClientAsync(sse, testCase);
+
+ // Act
+ var response = await client.GetApplicationResponseAsync("anyId", testCase.RequestModel);
+
+ // Assert
+ handler.Received().MockSend(
+ Arg.Is(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
+ Arg.Any());
+ response.Should().BeEquivalentTo(testCase.ResponseModel);
+ }
+}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/BackgroundGenerationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/BackgroundGenerationSerializationTests.cs
index 6754aa2..91c44c4 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/BackgroundGenerationSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/BackgroundGenerationSerializationTests.cs
@@ -1,4 +1,4 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using NSubstitute;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/BaiChuanApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/BaiChuanApiTests.cs
index 1184b1a..0d99cac 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/BaiChuanApiTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/BaiChuanApiTests.cs
@@ -1,6 +1,6 @@
using Cnblogs.DashScope.Core;
using Cnblogs.DashScope.Sdk.BaiChuan;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
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 0c74f01..94a3a5e 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,39 @@
-
- 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 be60571..93f7d4d 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientTests.cs
@@ -75,6 +75,36 @@ public void DashScopeClient_Constructor_WithApiKeyHeader()
.BeEquivalentTo(new AuthenticationHeaderValue("Bearer", apiKey));
}
+ [Fact]
+ public void DashScopeClient_Constructor_WithWorkspaceId()
+ {
+ // Arrange
+ const string apiKey = "key";
+ const string workspaceId = "workspaceId";
+ var client = new DashScopeClient(apiKey, null, null, workspaceId);
+
+ // Act
+ var value = HttpClientAccessor.GetValue(client) as HttpClient;
+
+ // Assert
+ value?.DefaultRequestHeaders.GetValues("X-DashScope-WorkSpace").Should().BeEquivalentTo(workspaceId);
+ }
+
+ [Fact]
+ public void DashScopeClient_Constructor_WithPrivateEndpoint()
+ {
+ // Arrange
+ const string apiKey = "key";
+ const string privateEndpoint = "https://dashscope.cnblogs.com/api/v1";
+ var client = new DashScopeClient(apiKey, null, privateEndpoint);
+
+ // Act
+ var value = HttpClientAccessor.GetValue(client) as HttpClient;
+
+ // Assert
+ value?.BaseAddress.Should().BeEquivalentTo(new Uri(privateEndpoint));
+ }
+
public static TheoryData ParamsShouldNotCache
=> new()
{
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/DeepSeekTextGenerationApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/DeepSeekTextGenerationApiTests.cs
new file mode 100644
index 0000000..461b037
--- /dev/null
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/DeepSeekTextGenerationApiTests.cs
@@ -0,0 +1,82 @@
+using Cnblogs.DashScope.Core;
+using Cnblogs.DashScope.Sdk.DeepSeek;
+using NSubstitute;
+
+namespace Cnblogs.DashScope.Sdk.UnitTests;
+
+public class DeepSeekTextGenerationApiTests
+{
+ [Fact]
+ public async Task TextCompletion_UseEnum_SuccessAsync()
+ {
+ // Arrange
+ var client = Substitute.For();
+
+ // Act
+ 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));
+ }
+
+ [Fact]
+ public async Task TextCompletion_UseCustomModel_SuccessAsync()
+ {
+ // Arrange
+ const string customModel = "deepseek-v3";
+ var client = Substitute.For();
+
+ // Act
+ 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));
+ }
+
+ [Fact]
+ public void StreamCompletion_UseEnum_SuccessAsync()
+ {
+ // Arrange
+ var client = Substitute.For();
+
+ // Act
+ _ = 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));
+ }
+
+ [Fact]
+ public void StreamCompletion_CustomModel_SuccessAsync()
+ {
+ // Arrange
+ const string customModel = "deepseek-v3";
+ var client = Substitute.For();
+
+ // Act
+ _ = 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));
+ }
+}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs
index e660852..ba17907 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ErrorTests.cs
@@ -1,5 +1,5 @@
using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs
index 05faa58..9b92f25 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/FileSerializationTests.cs
@@ -1,4 +1,4 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using NSubstitute;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ImageGenerationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ImageGenerationSerializationTests.cs
index d0d30b7..752e922 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ImageGenerationSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ImageGenerationSerializationTests.cs
@@ -1,4 +1,4 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using NSubstitute;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ImageSynthesisSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ImageSynthesisSerializationTests.cs
index 909c6bf..47013e0 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ImageSynthesisSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ImageSynthesisSerializationTests.cs
@@ -1,4 +1,4 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using NSubstitute;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/Llama2TextGenerationApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/Llama2TextGenerationApiTests.cs
index 29c1a8c..c4d5679 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/Llama2TextGenerationApiTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/Llama2TextGenerationApiTests.cs
@@ -1,6 +1,6 @@
using Cnblogs.DashScope.Core;
using Cnblogs.DashScope.Sdk.Llama2;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/MultimodalGenerationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/MultimodalGenerationSerializationTests.cs
index 8889981..0170d0d 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/MultimodalGenerationSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/MultimodalGenerationSerializationTests.cs
@@ -1,6 +1,6 @@
using System.Text;
using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using NSubstitute;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenMultimodalApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenMultimodalApiTests.cs
index e3b5424..c2c15ce 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()
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTextGenerationApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTextGenerationApiTests.cs
index ad9d5fb..ddd6640 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;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTokenizerTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTokenizerTests.cs
index 8f91a35..69f0192 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTokenizerTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/QWenTokenizerTests.cs
@@ -9,475 +9,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()
@@ -503,20 +70,9 @@ public void QWenTokenizer_Decode_SuccessAsync()
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);
+ tokens.Should().BeEquivalentTo(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-text-nosse.response.body.txt b/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/single-generation-text-nosse.response.body.txt
deleted file mode 100644
index 8d85edb..0000000
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/single-generation-text-nosse.response.body.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "output": {
- "finish_reason": "stop",
- "text": "1+1 等于 2。这是最基本的数学加法之一,在十进制计数体系中,任何情况下两个一相加的结果都是二。"
- },
- "usage": {
- "total_tokens": 43,
- "output_tokens": 35,
- "input_tokens": 8
- },
- "request_id": "4ef2ed16-4dc3-9083-a723-fb2e80c84d3b"
-}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/single-generation-text-nosse.response.header.txt b/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/single-generation-text-nosse.response.header.txt
deleted file mode 100644
index 3b4f8a2..0000000
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/RawHttpData/single-generation-text-nosse.response.header.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-HTTP/1.1 200 OK
-eagleeye-traceid: 5bc840127c9dd7ed1de5586c0865cf01
-content-type: application/json
-x-dashscope-call-gateway: true
-req-cost-time: 6004
-req-arrive-time: 1708924139239
-resp-start-time: 1708924145244
-x-envoy-upstream-service-time: 5998
-content-encoding: gzip
-vary: Accept-Encoding
-date: Mon, 26 Feb 2024 05:09:05 GMT
-server: istio-envoy
-transfer-encoding: chunked
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/TaskSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/TaskSerializationTests.cs
index 149d2db..745e9c5 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/TaskSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/TaskSerializationTests.cs
@@ -1,5 +1,5 @@
using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
namespace Cnblogs.DashScope.Sdk.UnitTests;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/TextEmbeddingApiTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/TextEmbeddingApiTests.cs
index bbe1abb..5ad66d3 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/TextEmbeddingApiTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/TextEmbeddingApiTests.cs
@@ -1,6 +1,6 @@
using Cnblogs.DashScope.Core;
using Cnblogs.DashScope.Sdk.TextEmbedding;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using NSubstitute;
namespace Cnblogs.DashScope.Sdk.UnitTests;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/TextEmbeddingSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/TextEmbeddingSerializationTests.cs
index a662ade..0b380ce 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/TextEmbeddingSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/TextEmbeddingSerializationTests.cs
@@ -1,4 +1,4 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using NSubstitute;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/TextGenerationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/TextGenerationSerializationTests.cs
index eda61e4..9a9406e 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/TextGenerationSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/TextGenerationSerializationTests.cs
@@ -1,6 +1,6 @@
using System.Text;
using Cnblogs.DashScope.Core;
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using NSubstitute;
@@ -68,18 +68,27 @@ public async Task SingleCompletion_MessageFormatNoSse_SuccessAsync(
response.Should().BeEquivalentTo(testCase.ResponseModel);
}
- [Fact]
- public async Task SingleCompletion_MessageFormatSse_SuccessAsync()
+ [Theory]
+ [MemberData(nameof(SingleGenerationMessageSseFormatData))]
+ public async Task SingleCompletion_MessageFormatSse_SuccessAsync(
+ RequestSnapshot,
+ ModelResponse> snapshot)
{
// Arrange
const bool sse = true;
- var testCase = Snapshots.TextGeneration.MessageFormat.SingleMessageIncremental;
+ var testCase = snapshot;
var (client, handler) = await Sut.GetTestClientAsync(sse, testCase);
// Act
var message = new StringBuilder();
+ var reasoning = new StringBuilder();
var outputs = await client.GetTextCompletionStreamAsync(testCase.RequestModel).ToListAsync();
- outputs.ForEach(x => message.Append(x.Output.Choices![0].Message.Content));
+ outputs.ForEach(
+ x =>
+ {
+ message.Append(x.Output.Choices![0].Message.Content);
+ reasoning.Append(x.Output.Choices![0].Message.ReasoningContent ?? string.Empty);
+ });
// Assert
handler.Received().MockSend(
@@ -88,8 +97,11 @@ public async Task SingleCompletion_MessageFormatSse_SuccessAsync()
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));
+ o => o.Excluding(y => y.Output.Choices![0].Message.Content)
+ .Excluding(y => y.Output.Choices![0].Message.ReasoningContent));
message.ToString().Should().Be(testCase.ResponseModel.Output.Choices![0].Message.Content);
+ reasoning.ToString().Should()
+ .Be(testCase.ResponseModel.Output.Choices![0].Message.ReasoningContent ?? string.Empty);
}
[Theory]
@@ -141,9 +153,15 @@ public async Task ConversationCompletion_MessageFormatSse_SuccessAsync(
public static readonly TheoryData,
ModelResponse>> SingleGenerationMessageFormatData = new(
Snapshots.TextGeneration.MessageFormat.SingleMessage,
+ Snapshots.TextGeneration.MessageFormat.SingleMessageReasoning,
Snapshots.TextGeneration.MessageFormat.SingleMessageWithTools,
Snapshots.TextGeneration.MessageFormat.SingleMessageJson);
+ public static readonly TheoryData,
+ ModelResponse>> SingleGenerationMessageSseFormatData = new(
+ Snapshots.TextGeneration.MessageFormat.SingleMessageIncremental,
+ Snapshots.TextGeneration.MessageFormat.SingleMessageReasoningIncremental);
+
public static readonly TheoryData,
ModelResponse>> ConversationMessageFormatSseData = new(
Snapshots.TextGeneration.MessageFormat.ConversationMessageIncremental,
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/TextGenerationStopConverterTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/TextGenerationStopConverterTests.cs
index 26c4af3..e269682 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/TextGenerationStopConverterTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/TextGenerationStopConverterTests.cs
@@ -50,20 +50,26 @@ public record TestObj(TextGenerationStop? Stop);
public static TheoryData Data
=> new()
{
- { new TextGenerationStop("hello"), """{"stop":"hello"}""" },
- { new TextGenerationStop(["hello", "world"]), """{"stop":["hello","world"]}""" },
- { new TextGenerationStop([12, 334]), """{"stop":[12,334]}""" },
- { new TextGenerationStop([[12, 334]]), """{"stop":[[12,334]]}""" },
- { null, """{"stop":null}""" }
+ { new TextGenerationStop("hello"), @"{""stop"":""hello""}" },
+ {
+ new TextGenerationStop(new List { "hello", "world" }.AsReadOnly()),
+ "{\"stop\":[\"hello\",\"world\"]}"
+ },
+ { new TextGenerationStop(new[] { 12, 334 }), "{\"stop\":[12,334]}" },
+ {
+ new TextGenerationStop(new List { new[] { 12, 334 } }.AsReadOnly()),
+ "{\"stop\":[[12,334]]}"
+ },
+ { null, "{\"stop\":null}" }
};
public static TheoryData InvalidJson
=> new()
{
- """{"stop":{}}""",
- """{"stop":[1234,"hello"]}""",
- """{"stop":["hello"}}""",
- """{"stop":[[34243,"hello"]]}""",
- """{"stop":[[34243,123]}}"""
+ "{\"stop\":{}}",
+ "{\"stop\":[1234,\"hello\"]}",
+ "{\"stop\":[\"hello\"}}",
+ "{\"stop\":[[34243,\"hello\"]]}",
+ "{\"stop\":[[34243,123]}}"
};
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/TokenizationSerializationTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/TokenizationSerializationTests.cs
index 08e4cae..6c3a49b 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/TokenizationSerializationTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/TokenizationSerializationTests.cs
@@ -1,4 +1,4 @@
-using Cnblogs.DashScope.Sdk.UnitTests.Utils;
+using Cnblogs.DashScope.Tests.Shared.Utils;
using FluentAssertions;
using NSubstitute;
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/ToolChoiceJsonConverterTests.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/ToolChoiceJsonConverterTests.cs
index 077a4b0..c9a23c7 100644
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/ToolChoiceJsonConverterTests.cs
+++ b/test/Cnblogs.DashScope.Sdk.UnitTests/ToolChoiceJsonConverterTests.cs
@@ -50,19 +50,19 @@ public record TestObj(ToolChoice? Choice);
public static TheoryData Data
=> new()
{
- { ToolChoice.AutoChoice, """{"choice":"auto"}""" },
- { ToolChoice.NoneChoice, """{"choice":"none"}""" },
- { ToolChoice.FunctionChoice("weather"), """{"choice":{"type":"function","function":{"name":"weather"}}}""" },
- { null, """{"choice":null}""" }
+ { ToolChoice.AutoChoice, "{\"choice\":\"auto\"}" },
+ { ToolChoice.NoneChoice, "{\"choice\":\"none\"}" },
+ { ToolChoice.FunctionChoice("weather"), "{\"choice\":{\"type\":\"function\",\"function\":{\"name\":\"weather\"}}}" },
+ { null, "{\"choice\":null}" }
};
public static TheoryData InvalidJson
=> new()
{
- """{"choice":{}}""",
- """{"choice":"other"}""",
- """{"choice":{"type":"other"}}""",
- """{"choice":{"type":"other", "function":{"name": "weather"}}}""",
- """{"choice":{"type":"function", "function": "other"}}"""
+ "{\"choice\":{}}",
+ "{\"choice\":\"other\"}",
+ "{\"choice\":{\"type\":\"other\"}}",
+ "{\"choice\":{\"type\":\"other\", \"function\":{\"name\": \"weather\"}}}",
+ "{\"choice\":{\"type\":\"function\", \"function\": \"other\"}}"
};
}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/Utils/GetCurrentWeatherParameters.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/Utils/GetCurrentWeatherParameters.cs
deleted file mode 100644
index 3d606f3..0000000
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/Utils/GetCurrentWeatherParameters.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Text.Json.Serialization;
-using Json.More;
-using Json.Schema.Generation;
-
-namespace Cnblogs.DashScope.Sdk.UnitTests.Utils;
-
-public record GetCurrentWeatherParameters(
- [property: Required]
- [property: Description("要获取天气的省市名称,例如浙江省杭州市")]
- string Location,
- [property: JsonConverter(typeof(EnumStringConverter))]
- [property: Description("温度单位")]
- TemperatureUnit Unit = TemperatureUnit.Celsius);
-
-public enum TemperatureUnit
-{
- Celsius,
- Fahrenheit
-}
diff --git a/test/Cnblogs.DashScope.Sdk.UnitTests/Utils/Snapshots.cs b/test/Cnblogs.DashScope.Sdk.UnitTests/Utils/Snapshots.cs
deleted file mode 100644
index b5e2483..0000000
--- a/test/Cnblogs.DashScope.Sdk.UnitTests/Utils/Snapshots.cs
+++ /dev/null
@@ -1,1808 +0,0 @@
-using Cnblogs.DashScope.Core;
-using Json.Schema;
-using Json.Schema.Generation;
-
-namespace Cnblogs.DashScope.Sdk.UnitTests.Utils;
-
-public static class Snapshots
-{
- public static class Error
- {
- public static readonly
- RequestSnapshot, DashScopeError>
- AuthError = new(
- "auth-error",
- new ModelRequest
- {
- Model = "qwen-max",
- Input = new TextGenerationInput { Prompt = "请问 1+1 是多少?" },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "text",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = false
- }
- },
- new DashScopeError
- {
- Code = "InvalidApiKey",
- Message = "Invalid API-key provided.",
- RequestId = "a1c0561c-1dfe-98a6-a62f-983577b8bc5e"
- });
-
- public static readonly
- RequestSnapshot, DashScopeError>
- ParameterError = new(
- "parameter-error",
- new ModelRequest
- {
- Model = "qwen-max",
- Input = new TextGenerationInput { Prompt = "请问 1+1 是多少?", Messages = [] },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "text",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = false
- }
- },
- new DashScopeError
- {
- Code = "InvalidParameter",
- Message = "Role must be user or assistant and Content length must be greater than 0",
- RequestId = "a5898c04-d210-901b-965f-e4bd90478805"
- });
-
- public static readonly
- RequestSnapshot, DashScopeError>
- ParameterErrorSse = new(
- "parameter-error",
- new ModelRequest
- {
- Model = "qwen-max",
- Input = new TextGenerationInput { Prompt = "请问 1+1 是多少?", Messages = [] },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "text",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = true
- }
- },
- new DashScopeError
- {
- Code = "InvalidParameter",
- Message = "Role must be user or assistant and Content length must be greater than 0",
- RequestId = "7671ecd8-93cc-9ee9-bc89-739f0fd8b809"
- });
-
- public static readonly RequestSnapshot UploadErrorNoSse = new(
- "upload-file-error",
- new DashScopeError
- {
- Code = "invalid_request_error",
- Message = "'purpose' must be 'file-extract'",
- RequestId = string.Empty
- });
- }
-
- public static class TextGeneration
- {
- public static class TextFormat
- {
- public static readonly RequestSnapshot,
- ModelResponse>
- SinglePrompt = new(
- "single-generation-text",
- new ModelRequest
- {
- Model = "qwen-max",
- Input = new TextGenerationInput { Prompt = "请问 1+1 是多少?" },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "text",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = false
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- FinishReason = "stop", Text = "1+1 等于 2。这是最基本的数学加法之一,在十进制计数体系中,任何情况下两个一相加的结果都是二。"
- },
- RequestId = "4ef2ed16-4dc3-9083-a723-fb2e80c84d3b",
- Usage = new TextGenerationTokenUsage
- {
- InputTokens = 8,
- OutputTokens = 35,
- TotalTokens = 43
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- SinglePromptIncremental = new(
- "single-generation-text",
- new ModelRequest
- {
- Model = "qwen-max",
- Input = new TextGenerationInput { Prompt = "请问 1+1 是多少?" },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "text",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = true
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput { FinishReason = "stop", Text = "1+1等于2。" },
- RequestId = "5b441aa7-0b9c-9fbc-ae0a-e2b212b71eac",
- Usage = new TextGenerationTokenUsage
- {
- InputTokens = 16,
- OutputTokens = 6,
- TotalTokens = 22
- }
- });
- }
-
- public static class MessageFormat
- {
- public static readonly RequestSnapshot,
- ModelResponse>
- SingleMessage = new(
- "single-generation-message",
- new ModelRequest
- {
- Model = "qwen-max",
- Input =
- new TextGenerationInput { Messages = [TextChatMessage.User("请问 1+1 是多少?")] },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "message",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = false
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- Choices =
- [
- new TextGenerationChoice
- {
- FinishReason = "stop",
- Message = TextChatMessage.Assistant(
- "1+1 等于 2。这是最基本的数学加法之一,在十进制计数体系中,任何两个相同的数字相加都等于该数字的二倍。")
- }
- ]
- },
- RequestId = "e764bfe3-c0b7-97a0-ae57-cd99e1580960",
- Usage = new TextGenerationTokenUsage
- {
- TotalTokens = 47,
- OutputTokens = 39,
- InputTokens = 8
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- SingleChatClientMessage = new(
- "single-generation-message",
- new ModelRequest
- {
- Model = "qwen-max",
- Input =
- new TextGenerationInput { Messages = [TextChatMessage.User("请问 1+1 是多少?")] },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "message",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- ToolChoice = ToolChoice.AutoChoice
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- Choices =
- [
- new TextGenerationChoice
- {
- FinishReason = "stop",
- Message = TextChatMessage.Assistant(
- "1+1 等于 2。这是最基本的数学加法之一,在十进制计数体系中,任何两个相同的数字相加都等于该数字的二倍。")
- }
- ]
- },
- RequestId = "e764bfe3-c0b7-97a0-ae57-cd99e1580960",
- Usage = new TextGenerationTokenUsage
- {
- TotalTokens = 47,
- OutputTokens = 39,
- InputTokens = 8
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- SingleMessageJson = new(
- "single-generation-message-json",
- new ModelRequest
- {
- Model = "qwen-max",
- Input =
- new TextGenerationInput { Messages = [TextChatMessage.User("请问 1+1 是多少?用 JSON 格式输出。")] },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "message",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = false,
- ResponseFormat = DashScopeResponseFormat.Json
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- Choices =
- [
- new TextGenerationChoice
- {
- FinishReason = "stop",
- Message = TextChatMessage.Assistant("{\n \"result\": 2\n}")
- }
- ]
- },
- RequestId = "6af9571b-1033-98f9-a287-c06f2e9d6f7f",
- Usage = new TextGenerationTokenUsage
- {
- TotalTokens = 34,
- OutputTokens = 9,
- InputTokens = 25
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- SingleMessageIncremental = new(
- "single-generation-message",
- new ModelRequest
- {
- Model = "qwen-max",
- Input =
- new TextGenerationInput { Messages = [TextChatMessage.User("请问 1+1 是多少?")] },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "message",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = true
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- Choices =
- [
- new TextGenerationChoice
- {
- FinishReason = "stop",
- Message = TextChatMessage.Assistant(
- "1+1 等于 2。这是最基本的数学加法之一,在十进制计数体系中,任何情况下 1 加上另一个 1 的结果都是 2。")
- }
- ]
- },
- RequestId = "d272255f-82d7-9cc7-93c5-17ff77024349",
- Usage = new TextGenerationTokenUsage
- {
- TotalTokens = 48,
- OutputTokens = 40,
- InputTokens = 8
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- SingleMessageChatClientIncremental = new(
- "single-generation-message",
- new ModelRequest
- {
- Model = "qwen-max",
- Input =
- new TextGenerationInput { Messages = [TextChatMessage.User("请问 1+1 是多少?")] },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "message",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new[] { "你好" },
- IncrementalOutput = true,
- ToolChoice = ToolChoice.AutoChoice
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- Choices =
- [
- new TextGenerationChoice
- {
- FinishReason = "stop",
- Message = TextChatMessage.Assistant(
- "1+1 等于 2。这是最基本的数学加法之一,在十进制计数体系中,任何情况下 1 加上另一个 1 的结果都是 2。")
- }
- ]
- },
- RequestId = "d272255f-82d7-9cc7-93c5-17ff77024349",
- Usage = new TextGenerationTokenUsage
- {
- TotalTokens = 48,
- OutputTokens = 40,
- InputTokens = 8
- }
- });
-
- public static readonly
- RequestSnapshot,
- ModelResponse> SingleMessageWithTools =
- new(
- "single-generation-message-with-tools",
- new ModelRequest
- {
- Model = "qwen-max",
- Input = new TextGenerationInput { Messages = [TextChatMessage.User("杭州现在的天气如何?")] },
- Parameters = new TextGenerationParameters()
- {
- ResultFormat = "message",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- PresencePenalty = 1.2f,
- Temperature = 0.85f,
- Stop = new TextGenerationStop("你好"),
- EnableSearch = false,
- IncrementalOutput = false,
- Tools =
- [
- new ToolDefinition(
- "function",
- new FunctionDefinition(
- "get_current_weather",
- "获取现在的天气",
- new JsonSchemaBuilder().FromType(
- new SchemaGeneratorConfiguration
- {
- PropertyNameResolver = PropertyNameResolvers.LowerSnakeCase
- })
- .Build()))
- ],
- ToolChoice = ToolChoice.FunctionChoice("get_current_weather")
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- Choices =
- [
- new TextGenerationChoice
- {
- FinishReason = "stop",
- Message = TextChatMessage.Assistant(
- string.Empty,
- toolCalls:
- [
- new ToolCall(
- "call_cec4c19d27624537b583af",
- ToolTypes.Function,
- 0,
- new FunctionCall(
- "get_current_weather",
- """{"location": "浙江省杭州市"}"""))
- ])
- }
- ]
- },
- RequestId = "67300049-c108-9987-b1c1-8e0ee2de6b5d",
- Usage = new TextGenerationTokenUsage
- {
- InputTokens = 211,
- OutputTokens = 8,
- TotalTokens = 219
- }
- });
-
- public static readonly
- RequestSnapshot,
- ModelResponse> SingleMessageChatClientWithTools =
- new(
- "single-generation-message-with-tools",
- new ModelRequest
- {
- Model = "qwen-max",
- Input = new TextGenerationInput { Messages = [TextChatMessage.User("杭州现在的天气如何?")] },
- Parameters = new TextGenerationParameters()
- {
- ResultFormat = "message",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- PresencePenalty = 1.2f,
- Temperature = 0.85f,
- Tools =
- [
- new ToolDefinition(
- "function",
- new FunctionDefinition(
- "get_current_weather",
- "获取现在的天气",
- new JsonSchemaBuilder().FromType(
- new SchemaGeneratorConfiguration
- {
- PropertyNameResolver = PropertyNameResolvers.LowerSnakeCase
- })
- .Build()))
- ],
- ToolChoice = ToolChoice.FunctionChoice("get_current_weather")
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- Choices =
- [
- new TextGenerationChoice
- {
- FinishReason = "stop",
- Message = TextChatMessage.Assistant(
- string.Empty,
- toolCalls:
- [
- new ToolCall(
- "call_cec4c19d27624537b583af",
- ToolTypes.Function,
- 0,
- new FunctionCall(
- "get_current_weather",
- """{"location": "浙江省杭州市"}"""))
- ])
- }
- ]
- },
- RequestId = "67300049-c108-9987-b1c1-8e0ee2de6b5d",
- Usage = new TextGenerationTokenUsage
- {
- InputTokens = 211,
- OutputTokens = 8,
- TotalTokens = 219
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- ConversationPartialMessageNoSse = new(
- "conversation-generation-message-partial",
- new ModelRequest()
- {
- Model = "qwen-max",
- Input = new TextGenerationInput()
- {
- Messages =
- [
- TextChatMessage.User("请对“春天来了,大地”这句话进行续写,来表达春天的美好和作者的喜悦之情"),
- TextChatMessage.Assistant("春天来了,大地", true)
- ]
- },
- Parameters = new TextGenerationParameters()
- {
- ResultFormat = ResultFormats.Message,
- Seed = 1234,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false
- }
- },
- new ModelResponse()
- {
- RequestId = "4c45d7fd-3158-9ff4-96a0-6e92c710df2c",
- Output = new TextGenerationOutput()
- {
- Choices =
- [
- new TextGenerationChoice()
- {
- FinishReason = "stop",
- Message =
- TextChatMessage.Assistant(
- "仿佛从漫长的冬眠中苏醒过来,万物复苏。嫩绿的小草悄悄地探出了头,争先恐后地想要沐浴在温暖的阳光下;五彩斑斓的花朵也不甘示弱,竞相绽放着自己最美丽的姿态,将田野、山林装扮得分外妖娆。微风轻轻吹过,带来了泥土的气息与花香混合的独特香味,让人心旷神怡。小鸟们开始忙碌起来,在枝头欢快地歌唱,似乎也在庆祝这个充满希望的新季节的到来。这一切美好景象不仅让人感受到了大自然的魅力所在,更激发了人们对生活无限热爱和向往的心情。")
- }
- ]
- },
- Usage = new TextGenerationTokenUsage()
- {
- TotalTokens = 165,
- OutputTokens = 131,
- InputTokens = 34
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- ConversationMessageIncremental = new(
- "conversation-generation-message",
- new ModelRequest
- {
- Model = "qwen-max",
- Input =
- new TextGenerationInput
- {
- Messages =
- [
- TextChatMessage.User("现在请你记住一个数字,42"),
- TextChatMessage.Assistant("好的,我已经记住了这个数字。"),
- TextChatMessage.User("请问我刚才提到的数字是多少?")
- ]
- },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "message",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = true
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- Choices =
- [
- new TextGenerationChoice
- {
- FinishReason = "stop", Message = TextChatMessage.Assistant("您刚才提到的数字是42。")
- }
- ]
- },
- RequestId = "9188e907-56c2-9849-97f6-23f130f7fed7",
- Usage = new TextGenerationTokenUsage
- {
- TotalTokens = 33,
- OutputTokens = 9,
- InputTokens = 24
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- ConversationMessageWithFilesIncremental = new(
- "conversation-generation-message-with-files",
- new ModelRequest
- {
- Model = "qwen-long",
- Input =
- new TextGenerationInput
- {
- Messages =
- [
- TextChatMessage.File(
- ["file-fe-WTTG89tIUTd4ByqP3K48R3bn", "file-fe-l92iyRvJm9vHCCfonLckf1o2"]),
- TextChatMessage.User("这两个文件是相同的吗?")
- ]
- },
- Parameters = new TextGenerationParameters
- {
- ResultFormat = "message",
- Seed = 1234,
- MaxTokens = 1500,
- TopP = 0.8f,
- TopK = 100,
- RepetitionPenalty = 1.1f,
- Temperature = 0.85f,
- Stop = new int[][] { [37763, 367] },
- EnableSearch = false,
- IncrementalOutput = true
- }
- },
- new ModelResponse
- {
- Output = new TextGenerationOutput
- {
- Choices =
- [
- new TextGenerationChoice
- {
- FinishReason = "stop",
- Message = TextChatMessage.Assistant(
- "你上传的两个文件并不相同。第一个文件`test1.txt`包含两行文本,每行都是“测试”。而第二个文件`test2.txt`只有一行文本,“测试2”。尽管它们都含有“测试”这个词,但具体内容和结构不同。")
- }
- ]
- },
- RequestId = "7865ae43-8379-9c79-bef6-95050868bc52",
- Usage = new TextGenerationTokenUsage
- {
- TotalTokens = 115,
- OutputTokens = 57,
- InputTokens = 58
- }
- });
- }
- }
-
- public static class MultimodalGeneration
- {
- public static readonly RequestSnapshot,
- ModelResponse> VlNoSse =
- new(
- "multimodal-generation-vl",
- new ModelRequest
- {
- Model = "qwen-vl-plus",
- Input = new MultimodalInput
- {
- Messages =
- [
- MultimodalMessage.System(
- [MultimodalMessageContent.TextContent("You are a helpful assistant.")]),
- MultimodalMessage.User(
- [
- MultimodalMessageContent.ImageContent(
- "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg"),
- MultimodalMessageContent.TextContent("这个图片是哪里,请用简短的语言回答")
- ])
- ]
- },
- Parameters = new MultimodalParameters
- {
- Seed = 1234,
- TopK = 100,
- TopP = 0.81f,
- Temperature = 1.1f,
- VlHighResolutionImages = true,
- RepetitionPenalty = 1.3f,
- PresencePenalty = 1.2f,
- MaxTokens = 120,
- Stop = "你好"
- }
- },
- new ModelResponse
- {
- Output = new MultimodalOutput(
- [
- new MultimodalChoice(
- "stop",
- MultimodalMessage.Assistant(
- [
- MultimodalMessageContent.TextContent("海滩。")
- ]))
- ]),
- RequestId = "e81aa922-be6c-9f9d-bd4f-0f43e21fd913",
- Usage = new MultimodalTokenUsage
- {
- OutputTokens = 3,
- InputTokens = 3613,
- ImageTokens = 3577
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse> VlChatClientNoSse =
- new(
- "multimodal-generation-vl",
- new ModelRequest
- {
- Model = "qwen-vl-plus",
- Input = new MultimodalInput
- {
- Messages =
- [
- MultimodalMessage.User(
- [
- MultimodalMessageContent.ImageContent(
- "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg"),
- MultimodalMessageContent.TextContent("这个图片是哪里,请用简短的语言回答")
- ])
- ]
- },
- Parameters = new MultimodalParameters
- {
- Seed = 1234,
- TopK = 100,
- TopP = 0.81f,
- Temperature = 1.1f,
- RepetitionPenalty = 1.3f,
- PresencePenalty = 1.2f,
- MaxTokens = 120,
- }
- },
- new ModelResponse
- {
- Output = new MultimodalOutput(
- [
- new MultimodalChoice(
- "stop",
- MultimodalMessage.Assistant(
- [
- MultimodalMessageContent.TextContent("海滩。")
- ]))
- ]),
- RequestId = "e81aa922-be6c-9f9d-bd4f-0f43e21fd913",
- Usage = new MultimodalTokenUsage
- {
- OutputTokens = 3,
- InputTokens = 3613,
- ImageTokens = 3577
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse> VlSse =
- new(
- "multimodal-generation-vl",
- new ModelRequest
- {
- Model = "qwen-vl-plus",
- Input = new MultimodalInput
- {
- Messages =
- [
- MultimodalMessage.System(
- [MultimodalMessageContent.TextContent("You are a helpful assistant.")]),
- MultimodalMessage.User(
- [
- MultimodalMessageContent.ImageContent(
- "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg"),
- MultimodalMessageContent.TextContent("这个图片是哪里,请用简短的语言回答")
- ])
- ]
- },
- Parameters = new MultimodalParameters
- {
- IncrementalOutput = true,
- Seed = 1234,
- TopK = 100,
- TopP = 0.81f
- }
- },
- new ModelResponse
- {
- Output = new MultimodalOutput(
- [
- new MultimodalChoice(
- "stop",
- MultimodalMessage.Assistant(
- [
- MultimodalMessageContent.TextContent(
- "这是一个海滩,有沙滩和海浪。在前景中坐着一个女人与她的宠物狗互动。背景中有海水、阳光及远处的海岸线。由于没有具体标识物或地标信息,我无法提供更精确的位置描述。这可能是一个公共海滩或是私人区域。重要的是要注意不要泄露任何个人隐私,并遵守当地的规定和法律法规。欣赏自然美景的同时请尊重环境和其他访客。")
- ]))
- ]),
- RequestId = "13c5644d-339c-928a-a09a-e0414bfaa95c",
- Usage = new MultimodalTokenUsage
- {
- OutputTokens = 85,
- InputTokens = 1283,
- ImageTokens = 1247
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse> VlChatClientSse =
- new(
- "multimodal-generation-vl",
- new ModelRequest
- {
- Model = "qwen-vl-plus",
- Input = new MultimodalInput
- {
- Messages =
- [
- MultimodalMessage.User(
- [
- MultimodalMessageContent.ImageContent(
- "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg"),
- MultimodalMessageContent.TextContent("这个图片是哪里,请用简短的语言回答")
- ])
- ]
- },
- Parameters = new MultimodalParameters
- {
- IncrementalOutput = true,
- Seed = 1234,
- TopK = 100,
- TopP = 0.81f,
- }
- },
- new ModelResponse
- {
- Output = new MultimodalOutput(
- [
- new MultimodalChoice(
- "stop",
- MultimodalMessage.Assistant(
- [
- MultimodalMessageContent.TextContent(
- "这是一个海滩,有沙滩和海浪。在前景中坐着一个女人与她的宠物狗互动。背景中有海水、阳光及远处的海岸线。由于没有具体标识物或地标信息,我无法提供更精确的位置描述。这可能是一个公共海滩或是私人区域。重要的是要注意不要泄露任何个人隐私,并遵守当地的规定和法律法规。欣赏自然美景的同时请尊重环境和其他访客。")
- ]))
- ]),
- RequestId = "13c5644d-339c-928a-a09a-e0414bfaa95c",
- Usage = new MultimodalTokenUsage
- {
- OutputTokens = 85,
- InputTokens = 1283,
- ImageTokens = 1247
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- OcrNoSse = new(
- "multimodal-generation-vl-ocr",
- new ModelRequest
- {
- Model = "qwen-vl-ocr",
- Input = new MultimodalInput
- {
- Messages =
- [
- MultimodalMessage.User(
- [
- MultimodalMessageContent.ImageContent(
- "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/ctdzex/biaozhun.jpg",
- 3136,
- 1003520),
- MultimodalMessageContent.TextContent("Read all the text in the image.")
- ]),
- ]
- },
- Parameters = new MultimodalParameters
- {
- Temperature = 0.1f,
- RepetitionPenalty = 1.05f,
- MaxTokens = 2000,
- TopP = 0.01f
- }
- },
- new ModelResponse
- {
- RequestId = "195c98cd-4ee5-998b-b662-132b7aebc048",
- Output = new MultimodalOutput(
- [
- new MultimodalChoice(
- "stop",
- MultimodalMessage.Assistant(
- [
- MultimodalMessageContent.TextContent(
- "读者对象 如果你是Linux环境下的系统管理员,那么学会编写shell脚本将让你受益匪浅。本书并未细述安装 Linux系统的每个步骤,但只要系统已安装好Linux并能运行起来,你就可以开始考虑如何让一些日常 的系统管理任务实现自动化。这时shell脚本编程就能发挥作用了,这也正是本书的作用所在。本书将 演示如何使用shell脚本来自动处理系统管理任务,包括从监测系统统计数据和数据文件到为你的老板 生成报表。 如果你是家用Linux爱好者,同样能从本书中获益。现今,用户很容易在诸多部件堆积而成的图形环境 中迷失。大多数桌面Linux发行版都尽量向一般用户隐藏系统的内部细节。但有时你确实需要知道内部 发生了什么。本书将告诉你如何启动Linux命令行以及接下来要做什么。通常,如果是执行一些简单任 务(比如文件管理) , 在命令行下操作要比在华丽的图形界面下方便得多。在命令行下有大量的命令 可供使用,本书将会展示如何使用它们。")
- ]))
- ]),
- Usage = new MultimodalTokenUsage
- {
- InputTokens = 1248,
- OutputTokens = 225,
- ImageTokens = 1219
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- OcrSse = new(
- "multimodal-generation-vl-ocr",
- new ModelRequest
- {
- Model = "qwen-vl-ocr",
- Input = new MultimodalInput
- {
- Messages =
- [
- MultimodalMessage.User(
- [
- MultimodalMessageContent.ImageContent(
- "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/ctdzex/biaozhun.jpg",
- 3136,
- 1003520),
- MultimodalMessageContent.TextContent("Read all the text in the image.")
- ]),
- ]
- },
- Parameters = new MultimodalParameters
- {
- Temperature = 0.1f,
- RepetitionPenalty = 1.05f,
- MaxTokens = 2000,
- TopP = 0.01f,
- IncrementalOutput = true
- }
- },
- new ModelResponse
- {
- RequestId = "fb33a990-3826-9386-8b0a-8317dfc38c1c",
- Output = new MultimodalOutput(
- [
- new MultimodalChoice(
- "stop",
- MultimodalMessage.Assistant(
- [
- MultimodalMessageContent.TextContent(
- "读者对象 如果你是Linux环境下的系统管理员,那么学会编写shell脚本将让你受益匪浅。本书并未细述安装 Linux系统的每个步骤,但只要系统已安装好Linux并能运行起来,你就可以开始考虑如何让一些日常 的系统管理任务实现自动化。这时shell脚本编程就能发挥作用了,这也正是本书的作用所在。本书将 演示如何使用shell脚本来自动处理系统管理任务,包括从监测系统统计数据和数据文件到为你的老板 生成报表。 如果你是家用Linux爱好者,同样能从本书中获益。现今,用户很容易在诸多部件堆积而成的图形环境 中迷失。大多数桌面Linux发行版都尽量向一般用户隐藏系统的内部细节。但有时你确实需要知道内部 发生了什么。本书将告诉你如何启动Linux命令行以及接下来要做什么。通常,如果是执行一些简单任 务(比如文件管理) , 在命令行下操作要比在华丽的图形界面下方便得多。在命令行下有大量的命令 可供使用,本书将会展示如何使用它们。")
- ]))
- ]),
- Usage = new MultimodalTokenUsage
- {
- InputTokens = 1248,
- OutputTokens = 225,
- ImageTokens = 1219
- }
- });
-
- public static readonly RequestSnapshot,
- ModelResponse>
- AudioNoSse = new(
- "multimodal-generation-audio",
- new ModelRequest
- {
- Model = "qwen-audio-turbo",
- Input = new MultimodalInput
- {
- Messages =
- [
- MultimodalMessage.System(
- [MultimodalMessageContent.TextContent("You are a helpful assistant.")]),
- MultimodalMessage.User(
- [
- MultimodalMessageContent.AudioContent(
- "https://dashscope.oss-cn-beijing.aliyuncs.com/audios/2channel_16K.wav"),
- MultimodalMessageContent.TextContent("这段音频在说什么,请用简短的语言回答")
- ])
- ]
- },
- Parameters = new MultimodalParameters
- {
- Seed = 1234,
- TopK = 100,
- TopP = 0.81f
- }
- },
- new ModelResponse