通义千问1.5-1.8B-Chat-GPTQ-Int4 .NET开发者集成教程:C#调用大模型API

最近在星图镜像广场上看到了通义千问1.5-1.8B-Chat-GPTQ-Int4这个模型,对于咱们.NET开发者来说,这绝对是个好消息。这意味着我们可以在自己的C#项目里,轻松集成一个功能强大的对话AI,给应用加点“智能”的料。

你可能已经部署好了模型,或者正准备部署。但部署只是第一步,怎么在自己的C#代码里优雅地调用它,才是咱们今天要解决的核心问题。这篇文章,我就手把手带你走一遍,从定义数据模型到封装一个健壮的调用服务,最后集成到ASP.NET Core Web API里。整个过程,我会尽量用大白话讲清楚,保证你跟着做就能跑起来。

1. 准备工作与环境确认

在开始写代码之前,咱们得先确保几件事都到位了。这就像做饭前得先备好菜和锅一样。

首先,你得有一个已经部署好的通义千问1.5-1.8B-Chat-GPTQ-Int4模型服务。我假设你已经通过星图镜像广场完成了部署,并且拿到了服务的访问地址,比如 http://你的服务器地址:端口号/v1/chat/completions。这个地址就是我们后续所有API调用的终点。

其次,准备一个.NET开发环境。我用的例子是基于.NET 6或.NET 8的,这是目前的主流选择,语法现代,性能也好。你可以用Visual Studio 2022、Rider或者VS Code,看个人习惯。

最后,我们会在项目里用到几个基本的NuGet包,主要是用来处理HTTP请求和JSON序列化的。别担心,这些都很常见。

2. 理解API:请求与响应的模样

调用任何API,第一步都是搞清楚它“吃”进去什么,“吐”出来什么。通义千问的Chat接口,遵循了比较通用的聊天补全格式。

简单来说,你需要发送一个JSON结构过去,里面包含了对话历史(messages)和一些控制生成的参数。模型处理完后,会返回另一个JSON结构,里面就有你想要的AI回复。

为了让C#能舒服地处理这些JSON,我们得先定义好对应的数据模型类。这就像给数据穿上C#的“衣服”。

2.1 定义请求数据模型

我们先在项目里创建一个叫 QwenRequest.cs 的类。这个类描述了我们要发送给模型的所有信息。

using System.Text.Json.Serialization;

namespace YourProject.Models
{
    public class QwenRequest
    {
        // 核心:对话消息列表
        [JsonPropertyName("messages")]
        public List<ChatMessage> Messages { get; set; } = new List<ChatMessage>();

        // 模型名称,虽然端点固定,但有时API要求指明
        [JsonPropertyName("model")]
        public string Model { get; set; } = "qwen1.5-1.8b-chat";

        // 控制生成随机性的温度,0-2之间,越高越随机
        [JsonPropertyName("temperature")]
        public float Temperature { get; set; } = 0.7f;

        // 每次生成的最大令牌数,控制回复长度
        [JsonPropertyName("max_tokens")]
        public int MaxTokens { get; set; } = 1024;

        // 是否启用流式输出,我们先用简单的非流式
        [JsonPropertyName("stream")]
        public bool Stream { get; set; } = false;
    }

    // 单独定义一条消息的结构
    public class ChatMessage
    {
        [JsonPropertyName("role")]
        public string Role { get; set; } // "system", "user", "assistant"

        [JsonPropertyName("content")]
        public string Content { get; set; }
    }
}

这里有几个关键点:

  • Messages 是对话的核心,它是一个列表,按顺序存放了系统指令、用户问题和AI的历史回答。
  • Role 有三种:system(设定AI角色)、user(用户)、assistant(AI助手)。
  • TemperatureMaxTokens 是常用的“旋钮”,用来调整回复的创造性和长度,你可以根据场景微调。

2.2 定义响应数据模型

模型返回的数据也需要一个“家”。创建 QwenResponse.cs

using System.Text.Json.Serialization;

namespace YourProject.Models
{
    public class QwenResponse
    {
        [JsonPropertyName("id")]
        public string Id { get; set; }

        [JsonPropertyName("object")]
        public string Object { get; set; }

        [JsonPropertyName("created")]
        public long Created { get; set; }

        [JsonPropertyName("model")]
        public string Model { get; set; }

        // 这是最重要的部分,包含了模型的选择和回复
        [JsonPropertyName("choices")]
        public List<ChatChoice> Choices { get; set; }

        // 使用情况统计,比如消耗了多少token
        [JsonPropertyName("usage")]
        public TokenUsage Usage { get; set; }
    }

    public class ChatChoice
    {
        [JsonPropertyName("index")]
        public int Index { get; set; }

        [JsonPropertyName("message")]
        public ChatMessage Message { get; set; } // 复用请求里的ChatMessage类

