Lychee-Rerank快速原型开发.NET Core集成示例你是不是也遇到过这样的场景自己开发的搜索或推荐系统返回的结果虽然相关但排序总感觉差点意思不够“聪明”用户想要的答案可能藏在第二页或者一堆相似结果里最贴切的那个没排在最前面。传统的基于关键词匹配的排序有时候确实力不从心。这时候就需要引入重排序Rerank技术。简单来说就是先用一个快速的检索引擎比如Elasticsearch捞出一批候选结果再用一个更精细、更理解语义的模型对这批结果重新打分和排序把最可能符合用户意图的答案推到最前面。今天要聊的Lychee-Rerank就是一个专为这个任务设计的轻量级、高性能重排序模型。而作为.NET开发者我们最关心的就是这东西怎么快速集成到我的.NET Core项目里别担心这篇文章就是为你准备的。我会手把手带你走一遍从零开始集成Lychee-Rerank的全过程涵盖包管理、服务配置、异步调用和单元测试让你在Windows或Linux上都能轻松跑起来。1. 项目准备与环境搭建在开始写代码之前我们得先把舞台搭好。这里假设你已经有一个正在开发的.NET Core Web API项目或者至少知道怎么创建一个。我用的环境是.NET 8但.NET 6/7的步骤基本一样。1.1 创建项目与引入NuGet包首先打开你的终端或者Visual Studio创建一个新的Web API项目或者直接在你现有的项目里操作。dotnet new webapi -n LycheeRerankDemo cd LycheeRerankDemo接下来是关键一步引入Lychee-Rerank的.NET客户端包。目前社区可能有多个封装版本我们需要选择一个稳定、维护积极的。这里我假设我们找到了一个名为Lychee.Rerank.Client的包请注意这是一个示例包名实际使用时请搜索并确认正确的NuGet包名。通过NuGet包管理器控制台或者命令行添加dotnet add package Lychee.Rerank.Client或者直接编辑你的.csproj文件在ItemGroup里添加包引用ItemGroup PackageReference IncludeLychee.Rerank.Client Version1.0.0 / !-- 其他项目依赖... -- /ItemGroup添加完成后记得运行dotnet restore来还原包。1.2 配置模型服务端点Lychee-Rerank模型需要在一个地方运行并提供API服务。通常有两种方式本地部署在自己服务器上部署模型服务适合对延迟、数据隐私要求高的场景。使用托管API调用第三方提供的托管服务省去运维成本。为了演示我们假设你已经在http://localhost:8000本地部署好了Lychee-Rerank的服务例如使用其官方Docker镜像。我们需要把这个地址配置到项目中。在appsettings.json或appsettings.Development.json中添加配置{ LycheeRerank: { BaseUrl: http://localhost:8000/v1, ApiKey: your-api-key-here, // 如果服务需要认证 TimeoutSeconds: 30 } }如果你的服务不需要API Key可以省略那项。TimeoutSeconds是为了防止网络问题导致请求一直挂起。2. 核心服务封装与依赖注入配置好了接下来我们要创建一个服务类来封装所有与Lychee-Rerank API的交互逻辑并用.NET Core强大的依赖注入DI容器来管理它。2.1 定义服务接口与实现一个好的实践是先定义接口再写实现。这样便于单元测试和解耦。在项目里创建一个Services文件夹然后添加接口ILycheeRerankService.cs// Services/ILycheeRerankService.cs using LycheeRerankDemo.Models; namespace LycheeRerankDemo.Services; public interface ILycheeRerankService { /// summary /// 对一组文档进行重排序 /// /summary /// param namequery用户查询语句/param /// param namedocuments待排序的文档列表/param /// param namecancellationToken取消令牌/param /// returns按相关性得分降序排列的文档列表/returns TaskListRerankedDocument RerankAsync(string query, Liststring documents, CancellationToken cancellationToken default); }同时定义返回结果的模型RerankedDocument.cs// Models/RerankedDocument.cs namespace LycheeRerankDemo.Models; public class RerankedDocument { public string Text { get; set; } string.Empty; public int Index { get; set; } // 原始列表中的索引 public double Score { get; set; } // 相关性得分 }现在创建接口的实现LycheeRerankService.cs。这里我们会用到HttpClient来调用API。// Services/LycheeRerankService.cs using System.Net.Http.Json; using System.Text.Json; using LycheeRerankDemo.Models; using Microsoft.Extensions.Options; namespace LycheeRerankDemo.Services; public class LycheeRerankService : ILycheeRerankService { private readonly HttpClient _httpClient; private readonly LycheeRerankSettings _settings; private readonly ILoggerLycheeRerankService _logger; // 定义请求和响应的内部类用于JSON序列化 private class RerankRequest { public string Query { get; set; } string.Empty; public Liststring Documents { get; set; } new(); } private class RerankResponseItem { public int Index { get; set; } public double Score { get; set; } } public LycheeRerankService(HttpClient httpClient, IOptionsLycheeRerankSettings settings, ILoggerLycheeRerankService logger) { _httpClient httpClient; _settings settings.Value; _logger logger; // 配置HttpClient基础地址和超时 _httpClient.BaseAddress new Uri(_settings.BaseUrl); _httpClient.Timeout TimeSpan.FromSeconds(_settings.TimeoutSeconds); // 如果需要API Key可以在这里添加到默认请求头 if (!string.IsNullOrEmpty(_settings.ApiKey)) { _httpClient.DefaultRequestHeaders.Add(Authorization, $Bearer {_settings.ApiKey}); } } public async TaskListRerankedDocument RerankAsync(string query, Liststring documents, CancellationToken cancellationToken default) { if (string.IsNullOrWhiteSpace(query)) throw new ArgumentException(Query cannot be null or empty., nameof(query)); if (documents null || documents.Count 0) return new ListRerankedDocument(); var requestPayload new RerankRequest { Query query, Documents documents }; try { _logger.LogDebug(Sending rerank request for query: {Query} with {Count} documents., query, documents.Count); // 发送POST请求到重排序端点假设端点是 /rerank var response await _httpClient.PostAsJsonAsync(/rerank, requestPayload, cancellationToken); response.EnsureSuccessStatusCode(); // 确保HTTP状态码为2xx var responseItems await response.Content.ReadFromJsonAsyncListRerankResponseItem(cancellationToken: cancellationToken); if (responseItems null) { _logger.LogWarning(Rerank API returned null or empty response.); return documents.Select((doc, idx) new RerankedDocument { Text doc, Index idx, Score 0 }).ToList(); } // 将API响应映射到我们的模型并按分数降序排列 var rerankedDocs responseItems .OrderByDescending(item item.Score) .Select(item new RerankedDocument { Text documents[item.Index], Index item.Index, Score item.Score }).ToList(); _logger.LogDebug(Rerank completed. Top score: {TopScore}, rerankedDocs.FirstOrDefault()?.Score); return rerankedDocs; } catch (HttpRequestException ex) { _logger.LogError(ex, HTTP request failed during rerank for query: {Query}, query); throw; // 或者根据业务需求返回原始列表 } catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested) { _logger.LogInformation(Rerank operation was cancelled.); throw; } catch (Exception ex) { _logger.LogError(ex, An unexpected error occurred during rerank for query: {Query}, query); throw; } } }还需要一个配置类LycheeRerankSettings.cs来强类型化我们的配置// Models/LycheeRerankSettings.cs namespace LycheeRerankDemo.Models; public class LycheeRerankSettings { public string BaseUrl { get; set; } string.Empty; public string ApiKey { get; set; } string.Empty; public int TimeoutSeconds { get; set; } 30; }2.2 注册服务到依赖注入容器服务写好了怎么让其他部分比如控制器能用上呢我们需要在Program.cs里注册它。打开Program.cs文件添加以下代码// Program.cs using LycheeRerankDemo.Models; using LycheeRerankDemo.Services; var builder WebApplication.CreateBuilder(args); // 添加服务到容器 builder.Services.AddControllers(); // 学习更多关于使用Swagger/OpenAPI的信息: https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // 1. 配置LycheeRerankSettings builder.Services.ConfigureLycheeRerankSettings( builder.Configuration.GetSection(LycheeRerank)); // 2. 注册一个命名的HttpClient专用于LycheeRerank服务 builder.Services.AddHttpClientILycheeRerankService, LycheeRerankService((serviceProvider, client) { // 配置已在LycheeRerankService构造函数中完成这里可以添加一些全局配置如重试策略 // 例如client.DefaultRequestHeaders.Add(User-Agent, MyApp/1.0); }) .ConfigurePrimaryHttpMessageHandler(() new HttpClientHandler { // 根据需求配置Handler例如忽略SSL证书验证仅用于开发 // ServerCertificateCustomValidationCallback (message, cert, chain, errors) true }) .SetHandlerLifetime(TimeSpan.FromMinutes(5)); // 设置Handler的生命周期 // 也可以直接注册服务但上面用AddHttpClient方式更利于管理HttpClient生命周期 // builder.Services.AddScopedILycheeRerankService, LycheeRerankService(); var app builder.Build(); // 配置HTTP请求管道 if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();这里我们用了AddHttpClientTService, TImplementation这个扩展方法它做了几件好事将LycheeRerankService注册为ILycheeRerankService的作用域服务。自动注入一个配置好的HttpClient实例到LycheeRerankService的构造函数。帮我们管理HttpClient的生命周期避免了手动管理时可能遇到的套接字耗尽问题。3. 在控制器中使用与异步编程服务注册好了现在让我们在一个API控制器里实际使用它。假设我们有一个搜索场景先通过简单匹配获取一批文档再用Lychee-Rerank进行智能排序。3.1 创建搜索控制器在Controllers文件夹下创建一个新的控制器SearchController.cs// Controllers/SearchController.cs using LycheeRerankDemo.Models; using LycheeRerankDemo.Services; using Microsoft.AspNetCore.Mvc; namespace LycheeRerankDemo.Controllers; [ApiController] [Route(api/[controller])] public class SearchController : ControllerBase { private readonly ILycheeRerankService _rerankService; private readonly ILoggerSearchController _logger; public SearchController(ILycheeRerankService rerankService, ILoggerSearchController logger) { _rerankService rerankService; _logger logger; } [HttpPost(rerank)] public async TaskActionResultSearchResponse RerankDocuments([FromBody] SearchRequest request, CancellationToken cancellationToken) { if (!ModelState.IsValid) { return BadRequest(ModelState); } try { _logger.LogInformation(Received rerank request for query: {Query}, request.Query); // 模拟第一步从数据库或搜索引擎获取初始文档列表 var initialDocuments await FetchInitialDocumentsAsync(request.Query, cancellationToken); if (initialDocuments.Count 0) { return Ok(new SearchResponse { Query request.Query, Documents new ListRerankedDocument() }); } // 第二步调用Lychee-Rerank服务进行智能重排序 var rerankedDocuments await _rerankService.RerankAsync(request.Query, initialDocuments, cancellationToken); // 第三步构造响应 var response new SearchResponse { Query request.Query, Documents rerankedDocuments }; return Ok(response); } catch (OperationCanceledException) { _logger.LogWarning(Search request was cancelled.); return StatusCode(499); // Client Closed Request } catch (Exception ex) { _logger.LogError(ex, An error occurred while processing search request for query: {Query}, request.Query); return StatusCode(500, An internal error occurred. Please try again later.); } } /// summary /// 模拟从数据源获取初始文档的方法 /// /summary private async TaskListstring FetchInitialDocumentsAsync(string query, CancellationToken cancellationToken) { // 这里应该是真实的数据库查询或外部搜索API调用 // 为了演示我们返回一些模拟数据 await Task.Delay(50, cancellationToken); // 模拟网络延迟 var mockDocuments new Liststring { 苹果公司发布了新一代iPhone手机搭载了更快的芯片。, 多吃水果对身体有益比如苹果、香蕉和橙子。, 关于苹果的最新财报显示其营收增长强劲。, 如何种植苹果树这里有一份详细的园艺指南。, 牛顿因为一个苹果发现了万有引力定律。 }; // 简单模拟一个基于关键词的初筛实际项目会更复杂 return mockDocuments.Where(doc doc.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList(); } } // 请求和响应模型 public class SearchRequest { public string Query { get; set; } string.Empty; public int? TopK { get; set; } // 可选参数指定返回前K个结果 } public class SearchResponse { public string Query { get; set; } string.Empty; public ListRerankedDocument Documents { get; set; } new(); }这个控制器做了几件事定义了一个POST /api/search/rerank的API端点。接收一个包含查询词Query的请求体。模拟了从数据源获取初始文档列表的过程FetchInitialDocumentsAsync。调用了我们之前封装好的_rerankService.RerankAsync方法。处理了取消请求和异常情况并返回结构化的响应。3.2 异步编程模式要点注意代码中大量使用了async/await和CancellationToken。这是现代.NET高性能服务的标配。async/await让代码在等待I/O操作如网络请求、数据库查询时不会阻塞线程提高了服务器的并发处理能力。CancellationToken允许客户端取消一个长时间运行的请求。我们从控制器方法参数开始一路将这个令牌传递到最终的HTTP调用和模拟延迟中确保了取消请求时资源能被及时释放。4. 运行测试与验证代码写完了我们得验证它能不能跑通效果怎么样。4.1 启动项目并测试API首先确保你的Lychee-Rerank模型服务已经在http://localhost:8000运行起来了。然后在项目根目录运行dotnet run或者用Visual Studio直接启动。项目启动后通常会打开Swagger页面如果配置了的话地址是https://localhost:xxxx/swagger。在Swagger UI里找到POST /api/search/rerank这个端点点击“Try it out”。在请求体里输入{ query: 苹果 }点击“Execute”。如果一切顺利你应该会收到一个响应里面包含了5个文档并且它们应该按照与“苹果”这个词的语义相关性重新排序了。比如关于“苹果公司”和“水果苹果”的文档得分可能会根据模型的理解有所不同。4.2 编写单元测试为了保证服务逻辑的可靠性特别是HTTP客户端和错误处理部分我们最好写一些单元测试。这里使用xUnit和Moq来模拟HttpClient。首先为测试项目添加必要的NuGet包dotnet add package Microsoft.NET.Test.Sdk dotnet add package xunit dotnet add package xunit.runner.visualstudio dotnet add package Moq dotnet add package Microsoft.Extensions.Options然后创建一个测试类LycheeRerankServiceTests.cs// Tests/LycheeRerankServiceTests.cs using System.Net; using LycheeRerankDemo.Models; using LycheeRerankDemo.Services; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; using Moq.Protected; namespace LycheeRerankDemo.Tests; public class LycheeRerankServiceTests { private readonly MockIOptionsLycheeRerankSettings _mockOptions; private readonly MockILoggerLycheeRerankService _mockLogger; private readonly LycheeRerankSettings _settings; private readonly HttpClient _httpClient; private readonly MockHttpMessageHandler _mockHttpMessageHandler; public LycheeRerankServiceTests() { _settings new LycheeRerankSettings { BaseUrl http://test.local, TimeoutSeconds 5 }; _mockOptions new MockIOptionsLycheeRerankSettings(); _mockOptions.Setup(o o.Value).Returns(_settings); _mockLogger new MockILoggerLycheeRerankService(); _mockHttpMessageHandler new MockHttpMessageHandler(); _httpClient new HttpClient(_mockHttpMessageHandler.Object) { BaseAddress new Uri(_settings.BaseUrl) }; } [Fact] public async Task RerankAsync_SuccessfulResponse_ReturnsOrderedDocuments() { // Arrange var query test query; var documents new Liststring { doc1, doc2, doc3 }; var expectedResponseJson [{index:2,score:0.9},{index:0,score:0.7},{index:1,score:0.5}]; // 模拟成功的HTTP响应 _mockHttpMessageHandler.Protected() .SetupTaskHttpResponseMessage( SendAsync, ItExpr.IsAnyHttpRequestMessage(), ItExpr.IsAnyCancellationToken() ) .ReturnsAsync(new HttpResponseMessage { StatusCode HttpStatusCode.OK, Content new StringContent(expectedResponseJson) }); var service new LycheeRerankService(_httpClient, _mockOptions.Object, _mockLogger.Object); // Act var result await service.RerankAsync(query, documents); // Assert Assert.NotNull(result); Assert.Equal(3, result.Count); // 检查是否按分数降序排列 Assert.Equal(2, result[0].Index); // 分数0.9原索引2 Assert.Equal(0, result[1].Index); // 分数0.7原索引0 Assert.Equal(1, result[2].Index); // 分数0.5原索引1 Assert.Equal(doc3, result[0].Text); } [Fact] public async Task RerankAsync_HttpRequestFails_ThrowsException() { // Arrange var query test; var documents new Liststring { doc }; _mockHttpMessageHandler.Protected() .SetupTaskHttpResponseMessage( SendAsync, ItExpr.IsAnyHttpRequestMessage(), ItExpr.IsAnyCancellationToken() ) .ThrowsAsync(new HttpRequestException(Network error)); var service new LycheeRerankService(_httpClient, _mockOptions.Object, _mockLogger.Object); // Act Assert await Assert.ThrowsAsyncHttpRequestException(() service.RerankAsync(query, documents)); } [Fact] public async Task RerankAsync_EmptyDocuments_ReturnsEmptyList() { // Arrange var service new LycheeRerankService(_httpClient, _mockOptions.Object, _mockLogger.Object); // Act var result await service.RerankAsync(query, new Liststring()); // Assert Assert.NotNull(result); Assert.Empty(result); } }这个测试类覆盖了几个关键场景成功响应并正确排序、HTTP请求失败、以及空文档列表的边界情况。通过模拟HttpMessageHandler我们可以完全控制HttpClient的行为而无需依赖真实的网络服务这使得测试快速、可靠且可重复。运行测试dotnet test如果所有测试都通过恭喜你核心服务的逻辑基本稳了。5. 总结与后续步骤走完这一趟你应该已经成功地把Lychee-Rerank集成到了你的.NET Core项目中。我们从头到尾经历了几个关键步骤通过NuGet管理依赖、用强类型配置管理服务参数、利用依赖注入优雅地组织服务生命周期、在控制器中遵循异步模式进行调用最后还写了单元测试来保驾护航。实际用起来你会发现这种“粗排精排”的架构对提升搜索和推荐质量很有帮助。Lychee-Rerank作为一个专门的重排序模型比通用模型更轻量、更专注在延迟和效果之间往往能取得不错的平衡。当然这只是个开始。在实际生产环境中你可能还需要考虑更多性能优化比如对HttpClient使用池化、对频繁相同的查询和文档组合实施缓存。弹性处理为HTTP调用添加重试策略可以用Polly库、熔断机制提高服务的健壮性。监控与日志记录更详细的性能指标如延迟、调用成功率方便问题排查和系统优化。配置管理在云原生环境中可能使用环境变量或配置中心来管理服务地址和密钥。建议你先在开发环境把整个流程跑通然后用一些真实的业务数据做个小规模的对比测试看看重排序带来的效果提升是否符合预期。如果效果不错就可以逐步规划在生产环境的上线了。希望这个示例能成为你项目里一个有用的起点。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。