Skip to content

feat: support reasoning models #87

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 54 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ builder.AddDashScopeClient(builder.Configuration);
```json
{
"DashScope": {
"ApiKey": "your-api-key"
"ApiKey": "your-api-key",
}
}
```
Expand All @@ -66,21 +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.Sdk.UnitTests/Utils/Snapshots.cs) for calling samples.

Visit [tests](./test/Cnblogs.DashScope.Sdk.UnitTests) for more usage of each api.

## General Text Completion API

Use `client.GetTextCompletionAsync` and `client.GetTextCompletionStreamAsync` to access text generation api directly.

```csharp
var completion = await dashScopeClient.GetTextCompletionAsync(
new ModelRequest<TextGenerationInput, ITextGenerationParameters>
{
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<TextGenerationInput, ITextGenerationParameters>
{
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

Expand All @@ -90,6 +123,19 @@ var completion = await client.GetQWenCompletionAsync(QWenLlm.QWenMax, prompt);
Console.WriteLine(completion.Output.Text);
```

## Reasoning

Use `completion.Output.Choices![0].Message.ReasoningContent` to access the reasoning content from model.

```csharp
var history = new List<ChatMessage>
{
ChatMessage.User("Calculate 1+1")
};
var completion = await client.GetDeepSeekChatCompletionAsync(DeepSeekLlm.DeepSeekR1, history);
Console.WriteLine(completion.Output.Choices[0]!.Message.ReasoningContent);
```

## Multi-round chat

```csharp
Expand Down
69 changes: 59 additions & 10 deletions README.zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,23 +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`
- 应用调用 `dashScopeClient.GetApplicationResponseAsync` 和 `dashScopeClient.GetApplicationResponseStreamAsync()`
- 文生图 - `CreateWanxImageSynthesisTaskAsync()` `GetWanxImageSynthesisTaskAsync()`
- 人像风格重绘 - `CreateWanxImageGenerationTaskAsync()` `GetWanxImageGenerationTaskAsync()`
- 图像背景生成 - `CreateWanxBackgroundGenerationTaskAsync()` `GetWanxBackgroundGenerationTaskAsync()`
- 适用于 QWen-Long 的文件 API `UploadFileAsync()` 和 `DeleteFileAsync`
- 应用调用 `GetApplicationResponseAsync` 和 `GetApplicationResponseStreamAsync()`
- 其他使用相同 Endpoint 的模型

# 示例

查看 [Snapshots.cs](./test/Cnblogs.DashScope.Sdk.UnitTests/Utils/Snapshots.cs) 获得 API 调用参数示例.

查看 [测试](./test) 获得更多 API 使用示例。

## 文本生成

使用 `dashScopeClient.GetTextCompletionAsync` 和 `dashScopeClient.GetTextCompletionStreamAsync` 来直接访问文本生成接口。

相关文档:https://help.aliyun.com/zh/model-studio/user-guide/text-generation/

```csharp
var completion = await dashScopeClient.GetTextCompletionAsync(
new ModelRequest<TextGenerationInput, ITextGenerationParameters>
{
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<TextGenerationInput, ITextGenerationParameters>
{
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
Expand All @@ -108,6 +144,19 @@ 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>
{
TextChatMessage.User("Calculate 1+1")
};
var completion = await client.GetDeepSeekChatCompletionAsync(DeepSeekLlm.DeepSeekR1, history);
Console.WriteLine(completion.Output.Choices[0]!.Message.ReasoningContent);
```

## 工具调用

创建一个可供模型使用的方法。
Expand Down
5 changes: 3 additions & 2 deletions src/Cnblogs.DashScope.AI/DashScopeChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
{
update.Contents.Add(
new UsageContent(
new UsageDetails()
new UsageDetails
{
InputTokenCount = response.Usage.InputTokens,
OutputTokenCount = response.Usage.OutputTokens,
Expand All @@ -208,7 +208,7 @@ public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
RawRepresentation = completion.Messages[0].RawRepresentation,
CreatedAt = completion.CreatedAt,
FinishReason = completion.FinishReason,
ModelId = completion.ModelId,
ModelId = completion.ModelId
};
}
else
Expand Down Expand Up @@ -467,6 +467,7 @@ private IEnumerable<TextChatMessage> ToTextChatMessages(
from.Text,
from.AuthorName,
null,
null,
functionCall.Count > 0 ? functionCall : null);
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/Cnblogs.DashScope.Core/TextChatMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ namespace Cnblogs.DashScope.Core;
/// <param name="Content">The content of this message.</param>
/// <param name="Name">Used when role is tool, represents the function name of this message generated by.</param>
/// <param name="Partial">Notify model that next message should use this message as prefix.</param>
/// <param name="ReasoningContent">Reasoning content for reasoning model.</param>
/// <param name="ToolCalls">Calls to the function.</param>
[method: JsonConstructor]
public record TextChatMessage(
string Role,
string Content,
string? Name = null,
bool? Partial = null,
string? ReasoningContent = null,
List<ToolCall>? ToolCalls = null) : IMessage<string>
{
/// <summary>
Expand Down Expand Up @@ -84,11 +86,17 @@ public static TextChatMessage System(string content)
/// <param name="content">The content of the message.</param>
/// <param name="partial">When set to true, content of this message would be the prefix of next model output.</param>
/// <param name="name">Author name.</param>
/// <param name="reasoningContent">Think content when reasoning.</param>
/// <param name="toolCalls">Tool calls by model.</param>
/// <returns></returns>
public static TextChatMessage Assistant(string content, bool? partial = null, string? name = null, List<ToolCall>? toolCalls = null)
public static TextChatMessage Assistant(
string content,
bool? partial = null,
string? name = null,
string? reasoningContent = null,
List<ToolCall>? toolCalls = null)
{
return new TextChatMessage(DashScopeRoleNames.Assistant, content, name, partial, toolCalls);
return new TextChatMessage(DashScopeRoleNames.Assistant, content, name, partial, reasoningContent, toolCalls);
}

/// <summary>
Expand Down
17 changes: 17 additions & 0 deletions src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekLlm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Cnblogs.DashScope.Sdk.DeepSeek;

/// <summary>
/// DeepSeek models.
/// </summary>
public enum DeepSeekLlm
{
/// <summary>
/// deepseek-v3 model.
/// </summary>
DeepSeekV3 = 1,

/// <summary>
/// deepseek-r1 model.
/// </summary>
DeepSeekR1 = 2
}
14 changes: 14 additions & 0 deletions src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekLlmName.cs
Original file line number Diff line number Diff line change
@@ -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)
};
}
}
87 changes: 87 additions & 0 deletions src/Cnblogs.DashScope.Sdk/DeepSeek/DeepSeekTextGenerationApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using Cnblogs.DashScope.Core;