        [JsonPropertyName("finish_reason")]
        public string FinishReason { get; set; }
    }

    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#就能自动把JSON字符串转换成我们熟悉的强类型对象,后续处理起来就方便多了。

3. 封装HTTP调用服务

直接在每个地方都写 HttpClient 调用代码会显得很乱,也不利于维护。最好的做法是封装一个专门的服务类。我们来创建一个 QwenAIService.cs

这个服务要干几件事:管理HTTP客户端、构造请求、发送请求、处理响应和可能发生的错误。

using System.Net.Http.Json;
using YourProject.Models;

namespace YourProject.Services
{
    public interface IQwenAIService
    {
        Task<string> GetChatResponseAsync(string userInput, string systemPrompt = null, CancellationToken cancellationToken = default);
    }

    public class QwenAIService : IQwenAIService
    {
        private readonly HttpClient _httpClient;
        private readonly string _apiEndpoint;
        private readonly ILogger<QwenAIService> _logger;

        // 通过构造函数注入配置和HttpClient
        public QwenAIService(HttpClient httpClient, IConfiguration configuration, ILogger<QwenAIService> logger)
        {
            _httpClient = httpClient;
            _logger = logger;
            // 从appsettings.json读取API地址
            _apiEndpoint = configuration["QwenAI:ApiEndpoint"] 
                ?? throw new ArgumentNullException("QwenAI:ApiEndpoint is not configured.");
            
            // 可以在这里设置一些默认的HTTP头,比如认证信息
            // _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
        }

        public async Task<string> GetChatResponseAsync(string userInput, string systemPrompt = null, CancellationToken cancellationToken = default)
        {
            // 1. 构造请求消息列表
            var messages = new List<ChatMessage>();
            
            if (!string.IsNullOrWhiteSpace(systemPrompt))
            {
                messages.Add(new ChatMessage { Role = "system", Content = systemPrompt });
            }
            
            messages.Add(new ChatMessage { Role = "user", Content = userInput });

            // 2. 构造请求体
            var request = new QwenRequest
            {
                Messages = messages,
                Model = "qwen1.5-1.8b-chat",
                Temperature = 0.7f,
                MaxTokens = 512, // 根据需求调整
                Stream = false
            };

            try
            {
                _logger.LogInformation("Sending request to Qwen AI: {UserInput}", userInput);
                
                // 3. 发送POST请求
                var response = await _httpClient.PostAsJsonAsync(_apiEndpoint, request, cancellationToken);
                
                // 4. 确保响应成功
                response.EnsureSuccessStatusCode();
                
                // 5. 读取并解析响应
                var qwenResponse = await response.Content.ReadFromJsonAsync<QwenResponse>(cancellationToken: cancellationToken);
                
                // 6. 提取AI回复内容
                var aiReply = qwenResponse?.Choices?.FirstOrDefault()?.Message?.Content?.Trim();
                
                if (string.IsNullOrEmpty(aiReply))
                {
                    _logger.LogWarning("Received empty or invalid response from Qwen AI.");
                    return "抱歉,我暂时没有理解你的问题。";
                }

                _logger.LogInformation("Received response from Qwen AI. Tokens used: {Total}", qwenResponse?.Usage?.TotalTokens);
                return aiReply;
            }
            catch (HttpRequestException ex)
            {
                _logger.LogError(ex, "Network error when calling Qwen AI API.");
                // 这里可以细化处理不同的HTTP状态码
                return $"网络请求出错: {ex.StatusCode}";
            }
            catch (TaskCanceledException ex) when (cancellationToken.IsCancellationRequested)
            {
                _logger.LogWarning("The request to Qwen AI was cancelled.");
                return "请求已取消。";
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An unexpected error occurred when calling Qwen AI.");
                return "处理您的请求时出现了意外错误。";
            }
        }
    }
}

这个服务类做了很好的抽象:

  • 它通过接口 IQwenAIService 定义契约,方便后续测试和替换。
  • 使用依赖注入,配置和 HttpClient 都由外部管理,符合.NET Core的最佳实践。
  • 包含了基本的日志记录,方便出问题时排查。
  • 对异常进行了初步处理,返回用户友好的错误信息。

4. 添加重试与容错机制

网络请求总有可能因为各种原因失败,比如瞬间的网络抖动或者服务端压力大。对于AI对话这种体验要求较高的场景,加上简单的重试机制能提升不少稳定性。

我们不需要搞得太复杂,一个针对短暂故障的重试策略就很有用。我们可以用 Polly 这个流行的库来实现。首先,通过NuGet安装 Polly 包。

然后,修改我们的服务类,或者更好的是,在依赖注入注册时配置重试策略。这里我们在 Program.cs 中配置。

// 在Program.cs或Startup.cs中
using Polly;
using Polly.Extensions.Http;

var builder = WebApplication.CreateBuilder(args);

// ... 其他服务配置 ...

// 1. 配置一个针对HttpRequestException和5xx状态码的等待重试策略
var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError() // 处理网络错误和5xx服务器错误
    .WaitAndRetryAsync(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(3),
        TimeSpan.FromSeconds(5)
    }, onRetry: (outcome, timespan, retryAttempt, context) =>
    {
        // 记录重试日志
        var logger = context.GetLogger();
        logger?.LogWarning("Retry {RetryAttempt} after {Delay}ms for {OperationKey}. Exception: {Exception}", 
            retryAttempt, timespan.TotalMilliseconds, context.OperationKey, outcome.Exception?.Message);
    });

