Skip to content

feat: 评估数据集自动生成支持断点续跑#834

Open
shji47 wants to merge 6 commits into
xerrors:mainfrom
shji47:dev/task_resume
Open

feat: 评估数据集自动生成支持断点续跑#834
shji47 wants to merge 6 commits into
xerrors:mainfrom
shji47:dev/task_resume

Conversation

@shji47

@shji47 shji47 commented Jul 16, 2026

Copy link
Copy Markdown

变更描述

简要描述这个 PR 做了什么

  1. 评估数据集自动生成支持断点续跑,生成过程中按 YUXI_DATASET_PERSIST_BATCH_SIZE 批量持久化,任务失败后可从已持久化进度继续;
  2. 新增 resume 接口与前端"继续生成"按钮;

变更类型

  • 新功能
  • Bug 修复
  • 文档更新
  • 其他

测试

  • 已在 Docker 环境测试
  • 相关功能正常工作

相关日志或者截图
在代码中硬截断任务进度,模拟任务失败场景
在代码中截断任务,模拟失败
任务生成失败
已生成数据集已在数据库中持久化
已生成任务完成持久化
重新生成任务
任务重新生成
任务从中断处重新生成
最终生成的结果

说明

  • 原本想加入任务生成失败数据集的前端预览功能,但是后端接口加入了【未完成数据集不可预览限制】,因此本功能未添加,不过我认为这个功能是有必要的
  • 跑全部测试回归的时候,有单测和集成测试文件重名报错的情况,不涉及本次功能,未修复

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

Copy link
Copy Markdown
Contributor

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 a resume and checkpointing feature for the automatic generation of evaluation datasets, allowing tasks to persist progress in batches and resume after failure. It adds a new resume API endpoint, UI support, and corresponding tests. The review feedback highlights three key issues: a potential TypeError when total_progress is None, a critical design flaw where the generator gathers all items before yielding (which prevents real-time batch persistence from working during a crash), and an incorrect completion check (start_index == 0) that fails to catch when no new items are generated during a resume attempt.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +328 to +329
progress = int(99 * (progress_base + generated) / max(total_progress, 1))
message = f"已生成 {progress_base + generated}/{total_progress}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

此处如果 total_progressNone(其默认值为 None),调用 max(total_progress, 1) 会抛出 TypeError 导致运行时崩溃。建议在计算进度前对 total_progress 进行空值处理,可以使用 total_progress if total_progress is not None else (progress_base + count) 作为安全回退。

Suggested change
progress = int(99 * (progress_base + generated) / max(total_progress, 1))
message = f"已生成 {progress_base + generated}/{total_progress}"
total_val = total_progress if total_progress is not None else (progress_base + count)
progress = int(99 * (progress_base + generated) / max(total_val, 1))
message = f"已生成 {progress_base + generated}/{total_val}"

Comment on lines +521 to +523
buffer.append(item)
if len(buffer) >= batch_size:
await flush_items()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

这里存在一个关键的设计缺陷:当前的 iter_generated_benchmark_items 生成器内部实现是「先通过 asyncio.gather 等待所有 worker 协程执行完毕,收集到全部结果后才统一 yield」(见 benchmark_generation.py 第 335-350 行)。

这意味着,在生成任务运行期间,此处的 async for item in iter_generated_benchmark_items(...) 循环在所有题目生成完毕之前不会收到任何数据。如果生成过程中任务因报错、超时或被取消而中断,asyncio.gather 会直接抛出异常,导致已生成的题目全部丢失,此处的 flush_items() 根本没有机会被执行。

这使得「按 YUXI_DATASET_PERSIST_BATCH_SIZE 批量持久化以支持断点续跑」的设计在单次任务失败时无法生效(单元测试之所以能通过,是因为单测中使用了自定义的 fake_iter 模拟了流式 yield,而没有反映真实的生成器行为)。

建议:重构 iter_generated_benchmark_items,引入 asyncio.Queue 让 worker 协程实时将结果放入队列,生成器再从队列中消费并实时 yield,从而实现真正的流式批量持久化。

Comment on lines 531 to 532
if start_index == 0:
raise ValueError("未生成有效评估题目")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

