Repository navigation
[Bug] Xinference rerank 吞掉上游异常并返回空列表,导致知识库丢失已融合的检索结果 #10000
Description
问题描述
知识库 RetrievalManager.retrieve() 只在重排序抛出异常时保留融合结果。但 XinferenceRerankProvider.rerank() 在上游失败或模型未初始化时返回 [],管理器把它视为正常结果,覆盖原有非空候选。
本次给融合阶段提供 1 条有效文档候选,让 Xinference 模型调用抛出异常,最终检索返回 0 条。对照让异常向管理器传播,原候选正确保留。上游临时故障因此会表现成知识库无匹配结果,预设降级路径未生效。
代码定位
- astrbot/core/provider/sources/xinference_rerank_source.py:107
- astrbot/core/provider/sources/xinference_rerank_source.py:135
- astrbot/core/knowledge_base/retrieval/manager.py:215
- astrbot/core/knowledge_base/retrieval/manager.py:296
如何复现?
- 让融合结果包含至少 1 个存在元数据的文档块。
- 配置 Xinference 重排序,并让模型的
rerank()抛出上游不可用异常。 - 调用完整的
RetrievalManager.retrieve()方法。 - 对照令 provider 直接向管理器传播异常。
预期:故障有日志且保留融合候选。实际:Xinference 当前路径返回空检索结果;传播异常的对照路径返回原候选。
下面脚本已在该提交的仓库根目录用 Python 3.12.14 独立运行。为避免加载完整 AstrBot 和外部依赖,通过 AST 读取并执行仓库原始类定义;方法体保持不变,日志、数据库或上游模型按用例使用 mock。断言用于确认当前缺陷与对照结果,无真实模型调用或线上平台测试。
可运行的源码级复现(仅 Python 标准库)
保存为临时 Python 脚本,在 AstrBot 仓库根目录运行:
import ast, asyncio, abc, copy, functools, re, time, dataclasses
from pathlib import Path
from types import SimpleNamespace as NS
from unittest.mock import AsyncMock, Mock
ROOT = Path.cwd()
def load(path, names, env=None):
tree = ast.parse((ROOT/path).read_text())
selected = [n for n in tree.body if isinstance(n, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)) and n.name in names]
scope = dict(__name__='__main__', asyncio=asyncio, abc=abc, copy=copy, functools=functools, re=re, time=time, dataclass=dataclasses.dataclass, logger=Mock(), BaseChunker=object, deprecated=lambda **kw: lambda f:f)
scope.update(env or {})
mod=ast.Module(body=[ast.ImportFrom(module='__future__', names=[ast.alias(name='annotations')],level=0), *selected],type_ignores=[])
exec(compile(ast.fix_missing_locations(mod),str(ROOT/path),'exec'),scope)
return scope
async def main():
class Base: pass
s=load('astrbot/core/provider/sources/xinference_rerank_source.py',['XinferenceRerankProvider'],{'RerankProvider':Base,'register_provider_adapter':lambda *a,**k:lambda c:c,'ProviderType':NS(RERANK='rerank')})
provider=object.__new__(s['XinferenceRerankProvider']); provider.model=NS(rerank=AsyncMock(side_effect=RuntimeError('upstream down')))
s=load('astrbot/core/knowledge_base/retrieval/manager.py',['RetrievalResult','RetrievalManager'])
fused=NS(chunk_id='chunk1',doc_id='doc1',kb_id='kb1',content='known answer',score=.8,chunk_index=0)
db=NS(get_documents_with_metadata_batch=AsyncMock(return_value={'doc1':{'document':NS(doc_name='doc'),'knowledge_base':NS(kb_name='kb')}}))
mgr=s['RetrievalManager'](NS(retrieve=AsyncMock(return_value=[])),NS(fuse=AsyncMock(return_value=[fused])),db)
mgr._dense_retrieve=AsyncMock(return_value=[])
kb=NS(top_k_dense=50,top_k_sparse=50,top_m_final=5,rerank_provider_id='rp')
result=await mgr.retrieve('query',['kb1'],{'kb1':NS(kb=kb,vec_db=NS(rerank_provider=provider))})
print('6 failed Xinference rerank: fused candidates=1, returned=',len(result))
assert result==[]
provider.rerank=AsyncMock(side_effect=RuntimeError('propagated failure'))
result=await mgr.retrieve('query',['kb1'],{'kb1':NS(kb=kb,vec_db=NS(rerank_provider=provider))})
assert len(result)==1
asyncio.run(main())AstrBot 版本
4.28.0,master 提交 6494ac22e536d38ef6e07014d27b808d8c114bd2(2026-09-09 扫描)。
操作系统
Linux,Python 3.12.14。
部署方式
源码级单元复现;未启动完整服务。
使用的消息平台适配器
无;核心代码测试,不加载插件或平台适配器。
错误日志
以下为实际运行脚本的输出,非生产日志:
6 failed Xinference rerank: fused candidates=1, returned= 0
建议修复与验证
统一 provider 失败语义:网络/服务异常和未初始化状态应抛出明确异常,使管理器的降级逻辑生效。可同时对非空候选被异常空结果替换增加保护。补充上游失败、未初始化、成功重排三种测试。
重复检查
已检索 Xinference/rerank 与空结果相关 issue 和开放 PR。#9383/#9384 讨论代理支持,与错误被转换为空列表导致检索候选丢失不同。
检查清单
- 已搜索相关 issue 和开放 PR,未找到相同根因的报告。
- 复现未加载插件,问题位于 AstrBot 核心代码。
- 已阅读项目贡献指南及行为准则。
- 已明确区分源码级复现、mock 边界与未验证的线上场景。
Activity
fix: propagate Xinference rerank failures instead of returning empty …