通义千问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;
        }
    }
}

这个类里有几个值得注意的点:

  1. 依赖注入HttpClientILogger 都是注入进来的,遵循了.NET Core的最佳实践。
  2. 配置化:API地址从 IConfiguration 读取,这样不同环境(开发、测试、生产)可以轻松切换。
  3. 错误处理:使用 try-catch 包裹了核心调用,区分了网络错误、取消请求和一般异常,并记录了日志。
  4. PostAsJsonAsyncReadFromJsonAsync:这两个扩展方法让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星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