☰
openJiuwen 的 ChatReranker 实战:基于 Chat Completion Logprobs 的相关性重排
2026/10/15 23:31:06 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 框架
  • 工具调用
  • Agent 工作流
  • RAG
  • MCP Clients
  • 强化学习

【免费下载链接】agent-core-java

openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。

项目地址:https://gitcode.com/openJiuwen/agent-core-java
点击查看免费下载

导读

ChatReranker是 openJiuwen agent-core-java(com.openjiuwen.retrieval.reranker.ChatReranker)提供的一种特殊重排器:它不依赖专用的 rerank 端点,而是复用标准 chat completion 服务,通过yes/no两个 token 的 logprob 计算候选文档与查询的相关性分数。本文围绕该类的设计原理、RerankerConfig配置要点、请求构造细节、分数解析算法与兼容性探测方法展开,并结合仓库源码与测试用例逐层拆解,帮助你快速在检索(RAG)链路中接入基于 chat 服务的重排能力。

一、类定位:ChatReranker extends StandardReranker

在 openJiuwen 的检索模块中,重排器统一继承自 Reranker 抽象基类,其核心接口只有两个:

  • CompletableFuture<Map<String, Double>> rerank(...):异步重排,返回"文档 → 相关性分数"映射;
  • Map<String, Double> rerankSync(...):同步重排。

ChatReranker与 StandardReranker 共享这套抽象,但两者面向的服务与调用方式完全不同:

维度StandardRerankerChatReranker
请求端点POST {apiBase}/rerank(vLLM 风格)POST {apiBase}/chat/completions
打分依据服务端返回的relevance_score客户端根据yes/notoken 的 logprob 自行计算
单次调用候选数可一次传入多个文档每个候选单独发起一次请求
是否依赖 logprobs 能力不依赖强依赖(服务不支持则直接报错)

ChatReranker的类声明位于 retrieval/src/main/java/com/openjiuwen/retrieval/reranker/ChatReranker.java#L38,源码注释明确指出它是对 Python 版openjiuwen/core/retrieval/reranker/chat_reranker.py的 Java 对齐实现,因此接口语义与 Python 版本保持一致。

从源码结构看,设计者将ChatReranker放在独立的retrieval模块中(com.openjiuwen.retrieval.reranker包),而配置类RerankerConfig、数据模型Document与抽象基类Reranker位于核心模块com.openjiuwen.core.foundation.store.base_reranker包,模块边界清晰。

二、核心原理:用 yes/no token 的 logprob 计算相关性

ChatReranker的文档说明非常简洁,只有三条:

  1. 端点固定为/chat/completions;
  2. 每个候选会单独发起一次请求;
  3. RerankerConfig.yesNoIds必须包含两个 token id,测试确认缺失时会抛出异常。

这三条背后对应着一套完整的算法流程。整条链路是:将查询与候选文档拼成对话,请求模型只生成 1 个 token,并开启 logprobs 返回 top 候选 token 及其对数概率,最后用 yes/no 概率之比归一化出分数。

2.1 请求的构造(requestParams)

