通义千问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 ListChatMessage 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 ListChatChoice 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 { TaskChatCompletionResponse GetChatCompletionAsync(ListChatMessage messages, CancellationToken cancellationToken default); Taskstring 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 ILoggerQwenAIClient _logger; private readonly string _apiEndpoint; // 通过构造函数注入HttpClient和配置 public QwenAIClient(HttpClient httpClient, ILoggerQwenAIClient logger, IConfiguration configuration) { _httpClient httpClient; _logger logger; // 从appsettings.json读取API地址默认值用于开发 _apiEndpoint configuration[QwenAI:ApiBaseUrl] ?? http://localhost:8000/v1/chat/completions; } public async TaskChatCompletionResponse GetChatCompletionAsync(ListChatMessage 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.ReadFromJsonAsyncChatCompletionResponse(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 Taskstring GetChatCompletionSimpleAsync(string userMessage, CancellationToken cancellationToken default) { var messages new ListChatMessage { 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.AddHttpClientIQwenAIClient, QwenAIClient((serviceProvider, client) { var configuration serviceProvider.GetRequiredServiceIConfiguration(); 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.AddScopedIQwenAIClient, 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 ILoggerChatController _logger; public ChatController(IQwenAIClient qwenAIClient, ILoggerChatController logger) { _qwenAIClient qwenAIClient; _logger logger; } [HttpPost(completion)] public async TaskActionResultChatCompletionResponse 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 TaskActionResultstring 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星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。