当前位置: 首页 > news >正文

.NET Core Web API集成SmallThinker-3B-Preview模型服务详解

.NET Core Web API集成SmallThinker-3B-Preview模型服务详解

你是不是也遇到过这样的场景?手头有一个不错的AI模型服务,比如最近挺火的SmallThinker-3B-Preview,想把它集成到自己的.NET Core Web API项目里,给用户提供智能文本处理能力。但一上手就发现,从简单的HttpClient调用到生产级的稳定集成,中间还有不少坑要填。

今天,我就以一个.NET老开发的身份,带你走一遍完整的集成流程。我们不只讲怎么调通接口,更会聊聊怎么把这件事做得更专业、更可靠。比如,怎么优雅地管理HttpClient的生命周期,怎么用强类型对象让代码更清晰,以及怎么应对网络服务偶尔的“小脾气”。最后,我们会一起实现一个文本摘要的接口,把整个流程串起来。

1. 开篇:明确我们的目标

在开始敲代码之前,我们先搞清楚要做什么。假设SmallThinker-3B-Preview模型服务已经部署好了,它提供了一个HTTP API端点,比如http://your-model-service/v1/chat/completions。我们的任务是在一个ASP.NET Core Web API项目中,创建一个可复用的、健壮的服务层来调用这个模型,并对外提供我们自己的API。

这样做的好处很明显:业务逻辑和模型调用解耦了。以后模型服务地址变了、升级了,或者我们要换一个模型,只需要改动服务层内部的实现,对外提供的API接口可以保持不变,调用方完全无感知。

整个教程会分为几个清晰的步骤:首先创建项目基础结构,然后构建核心的模型调用服务,接着处理网络调用的稳定性,最后在控制器里使用它。我们一步步来。

2. 第一步:准备项目环境

万事开头难,但准备工作做好了,后面就顺了。我们从一个干净的Web API项目开始。

2.1 创建新项目

打开你的终端或命令行工具,找一个合适的目录,执行下面的命令。这里我用的是.NET 8,但.NET 6/7的步骤基本一样。

dotnet new webapi -n SmartModelIntegration cd SmartModelIntegration

这个命令会创建一个名为SmartModelIntegration的新Web API项目,并包含了基础的WeatherForecast示例。我们可以先把那个示例控制器删掉,保持项目干净。

2.2 安装必要的NuGet包

我们需要几个额外的包来增强功能:

  • Microsoft.Extensions.Http:用于注册和配置IHttpClientFactory,这是现代.NET中管理HttpClient的最佳实践。
  • Polly.Extensions.Http:用来给我们的HttpClient添加重试、熔断等弹性策略,让调用更稳定。
  • System.Text.Json:通常已经包含,我们用它来序列化和反序列化JSON数据。

在项目目录下,运行:

dotnet add package Microsoft.Extensions.Http dotnet add package Polly.Extensions.Http

好了,项目骨架和工具包就位,接下来我们进入核心部分——定义数据模型。

3. 第二步:定义强类型的数据契约

直接使用dynamic或者匿名对象来处理JSON虽然快,但不利于维护和调试。为模型服务的请求和响应定义明确的类,能让代码意图更清晰,也能享受到编译时检查的好处。

我们在项目里创建一个Models文件夹,然后在里面添加两个类。

3.1 定义请求模型

首先,定义调用SmallThinker模型所需的请求体。根据常见的聊天补全API格式,它通常需要消息列表和模型名称等参数。

// Models/ThinkerRequest.cs namespace SmartModelIntegration.Models; public class ThinkerRequest { public List<ChatMessage> Messages { get; set; } = new(); public string Model { get; set; } = "smallthinker-3b-preview"; public double? Temperature { get; set; } = 0.7; public int? MaxTokens { get; set; } = 500; // 可以根据需要添加其他参数,如top_p, stream等 } public class ChatMessage { public string Role { get; set; } = "user"; // "system", "user", "assistant" public string Content { get; set; } = string.Empty; }

这个ThinkerRequest类封装了所有必要的参数。Messages列表是对话的核心,Model字段指定使用的模型,TemperatureMaxTokens控制生成文本的随机性和长度。

3.2 定义响应模型

接下来,定义模型服务返回的响应结构。我们关注最核心的回复内容。

