Skip to content

fix: support both Bailian Rerank API formats based on URL endpoint#7250

Open
Neko-Yukari wants to merge 4 commits intoAstrBotDevs:masterfrom
Neko-Yukari:fix/bailian-rerank-api-format
Open

fix: support both Bailian Rerank API formats based on URL endpoint#7250
Neko-Yukari wants to merge 4 commits intoAstrBotDevs:masterfrom
Neko-Yukari:fix/bailian-rerank-api-format

Conversation

@Neko-Yukari
Copy link
Copy Markdown
Contributor

@Neko-Yukari Neko-Yukari commented Mar 31, 2026

Summary

阿里云百炼 rerank provider 存在两个 API 端点格式不兼容的问题。

Bug 触发方式

  1. 配置阿里云百炼 rerank provider
  2. 设置 rerank_api_basehttps://dashscope.aliyuncs.com/compatible-api/v1/reranks
  3. 选择模型 qwen3-rerank
  4. 点击测试 → 返回 "Rerank provider test failed, no results returned"

根因:阿里云百炼有两个不同的 rerank API 端点,请求/响应格式不同:

端点 请求格式 响应格式
/compatible-api/v1/reranks 扁平 {model, query, documents} results 在根级别
/api/v1/services/rerank/... input 包装 {model, input: {...}} output.results

代码之前只根据模型名判断格式,导致 qwen3-rerank + compatible-api 组合失败。

相关 PR:PR #7217 已修复响应解析,但请求格式仍有问题。

Changes

  • _build_payload(): 根据 URL 是否含 compatible-api 决定请求格式
  • _parse_results(): 根据 URL 判断响应中 results 的位置

Test

  1. 配置阿里云百炼 qwen3-rerank,使用 https://dashscope.aliyuncs.com/compatible-api/v1/reranks
  2. 点击测试 rerank provider
  3. 验证能正确返回重排序结果

Fixes #7161

Summary by Sourcery

Support both Bailian Rerank API formats by switching request and response handling based on the configured endpoint URL.

Bug Fixes:

  • Fix Bailian qwen3-rerank provider failures when using the compatible-api rerank endpoint by aligning payload and result parsing with that endpoint’s format.

Enhancements:

  • Unify rerank payload construction and result parsing logic to automatically adapt to Bailian compatible-api and standard rerank endpoints.

阿里云百炼有两个不同的 rerank API 端点:
- /compatible-api/v1/reranks: 使用扁平请求格式 {model, query, documents}
- /api/v1/services/rerank/...: 需要 input 包装 {model, input: {...}}

之前代码只根据模型名判断格式,导致 qwen3-rerank + compatible-api 组合失败。

修复内容:
- _build_payload(): 根据 URL 是否含 'compatible-api' 决定请求格式
- _parse_results(): 根据 URL 判断响应中 results 的位置

Fixes AstrBotDevs#7161
Copilot AI review requested due to automatic review settings March 31, 2026 17:56
@auto-assign auto-assign bot requested review from LIghtJUNction and Soulter March 31, 2026 17:56
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 31, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • Using "compatible-api" in self.base_url to switch request/response formats is brittle; consider parsing the URL path or matching against explicit known endpoints to avoid false positives or future URL changes breaking the behavior.
  • The construction of the params dictionary in _build_payload is duplicated in both branches; factor it out once before the if is_compatible_api to reduce repetition and keep the behavior consistent.
  • In _parse_results, the compatible API path treats any presence of code as an error; if the API can return a code field on success (e.g., "200"), you may want to align the condition with the non-compatible branch and only raise when code != "200" or per the official spec.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using `"compatible-api" in self.base_url` to switch request/response formats is brittle; consider parsing the URL path or matching against explicit known endpoints to avoid false positives or future URL changes breaking the behavior.
- The construction of the `params` dictionary in `_build_payload` is duplicated in both branches; factor it out once before the `if is_compatible_api` to reduce repetition and keep the behavior consistent.
- In `_parse_results`, the compatible API path treats any presence of `code` as an error; if the API can return a `code` field on success (e.g., `"200"`), you may want to align the condition with the non-compatible branch and only raise when `code != "200"` or per the official spec.

## Individual Comments

