feat: 评估数据集自动生成支持断点续跑#834
Conversation
There was a problem hiding this comment.
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.
| progress = int(99 * (progress_base + generated) / max(total_progress, 1)) | ||
| message = f"已生成 {progress_base + generated}/{total_progress}" |
There was a problem hiding this comment.
此处如果 total_progress 为 None(其默认值为 None),调用 max(total_progress, 1) 会抛出 TypeError 导致运行时崩溃。建议在计算进度前对 total_progress 进行空值处理,可以使用 total_progress if total_progress is not None else (progress_base + count) 作为安全回退。
| 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}" |
| buffer.append(item) | ||
| if len(buffer) >= batch_size: | ||
| await flush_items() |
There was a problem hiding this comment.
这里存在一个关键的设计缺陷:当前的 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,从而实现真正的流式批量持久化。
| if start_index == 0: | ||
| raise ValueError("未生成有效评估题目") |
There was a problem hiding this comment.
在断点续跑场景下,如果恢复执行后未能生成任何新的有效评估题目(例如因为 LLM 持续报错),此时 start_index 依然等于 existing_count(大于 0)。如果仅判断 start_index == 0,任务会直接被标记为 completed(完成),导致用户无法感知生成未达到预期数量(total_count)的问题。
建议将 start_index == 0 的判断修改为 start_index == existing_count,这样无论是首次生成(existing_count == 0)还是断点续跑,只要没有新题目生成且未达到目标,都能正确抛出异常并使任务进入 failed 状态。
| if start_index == 0: | |
| raise ValueError("未生成有效评估题目") | |
| if start_index == existing_count: | |
| raise ValueError("未生成新的有效评估题目") |
|
是的,这个时候这个限制就该移除了:【未完成数据集不可预览限制】 |
xerrors
left a comment
There was a problem hiding this comment.
供参考
[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
|
|
我觉得应该生成器边生成边 yield,service 批量落库。然后还有一个上次忘记补充了,这个 batch_size 不要放到 config 里面,直接读取环境变量甚至在代码对应的文件开头写一个全局变量就可以了。这个不必放到全局 config 里面。Q1 是因为 PR 期间主分支在修复另外一个 bug 的时候修改了 knowledgebase 的导入路径。 |
冲突解决:app.py 采用 upstream 移除 _handle_environment 的重构;service.py 保留 upstream 统一异常处理(uncancel/取消文案)并插入 flush_items_best_effort 保留残余落库;同步 FakeContext/FakeRepo 契约以匹配 TaskContext 新取消语义。
基于你这边提供的建议,我完成了以下的修改,目前本地已经完成测试,暂无问题:
|
变更描述
简要描述这个 PR 做了什么
变更类型
测试
相关日志或者截图






在代码中硬截断任务进度,模拟任务失败场景
已生成数据集已在数据库中持久化
重新生成任务
说明