Conversation
背景:LLM 图谱抽取器对每个分块调一次模型,该调用的超时写死 60 秒。生产实测配置的 抽取模型是推理型模型,单块抽取耗时中位约 40s、P90 约 69s,于是约 1/5 的抽取第一次 必然超时,靠最多 3 次重试才勉强成功——抽取失败计数为 0,但白烧了 LLM 调用与时间。 (注意 timeout 是单次 HTTP 请求的预算,openai SDK 内部还有 max_retries,因此单块 实际耗时可能远大于配置值。) 同时该超时无法通过现有的「模型参数 JSON」设置:yuxi/models/chat.py 的 _langchain_kwargs 里是 langchain_kwargs.update(kwargs),显式传入的 timeout 会覆盖 model_params 里的同名项,所以写在模型参数里的 timeout 一直是静默无效的。 改动:新增 extractor_options.timeout_seconds,并在图谱抽取配置弹窗里加输入框与提示。 默认保持 60 秒不变,避免改变既有部署的行为。 校验上挡掉了三类会被静默放行、进而在构建期整库失败的输入:bool(float(True)==1.0 会配出 1 秒超时)、NaN/inf(使区间比较恒为 False)、超大整数(float() 抛 OverflowError, 会穿透成 500 而非 400)。
CI 的 Ruff Format & Lint 在这一行失败:line-length 设为 120,该 raise 收成一行即可容纳, ruff format 要求合并。本地 ruff 能完整复现同一报错(非版本差异),已按格式化结果修改。 纯格式,无行为变化。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
变更说明
问题:LLM 图谱抽取器对每个分块调用一次模型,而这次调用的超时写死 60 秒(
extractors/llm.py的select_model(..., timeout=60.0, ...))。在一次真实的图谱构建(914 个分块)里实测:
超时后走最多 3 次重试,所以最终
抽取失败计数是 0——表面上没坏,代价是约 1/5 的抽取白跑 2–4 次,浪费 LLM 调用与时间。构建耗时从数分钟被拉到十几分钟。而且这个超时没法从 UI 改:
yuxi/models/chat.py的_langchain_kwargs是调用方显式传了
timeout=60.0,所以用户在「模型参数 JSON」里写{"timeout": 300}一直是静默无效的。这一点在 UI 上也看不出来。改动:把超时变成
extractor_options.timeout_seconds,并在图谱抽取配置弹窗里加一个输入框(含提示)。默认保持 60 秒不变,不改变既有部署的行为。feature_langchain_kwargs的覆盖顺序(见「替代方案」);不引入"单块总预算"语义(当前timeout是单次 HTTP 请求的预算,见「未验证范围与风险」)。工程主张与 Owner
extractor_options.timeout_seconds生效于抽取调用的模型客户端。Owner:
LLMGraphExtractor.extract()传入select_model的timeout。Owner:
DEFAULT_EXTRACTION_TIMEOUT_SECONDS与_resolve_timeout_seconds的默认分支。Owner:
_resolve_timeout_seconds的校验 + 路由把ValueError映射为 400。验证情况
主张 1:配置值真的传到模型调用
docker run ... pytest test/unit/graphs -q→ 53 项通过。其中test_llm_graph_extractor_passes_configured_timeout_to_model断言select_model收到timeout=300.0。extract()改回硬编码timeout=60.0后该用例失败(变异校验已跑)。主张 2:默认值不变
test_llm_graph_extractor_defaults_timeout_when_absent经extract()断言默认60.0也真的传下去。主张 3:非法配置在保存时被拒绝
{"timeout_seconds": true}→float(True)==1.0被区间校验放行,配出 1 秒超时;NaN/inf→ 区间比较恒为 False 被放行;超大整数 →float()抛OverflowError,穿透成 500 而非 400。0 / -1 / 600.0001 / "abc" / null / "" / true / false / nan / "NaN" / inf / 10**400,全部断言抛ValueError;另有正向用例断言300 / "300" / 600 / 0.5被接受(补上界与字符串数字,避免 off-by-one 漏测)。isinstance(raw, bool)与math.isfinite(...)两个守卫后,[true]与[nan]两条用例以正确原因失败(变异校验已跑)。主张 4:真实页面可配置且往返一致
extractor_options = {"schema": "", "model_spec": "...", "model_params": {}, "timeout_seconds": 180, "concurrency_count": 50}。主张 5:不回归
ruff check→ All checks passed;前端eslint . --max-warnings=0通过、node --test351 项通过、vite build退出码 0;git diff --check干净。简化 / 删除验收
不涉及(
feature)。独立语义 Review
已执行。由不具备开发上下文的全新 Reviewer 覆盖需求、完整 diff、测试与规范。其发现并已修复的问题:
timeout_seconds: true被静默接受为 1.0 秒(float(True)==1.0落在合法区间内)→ 保存返回 200,构建时每块必超时、全部标记失败。已修(显式拒绝 bool)+ 用例。NaN/"NaN"/inf被静默接受(区间比较对 NaN 恒为 False)→ 配置阶段通过、抽取阶段直接报错。已修(math.isfinite)+ 用例。OverflowError→ HTTP 500 而非 400。已修(except补OverflowError)+ 用例。Reviewer 同时确认了本 PR 的两条核心论据成立(
_langchain_kwargs的覆盖顺序、默认值与旧硬编码等价),并明确排除了若干怀疑(None语义、两次解析会不一致、字符串数字、与concurrency_count的对称性)。替代方案(为什么不那么做)
_langchain_kwargs让model_params能覆盖timeout:否决。那会让用户可控的任意 JSON 覆盖所有调用点的显式 kwargs——包括metadata(yuxi 在这里盖 provider/model 身份戳)、会话路由用的default_headers、stream_usage、max_retries,甚至api_key/base_url(会直接引发重复关键字参数冲突)。爆炸半径远大于一个专用字段,且model_params目前没有任何 schema 校验。本 PR 采用「专用字段 + 显式传入」是更收敛的做法。model.call外面包asyncio.wait_for。当前timeout是单次 HTTP 请求的预算,语义见下节。属后续项,不在本 PR。未验证范围与风险
Passed,取舍已明确):本 PR 让问题「可修」而非「已修」。是否应当直接调大默认值(120–180 比 600 更稳妥)属仓库策略,我没有替维护者决定。timeout_seconds不是单块总预算(Passed,语义澄清):它是 per-HTTP-request 的超时,而ChatOpenAI默认max_retries=2(全仓未设置),所以单块最坏耗时可达配置值 × 3。实测timeout=1.0时单次尝试打了 3 个 HTTP 请求、耗时 4.3s。这既解释了背景里「最大到过 234s」,也意味着把上限设成 600 的静默成本比直观更大。model_params.timeout仍静默无效(Not run):新输入框的 placeholder 提示了新用户,但已经按老办法在model_params里写timeout的部署不会得到任何提示。是否需要对存量配置做一次性告警,请维护者判断。Not run):本 PR 只改了超时来源与配置校验,未在真实构建中对比「60s vs 调大后的重试次数」。背景数据来自改动前的生产观测。Passed,可接受):Vue侧 60/600 与后端常量重复,将来若调整会静默漂移;更严的做法是由配置接口下发 min/max/default。界面变更
「修改图谱抽取配置」弹窗新增「单次抽取超时(秒)」,位于「LLM 抽取并发数」右侧;「模型参数 JSON」移到下一行整行显示(并在 placeholder 里点明它不能用于设置超时)。
(截图为配置弹窗本身,不含账号或知识库内容;托管在 fork 的独立分支
docs/pr-screenshots,未混入本 PR 的 diff。)关联事项
无关联 Issue。
补充说明
timeout_seconds是extractor_options里的可选键,缺省即旧行为;不改变任何既有配置的解析结果,也不需要数据迁移。已在库的配置(如{"model_spec": ..., "concurrency_count": 50})会继续以 60 秒运行,用户想调整时在 UI 里改即可。concurrency_count保持一致(try/except+ 区间 + 中文错误),并额外挡住了 bool / 非有限值 / 溢出这三类会被静默放行的输入。