Iris 装完能启动、能进界面,但输入问题后返回空结果或直接报错,通常不是“没装好”这一件事,而是两条链路里有一条断了:模型客户端没初始化成功,或者检索工具没被注册进可用列表。判断顺序建议是——先看启动日志停在启动阶段还是调用阶段,再用一条不经过检索的直接提问单独验证模型通路,最后把检索工具的返回单独打印出来。先定位空结果出现在哪一步,再决定改哪一侧的配置。
适用场景是服务能启动、问答返回空或报错的排查。操作动作:第一步在启动日志里确认模型客户端初始化结果,第二步用一条绕过检索的直接请求验证模型通路,第三步检查检索工具的注册配置和启动时打印的工具列表。验证方式:每一步都要能单独看到非空的返回,模型侧看到文本、检索侧看到命中条目。风险边界:在没确认断点前同时改模型配置和检索配置,会让复测结果失去对照,建议一次只动一处。
在启动日志里确认模型客户端是否初始化成功
启动阶段失败和启动成功但调用阶段失败,是两种不同的处理方向。前者通常在服务刚起来时就抛异常或打 warning,后者日志看着一切正常,只有你发问时才报错。先把启动日志完整抓下来,不要只看终端最后几行。
需要搜索的关键词类别可以分三组:初始化类(init、client、model、provider、load)、凭据与地址类(api key、token、base url、endpoint、auth)、连接类(connect、timeout、refused、ssl、traceback、error)。
grep -Ei "client|init|model|provider|api[_ -]?key|token|base[_ ]?url|auth|connect|timeout|refused|error|traceback" app.log正常输出与异常输出的形态差别在于:正常时你能在启动完成附近看到模型名、端点、初始化完成之类的记录,没有 traceback;异常时要么直接中断启动,要么出现密钥缺失、地址解析失败、连接超时这类条目,并伴随堆栈。注意不要拿某一句固定文案去比对,不同版本的措辞不一样,看的是“有没有初始化成功的记录”和“有没有异常堆栈”。这一层只判断模型客户端有没有起来,不判断它能不能正确回答。
用一条不经过检索的直接提问验证模型通路
这一步的目的是把检索完全摘出去,只确认模型侧可用。请求地址、密钥、模型名都从环境变量读取,不要写死在脚本里,这样和服务里的配置是同一份来源。
import os, requests
base = os.environ["MODEL_BASE_URL"] # 形如 http://host:port/v1
key = os.environ["MODEL_API_KEY"]
model = os.environ.get("MODEL_NAME") # 缺省时按你的接入层默认值填
resp = requests.post(
f"{base}/chat/completions",
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
json={
"model": model,
"messages": [{"role": "user", "content": "只回复两个字:可用"}],
"stream": False,
},
timeout=30,
)
print("status:", resp.status_code)
print(resp.text[:500])期望的返回形态是:HTTP 状态正常,响应体里有选择项或文本字段,内容非空。如果这里就报 401、403、404 或连接超时,问题在模型侧,先不要动检索配置。路径、模型名、鉴权头这些按你的接入层替换,不保证所有服务端都叫同一个路由。
检查检索工具的注册与可用性配置
检索侧要确认的第一件事不是“索引里有没有数据”,而是“工具有没有被加载进可用列表”。相关配置项类别通常包括:工具或插件开关(tool / plugin / enable)、检索器或知识库指向(retriever / knowledge base / index / collection)、存储位置与凭据(path、host、collection name、embedding 模型名)。这些项如果留空、写错层级或被默认关闭,服务照样启动,但工具不会出现在列表里。
很多接入层会在启动时打印一份已加载的工具列表。启动后先找这份列表,确认检索工具在不在里面;不在,就是注册没生效,和模型没关系。如果在,再发一次真实提问,观察调用工具时留下的可见痕迹:通常包括工具调用日志、命中条数、耗时、使用的查询串。痕迹一条都没有,说明模型根本没触发工具;有痕迹但结果为空,说明工具跑了但没取到内容。这两者后续处理不同,别混在一起改。
把检索工具的返回单独打印出来
这一步用来区分“工具返回为空”还是“工具返回了但模型没用”。做法是在检索调用之后、模型调用之前,把原始返回打出来,再和模型最终回答并列看。
hits = retriever.search(query, top_k=3) # 函数名按你的接入层替换
print("命中条数:", len(hits or []))
for i, h in enumerate(hits or []):
text = getattr(h, "text", "") or ""
print(i, "score=", getattr(h, "score", None), "len=", len(text))
print(repr(text[:200]))
answer = llm.answer(query, context=hits) # 走你们正常的问答入口
print("最终回答:", repr(answer))对照方式很直接:命中条数为 0 或每条 text 长度为 0,空返回在检索侧,先查索引、过滤条件、embedding 模型是否和建索引时一致;命中条数正常、内容也有,但最终回答仍是空或明显没用这些内容,问题在提示词拼接或模型侧,重点看上下文有没有真的塞进请求里。把这三种情况标出来,就不会再靠猜。
按失败节点分两条分支处理并复测
确认断点之后,只改一侧,改完立刻复测同一个问题,不要顺手把另一侧也调一下。
模型侧检查清单:密钥是否过期或格式不对、base url 是否需要带版本路径、模型名是否和服务端一致、是否需要特定请求头、出网是否受限、超时是否过短。单点改动只动其中一项,例如只把超时从默认值调大,然后重跑上一节那条不经过检索的直接提问,看是否拿到非空文本。
检索侧检查清单:工具开关是否打开、索引或集合是否真的建好、建索引与查询是否用同一个 embedding 模型、过滤条件是否把所有内容都排除了、存储路径和权限是否可读。单点改动只动一项,例如临时去掉过滤条件,再跑一次工具返回打印,看命中条数是否从 0 变成非 0。
复测结果要能对上:模型侧单独提问有非空回答、检索侧单独调用有命中,再去发真实提问。如果两边各自都通、合起来还是空,就回到上一节看上下文拼接那一步。需要结合你的部署环境确认的具体项,主要是端口映射、凭据来源和索引目录这三处,这几处通常是环境差异最大的地方。