// Models/ThinkerResponse.cs namespace SmartModelIntegration.Models; public class ThinkerResponse { public string Id { get; set; } = string.Empty; public string Object { get; set; } = string.Empty; public long Created { get; set; } public List<ChatCompletionChoice> Choices { get; set; } = new(); public UsageInfo Usage { get; set; } = new(); } public class ChatCompletionChoice { public int Index { get; set; } public ChatMessage Message { get; set; } = new(); public string FinishReason { get; set; } = string.Empty; } public class UsageInfo { public int PromptTokens { get; set; } public int CompletionTokens { get; set; } public int TotalTokens { get; set; } }

定义好这些类之后,我们在代码里就能用thinkerResponse.Choices[0].Message.Content这样清晰的方式来获取AI生成的文本了,而不是在一堆JObject里摸索。

4. 第三步:构建模型调用服务

这是集成的心脏部分。我们将创建一个服务类,专门负责和SmallThinker的API对话。这里的关键是使用IHttpClientFactory来创建HttpClient,而不是直接new HttpClient(),前者能更好地管理连接和避免端口耗尽问题。

在项目根目录创建一个Services文件夹,然后添加服务接口和实现。

4.1 定义服务接口

先定义一个接口,这符合依赖注入的原则,也方便后续单元测试。

// Services/ISmallThinkerService.cs namespace SmartModelIntegration.Services; public interface ISmallThinkerService { Task<string> GetChatCompletionAsync(string userMessage, CancellationToken cancellationToken = default); Task<ThinkerResponse> GetChatCompletionDetailedAsync(ThinkerRequest request, CancellationToken cancellationToken = default); }

接口提供了两个方法:一个简单的,只发用户消息;一个详细的,可以完全控制请求参数。

4.2 实现服务类

现在来实现这个接口。注意看构造函数,它注入了一个IHttpClientFactory

// Services/SmallThinkerService.cs using System.Text; using System.Text.Json; using Microsoft.Extensions.Options; using SmartModelIntegration.Models; namespace SmartModelIntegration.Services; public class SmallThinkerService : ISmallThinkerService { private readonly HttpClient _httpClient; private readonly JsonSerializerOptions _jsonOptions; private readonly ILogger<SmallThinkerService> _logger; // 建议将BaseUrl等配置放在appsettings.json中 private const string BaseUrl = "http://your-model-service:port"; // 替换为你的实际地址 private const string ApiEndpoint = "/v1/chat/completions"; public SmallThinkerService(IHttpClientFactory httpClientFactory, ILogger<SmallThinkerService> logger) { // 使用具名的HttpClient,配置可以在Program.cs中统一管理 _httpClient = httpClientFactory.CreateClient("SmallThinkerClient"); _logger = logger; _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; } public async Task<string> GetChatCompletionAsync(string userMessage, CancellationToken cancellationToken = default) { var request = new ThinkerRequest { Messages = new List<ChatMessage> { new ChatMessage { Role = "user", Content = userMessage } } }; var response = await GetChatCompletionDetailedAsync(request, cancellationToken); return response.Choices?.FirstOrDefault()?.Message?.Content ?? "模型未返回有效内容。"; } public async Task<ThinkerResponse> GetChatCompletionDetailedAsync(ThinkerRequest request, CancellationToken cancellationToken = default) { var url = $"{BaseUrl}{ApiEndpoint}"; var jsonContent = JsonSerializer.Serialize(request, _jsonOptions); var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json"); _logger.LogInformation("调用模型服务: {Url}", url); var response = await _httpClient.PostAsync(url, httpContent, cancellationToken); response.EnsureSuccessStatusCode(); // 如果状态码不是2xx,会抛出HttpRequestException var responseString = await response.Content.ReadAsStringAsync(cancellationToken); var thinkerResponse = JsonSerializer.Deserialize<ThinkerResponse>(responseString, _jsonOptions); if (thinkerResponse == null) { throw new InvalidOperationException("反序列化模型响应失败。"); } _logger.LogInformation("模型调用成功,消耗Token数: {TotalTokens}", thinkerResponse.Usage.TotalTokens); return thinkerResponse; } }

代码逻辑很直白:序列化请求对象,发送POST请求,检查响应状态,然后反序列化返回结果。日志记录能帮助我们在出问题时快速定位。

5. 第四步:配置依赖注入与弹性策略

服务写好了,怎么让它融入ASP.NET Core的血液里呢?通过依赖注入。同时,我们给HttpClient穿上“防弹衣”——加上Polly重试策略。

打开Program.cs文件,我们来添加配置。

