Skip to content

Commit 6e4fafd

Browse files
committed
test: add unit tests for qwen-deep-research
1 parent ca16220 commit 6e4fafd

File tree

28 files changed

+624
-283
lines changed

28 files changed

+624
-283
lines changed

README.zh-Hans.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,8 +1597,6 @@ Deleting file1...Success
15971597

15981598
需要注意的是,使用 `qwen-deep-research` 模型时,模型回复会放在 `chunk.Output.Message` 里,而不是 `chunk.Output.Choice[0].Message`
15991599

1600-
模型的研究阶段可以在 `chunk.Output.Message.Phase` 里得到。
1601-
16021600
示例请求:
16031601

16041602
```csh
Lines changed: 1 addition & 274 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
using System.Diagnostics;
2-
using System.Text;
3-
using System.Text.Json;
4-
using Cnblogs.DashScope.Core;
1+
using Cnblogs.DashScope.Core;
52
using Cnblogs.DashScope.Sample;
63
using Cnblogs.DashScope.Sample.Text;
7-
using Cnblogs.DashScope.Sdk;
8-
using Cnblogs.DashScope.Sdk.QWen;
9-
using Cnblogs.DashScope.Sdk.Wanx;
10-
using Json.Schema;
11-
using Json.Schema.Generation;
12-
using Microsoft.Extensions.AI;
134

145
Console.WriteLine("Reading key from environment variable DASHSCOPE_KEY");
156
var apiKey = Environment.GetEnvironmentVariable("DASHSCOPE_KEY", EnvironmentVariableTarget.Process)
@@ -45,267 +36,3 @@
4536
}
4637

4738
await samples[index].RunAsync(dashScopeClient);
48-
return;
49-
50-
// text completion
51-
async Task TextCompletionAsync(string prompt)
52-
{
53-
var response = await dashScopeClient.GetQWenCompletionAsync(QWenLlm.QWenMax, prompt);
54-
Console.WriteLine(response.Output.Text);
55-
}
56-
57-
// text completion stream
58-
async Task TextCompletionStreamAsync(string prompt)
59-
{
60-
var stream = dashScopeClient.GetQWenCompletionStreamAsync(
61-
QWenLlm.QWenMax,
62-
prompt,
63-
new TextGenerationParameters { IncrementalOutput = true });
64-
await foreach (var modelResponse in stream)
65-
{
66-
Console.Write(modelResponse.Output.Text);
67-
}
68-
}
69-
70-
async Task ChatStreamAsync()
71-
{
72-
var history = new List<TextChatMessage>();
73-
while (true)
74-
{
75-
Console.Write("user > ");
76-
var input = Console.ReadLine()!;
77-
history.Add(TextChatMessage.User(input));
78-
var stream = dashScopeClient
79-
.GetQWenChatStreamAsync(
80-
QWenLlm.QWenPlusLatest,
81-
history,
82-
new TextGenerationParameters
83-
{
84-
IncrementalOutput = true,
85-
ResultFormat = ResultFormats.Message,
86-
EnableThinking = true
87-
});
88-
var role = string.Empty;
89-
var message = new StringBuilder();
90-
await foreach (var modelResponse in stream)
91-
{
92-
var chunk = modelResponse.Output.Choices![0];
93-
if (string.IsNullOrEmpty(role) && string.IsNullOrEmpty(chunk.Message.Role) == false)
94-
{
95-
role = chunk.Message.Role;
96-
Console.Write(chunk.Message.Role + " > ");
97-
}
98-
99-
message.Append(chunk.Message.Content);
100-
var write = string.IsNullOrEmpty(chunk.Message.ReasoningContent)
101-
? chunk.Message.Content
102-
: chunk.Message.ReasoningContent;
103-
Console.Write(write);
104-
}
105-
106-
Console.WriteLine();
107-
history.Add(new TextChatMessage(role, message.ToString()));
108-
}
109-
110-
// ReSharper disable once FunctionNeverReturns
111-
}
112-
113-
async Task ChatWithImageAsync()
114-
{
115-
var image = File.OpenRead("Lenna.jpg");
116-
var ossLink = await dashScopeClient.UploadTemporaryFileAsync("qvq-plus", image, "Lenna.jpg");
117-
Console.WriteLine($"Successfully uploaded temp file: {ossLink}");
118-
var response = dashScopeClient.GetMultimodalGenerationStreamAsync(
119-
new ModelRequest<MultimodalInput, IMultimodalParameters>()
120-
{
121-
Model = "qvq-plus",
122-
Input = new MultimodalInput()
123-
{
124-
Messages =
125-
[
126-
MultimodalMessage.User(
127-
[
128-
MultimodalMessageContent.ImageContent(ossLink),
129-
MultimodalMessageContent.TextContent("她是谁?")
130-
])
131-
]
132-
},
133-
Parameters = new MultimodalParameters { IncrementalOutput = true, VlHighResolutionImages = false }
134-
});
135-
var reasoning = false;
136-
await foreach (var modelResponse in response)
137-
{
138-
var choice = modelResponse.Output.Choices.FirstOrDefault();
139-
if (choice != null)
140-
{
141-
if (choice.FinishReason != "null")
142-
{
143-
break;
144-
}
145-
146-
if (string.IsNullOrEmpty(choice.Message.ReasoningContent) == false)
147-
{
148-
if (reasoning == false)
149-
{
150-
reasoning = true;
151-
Console.WriteLine("<think>");
152-
}
153-
154-
Console.Write(choice.Message.ReasoningContent);
155-
continue;
156-
}
157-
158-
if (reasoning)
159-
{
160-
reasoning = false;
161-
Console.WriteLine("</think>");
162-
}
163-
164-
Console.Write(choice.Message.Content[0].Text);
165-
}
166-
}
167-
}
168-
169-
async Task ChatWithFilesAsync()
170-
{
171-
var history = new List<TextChatMessage>();
172-
Console.WriteLine("uploading file \"test.txt\" ");
173-
var file = new FileInfo("test.txt");
174-
var uploadedFile = await dashScopeClient.UploadFileAsync(file.OpenRead(), file.Name);
175-
Console.WriteLine("file uploaded, id: " + uploadedFile.Id);
176-
Console.WriteLine();
177-
178-
var fileMessage = TextChatMessage.File(uploadedFile.Id);
179-
history.Add(fileMessage);
180-
Console.WriteLine("system > " + fileMessage.Content);
181-
var userPrompt = TextChatMessage.User("该文件的内容是什么");
182-
history.Add(userPrompt);
183-
Console.WriteLine("user > " + userPrompt.Content);
184-
var stream = dashScopeClient.GetQWenChatStreamAsync(
185-
QWenLlm.QWenLong,
186-
history,
187-
new TextGenerationParameters { IncrementalOutput = true, ResultFormat = ResultFormats.Message });
188-
var role = string.Empty;
189-
var message = new StringBuilder();
190-
await foreach (var modelResponse in stream)
191-
{
192-
var chunk = modelResponse.Output.Choices![0];
193-
if (string.IsNullOrEmpty(role) && string.IsNullOrEmpty(chunk.Message.Role) == false)
194-
{
195-
role = chunk.Message.Role;
196-
Console.Write(chunk.Message.Role + " > ");
197-
}
198-
199-
message.Append(chunk.Message.Content);
200-
Console.Write(chunk.Message.Content);
201-
}
202-
203-
Console.WriteLine();
204-
history.Add(new TextChatMessage(role, message.ToString()));
205-
206-
Console.WriteLine();
207-
Console.WriteLine("Deleting file by id: " + uploadedFile.Id);
208-
var result = await dashScopeClient.DeleteFileAsync(uploadedFile.Id);
209-
Console.WriteLine("Deletion result: " + result.Deleted);
210-
}
211-
212-
async Task ChatWithToolsAsync()
213-
{
214-
var history = new List<TextChatMessage>();
215-
var tools = new List<ToolDefinition>
216-
{
217-
new(
218-
ToolTypes.Function,
219-
new FunctionDefinition(
220-
nameof(GetWeather),
221-
"获得当前天气",
222-
new JsonSchemaBuilder().FromType<WeatherReportParameters>().Build()))
223-
};
224-
var chatParameters = new TextGenerationParameters { ResultFormat = ResultFormats.Message, Tools = tools };
225-
var question = TextChatMessage.User("请问现在杭州的天气如何?");
226-
history.Add(question);
227-
Console.WriteLine($"{question.Role} > {question.Content}");
228-
229-
var response = await dashScopeClient.GetQWenChatCompletionAsync(QWenLlm.QWenMax, history, chatParameters);
230-
var toolCallMessage = response.Output.Choices![0].Message;
231-
history.Add(toolCallMessage);
232-
Console.WriteLine(
233-
$"{toolCallMessage.Role} > {toolCallMessage.ToolCalls![0].Function.Name}{toolCallMessage.ToolCalls[0].Function.Arguments}");
234-
235-
var toolResponse = GetWeather(
236-
JsonSerializer.Deserialize<WeatherReportParameters>(toolCallMessage.ToolCalls[0].Function.Arguments!)!);
237-
var toolMessage = TextChatMessage.Tool(toolResponse, nameof(GetWeather));
238-
history.Add(toolMessage);
239-
Console.WriteLine($"{toolMessage.Role} > {toolMessage.Content}");
240-
241-
var answer = await dashScopeClient.GetQWenChatCompletionAsync(QWenLlm.QWenMax, history, chatParameters);
242-
Console.WriteLine($"{answer.Output.Choices![0].Message.Role} > {answer.Output.Choices[0].Message.Content}");
243-
244-
string GetWeather(WeatherReportParameters parameters)
245-
{
246-
return "大部多云,气温 "
247-
+ parameters.Unit switch
248-
{
249-
TemperatureUnit.Celsius => "18 摄氏度",
250-
TemperatureUnit.Fahrenheit => "64 华氏度",
251-
_ => throw new InvalidOperationException()
252-
};
253-
}
254-
}
255-
256-
async Task ChatWithMicrosoftExtensions()
257-
{
258-
Console.WriteLine("Requesting model...");
259-
var chatClient = dashScopeClient.AsChatClient("qwen-max");
260-
List<ChatMessage> conversation =
261-
new() { new(ChatRole.System, "You are a helpful AI assistant"), new(ChatRole.User, "What is AI?") };
262-
var response = await chatClient.GetResponseAsync(conversation);
263-
var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true };
264-
Console.WriteLine(JsonSerializer.Serialize(response, serializerOptions));
265-
}
266-
267-
async Task Text2ImageAsync()
268-
{
269-
Console.Write("Prompt> ");
270-
var prompt = Console.ReadLine();
271-
if (string.IsNullOrEmpty(prompt))
272-
{
273-
Console.WriteLine("Using sample prompt");
274-
prompt = "A fluffy cat";
275-
}
276-
277-
var task = await dashScopeClient.CreateWanxImageSynthesisTaskAsync(
278-
WanxModel.WanxV21Turbo,
279-
prompt,
280-
null,
281-
new ImageSynthesisParameters { Style = ImageStyles.OilPainting });
282-
Console.WriteLine($"Task({task.TaskId}) submitted, checking status...");
283-
var watch = Stopwatch.StartNew();
284-
while (watch.Elapsed.TotalSeconds < 120)
285-
{
286-
var result = await dashScopeClient.GetWanxImageSynthesisTaskAsync(task.TaskId);
287-
Console.WriteLine($"{watch.ElapsedMilliseconds}ms - Status: {result.Output.TaskStatus}");
288-
if (result.Output.TaskStatus == DashScopeTaskStatus.Succeeded)
289-
{
290-
Console.WriteLine($"Image generation finished, URL: {result.Output.Results![0].Url}");
291-
return;
292-
}
293-
294-
if (result.Output.TaskStatus == DashScopeTaskStatus.Failed)
295-
{
296-
Console.WriteLine($"Image generation failed, error message: {result.Output.Message}");
297-
return;
298-
}
299-
300-
await Task.Delay(500);
301-
}
302-
303-
Console.WriteLine($"Task timout, taskId: {task.TaskId}");
304-
}
305-
306-
async Task ApplicationCallAsync(string applicationId, string prompt)
307-
{
308-
var request = new ApplicationRequest { Input = new ApplicationInput { Prompt = prompt } };
309-
var response = await dashScopeClient.GetApplicationResponseAsync(applicationId, request);
310-
Console.WriteLine(response.Output.Text);
311-
}

sample/Cnblogs.DashScope.Sample/Text/ChatSample.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ public async Task RunAsync(IDashScopeClient client)
3434
var usage = completion.Usage;
3535
if (usage != null)
3636
{
37-
Console.WriteLine($"Usage: in({usage.InputTokens})/out({usage.OutputTokens})/total({usage.TotalTokens})");
37+
Console.WriteLine(
38+
$"Usage: in({usage.InputTokens})/out({usage.OutputTokens})/total({usage.TotalTokens})");
3839
}
3940

4041
messages.Add(TextChatMessage.Assistant(completion.Output.Choices[0].Message.Content));
4142
}
43+
44+
// ReSharper disable once FunctionNeverReturns
4245
}
4346
}
4447

sample/Cnblogs.DashScope.Sample/Text/ChatWebSearchSample.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Text;
2-
using System.Text.Json;
32
using Cnblogs.DashScope.Core;
43

54
namespace Cnblogs.DashScope.Sample.Text;

src/Cnblogs.DashScope.Core/DashScopeDeepResearchTask.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ public class DashScopeDeepResearchTask
3333
/// The content from tool calls.
3434
/// </summary>
3535
[JsonPropertyName("learningMap")]
36-
public Dictionary<string, object>? LearningMap { get; set; }
36+
public Dictionary<string, string>? LearningMap { get; set; }
3737
}

test/Cnblogs.DashScope.Sdk.UnitTests/TextGenerationSerializationTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,41 @@ public async Task ConversationCompletion_MessageFormatSse_SuccessAsync(
169169
Assert.Equivalent(testCase.ResponseModel, last);
170170
}
171171

172+
[Fact]
173+
public async Task ConversationCompletion_DeepResearchSse_ValidateRequestAsync()
174+
{
175+
// Arrange
176+
const bool sse = true;
177+
var testCase = Snapshots.TextGeneration.MessageFormat.DeepResearchTypingIncremental;
178+
var (client, handler) = await Sut.GetTestClientAsync(sse, testCase);
179+
180+
// Act
181+
await client.GetTextCompletionStreamAsync(testCase.RequestModel).ToListAsync();
182+
183+
// Assert
184+
handler.Received().MockSend(
185+
Arg.Is<HttpRequestMessage>(m => Checkers.IsJsonEquivalent(m.Content!, testCase.GetRequestJson(sse))),
186+
Arg.Any<CancellationToken>());
187+
}
188+
189+
[Theory]
190+
[MemberData(nameof(DeepResearchSseData))]
191+
public async Task ConversationCompletion_DeepResearchSse_SuccessAsync(
192+
RequestSnapshot<ModelRequest<TextGenerationInput, ITextGenerationParameters>,
193+
ModelResponse<TextGenerationOutput, TextGenerationTokenUsage>> testCase)
194+
{
195+
// Arrange
196+
const bool sse = true;
197+
var (client, _) = await Sut.GetTestClientAsync(sse, testCase);
198+
199+
// Act
200+
var outputs = await client.GetTextCompletionStreamAsync(testCase.RequestModel).ToListAsync();
201+
var response = outputs.First();
202+
203+
// Assert
204+
Assert.Equivalent(testCase.ResponseModel, response);
205+
}
206+
172207
public static readonly TheoryData<RequestSnapshot<ModelRequest<TextGenerationInput, ITextGenerationParameters>,
173208
ModelResponse<TextGenerationOutput, TextGenerationTokenUsage>>> SingleGenerationMessageFormatData = new(
174209
Snapshots.TextGeneration.MessageFormat.SingleMessage,
@@ -195,4 +230,16 @@ public async Task ConversationCompletion_MessageFormatSse_SuccessAsync(
195230
public static readonly TheoryData<RequestSnapshot<ModelRequest<TextGenerationInput, ITextGenerationParameters>,
196231
ModelResponse<TextGenerationOutput, TextGenerationTokenUsage>>> ConversationMessageFormatNoSseData = new(
197232
Snapshots.TextGeneration.MessageFormat.ConversationPartialMessageNoSse);
233+
234+
public static readonly TheoryData<RequestSnapshot<ModelRequest<TextGenerationInput, ITextGenerationParameters>,
235+
ModelResponse<TextGenerationOutput, TextGenerationTokenUsage>>> DeepResearchSseData = new(
236+
Snapshots.TextGeneration.MessageFormat.DeepResearchTypingIncremental,
237+
Snapshots.TextGeneration.MessageFormat.DeepResearchWebResearchStreamingQueriesIncremental,
238+
Snapshots.TextGeneration.MessageFormat.DeepResearchWebResearchStreamingQueriesResearchGoalIncremental,
239+
Snapshots.TextGeneration.MessageFormat.DeepResearchWebResearchStreamingWebResultsIncremental,
240+
Snapshots.TextGeneration.MessageFormat.DeepResearchWebResearchStreamingWebResultsLearningMapIncremental,
241+
Snapshots.TextGeneration.MessageFormat.DeepResearchWebResearchWebResultFinishedIncremental,
242+
Snapshots.TextGeneration.MessageFormat.DeepResearchAnswerReferenceIncremental,
243+
Snapshots.TextGeneration.MessageFormat.DeepResearchAnswerFinishedIncremental,
244+
Snapshots.TextGeneration.MessageFormat.DeepResearchKeepAliveIncremental);
198245
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
id:1
2+
event:result
3+
:HTTP_STATUS/200
4+
data:{"output":{"message":{"phase":"answer","role":"assistant","content":"","extra":{"deep_research":{}},"status":"finished"},"fininshed":false,"fininshed_reason":"null"},"usage":{"input_tokens":652,"output_tokens":8930},"request_id":"eee227a4-d38f-4b8e-8c7f-167e76dbdc34"}

0 commit comments

Comments
 (0)