在断点续跑场景下,如果恢复执行后未能生成任何新的有效评估题目(例如因为 LLM 持续报错),此时 start_index 依然等于 existing_count(大于 0)。如果仅判断 start_index == 0,任务会直接被标记为 completed(完成),导致用户无法感知生成未达到预期数量(total_count)的问题。

建议将 start_index == 0 的判断修改为 start_index == existing_count,这样无论是首次生成(existing_count == 0)还是断点续跑,只要没有新题目生成且未达到目标,都能正确抛出异常并使任务进入 failed 状态。

Suggested change
if start_index == 0:
raise ValueError("未生成有效评估题目")
if start_index == existing_count:
raise ValueError("未生成新的有效评估题目")

@xerrors

xerrors commented Jul 16, 2026

Copy link
Copy Markdown
Owner

是的,这个时候这个限制就该移除了:【未完成数据集不可预览限制】

@xerrors
xerrors self-requested a review July 17, 2026 05:56

@xerrors xerrors left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

供参考

[P1] service.py:7 导入了不存在的 yuxi.knowledge.knowledge_base。实际模块随后又从 yuxi.knowledge.runtime 导入,导致 EvaluationService 直接 ImportError,新增测试在收集阶段失败,相关 API 也无法正常加载。

[P1] benchmark_generation.py:335 等所有 worker 完成后才统一 yield。如果生成中途异常,已经生成的结果仍留在内存,flush_items() 根本不会执行,因此本 PR 的核心“断点持久化”能力实际无效。测试使用了流式 fake_iter,没有覆盖真实生成器。

[P1] service.py:299 先查询任务、再调用普通 enqueue,存在并发窗口。两个恢复请求可能同时创建任务,从相同 item_index 开始写入,最终触发唯一约束冲突并相互覆盖任务元数据。项目已有原子化的 enqueue_unique_by_payload 可直接使用。

[P2] service.py:532 恢复时若一个新题目都没生成,start_index 仍大于 0,任务会被错误标记为完成,即使题目数没有达到 total_count。应比较恢复前后的数量,或直接要求最终数量达到目标。

另外,benchmark_generation.py:328 的 total_progress=None 默认值会使 max(total_progress, 1) 抛出 TypeError,也需要修复。

@shji47

shji47 commented Jul 17, 2026

Copy link
Copy Markdown
Author

供参考

[P1] service.py:7 导入了不存在的 yuxi.knowledge.knowledge_base。实际模块随后又从 yuxi.knowledge.runtime 导入,导致 EvaluationService 直接 ImportError,新增测试在收集阶段失败,相关 API 也无法正常加载。

[P1] benchmark_generation.py:335 等所有 worker 完成后才统一 yield。如果生成中途异常,已经生成的结果仍留在内存,flush_items() 根本不会执行,因此本 PR 的核心“断点持久化”能力实际无效。测试使用了流式 fake_iter,没有覆盖真实生成器。

[P1] service.py:299 先查询任务、再调用普通 enqueue,存在并发窗口。两个恢复请求可能同时创建任务,从相同 item_index 开始写入,最终触发唯一约束冲突并相互覆盖任务元数据。项目已有原子化的 enqueue_unique_by_payload 可直接使用。

[P2] service.py:532 恢复时若一个新题目都没生成,start_index 仍大于 0,任务会被错误标记为完成,即使题目数没有达到 total_count。应比较恢复前后的数量,或直接要求最终数量达到目标。

另外,benchmark_generation.py:328 的 total_progress=None 默认值会使 max(total_progress, 1) 抛出 TypeError,也需要修复。

好的,麻烦验证了,我这边修复一下

@shji47

shji47 commented Jul 17, 2026

Copy link
Copy Markdown
Author

供参考

[P1] service.py:7 导入了不存在的 yuxi.knowledge.knowledge_base。实际模块随后又从 yuxi.knowledge.runtime 导入,导致 EvaluationService 直接 ImportError,新增测试在收集阶段失败,相关 API 也无法正常加载。

[P1] benchmark_generation.py:335 等所有 worker 完成后才统一 yield。如果生成中途异常,已经生成的结果仍留在内存,flush_items() 根本不会执行,因此本 PR 的核心“断点持久化”能力实际无效。测试使用了流式 fake_iter,没有覆盖真实生成器。

