通义千问1.5-1.8B-Chat-GPTQ-Int4 .NET开发集成:C#调用大模型API完整示例
本文介绍了如何在星图GPU平台上自动化部署通义千问1.5-1.8B-Chat-GPTQ-Int4镜像,并详细展示了通过C# .NET项目调用其API的完整流程。该方案使开发者能快速集成轻量化大语言模型,实现智能对话、文本生成等应用,显著提升.NET生态的AI开发效率。
通义千问1.5-1.8B-Chat-GPTQ-Int4 .NET开发集成:C#调用大模型API完整示例
最近有不少做.NET开发的朋友问我,怎么在自己的C#项目里调用那些大模型。特别是像通义千问这种已经量化好的小尺寸模型,部署起来方便,但怎么在代码里优雅地调用,很多人还没摸清楚。今天我就用一个具体的例子,手把手带你走一遍,从封装一个简单的HTTP客户端,到处理各种响应和异常,最后集成到一个Web API项目里。整个过程你跟着做,半小时内就能跑起来。
1. 开始之前:你需要准备什么
在写第一行代码之前,我们先看看需要哪些东西。这样你跟着做的时候,心里有底,不容易卡住。
首先,你得有一个已经部署好的通义千问1.5-1.8B-Chat-GPTQ-Int4模型的API服务。这个模型因为做了量化,体积小,对硬件要求不高,很多云服务商或者自己用Docker都能轻松部署。假设你的服务地址是 http://localhost:8000,并且有一个发送消息的接口,比如 /v1/chat/completions。如果你还没部署,可以先去搜一下相关的部署教程,把服务跑起来。
其次,你的开发环境需要准备好。我这边用的是 .NET 8 和 Visual Studio 2022,你用 .NET 6/7 或者 Rider 也完全没问题。确保你的项目能正常创建和编译。
最后,我们这次的重点是“怎么调用”,而不是“模型怎么工作”。所以我会把重心放在HTTP通信、数据封装和错误处理这些工程细节上,保证你拿到的是能直接用在生产环境里的代码片段。
2. 理解API:请求与响应长什么样
在动手封装客户端之前,我们得先搞清楚要和API“说什么话”,以及它会“回什么话”。这就像打电话,你得知道对方的语言。
通常,这类聊天模型的API设计都借鉴了OpenAI的风格,所以如果你用过ChatGPT的API,会觉得非常眼熟。一个最基本的请求,核心就是告诉模型:“用户说了什么”,以及“你扮演什么角色”。
请求体(我们发送给API的JSON)大概长这样:
{
"model": "qwen1.5-1.8b-chat",
"messages": [
{
"role": "system",
"content": "你是一个乐于助人的AI助手。"
},
{
"role": "user",
"content": "你好,请介绍一下你自己。"
}
],
"stream": false
}
这里有几个关键字段:
model: 指定要使用的模型名称,这里就是我们部署的qwen1.5-1.8b-chat。messages: 这是一个消息数组,定义了对话的历史和当前问题。role可以是system(设定助手行为)、user(用户输入)或assistant(助手之前的回复)。stream: 是否使用流式响应。为了简单起见,我们先设为false,一次拿到完整回复。
那么,API成功后会返回什么呢?响应体大概是这样:
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1677652288,
"model": "qwen1.5-1.8b-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "你好!我是通义千问,一个由阿里云开发的大语言模型..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 25,
"completion_tokens": 42,
"total_tokens": 67
}
}
我们需要的数据,就在 choices[0].message.content 里面。usage 字段则告诉我们这次对话消耗了多少token,对于监控和成本控制很有用。
知道了这些数据格式,我们接下来就可以用C#的类来把它们“描述”出来,这样序列化和反序列化就非常方便了。
3. 搭建项目:定义数据模型
我们来创建一个新的类库项目,或者在你的现有项目里添加这些类。我建议单独建一个 Models 文件夹来存放它们,这样结构清晰。
首先,定义消息对象。这对应请求体里 messages 数组中的每一个元素。
namespace YourProjectName.Models
{
public class ChatMessage
{
public string Role { get; set; } = string.Empty; // "system", "user", "assistant"
public string Content { get; set; } = string.Empty;
}
}
然后,定义整个请求对象。它包含了模型名称、消息列表和其他可选参数。
using System.Text.Json.Serialization;
namespace YourProjectName.Models
{
public class ChatCompletionRequest
{
[JsonPropertyName("model")]
public string Model { get; set; } = "qwen1.5-1.8b-chat";
[JsonPropertyName("messages")]
public List<ChatMessage> Messages { get; set; } = new();
[JsonPropertyName("stream")]
public bool Stream { get; set; } = false;
// 你可以根据需要添加更多参数,例如:
// public float Temperature { get; set; } = 0.7f;
// public int MaxTokens { get; set; } = 2048;
}
}
这里用了 [JsonPropertyName] 特性,确保序列化成JSON时字段名是小写开头的(snake_case风格),这和大多数Python后端API的默认风格一致。
接下来是响应对象。我们需要定义结构来承接API返回的复杂数据。
namespace YourProjectName.Models
{
public class ChatCompletionResponse
{
public string Id { get; set; } = string.Empty;
public string Object { get; set; } = string.Empty;
public long Created { get; set; }
public string Model { get; set; } = string.Empty;
public List<ChatChoice> Choices { get; set; } = new();
public TokenUsage Usage { get; set; } = new();
}
public class ChatChoice
{
public int Index { get; set; }
public ChatMessage Message { get; set; } = new();
public string FinishReason { get; set; } = string.Empty;
}
public class TokenUsage
{
public int PromptTokens { get; set; }
public int CompletionTokens { get; set; }
public int TotalTokens { get; set; }
}
}
数据模型定义好了,就像我们准备好了写信的信纸和信封。下一步,就是如何把这封信寄出去并取回回信了。
4. 核心封装:编写HTTP客户端服务
这是最关键的一步,我们要创建一个健壮、易用的客户端类。我习惯用 HttpClient,配合 IHttpClientFactory 来管理生命周期,这在ASP.NET Core里是推荐做法。
我们先创建一个接口,定义客户端需要具备的能力。这样便于后续做单元测试或者切换实现。
namespace YourProjectName.Services
{
public interface IQwenAIClient
{
Task<ChatCompletionResponse> GetChatCompletionAsync(List<ChatMessage> messages, CancellationToken cancellationToken = default);
Task<string> GetChatCompletionSimpleAsync(string userMessage, CancellationToken cancellationToken = default);
}
}
接口定义了两个方法:一个返回完整的响应对象,适合需要详细信息的场景;另一个只返回助手回复的文本内容,适合快速调用。
现在来实现这个接口。我们创建一个 QwenAIClient 类。
using System.Net.Http.Json; // 用于 ReadFromJsonAsync, PostAsJsonAsync
using Microsoft.Extensions.Logging;
using YourProjectName.Models;
namespace YourProjectName.Services
{
public class QwenAIClient : IQwenAIClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<QwenAIClient> _logger;
private readonly string _apiEndpoint;
// 通过构造函数注入HttpClient和配置
public QwenAIClient(HttpClient httpClient, ILogger<QwenAIClient> logger, IConfiguration configuration)
{
_httpClient = httpClient;
_logger = logger;
// 从appsettings.json读取API地址,默认值用于开发
_apiEndpoint = configuration["QwenAI:ApiBaseUrl"] ?? "http://localhost:8000/v1/chat/completions";
}
public async Task<ChatCompletionResponse> GetChatCompletionAsync(List<ChatMessage> messages, CancellationToken cancellationToken = default)
{
var request = new ChatCompletionRequest
{
Messages = messages,
Model = "qwen1.5-1.8b-chat" // 可以固化,也可以从配置读取
};
try
{
_logger.LogDebug("Sending request to Qwen AI API. Message count: {Count}", messages.Count);
// 关键的一行:发送POST请求并解析响应
var response = await _httpClient.PostAsJsonAsync(_apiEndpoint, request, cancellationToken);
// 确保HTTP请求本身是成功的(2xx状态码)
response.EnsureSuccessStatusCode();
// 将响应体反序列化为我们定义的模型
var completionResponse = await response.Content.ReadFromJsonAsync<ChatCompletionResponse>(cancellationToken: cancellationToken);
if (completionResponse?.Choices?.FirstOrDefault()?.Message?.Content == null)
{
throw new InvalidOperationException("API response does not contain valid content.");
}
_logger.LogDebug("Received response from Qwen AI. Token usage: {Total}", completionResponse.Usage.TotalTokens);
return completionResponse;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Network error occurred while calling Qwen AI API.");
throw new Exception($"API request failed: {ex.Message}", ex);
}
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
{
_logger.LogInformation("The request was cancelled by the user.");
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "An unexpected error occurred while processing the Qwen AI response.");
throw;
}
}
// 简化版方法,直接返回对话文本
public async Task<string> GetChatCompletionSimpleAsync(string userMessage, CancellationToken cancellationToken = default)
{
var messages = new List<ChatMessage>
{
new() { Role = "user", Content = userMessage }
};
var response = await GetChatCompletionAsync(messages, cancellationToken);
return response.Choices[0].Message.Content;
}
}
}
这个类里有几个值得注意的点:
- 依赖注入:
HttpClient和ILogger都是注入进来的,遵循了.NET Core的最佳实践。 - 配置化:API地址从
IConfiguration读取,这样不同环境(开发、测试、生产)可以轻松切换。 - 错误处理:使用
try-catch包裹了核心调用,区分了网络错误、取消请求和一般异常,并记录了日志。 PostAsJsonAsync和ReadFromJsonAsync:这两个扩展方法让JSON序列化变得极其简单,不需要手动处理字符串。
客户端写好了,怎么让它融入到我们的应用里呢?接下来看依赖注入的配置。
5. 集成配置:在ASP.NET Core项目中注入服务
假设我们正在构建一个ASP.NET Core Web API项目。我们需要在 Program.cs 中注册我们刚刚写的客户端服务。
打开 Program.cs 文件,添加以下代码:
// 在var builder = WebApplication.CreateBuilder(args);之后
// 注册一个命名的HttpClient,专门用于Qwen AI服务
builder.Services.AddHttpClient<IQwenAIClient, QwenAIClient>((serviceProvider, client) =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
var baseUrl = configuration["QwenAI:ApiBaseUrl"] ?? "http://localhost:8000";
// 设置基础地址,这样在QwenAIClient内部只需要拼接路径
client.BaseAddress = new Uri(baseUrl);
// 可以在这里设置默认请求头,比如API Key(如果需要)
// client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
// 根据你的部署环境,决定是否跳过证书验证(仅限开发环境!)
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
})
.SetHandlerLifetime(TimeSpan.FromMinutes(5)); // 设置Handler的生命周期
// 注册客户端服务本身
builder.Services.AddScoped<IQwenAIClient, QwenAIClient>();
同时,别忘了在 appsettings.json 文件中添加配置项:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"QwenAI": {
"ApiBaseUrl": "http://localhost:8000"
},
"AllowedHosts": "*"
}
这样,我们的服务就配置好了。在任何控制器或者别的服务里,你只需要通过构造函数注入 IQwenAIClient 接口,就可以直接使用了。
6. 实战演练:创建一个聊天API控制器
光说不练假把式,我们创建一个实际的Web API端点来感受一下。在Controllers文件夹下,新建一个 ChatController.cs。
using Microsoft.AspNetCore.Mvc;
using YourProjectName.Models;
using YourProjectName.Services;
namespace YourProjectName.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ChatController : ControllerBase
{
private readonly IQwenAIClient _qwenAIClient;
private readonly ILogger<ChatController> _logger;
public ChatController(IQwenAIClient qwenAIClient, ILogger<ChatController> logger)
{
_qwenAIClient = qwenAIClient;
_logger = logger;
}
[HttpPost("completion")]
public async Task<ActionResult<ChatCompletionResponse>> GetCompletion([FromBody] ChatCompletionRequest request)
{
if (request?.Messages == null || !request.Messages.Any())
{
return BadRequest("Messages cannot be empty.");
}
try
{
var response = await _qwenAIClient.GetChatCompletionAsync(request.Messages);
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing chat completion request.");
// 可以根据异常类型返回更精确的状态码
return StatusCode(500, "An error occurred while processing your request.");
}
}
[HttpPost("simple")]
public async Task<ActionResult<string>> GetSimpleCompletion([FromBody] SimpleChatRequest simpleRequest)
{
if (string.IsNullOrWhiteSpace(simpleRequest?.Message))
{
return BadRequest("Message cannot be empty.");
}
try
{
var reply = await _qwenAIClient.GetChatCompletionSimpleAsync(simpleRequest.Message);
return Ok(reply);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing simple chat request.");
return StatusCode(500, "An error occurred while processing your request.");
}
}
}
// 用于简化请求的模型
public class SimpleChatRequest
{
public string Message { get; set; } = string.Empty;
}
}
这个控制器提供了两个端点:
POST /api/chat/completion: 接收完整的聊天请求格式,返回完整的API响应。适合前端需要控制所有参数(如system prompt)的场景。POST /api/chat/simple: 只接收用户消息字符串,直接返回助手的回复文本。适合快速测试或简单集成。
现在,运行你的项目。你可以用Swagger UI(如果安装了)、Postman或者curl来测试。例如,向 http://localhost:your-port/api/chat/simple 发送一个POST请求,Body为 {"message": "你好,世界"},你应该就能收到通义千问的回复了。
7. 总结
整个过程走下来,其实核心思路很清晰:定义好数据契约(C# Model),封装一个负责通信的客户端服务,处理好异常和日志,最后通过依赖注入优雅地集成到你的应用框架中。我提供的代码示例已经考虑了生产环境需要的健壮性,比如配置化、日志记录和全面的错误处理。
你可能会发现,调用本地部署的模型和调用云服务商提供的API,在代码层面差别并不大,主要就是配置的地址和可能的鉴权方式不同。这种封装的最大好处是,如果你的后端服务以后需要切换模型提供商,你只需要修改 QwenAIClient 内部的实现,或者甚至实现一个新的 IAIClient,上层业务控制器代码几乎不用动。
当然,这只是最基础的同步调用。在实际项目中,你可能还需要考虑支持流式响应(Server-Sent Events)、实现重试机制、加入熔断策略,或者更细粒度的Token消耗监控。但有了今天这个扎实的起点,再去扩展那些高级功能,就会容易很多。希望这个完整的示例能帮你顺利地把大模型能力接入到你的.NET应用里。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐



所有评论(0)