5.1 注册HttpClient与Polly策略

Program.csbuilder.Services配置部分添加如下代码:

// Program.cs using Polly; using Polly.Extensions.Http; var builder = WebApplication.CreateBuilder(args); // ... 其他服务注册 ... // 1. 注册模型服务 builder.Services.AddScoped<ISmallThinkerService, SmallThinkerService>(); // 2. 配置具名的HttpClient,并添加Polly策略 builder.Services.AddHttpClient("SmallThinkerClient", client => { // 这里可以配置默认的BaseAddress,但我们在服务类里已经指定了完整URL。 // client.BaseAddress = new Uri("http://your-model-service:port/"); client.DefaultRequestHeaders.Add("Accept", "application/json"); // 如果需要API Key,在这里添加 // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "your-api-key"); }) .AddPolicyHandler(GetRetryPolicy()) // 添加重试策略 .AddPolicyHandler(GetCircuitBreakerPolicy()); // 添加熔断器策略(可选) // ... 后续的 app.MapControllers() 等 ...

5.2 定义Polly策略方法

Program.cs文件中,添加两个私有方法(可以放在文件末尾)来定义策略:

// 定义重试策略:对于网络波动、短暂的5xx错误或408超时进行重试 static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() // 处理5xx, 408, 502等 .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.TooManyRequests) // 也处理429(请求过多) .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 指数退避:2, 4, 8秒 onRetry: (outcome, timespan, retryAttempt, context) => { // 可以在这里记录日志 var logger = context.GetLogger(); logger?.LogWarning("第 {RetryAttempt} 次重试,延迟 {Delay}ms。触发原因: {StatusCode}", retryAttempt, timespan.TotalMilliseconds, outcome.Result?.StatusCode.ToString() ?? outcome.Exception.Message); }); } // 定义熔断器策略:当失败率达到阈值时,快速失败,避免拖垮系统 static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)); // 5次连续失败后熔断30秒 }

这样配置后,我们的SmallThinkerService在调用模型API时,就自动具备了重试和熔断的能力。比如遇到网络闪断,它会自动重试3次;如果远端服务完全不可用,连续失败5次后,会在30秒内快速返回失败,而不再真正发起请求,保护了我们自己的服务。

6. 第五步:在控制器中使用服务

最后一步,就是把我们打造好的服务用起来。创建一个新的控制器,提供一个简单的文本摘要接口。

Controllers文件夹下,新建一个SummarizeController.cs文件。

// Controllers/SummarizeController.cs using Microsoft.AspNetCore.Mvc; using SmartModelIntegration.Services; namespace SmartModelIntegration.Controllers; [ApiController] [Route("api/[controller]")] public class SummarizeController : ControllerBase { private readonly ISmallThinkerService _thinkerService; private readonly ILogger<SummarizeController> _logger; public SummarizeController(ISmallThinkerService thinkerService, ILogger<SummarizeController> logger) { _thinkerService = thinkerService; _logger = logger; } [HttpPost] public async Task<IActionResult> SummarizeText([FromBody] SummarizeRequest request) { if (string.IsNullOrWhiteSpace(request?.Text)) { return BadRequest("请求文本不能为空。"); } try { _logger.LogInformation("开始处理文本摘要请求,长度: {Length}", request.Text.Length); // 构造一个更明确的提示词,引导模型进行摘要 var userPrompt = $"请将以下文本总结为一段简洁的摘要:\n\n{request.Text}"; var summary = await _thinkerService.GetChatCompletionAsync(userPrompt); _logger.LogInformation("文本摘要生成成功。"); return Ok(new SummarizeResponse { Summary = summary.Trim() }); } catch (HttpRequestException ex) { _logger.LogError(ex, "调用模型服务时发生网络错误。"); return StatusCode(503, "模型服务暂时不可用,请稍后重试。"); } catch (Exception ex) { _logger.LogError(ex, "处理摘要请求时发生未知错误。"); return StatusCode(500, "服务器内部错误。"); } } } // 请求和响应的DTO,放在同一个文件里方便查看,也可以单独放 public class SummarizeRequest { public string Text { get; set; } = string.Empty; } public class SummarizeResponse { public string Summary { get; set; } = string.Empty; }

这个控制器很简单:接收一段文本,通过ISmallThinkerService调用模型生成摘要,然后返回结果。注意我们做了基本的参数校验和异常处理,并记录了日志,这对于生产环境很重要。

7. 跑起来看看效果