namespace Cnblogs.DashScope.Sdk.DeepSeek;

/// <summary>
/// Extensions for calling DeepSeek models, see: https://help.aliyun.com/zh/model-studio/developer-reference/deepseek
/// </summary>
public static class DeepSeekTextGenerationApi
{
private static TextGenerationParameters StreamingParameters { get; } = new() { IncrementalOutput = true };

/// <summary>
/// Get text completion from deepseek model.
/// </summary>
/// <param name="client">The <see cref="IDashScopeClient"/>.</param>
/// <param name="model">The model name.</param>
/// <param name="messages">The context messages.</param>
/// <returns></returns>
public static async Task<ModelResponse<TextGenerationOutput, TextGenerationTokenUsage>>
GetDeepSeekChatCompletionAsync(
this IDashScopeClient client,
DeepSeekLlm model,
IEnumerable<TextChatMessage> messages)
{
return await client.GetDeepSeekChatCompletionAsync(model.GetModelName(), messages);
}

/// <summary>
/// Get text completion from deepseek model.
/// </summary>
/// <param name="client">The <see cref="IDashScopeClient"/>.</param>
/// <param name="model">The model name.</param>
/// <param name="messages">The context messages.</param>
/// <returns></returns>
public static async Task<ModelResponse<TextGenerationOutput, TextGenerationTokenUsage>>
GetDeepSeekChatCompletionAsync(
this IDashScopeClient client,
string model,
IEnumerable<TextChatMessage> messages)
{
return await client.GetTextCompletionAsync(
new ModelRequest<TextGenerationInput, ITextGenerationParameters>
{
Model = model,
Input = new TextGenerationInput { Messages = messages },
Parameters = null
});
}

/// <summary>
/// Get streamed completion from deepseek model.
/// </summary>
/// <param name="client"></param>
/// <param name="model"></param>
/// <param name="messages"></param>
/// <returns></returns>
public static IAsyncEnumerable<ModelResponse<TextGenerationOutput, TextGenerationTokenUsage>>
GetDeepSeekChatCompletionStreamAsync(
this IDashScopeClient client,
DeepSeekLlm model,
IEnumerable<TextChatMessage> messages)
{
return client.GetDeepSeekChatCompletionStreamAsync(model.GetModelName(), messages);
}

/// <summary>
/// Get streamed completion from deepseek model.
/// </summary>
/// <param name="client"></param>
/// <param name="model"></param>
/// <param name="messages"></param>
/// <returns></returns>
public static IAsyncEnumerable<ModelResponse<TextGenerationOutput, TextGenerationTokenUsage>>
GetDeepSeekChatCompletionStreamAsync(
this IDashScopeClient client,
string model,
IEnumerable<TextChatMessage> messages)
{
return client.GetTextCompletionStreamAsync(
new ModelRequest<TextGenerationInput, ITextGenerationParameters>
{
Model = model,
Input = new TextGenerationInput { Messages = messages },
Parameters = StreamingParameters
});
}
}
7 changes: 6 additions & 1 deletion src/Cnblogs.DashScope.Sdk/QWen/QWenLlm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,10 @@ public enum QWenLlm
/// <summary>
/// qvq-72b-preview
/// </summary>
QwQ72BPreview = 25
QvQ72BPreview = 25,

/// <summary>
/// qwq-32b
/// </summary>
QwQ32B = 26
}
3 changes: 2 additions & 1 deletion src/Cnblogs.DashScope.Sdk/QWen/QWenLlmNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
}
Expand Down
Loading