源码 ChatReranker.java#L178-L202 展示了请求参数的完整组装逻辑:

  • 消息结构:固定两条消息——system消息内容为"Judge whether the Document meets the requirements based on the Query and the Instruct provided. ",user消息内容由三部分模板拼接而成:

    <Instruct>: %s <Query>: %s <Document>: %s

    其中instruct传Boolean.TRUE时使用默认指令"Given a search query, retrieve relevant candidates that answer the query.";传入自定义字符串时使用自定义指令;传入Boolean.FALSE时仅保留Query与Document部分(见buildQuery,ChatReranker.java#L277-L282)。

  • 固定生成参数(测试 ChatRerankerTest.java#L52-L79 明确断言了这些取值):

    参数值作用
    modelRerankerConfig.modelName指定模型
    temperature0强制确定性输出,避免采样抖动影响分数
    max_tokens1只让模型输出一个 token,即 yes 或 no
    logprobstrue开启对数概率返回
    top_logprobs5返回概率最高的 5 个候选 token
    logit_bias两个 yes/no token id 各加偏置5抬高 yes/no 的生成概率,引导模型二选一
  • logit_bias的构造:遍历config.getYesNoIds(),对两个 token id 分别施加+5的偏置(ChatReranker.java#L193-L197)。这正是yes_no_ids配置存在的意义——你必须提供目标模型词表中"是/否"两个 token 的 id,重排器才能精准引导并识别输出。

  • extra_body透传:RerankerConfig.extraBody中额外的自定义参数会以putAll方式合并进请求体(ChatReranker.java#L198-L200),方便对接不同服务商特有的请求字段。

2.2 响应的解析与分数计算(parseResponse)

拿到 chat completion 响应后,parseResponse(ChatReranker.java#L132-L170)执行以下步骤:

  1. 从choices[0].logprobs.content[0].top_logprobs中取出 top 候选 token 列表;
  2. 对每个 token,用Math.exp(logprob)还原概率值;
  3. 将 token 文本小写化后按前缀匹配:以"yes"开头计入 yes 概率,以"no"开头计入 no 概率(因此"YES,"、"No."这类带标点或大小写的变体也能正确识别,Python Parity 测试 ChatRerankerPythonParityTest.java#L155-L177 验证了大小写与前缀场景);
  4. 对 yes、no 各自取最大值作为该侧概率;
  5. 最终相关性分数 =yesScore / (yesScore + noScore);若两者之和为 0,则分数取 0(ChatReranker.java#L165-L168)。

可以直观理解为:模型认为候选文档越能满足查询,输出 yes 的概率越高,归一化后分数越接近 1;反之越接近 0。

2.3 为什么每个候选单独请求一次

与StandardReranker支持批量传入文档不同,ChatReranker的输入被严格限制为List且 size 必须为 1(extractSingleDocument,ChatReranker.java#L255-L267),元素可以是String或 Document。这是因为 yes/no 打分天然是一对一的判别任务:一次只让模型对一个文档做"是否相关"的判定,才能得到干净的概率。传多个文档、空列表或非 List 输入都会抛出"input to chat reranker must be a list[str | Document] of size 1"异常(ChatReranker.java#L269-L275)。需要重排多个候选时,由调用方循环发起多次请求。

三、RerankerConfig 配置要点

ChatReranker的构造参数是 RerankerConfig,这是一个带 Lombok Builder 的配置类,关键字段如下:

字段JSON 字段名默认值说明
apiKeyapi_key""API Key,非空时自动附带Authorization: Bearer <key>请求头
apiBaseapi_base无服务地址,不要带/chat/completions后缀,构造时会自动归一化
modelNamemodel_name(兼容别名model)""模型名称,请求体中的model字段
timeout—(timeout)10.0秒单次 HTTP 请求超时
temperaturetemperature0.95注意:ChatReranker请求时强制写死temperature=0,此配置对 chat 重排不生效
topPtop_p0.1同上,chat 请求中未透传
yesNoIdsyes_no_ids无必填,恰好两个整数 token id
extraBodyextra_body空 Map透传到请求体的额外参数

3.1 yes_no_ids 的校验逻辑

构造ChatReranker时,isValidYesNoIds(ChatReranker.java#L251-L253)要求yesNoIds非空、size 恰好为 2 且两个元素均非 null。任一条不满足都会抛出RETRIEVAL_RERANKER_INPUT_INVALID错误,错误信息为"chat reranker require \"yes_no_ids\" to be specified in RerankerConfig"。测试 ChatRerankerTest.java#L36-L49 分别用null和仅一个 id 的配置验证了异常行为;此外 ChatRerankerPythonParityTest.java#L67-L80 还验证了非整数类型的 id(如["yes","no"])在 JSON 反序列化阶段就会失败。

具体取哪两个 token id,取决于目标模型的词表。示例代码注释中给出了一组参考值9454,2753(见 ChatRerankerExample.java#L36-L37),实际使用请按模型词表查询确认。

3.2 api_base 的归一化

无论apiBase是否携带尾部斜杠或/chat/completions后缀,normalizeBaseUrl(ChatReranker.java#L323-L329)都会将其规整为纯基地址,最终请求 URI 固定为{apiBase}/chat/completions。测试 ChatRerankerTest.java#L183 断言了https://api.example.com/v1对应的最终地址为https://api.example.com/v1/chat/completions。

3.3 请求头与重试

请求头默认包含Content-Type: application/json,apiKey非空时追加Authorization: Bearer <key>,额外头extraHeaders会合并覆盖(ChatReranker.java#L88-L95)。构造重载可指定maxRetries、retryWait和自定义HttpClient;默认值为重试 3 次、等待 0.1 秒。重试逻辑见 ChatReranker.java#L204-L249:仅在非 2xx 响应或 IO 异常时重试,最终失败抛出RETRIEVAL_RERANKER_REQUEST_CALL_FAILED。

四、代码示例:从配置到打分

仓库提供了可直接运行的完整示例 ChatRerankerExample.java,下面给出核心使用范式:

import com.openjiuwen.retrieval.reranker.ChatReranker; import com.openjiuwen.core.foundation.store.base_reranker.RerankerConfig; // 1. 构造配置:yes_no_ids 必填两个 token id RerankerConfig config = RerankerConfig.builder() .apiBase("https://your-endpoint/v1") .apiKey("your-api-key") .modelName("your-model") .yesNoIds(List.of(9454, 2753)) // 参考值,按模型词表确认 .build(); // 2. 实例化重排器 ChatReranker reranker = new ChatReranker(config); // 3.(可选)兼容性探测:确认服务支持 logprobs if (!reranker.testCompatibility()) { // 服务不支持 chat-completion 重排,需更换服务或改用 StandardReranker return; } // 4. 重排单个候选:查询 + 单个文档(String 或 Document 均可),instruct 可用 Boolean 或自定义字符串 Map<String, Double> score = reranker.rerankSync( "Hello", List.of("bonjour"), Boolean.TRUE, Map.of()); Map<String, Double> customScore = reranker.rerankSync( "Hello", List.of("bonjour"), "greeting in french", Map.of());

示例程序会分别打印默认指令与自定义指令下的分数,并输出两者之差(default=... custom=... delta=...,见 ChatRerankerExample.java#L57-L67),直观展示自定义指令对相关度判断的影响。

4.1 运行方式

示例运行说明见 examples/retrieval/README.md,简要流程为:

  1. 在仓库根目录编译并生成 classpath:

    mvn -DskipTests compile mvn dependency:build-classpath "-Dmdep.outputFile=target/retrieval_examples.classpath"
  2. 运行ChatRerankerExample,并用 JVM 参数覆盖配置:

    java -DCHAT_RERANKER_YES_NO_IDS=9454,2753 ` -cp "target/classes;examples;examples/retrieval;$(Get-Content target/retrieval_examples.classpath -Raw)" ` ChatRerankerExample

    相关覆盖项包括CHAT_RERANKER_MODEL、CHAT_RERANKER_API_BASE、CHAT_RERANKER_API_KEY与必填的CHAT_RERANKER_YES_NO_IDS;未设置后者时示例会提示配置错误并直接退出。

五、兼容性探测:为什么需要 testCompatibility

testCompatibility()(ChatReranker.java#L100-L111)是 chat 重排接入前的重要"试金石":它用("test", ["test"])发起一次最小请求,若成功则说明服务支持 logprobs,返回true;若抛异常则记录"The selected service does not support chat-completion-based reranking"并返回false。

由于 yes/no 打分强依赖choices[0].logprobs.content[0].top_logprobs,服务端若关闭 logprobs 或返回结构不符合 OpenAI 兼容格式,parseResponse会抛出RETRIEVAL_RERANKER_REQUEST_CALL_FAILED("the service does not support logprobs for chat reranker to function",ChatReranker.java#L138-L143)。示例 ChatRerankerExample.java#L41-L46 在正式重排前先执行探测,探测失败即中止,避免后续逐条请求全部失败。测试 ChatRerankerTest.java#L187-L193 与 ChatRerankerPythonParityTest.java#L299-L313 分别覆盖了成功与失败两条路径。

六、实现与测试佐证

本功能的测试覆盖非常完整,可以作为理解实现细节的"活文档":

  • ChatRerankerTest.java:校验 yes_no_ids 缺失异常、请求参数与 extraBody 合并、输入校验、logprobs 解析、端点地址与额外请求头;
  • ChatRerankerPythonParityTest.java:逐条对齐 Python 版TestChatReranker的用例,覆盖 token 大小写、前缀匹配、非整数 id、零概率场景、异步rerank与同步rerankSync、兼容性探测等;
  • 基类抽象与文档模型:Reranker.java、Document.java。

关于异步与线程模型

rerank异步方法通过CompletableFuture.supplyAsync(..., IO_EXECUTOR)执行,IO_EXECUTOR是一个由OpenJiuwenExecutors.newBoundedModulePool("chat-reranker-io", true)创建的有界模块线程池(ChatReranker.java#L40-L42)。由于每个候选一次请求、批量候选需多次请求,在需要重排大量候选时建议使用异步接口并发提交,并留意线程池容量。

七、适用场景与注意事项

适用场景:当你的部署环境只有 chat completion 端点(例如未部署 vLLM 专用 rerank 服务、或模型本身没有 rerank 头),且服务端支持 logprobs 返回时,ChatReranker可以零额外服务地提供重排能力,适合 RAG 检索后的粗排/精排、答案候选筛选等场景。

注意事项:

  1. yes_no_ids必须配置且恰好两个 token id,否则构造直接抛异常;
  2. 每个候选一次请求,批量重排会产生 N 次模型调用,存在明显的成本与延迟放大,需按业务量评估;
  3. 服务必须支持logprobs=true且返回top_logprobs结构,接入前务必先跑testCompatibility();
  4. temperature、top_p等配置在 chat 请求中会被固定的temperature=0覆盖,不要依赖它们调节输出;
  5. 源码在构造时打印"ChatReranker support is experimental in openJiuwen, you have been warned."警告日志(ChatReranker.java#L75),当前实现仍标注为实验性能力,生产接入前建议结合自身服务验证打分稳定性。

结语

ChatReranker提供了一条"复用 chat completion 实现重排"的轻量路径:固定端点、yes/no token 打分、单候选逐次请求,配合严格的yes_no_ids校验与兼容性探测,保证了方案的可用边界清晰可控。结合 StandardReranker 与 RerankerConfig,开发者可以在同一检索链路中按服务能力灵活选择重排实现。

  • 人工智能
  • AI Agent
  • Agent 框架
  • 工具调用
  • Agent 工作流
  • RAG
  • MCP Clients
  • 强化学习

【免费下载链接】agent-core-java

openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。

项目地址:https://gitcode.com/openJiuwen/agent-core-java
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询