// 2. 为名为“QwenAI”的HttpClient配置这个策略
builder.Services.AddHttpClient<QwenAIService>("QwenAI")
    .AddPolicyHandler(retryPolicy); // 应用重试策略

// 3. 注册我们的AI服务
builder.Services.AddScoped<IQwenAIService, QwenAIService>();

这样配置后,当 QwenAIService 发起的HTTP请求遇到可重试的错误时,它会自动按照1秒、3秒、5秒的间隔重试三次。很多间歇性问题通过重试就能解决,用户几乎无感知。

5. 集成到ASP.NET Core Web API

现在,我们有了一个健壮的AI服务,接下来就是把它“塞进”一个Web API项目里,暴露给前端或其他服务调用。假设我们要创建一个简单的聊天控制器。

创建一个 ChatController.cs

using Microsoft.AspNetCore.Mvc;
using YourProject.Services;

namespace YourProject.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;
        }

        // 最简单的单轮对话端点
        [HttpPost("simple")]
        public async Task<IActionResult> SimpleChat([FromBody] SimpleChatRequest request)
        {
            if (string.IsNullOrWhiteSpace(request?.Message))
            {
                return BadRequest("Message cannot be empty.");
            }

            try
            {
                var response = await _qwenAIService.GetChatResponseAsync(request.Message);
                return Ok(new { reply = response });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing chat request.");
                return StatusCode(500, "An internal error occurred.");
            }
        }

        // 支持系统指令和上下文的多轮对话端点(简化版)
        [HttpPost("conversation")]
        public async Task<IActionResult> ConversationalChat([FromBody] ConversationalChatRequest request)
        {
            // 这里可以扩展为维护一个会话ID,在服务层或缓存中保存对话历史
            // 本例简化处理,每次只携带当前系统提示和用户问题
            var systemPrompt = request?.SystemPrompt ?? "你是一个乐于助人的AI助手。";
            var userMessage = request?.UserMessage;

            if (string.IsNullOrWhiteSpace(userMessage))
            {
                return BadRequest("UserMessage cannot be empty.");
            }

            try
            {
                var response = await _qwenAIService.GetChatResponseAsync(userMessage, systemPrompt);
                return Ok(new { reply = response });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing conversational chat request.");
                return StatusCode(500, "An internal error occurred.");
            }
        }
    }

    // 请求模型
    public class SimpleChatRequest
    {
        public string Message { get; set; }
    }

    public class ConversationalChatRequest
    {
        public string SystemPrompt { get; set; }
        public string UserMessage { get; set; }
    }
}

别忘了在 appsettings.json 里配置你的模型API地址:

{
  "QwenAI": {
    "ApiEndpoint": "http://你的服务器地址:端口号/v1/chat/completions"
  },
  // ... 其他配置
}

现在,运行你的Web API项目。你可以用Swagger UI、Postman或者任何HTTP客户端来测试这两个端点:

  • POST /api/chat/simple 发送一个简单的用户消息。
  • POST /api/chat/conversation 发送一个可带系统指令的用户消息。

6. 总结与后续建议

走完这一套流程,你应该已经成功在.NET项目里接入了通义千问模型。整个过程其实并不复杂,核心就是理解API格式、封装HTTP调用、处理好错误,最后集成到你的应用框架里。

用下来感觉,这套封装方式挺实用的,特别是加了重试机制之后,稳定性好了不少。对于刚开始接触AI集成的.NET开发者来说,从这种简单的非流式调用入手,门槛比较低,也容易理解整个数据流转的过程。

当然,这只是个起点。在实际项目里,你可能还需要考虑更多东西,比如:

  • 对话历史管理:现在的例子是单轮或简单的伪多轮。真正的多轮对话需要你维护一个 messages 列表,每次把新的用户消息和AI回复追加进去,再发给模型。这部分状态可以保存在服务端的内存、数据库或者分布式缓存里,并关联一个会话ID。
  • 流式响应:如果希望实现像ChatGPT那样一个字一个字出来的效果,就需要处理服务端返回的流式数据。这涉及到使用 HttpCompletionOption.ResponseHeadersRead 并逐步读取响应流,对前端和服务端都有额外要求。
  • 性能与超时:根据模型部署的硬件情况,调整 HttpClient 的超时设置。对于长文本生成,MaxTokens 设得太大可能导致响应时间很长,需要做好用户体验上的处理,比如前端显示“正在思考...”。
  • 更细化的错误处理:根据API返回的具体错误码(比如token超限、模型忙),给用户更精准的反馈。

建议你先拿这个基础版本跑起来,看看效果。然后根据你的具体业务场景,再一步步去添加上面这些高级功能。最重要的是先让整个链路通起来,有了可工作的原型,后面的优化就都有方向了。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

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

更多推荐