数据截至 (上游 commit 298b68274234)
Ragas 执行与实验
本章讲底座:评测和测试集生成都要发成百上千次 LLM 请求,
Executor负责把它们并发跑完且不互相拖垮;以及 Ragas 正在用@experiment取代evaluate()的新范式。
1. 为什么需要一个专门的执行器
评测 100 条样本 × 4 个指标 = 400 个任务,每个任务里还可能套 2~3 次 LLM 调用。串行跑会等到天荒地老;裸 asyncio.gather 又会一口气打满 API 限流、且一个失败可能拖垮全部。
Executor 解决四件事:限制并发数、单任务失败不影响全局、结果按提交顺序排回、可中途取消。它在 evaluate 和 testset 生成里被反复复用。
2. 主线:submit → 包装 → 并发跑 → 排序还原
executor.submit(metric.single_turn_ascore, sample, ...) ← 攒任务
│ 每个任务被 wrap_callable_with_index 包一层:
│ · 记录提交序号 counter
│ · try/except: 失败时(默认)吞掉 → 返回 (counter, np.nan)
▼
executor.aresults() / results()
│ _process_jobs: 把所有任务转协程,as_completed 限并发跑
│ (可选 batch_size 分批,带嵌套进度条)
▼
sorted(results, key=lambda x: x[0]) ← 按 counter 排回提交顺序
│
▼
[r[1] for r in ...] ← 剥掉序号,返回纯结果列表
索引是关键。 并发完成顺序是乱的,但调用方要求结果顺序 == 提交顺序。wrap_callable_with_index 给每个任务绑一个 counter,完成时返回 (counter, result)(src/ragas/executor.py:64-86);aresults 最后按 counter 排序再剥掉序号(src/ragas/executor.py:193-202)。evaluate 就是靠这个顺序把结果切回「第 i 行第 j 个指标」(src/ragas/evaluation.py:292-300)。
容错。 wrap_callable_with_index 里,默认 raise_exceptions=False 时把异常记 log 并返回 np.nan,让这一条样本失败不毁掉整批(src/ragas/executor.py:71-84)。这就是为什么调试时要开 raise_exceptions=True。
限并发。 _process_coroutines 用 as_completed(coroutines, max_workers, cancel_check=...) 把并发上限钉在 run_config.max_workers(src/ragas/executor.py:180-191),避免打爆 API。
可取消。 cancel() 置一个 threading.Event,批处理循环每批前检查 is_cancelled()(src/ragas/executor.py:56-62、156-158)。evaluate(..., return_executor=True) 把 executor 还给你,就为了能调 executor.cancel()。
幂等与可复用。 _process_jobs 一进来就把 jobs 拷贝一份再清空原列表,防止重复执行(src/ragas/executor.py:115-117);_jobs_processed 跨多次 run 持续累加,保证索引连续(src/ragas/executor.py:98-103)。
3. nest_asyncio:在 Jupyter 里跑异步的妥协
Ragas 大量用户在 Jupyter notebook 里用,而 notebook 本身已经有一个跑着的事件循环——这时再 asyncio.run 会报「event loop already running」。
Ragas 的应对是 nest_asyncio(允许事件循环嵌套):同步入口 results() 先 apply_nest_asyncio() 打补丁再 run(src/ragas/executor.py:204-215)。
但 nest_asyncio 在生产异步服务里会引发问题,所以 evaluate 给了 allow_nest_asyncio=False:此时走纯 asyncio.run 新建事件循环,不打补丁(src/ragas/evaluation.py:475-484)。而 aevaluate(纯异步版)从头就不打补丁(src/ragas/evaluation.py:79-82)。这是「兼容 notebook」与「生产安全」之间的取舍。