所有代码都写完了,让我们启动项目测试一下。

  1. 运行项目:在终端里执行dotnet run
  2. 准备测试:使用Postman、curl或者你喜欢的任何API测试工具。
  3. 发送请求
    • URL:POST http://localhost:port/api/summarize
    • Body (JSON):
      { "text": "人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大,可以设想,未来人工智能带来的科技产品,将会是人类智慧的‘容器’。" }
  4. 查看响应:你应该会收到一个JSON响应,里面包含了由SmallThinker模型生成的文本摘要。

整个过程走下来,你会发现集成一个外部模型服务到.NET Core API里,并不是简单写个HttpClient调用就完事了。通过服务抽象、依赖注入、弹性策略这些步骤,我们构建了一个更健壮、更易维护的集成方案。当你的模型服务需要升级,或者你要测试另一个模型时,大部分改动都集中在SmallThinkerService这个类里,控制器和其他业务逻辑几乎不用动。

当然,这只是个起点。在实际项目中,你可能还需要考虑配置中心管理模型地址、更完善的监控和指标收集、请求响应的缓存、以及更复杂的提示词工程等等。但有了这个坚实的基础,后续的扩展都会顺畅很多。希望这个详细的步骤能帮你省下一些摸索的时间。


获取更多AI镜像

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

http://www.jsqmd.com/news/563846/

相关文章:

  • 3步终极方案:免费解锁QQ音乐加密文件,实现音乐自由播放
  • SmolVLA多轮对话效果实测:复杂上下文理解与记忆能力
  • 篇文章彻底搞懂 MySQL 和 Redis:原理、区别、项目用法全解析(建议收藏)
  • STM32定时器时基单元详解:从PSC到ARR的完整配置指南(附代码)
  • ChatGLM3-6B GPU算力方案:多实例隔离部署保障不同部门QoS
  • Linux 内核中的进程调度:从 CFS 到实时调度
  • 5分钟搞定雪女AI:斗罗大陆造相Z-Turbo快速安装与体验
  • 别再用云端API了!手把手教你用FunASR在Android手机本地部署离线语音识别(ASR)
  • 保姆级图解:PCIe物理层逻辑子层到底在忙活啥?(从8b/10b编码到多通道数据分发)
  • Matplotlib中文显示问题终极指南:从报错到完美解决
  • 告别手动抓取!用Python脚本5分钟批量下载Mapillary指定区域的街景图片
  • 别让临时存储拖垮集群!K8s中emptyDir的正确使用姿势与替代方案
  • 07 从 MLP 到 LeNet:感知机到底解决了什么问题?
  • IEEE会议论文避雷指南:如何用GSview+Photoshop搞定EPS图片压缩与特殊字符命名
  • 超级千问语音设计世界实战:一句话轻松变出英雄、魔王四种声音
  • 避坑指南:ESP32+MicroPython混合编程时C库编译的3个常见错误
  • 大恒相机硬触发实战:从IO配置到回调函数处理的完整流程(附避坑指南)
  • Python自动化操作Synology群晖文件:从下载到上传的完整实践
  • 别再让串口打印卡死你的STM32了!用FreeRTOS队列实现异步日志(附完整代码)
  • 快速排序图解:5分钟搞懂分治法的核心思想(含动态演示)
  • ZYNQ UART中断的四种工作模式详解:除了回环,还能怎么玩?
  • 2026年超低压钢带管优质品牌推荐榜:防腐钢带管、高压钢带管、SFB钢带管、SF钢带管、WF屋顶钢带管、低噪声钢带管选择指南 - 优质品牌商家
  • Linux 内核中的网络协议栈:从数据包到应用程序
  • 2026除甲醛果壳活性炭优质生产厂家推荐指南:除甲醛活性炭、除甲醛粉末活性炭、除甲醛粉状活性炭、净水木质活性炭选择指南 - 优质品牌商家
  • 第六章、Isaacsim中的USD资产:从零开始构建自定义机器人模型
  • DASD-4B-Thinking在Ubuntu系统管理中的智能助手应用
  • 收藏!一张图带你入门AIAgent全流程:从提问到结果返回的17步详解(小白程序员必备)
  • 简单几步,让通义千问3-4B-Instruct-2507支持外部设备访问
  • Qwen3-VL-8B效果惊艳展示:识别电路图并解释工作原理与元器件作用
  • 组态王与施耐德M580 PLC的Modbus TCP通信实战指南