[P1] service.py:299 先查询任务、再调用普通 enqueue,存在并发窗口。两个恢复请求可能同时创建任务,从相同 item_index 开始写入,最终触发唯一约束冲突并相互覆盖任务元数据。项目已有原子化的 enqueue_unique_by_payload 可直接使用。

[P2] service.py:532 恢复时若一个新题目都没生成,start_index 仍大于 0,任务会被错误标记为完成,即使题目数没有达到 total_count。应比较恢复前后的数量,或直接要求最终数量达到目标。

另外,benchmark_generation.py:328 的 total_progress=None 默认值会使 max(total_progress, 1) 抛出 TypeError,也需要修复。

hello wenjie,我浏览了一下你这提供的问题清单,可以讨论一下具体的修复吗,主要是针对Q2的,不过在这之前需要澄清一下,Q1的问题应该不是我引入的,看git记录,是在410dd47c14b9c5d3fea2822e9802c78f8510bb1a这个commit中引入的,我import应该在第6行:from yuxi.config.app import config
Q2确实是因为没有考虑并发的场景,目前我的思路需要改动生成器 worker 调度的核心代码,所以希望和你讨论一下这个思路的可行性:

  1. 引入 results_queue,每个 worker 把每次 attempt 的结果入队——成功 (attempt_no, item),失败/超额 (attempt_no, None),异常(ex),任务完成(_WORKER_DONE)。
  2. 主协程流式消费:正常路径用重排缓冲按 attempt_no 顺序即时 yield(重排保证有序,与原逻辑一致),yield 出的 item 由 service 按批持续落库。
  3. 任一 worker 异常则 fail-fast:取消所有 worker 并 gather,把已生成未 yield 的 items(队列中 + 重排缓冲中)drain 出来一并 yield,由 service 统一落库,最后才抛出异常。

@xerrors

xerrors commented Jul 18, 2026

Copy link
Copy Markdown
Owner

我觉得应该生成器边生成边 yield,service 批量落库。然后还有一个上次忘记补充了,这个 batch_size 不要放到 config 里面,直接读取环境变量甚至在代码对应的文件开头写一个全局变量就可以了。这个不必放到全局 config 里面。Q1 是因为 PR 期间主分支在修复另外一个 bug 的时候修改了 knowledgebase 的导入路径。

shji47 added 4 commits July 18, 2026 22:21
冲突解决:app.py 采用 upstream 移除 _handle_environment 的重构;service.py 保留 upstream 统一异常处理(uncancel/取消文案)并插入 flush_items_best_effort 保留残余落库;同步 FakeContext/FakeRepo 契约以匹配 TaskContext 新取消语义。
@shji47

shji47 commented Jul 18, 2026

Copy link
Copy Markdown
Author

我觉得应该生成器边生成边 yield,service 批量落库。然后还有一个上次忘记补充了,这个 batch_size 不要放到 config 里面,直接读取环境变量甚至在代码对应的文件开头写一个全局变量就可以了。这个不必放到全局 config 里面。Q1 是因为 PR 期间主分支在修复另外一个 bug 的时候修改了 knowledgebase 的导入路径。

基于你这边提供的建议,我完成了以下的修改,目前本地已经完成测试,暂无问题:

  • Q1:合并主分支后应该不存在冲突情况
  • Q2:已改为流式 yield——生成器通过 results_queue 边生成边 yield,service 端批量落库;异常时通过 flush_items_best_effort 尽量持久化已缓冲的题目。新增了使用真实生成器(非 fake_iter)的测试,覆盖并发场景下中途失败的断点持久化
  • Q3:改用 enqueue_unique_by_payload 原子化入队,消除并发窗口
  • Q4:完成判断改为比较恢复前后的 start_index,若未生成新题目则报错而非标记完成
  • Q5:total_progress=None 时回退为 progress_base + count,不再直接传入 max()
  • 额外修改:生成失败的数据集也支持点击预览已生成的结果;batch_size 从全局 config 移至模块级环境变量 DATASET_PERSIST_BATCH_SIZE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants