.NET开发者的AI助手:通义千问1.5-1.8B模型C#集成教程
本文介绍了如何在星图GPU平台上自动化部署通义千问1.5-1.8B-Chat-GPTQ-Int4 WebUI镜像,并详细指导.NET开发者使用C#集成该模型服务。通过封装HTTP客户端,开发者可轻松在ASP.NET Core Web API或WPF桌面应用中实现智能对话功能,为.NET应用快速添加AI助手能力。
.NET开发者的AI助手:通义千问1.8B模型C#集成教程
最近在.NET社区里,关于如何把大模型能力集成到自家应用里的讨论越来越多了。很多朋友觉得,这玩意儿听起来高大上,但真要动手做,是不是得从Python开始学起,还得搞一堆复杂的环境配置?
其实完全不用那么麻烦。如果你手头有已经部署好的模型服务,比如在星图GPU平台上跑起来的通义千问1.8B模型,那么用你最熟悉的C#和.NET技术栈,花上半小时,就能让应用拥有智能对话的能力。
今天我就带你走一遍完整的流程,从理解API怎么调用,到封装一个顺手好用的服务类,最后在ASP.NET Core Web API和WPF桌面程序里各集成一个聊天功能。整个过程,你只需要会写C#代码,会用HttpClient发请求,就能搞定。
1. 准备工作:理解我们要对接什么
在开始写代码之前,咱们先得搞清楚目标是什么。你不是在本地运行一个几十GB的模型,而是去调用一个已经部署好的服务。这个服务提供了标准的HTTP接口,你的C#程序只需要像调用普通Web API一样,给它发请求、收响应就行了。
通义千问1.8B模型的Chat API,通常提供一个/v1/chat/completions这样的端点。你发一段对话历史过去,它返回模型生成的回复。数据格式基本遵循OpenAI的Chat Completion API规范,这对我们来说是个好消息,因为社区里已经有成熟的封装模式可以参考。
你需要准备的东西很简单:
- 一个已经部署好的通义千问1.8B模型服务地址(比如
http://your-server-ip:port) - 一个能跑.NET 6/7/8的开发环境(Visual Studio 2022 或 VS Code都行)
- 基础的C#异步编程知识(会用
async/await)
模型服务那边怎么部署的,我们今天不展开,假设你已经通过星图镜像广场或者其他方式,让服务在某个地方跑起来了。我们的任务,就是让C#程序能和这个服务“对话”。
2. 核心步骤:封装模型服务客户端
直接裸用HttpClient发请求也能工作,但代码会显得很零散,不好维护。咱们先花点时间,封装一个专门用于调用通义千问模型的客户端类。这样,在主程序里用起来就干净多了。
2.1 定义数据模型
首先,定义API请求和响应对应的C#类。这能让序列化和反序列化变得非常直观。
using System.Text.Json.Serialization;
namespace QwenClient.Models
{
// 单条消息的格式
public class ChatMessage
{
[JsonPropertyName("role")]
public string Role { get; set; } = "user"; // "system", "user", "assistant"
[JsonPropertyName("content")]
public string Content { get; set; } = string.Empty;
}
// 发送给API的请求体
public class ChatCompletionRequest
{
[JsonPropertyName("model")]
public string Model { get; set; } = "qwen1.8b-chat"; // 模型名称,按实际部署的填
[JsonPropertyName("messages")]
public List<ChatMessage> Messages { get; set; } = new();
[JsonPropertyName("temperature")]
public float Temperature { get; set; } = 0.7f; // 控制随机性,0-2之间
[JsonPropertyName("max_tokens")]
public int MaxTokens { get; set; } = 1024; // 生成的最大长度
[JsonPropertyName("stream")]
public bool Stream { get; set; } = false; // 是否流式输出,我们先做非流式
}
// API返回的响应体(非流式)
public class ChatCompletionResponse
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("choices")]
public List<ChatChoice> Choices { get; set; } = new();
[JsonPropertyName("usage")]
public TokenUsage Usage { get; set; } = new();
}
public class ChatChoice
{
[JsonPropertyName("index")]
public int Index { get; set; }
[JsonPropertyName("message")]
public ChatMessage Message { get; set; } = new();
[JsonPropertyName("finish_reason")]
public string FinishReason { get; set; } = string.Empty;
}
public class TokenUsage
{
[JsonPropertyName("prompt_tokens")]
public int PromptTokens { get; set; }
[JsonPropertyName("completion_tokens")]
public int CompletionTokens { get; set; }
[JsonPropertyName("total_tokens")]
public int TotalTokens { get; set; }
}
}
这几个类定义了数据在C#世界里的形状。注意我们用JsonPropertyName特性来匹配JSON里的字段名,这样System.Text.Json序列化的时候就不会出错。
2.2 实现服务客户端
接下来是重头戏,实现一个QwenAIClient类,它负责所有和模型API的通信细节。
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace QwenClient.Services
{
public interface IQwenAIService
{
Task<string> GetChatResponseAsync(List<ChatMessage> messages, CancellationToken cancellationToken = default);
}
public class QwenAIClient : IQwenAIService
{
private readonly HttpClient _httpClient;
private readonly string _apiKey; // 如果API需要密钥
private readonly JsonSerializerOptions _jsonOptions;
public QwenAIClient(string baseAddress, string apiKey = "")
{
_httpClient = new HttpClient
{
BaseAddress = new Uri(baseAddress.TrimEnd('/') + "/") // 确保地址以/结尾
};
_apiKey = apiKey;
// 配置JSON序列化选项
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
// 设置默认请求头
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (!string.IsNullOrEmpty(_apiKey))
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
}
}
public async Task<string> GetChatResponseAsync(List<ChatMessage> messages, CancellationToken cancellationToken = default)
{
var request = new ChatCompletionRequest
{
Messages = messages,
Model = "qwen1.8b-chat",
Temperature = 0.7f,
MaxTokens = 1024
};
var jsonContent = JsonSerializer.Serialize(request, _jsonOptions);
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
try
{
var response = await _httpClient.PostAsync("v1/chat/completions", httpContent, cancellationToken);
response.EnsureSuccessStatusCode(); // 如果状态码不是2xx,会抛出异常
var responseJson = await response.Content.ReadAsStringAsync(cancellationToken);
var completionResponse = JsonSerializer.Deserialize<ChatCompletionResponse>(responseJson, _jsonOptions);
// 返回模型生成的回复内容
return completionResponse?.Choices?.FirstOrDefault()?.Message?.Content?.Trim()
?? "抱歉,我没有收到有效的回复。";
}
catch (HttpRequestException ex)
{
// 处理网络或HTTP错误
return $"请求API时出错: {ex.Message}";
}
catch (JsonException ex)
{
// 处理JSON解析错误
return $"解析响应时出错: {ex.Message}";
}
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
{
// 用户取消了请求
return "请求已取消。";
}
}
// 一个更方便的方法,直接发送用户消息并获取回复
public async Task<string> SendMessageAsync(string userMessage, CancellationToken cancellationToken = default)
{
var messages = new List<ChatMessage>
{
new ChatMessage { Role = "user", Content = userMessage }
};
return await GetChatResponseAsync(messages, cancellationToken);
}
// 带对话历史的方法
public async Task<string> SendMessageWithHistoryAsync(string userMessage, List<ChatMessage> history, CancellationToken cancellationToken = default)
{
var messages = new List<ChatMessage>();
messages.AddRange(history);
messages.Add(new ChatMessage { Role = "user", Content = userMessage });
var response = await GetChatResponseAsync(messages, cancellationToken);
// 把本次交互加入历史(可选)
history.Add(new ChatMessage { Role = "user", Content = userMessage });
history.Add(new ChatMessage { Role = "assistant", Content = response });
return response;
}
}
}
这个客户端类做了几件关键事情:
- 封装了HttpClient,管理连接和请求头。
- 处理了JSON的序列化(请求)和反序列化(响应)。
- 实现了基本的错误处理,避免程序因为网络问题直接崩溃。
- 提供了两个便捷方法,一个用于单次对话,一个用于维护对话历史的多轮聊天。
有了这个客户端,在业务代码里调用模型就变得非常简单,一两行代码就能搞定。
3. 实战集成:在ASP.NET Core Web API中使用
现在,我们把这个客户端用到一个实际的ASP.NET Core Web API项目中。假设你想提供一个聊天接口给前端调用。
3.1 配置依赖注入
首先,在Program.cs里注册我们的服务。
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 从配置中读取模型API地址
var qwenApiBaseUrl = builder.Configuration["QwenAI:BaseUrl"]
?? "http://localhost:8000"; // 默认地址
var qwenApiKey = builder.Configuration["QwenAI:ApiKey"] ?? string.Empty;
// 注册QwenAIClient为单例服务
builder.Services.AddSingleton<IQwenAIService>(sp =>
new QwenAIClient(qwenApiBaseUrl, qwenApiKey));
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
在appsettings.json里配置你的模型服务地址:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"QwenAI": {
"BaseUrl": "http://your-model-server:port",
"ApiKey": "your-api-key-if-required"
},
"AllowedHosts": "*"
}
3.2 创建聊天控制器
然后,创建一个API控制器来处理聊天请求。
using Microsoft.AspNetCore.Mvc;
using QwenClient.Services;
namespace QwenApiDemo.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ChatController : ControllerBase
{
private readonly IQwenAIService _qwenAIService;
private readonly ILogger<ChatController> _logger;
public ChatController(IQwenAIService qwenAIService, ILogger<ChatController> logger)
{
_qwenAIService = qwenAIService;
_logger = logger;
}
// POST api/chat/single
[HttpPost("single")]
public async Task<IActionResult> SendSingleMessage([FromBody] SingleChatRequest request)
{
if (string.IsNullOrWhiteSpace(request?.Message))
{
return BadRequest("消息内容不能为空。");
}
try
{
_logger.LogInformation("收到聊天请求: {Message}", request.Message);
var response = await _qwenAIService.SendMessageAsync(request.Message);
_logger.LogInformation("AI回复: {Response}", response);
return Ok(new { success = true, response });
}
catch (Exception ex)
{
_logger.LogError(ex, "处理聊天请求时出错");
return StatusCode(500, new { success = false, error = "处理请求时发生错误。" });
}
}
// POST api/chat/conversation
[HttpPost("conversation")]
public async Task<IActionResult> SendMessageWithHistory([FromBody] ConversationRequest request)
{
if (string.IsNullOrWhiteSpace(request?.UserMessage))
{
return BadRequest("用户消息不能为空。");
}
try
{
// 在实际项目中,对话历史应该从数据库或缓存中获取
// 这里为了演示,我们使用请求中传递的历史,或者新建一个
var history = request.History ?? new List<ChatMessage>();
var response = await _qwenAIService.SendMessageWithHistoryAsync(
request.UserMessage,
history
);
return Ok(new {
success = true,
response,
// 返回更新后的历史,前端可以保存起来用于下次请求
updatedHistory = history
});
}
catch (Exception ex)
{
_logger.LogError(ex, "处理对话请求时出错");
return StatusCode(500, new { success = false, error = "处理请求时发生错误。" });
}
}
}
// 请求模型类
public class SingleChatRequest
{
public string Message { get; set; } = string.Empty;
}
public class ConversationRequest
{
public string UserMessage { get; set; } = string.Empty;
public List<ChatMessage> History { get; set; } = new();
}
}
这个控制器提供了两个端点:
/api/chat/single:处理单次对话,不维护历史上下文。/api/chat/conversation:处理多轮对话,需要传递历史消息记录。
现在,你的前端应用就可以通过调用这些API接口,实现聊天功能了。启动项目,用Swagger或者Postman测试一下,应该能看到模型返回的回复。
4. 另一种场景:在WPF桌面应用中集成
除了Web API,在桌面应用里集成AI能力也很有用。比如,做一个智能助手工具,或者给现有桌面软件增加一个智能问答侧边栏。我们用WPF来演示一下。
4.1 创建WPF项目并安装必要的包
创建一个新的WPF项目,然后通过NuGet安装必要的包:
Microsoft.Extensions.Hosting(用于依赖注入)CommunityToolkit.Mvvm(可选,用于MVVM模式)
4.2 实现ViewModel和界面
我们先创建一个简单的ViewModel来管理聊天逻辑。
// ChatViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using QwenClient.Services;
using System.Collections.ObjectModel;
using System.Threading;
namespace QwenWpfDemo.ViewModels
{
public partial class ChatViewModel : ObservableObject
{
private readonly IQwenAIService _qwenAIService;
private CancellationTokenSource _cancellationTokenSource;
public ChatViewModel(IQwenAIService qwenAIService)
{
_qwenAIService = qwenAIService;
Messages = new ObservableCollection<ChatMessage>();
_cancellationTokenSource = new CancellationTokenSource();
}
[ObservableProperty]
private ObservableCollection<ChatMessage> _messages;
[ObservableProperty]
private string _userInput = string.Empty;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string _statusMessage = "就绪";
[RelayCommand]
private async Task SendMessageAsync()
{
if (string.IsNullOrWhiteSpace(UserInput))
return;
var userMessage = UserInput.Trim();
UserInput = string.Empty; // 清空输入框
// 添加用户消息到界面
Messages.Add(new ChatMessage { Role = "user", Content = userMessage });
// 显示AI正在思考
Messages.Add(new ChatMessage { Role = "assistant", Content = "正在思考..." });
IsLoading = true;
StatusMessage = "AI正在思考...";
try
{
// 准备对话历史(排除最后一条"正在思考..."的消息)
var history = Messages.Take(Messages.Count - 1).ToList();
// 调用AI服务
var response = await _qwenAIService.GetChatResponseAsync(
history,
_cancellationTokenSource.Token
);
// 替换"正在思考..."为实际回复
Messages.RemoveAt(Messages.Count - 1);
Messages.Add(new ChatMessage { Role = "assistant", Content = response });
StatusMessage = "就绪";
}
catch (TaskCanceledException)
{
// 用户取消了请求
Messages.RemoveAt(Messages.Count - 1);
StatusMessage = "请求已取消";
}
catch (Exception ex)
{
// 处理其他错误
Messages.RemoveAt(Messages.Count - 1);
Messages.Add(new ChatMessage {
Role = "assistant",
Content = $"出错了: {ex.Message}"
});
StatusMessage = "发生错误";
}
finally
{
IsLoading = false;
}
}
[RelayCommand]
private void CancelRequest()
{
_cancellationTokenSource.Cancel();
_cancellationTokenSource = new CancellationTokenSource(); // 重置
StatusMessage = "已取消";
}
[RelayCommand]
private void ClearChat()
{
Messages.Clear();
StatusMessage = "对话已清空";
}
}
}
4.3 创建简单的聊天界面
XAML界面可以设计得很简洁:
<!-- MainWindow.xaml -->
<Window x:Class="QwenWpfDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:QwenWpfDemo"
mc:Ignorable="d"
Title="通义千问桌面助手" Height="600" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 消息列表 -->
<ListView Grid.Row="0" ItemsSource="{Binding Messages}"
Background="#1E1E1E" Foreground="White"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<Border Margin="5" Padding="10" CornerRadius="8"
Background="{Binding Role, Converter={StaticResource RoleToColorConverter}}">
<StackPanel>
<TextBlock Text="{Binding Role}" FontWeight="Bold"
Foreground="LightGray" Margin="0,0,0,5"/>
<TextBlock Text="{Binding Content}" TextWrapping="Wrap"
FontSize="14"/>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- 输入区域 -->
<Grid Grid.Row="1" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding UserInput, UpdateSourceTrigger=PropertyChanged}"
Height="60" VerticalContentAlignment="Top"
AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
Padding="10" FontSize="14">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SendMessageCommand}"/>
</TextBox.InputBindings>
</TextBox>
<Button Grid.Column="1" Command="{Binding SendMessageCommand}"
Content="发送" Margin="10,0,5,0" Width="60" Height="30"
IsEnabled="{Binding IsLoading, Converter={StaticResource InverseBooleanConverter}}"/>
<Button Grid.Column="2" Command="{Binding CancelRequestCommand}"
Content="取消" Width="60" Height="30"
IsEnabled="{Binding IsLoading}"/>
</Grid>
<!-- 状态栏 -->
<StatusBar Grid.Row="2" Background="#252526">
<StatusBarItem>
<TextBlock Text="{Binding StatusMessage}" Foreground="White" Margin="5"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<Button Command="{Binding ClearChatCommand}" Content="清空对话"
Margin="5,0" Padding="10,2"/>
</StatusBarItem>
<StatusBarItem HorizontalAlignment="Right">
<ProgressBar Width="100" Height="10" IsIndeterminate="True"
Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
4.4 配置依赖注入和启动
在App.xaml.cs中配置依赖注入:
// App.xaml.cs
public partial class App : Application
{
private readonly IHost _host;
public App()
{
_host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
// 从appsettings.json读取配置
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.Build();
// 注册Qwen AI服务
var baseUrl = configuration["QwenAI:BaseUrl"] ?? "http://localhost:8000";
var apiKey = configuration["QwenAI:ApiKey"] ?? "";
services.AddSingleton<IQwenAIService>(sp =>
new QwenAIClient(baseUrl, apiKey));
// 注册ViewModels和Views
services.AddSingleton<ChatViewModel>();
services.AddSingleton<MainWindow>();
})
.Build();
}
protected override async void OnStartup(StartupEventArgs e)
{
await _host.StartAsync();
var mainWindow = _host.Services.GetRequiredService<MainWindow>();
mainWindow.DataContext = _host.Services.GetRequiredService<ChatViewModel>();
mainWindow.Show();
base.OnStartup(e);
}
protected override async void OnExit(ExitEventArgs e)
{
await _host.StopAsync();
_host.Dispose();
base.OnExit(e);
}
}
这样,一个简单的WPF聊天应用就完成了。运行起来,输入文字,点击发送,就能看到AI的回复了。
5. 一些实用技巧和注意事项
在实际使用中,你可能会遇到一些具体问题。这里分享几个小技巧,能帮你少走弯路。
连接和超时设置:如果模型服务响应比较慢,或者网络不太稳定,可以调整HttpClient的超时设置。在创建QwenAIClient时,可以这样配置:
_httpClient.Timeout = TimeSpan.FromSeconds(60); // 设置60秒超时
处理流式响应:上面的例子用的是非流式接口,一次返回完整回复。如果模型支持流式输出(像ChatGPT那样一个字一个字往外蹦),你可以用HttpCompletionOption.ResponseHeadersRead模式来逐步读取响应。不过处理起来会复杂一些,需要解析SSE(Server-Sent Events)格式。
管理对话历史:在多轮对话中,历史消息会越来越长。模型通常有上下文长度限制(比如4096个token),超出限制的旧消息会被截断。你可以在客户端里加个逻辑,当历史消息总长度接近限制时,自动移除最早的一些消息,或者进行总结压缩。
错误处理和重试:网络请求总有可能失败。对于非关键操作,可以实现简单的重试机制。可以用Polly这样的库,轻松添加重试策略:
// 安装Polly包后
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TaskCanceledException>()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
var response = await retryPolicy.ExecuteAsync(async () =>
await _qwenAIService.GetChatResponseAsync(messages));
性能考虑:如果你的应用并发量比较大,要注意HttpClient的最佳实践。.NET Core推荐使用IHttpClientFactory来管理HttpClient实例的生命周期,避免Socket耗尽问题。可以把QwenAIClient改造成使用IHttpClientFactory。
6. 总结
走完这一趟,你会发现用C#集成大模型API,其实和调用其他任何RESTful服务没有本质区别。核心就是三个步骤:定义好数据模型、封装好HTTP客户端、在业务逻辑里调用。
封装好的QwenAIClient类是个很好的起点,你可以根据实际需求扩展它,比如添加流式响应支持、实现更复杂的对话历史管理、或者加入更多的模型参数控制。在ASP.NET Core里,通过依赖注入来使用这个服务,能让代码保持整洁和可测试。在WPF里,结合MVVM模式,可以快速构建出响应式的用户界面。
实际用下来,这种集成方式对.NET开发者来说非常友好,不需要离开熟悉的技术栈,就能给应用加上AI能力。无论是做个内部工具,还是给产品增加智能特性,这条路都走得通。当然,实际项目中还会遇到更多细节问题,比如身份认证、限流、监控等等,但有了这个基础,那些都是可以逐步完善的。
如果你之前没接触过大模型集成,建议先从简单的单次对话功能开始,跑通整个流程。等熟悉了,再慢慢加入更复杂的功能,比如多轮对话、流式输出、或者同时支持多个不同的模型。最重要的是动手试起来,遇到问题就查查文档,或者看看社区里有没有类似的解决方案。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐



所有评论(0)