### Comment 1
<location path="astrbot/core/provider/sources/bailian_rerank_source.py" line_range="91" />
<code_context>
-        # qwen3-rerank follows a model-specific payload:
-        # query/documents/top_n/instruct should be at the top level.
-        if normalized_model == self.QWEN3_RERANK_MODEL:
+        is_compatible_api = "compatible-api" in self.base_url
+        if normalized_model == self.QWEN3_RERANK_MODEL and is_compatible_api:
             payload = {
</code_context>
<issue_to_address>
**issue (complexity):** Consider centralizing the compatibility-specific logic in `_build_payload` and `_parse_results` so shared behavior (params building, error handling, and result extraction) is implemented once instead of duplicated across branches.

You can simplify both `_build_payload` and `_parse_results` without changing behavior by centralizing the compatibility differences.

### `_build_payload`

The `params` dict is built twice with identical logic, and the only structural difference is where `query`/`documents` live. You can compute `params` once and make the `base` shape conditional:

```python
def _build_payload(self, query: str, documents: list[str], top_n: int | None = None) -> dict:
    normalized_model = self.model.strip().lower()
    normalized_top_n = top_n if top_n is not None and top_n > 0 else None
    is_compatible_api = "compatible-api" in self.base_url

    # qwen3-rerank follows a model-specific payload when using the compatible API
    if normalized_model == self.QWEN3_RERANK_MODEL and is_compatible_api:
        payload = {
            "model": self.model,
            "query": query,
            "documents": documents,
        }
        if normalized_top_n is not None:
            payload["top_n"] = normalized_top_n
        if self.instruct:
            payload["instruct"] = self.instruct
        if self.return_documents:
            logger.warning(
                "qwen3-rerank does not support return_documents; "
                "this option will be ignored."
            )
        return payload

    payload_input = {"query": query, "documents": documents}
    base: dict[str, Any] = {"model": self.model}
    if is_compatible_api:
        base.update(payload_input)  # top-level query/documents
    else:
        base["input"] = payload_input  # nested input

    params = {
        k: v
        for k, v in [
            ("top_n", normalized_top_n),
            ("return_documents", True if self.return_documents else None),
        ]
        if v is not None
    }
    if params:
        base["parameters"] = params
    return base
```

This keeps all semantics while removing the duplicated `params` block and branching.

### `_parse_results`

You can normalize the two response shapes into local variables first, then have a single error‑handling and extraction path:

```python
def _parse_results(self, data: dict) -> list[RerankResult]:
    is_compatible_api = "compatible-api" in self.base_url

    if is_compatible_api:
        code = data.get("code")             # None on success, set on error
        message = data.get("message", "")
        results = data.get("results", [])
        has_error = bool(code)
    else:
        code = data.get("code", "200")      # "200" on success
        message = data.get("message", "")
        results = data.get("output", {}).get("results", [])
        has_error = code != "200"

    if has_error:
        raise BailianAPIError(f"百炼 API 错误: {code}{message}")

    # 转换为RerankResult对象,使用.get()避免KeyError
    rerank_results: list[RerankResult] = []
    for idx, result in enumerate(results):
        try:
            index = result.get("index", idx)
            relevance_score = result.get("relevance_score", 0.0)
            if relevance_score is None:
                logger.warning(f"结果 {idx} 缺少 relevance_score,使用默认值 0.0")
                relevance_score = 0.0
            rerank_results.append(
                RerankResult(
                    index=index,
                    relevance_score=relevance_score,
                    document=result.get("document"),
                )
            )
        except Exception as e:
            logger.error(f"解析结果 {idx} 时发生错误: {e},原始结果: {result}")

    return rerank_results
```

This keeps the compatibility-specific semantics (different success conditions and response shapes) but centralizes error handling and result extraction, reducing branching and making future changes safer.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dosubot dosubot bot added the area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. label Mar 31, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for a 'compatible-api' version of the Bailian rerank source by conditionally adjusting the payload structure and response parsing based on the base URL. The review identifies opportunities to reduce code duplication in the _build_payload and _parse_results methods by extracting shared logic for parameter construction and error handling.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Bailian (阿里云百炼) rerank integration by selecting request/response formats based on which rerank API endpoint is configured, addressing incompatibilities between the compatible-api endpoint and the legacy service endpoint.

Changes:

  • Update _build_payload() to choose between flat vs input-wrapped request bodies based on whether rerank_api_base contains compatible-api.
  • Update _parse_results() to read rerank results from either results (compatible endpoint) or output.results (legacy endpoint).
  • Adjust error handling logic to differ between compatible and legacy endpoints.
Comments suppressed due to low confidence (1)

astrbot/core/provider/sources/bailian_rerank_source.py:139

  • For the compatible-api endpoint branch, top_n / return_documents are currently placed under parameters (via base["parameters"] = params). The PR description indicates the compatible endpoint expects a flat payload, so these options should likely be top-level fields (as already done in the qwen3-specific branch) rather than nested under parameters, otherwise non-qwen3 models on the compatible endpoint may receive an incompatible request body.
        if params:
            base["parameters"] = params

…ndling

- Extract params building outside the if-else branch
- Add back empty results warning log
- Simplify error handling variable assignment
@Neko-Yukari
Copy link
Copy Markdown
Contributor Author

回复评论

感谢各位的代码审查!

已采纳的修改

  1. 减少重复代码 - 将 params 字典构建提取到 if-else 分支外部,避免代码重复。

  2. 恢复空结果警告日志 - 添加回 if not results: logger.warning(...) 便于诊断。

  3. 简化错误处理 - 显式分离 is_compatible_api 的两种错误检测逻辑,代码更清晰。

未采纳的问题

关于 "compatible-api" in self.base_url 检测方式:这种检测方式虽然看似脆弱,但实践中是可靠且符合阿里云文档的。理由:

  • 阿里云只有两个 rerank 端点:/compatible-api/v1/reranks/api/v1/services/rerank/...
  • URL 一旦配置好就不会频繁变动
  • 如果未来 URL 变化,用户需要重新配置,这是可接受的行为

关于 compatible-api 上的 parameters 问题:Copilot 提到 non-qwen3 模型在 compatible-api 上 top_n 被放在 parameters 下会有问题。但实际上只有 qwen3-rerank 模型支持 compatible-api 端点,其他模型(如 gte-rerank-v2)使用该端点会返回 "Unsupported model" 错误。因此这个问题在实践中不会发生。

测试验证

两种端点格式均已验证通过:

qwen3-rerank + compatible-api: 3 results ✅
gte-rerank-v2 + standard API: 3 results ✅

@Neko-Yukari
Copy link
Copy Markdown
Contributor Author

关联 PR #7217

本 PR 与 he-yufeng 的 PR #7217 互补:

PR #7217 PR #7250
修复内容 响应解析 (output.results vs results) 请求格式 + 响应解析
请求格式 ❌ 未修复 ✅ 根据 URL 判断用 input 包装还是扁平格式
响应解析 ✅ 用 or 回退 ✅ 用显式 if-else 分支

为什么需要本 PR

即使合并 PR #7217,使用 qwen3-rerank + compatible-api/v1/reranks 端点仍会失败,因为:

  1. PR fix: support both old and new Bailian Rerank API response formats #7217 只修复了响应解析,没有修复请求格式
  2. 标准 API 需要 input 包装:{"model": "...", "input": {"query": ..., "documents": ...}}
  3. compatible-api 需要扁平格式:{"model": "...", "query": ..., "documents": ...}
  4. 代码之前只根据模型名判断格式,导致请求格式错误

评论指出的 or 回退问题

PR #7217 使用 data.get("output", {}).get("results") or data.get("results", []) 存在两个问题:

  1. 空列表 [] 会被 or 当作 falsy 值处理
  2. data.get("output", {})outputnull 时返回 None,后续调用 .get() 会抛出 AttributeError

本 PR 使用显式 if-else 分支避免了这些问题。

qwen3-rerank always uses flat format regardless of API endpoint.
Other models (gte-rerank-v2, etc.) use input wrapper format.

This simplifies the logic and correctly handles all model/URL combinations.
Tested: qwen3-rerank accepts both formats, gte-rerank-v2 only supports input wrapper.
@dosubot dosubot bot added size:S This PR changes 10-29 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Apr 3, 2026
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 使用阿里云百炼 qwen3-rerank 重排序模型时报错

2 participants