fix(eval): apply experiment config to the runtime and record requested/effective (#114) - #116
Conversation
…d/effective (helsome#114) Requested model/provider/thinking was stored in ExperimentConfig and echoed back in metadata without ever reaching the runtime, so a run labeled model A could silently execute the runtime's previous/default model. - ExperimentService now applies the requested model/provider (and optional thinking level) via the runtime LLM control surface BEFORE each case run, then confirms by readback; application failure or readback mismatch fails the run with CONFIG_APPLY_FAILED instead of executing mislabeled. - New EffectiveRuntimeConfig on EvaluationRun records the readback-confirmed model/provider/thinking per run; dimensions with no control surface (strategyId, everything in fixture mode) are recorded as unapplied with a reason. Historical runs simply omit the field (never backfilled). - ExperimentKernel.getLlmApi widened to ExperimentLlmControl (structural subset of LlmRuntimeApi, optional setModel/setThinkingLevel). - eval CLI: --thinking flag wired to config.thinkingLevel; new requested-vs-effective summary printed per run. - Fake-kernel tests cover: apply-before-startRun ordering, readback mismatch blocking, unsupported model failing explicitly, no config leakage between consecutive experiments, fixture-mode unapplied marking, readback-only effective recording, thinking apply + mismatch.
|
PR #116 已提,CI 状态正常:Typecheck / Focused tests / Full unit tests (advisory) / Secret scan 4 项全过,剩下的 "Full unit tests" 是 PR 上按 pr.yml 设计跳过的(只在 push main 时跑),不是失败。 |
helsome
left a comment
There was a problem hiding this comment.
实现方向正确,CI success 也已核对。当前有两个很具体的接线问题,请局部修正,不用扩成新框架:
- CLI 仍只从
--model provider/model-id推断 provider,没有把 config.model 去掉 provider 前缀;新增 applyRequestedConfig 会实际调用 setModel('provider', 'provider/model-id')。请在 CLI 边界拆出 provider + 纯 modelId(保留 modelId 本身后续的 /),并覆盖 CLI 输入→真实参数归一化→runtime fake→run metadata 的测试。目前新增用例直接传 config={provider:'provA',model:'m1'},没有覆盖这条公开命令。 - trace 元数据仍使用 effectiveConfig?.model ?? config.model(provider 同理);当控制面不存在/维度 unapplied 时,又会把 requested 写回实际模型标签。请把 requested 单独命名记录,没有回读依据的 actual/effective 保持 unknown,不要退回 requested 冒充。
验收材料请补 Bun 版本、OS、相邻80条测试的准确命令及 pass/fail/skip,已有聚焦范围不用扩大。缺 Key 是环境条件,不是代码缺陷;#114 明确要求的真实双模型验证可以由具备授权环境的维护者协助。若先落内部接线增量,改成 Refs #114,并把尚未验证的真实模型边界留在父任务;不要在未完成 A/B 时 Closes #114。
#122 也含 CLI 模型前缀归一化,请双方协调复用这一小段处理;effective-config 契约以本 PR 为主,不要求两套独立模型选择设计。
…th requested values (helsome#114) Addresses the two wiring defects from review. 1. `--model provider/model-id` only inferred the provider; the prefixed id still travelled as the model, so applyRequestedConfig called setModel('provider', 'provider/model-id') and the run metadata carried a model id no provider serves. The split now happens once, at the CLI boundary: normalizeModelSelection() returns provider + bare model id (only the first segment is the provider, so `openrouter/anthropic/x` keeps `anthropic/x` as the id). The helper lives in packages/shared/src/evaluation/model-selection.ts so helsome#122 can reuse the same snippet instead of growing a second normalization. 2. Trace metadata no longer falls back to the requested model/provider. The readback-confirmed values are passed only when they exist; a dimension with no runtime readback stays unknown instead of being filled in with the requested label. The request is recorded under its own keys (requestedModel / requestedProvider) in the Langfuse metadata record — deliberately not as `model:` / `provider:` tags, and not as the snapshot's model, so no generation span can claim a model that never ran. Tests: normalizeModelSelection cases (shorthand, multi-slash id, explicit --provider, trailing separator) plus the end-to-end chain CLI input → normalized params → fake runtime control call → run metadata, and a trace assertion that an unapplied request stays `requestedModel` while the confirmed readback lands under `model`.
|
两个接线问题已按您的方向做了局部修正(没有扩框架),新提交 1) CLI 边界拆出 provider + 纯 modelId 原实现只用
2) trace 不再把 requested 当成 actual
测试 新增 2 个用例,覆盖您点出的这条链路:
验收材料 环境:Bun 1.4.2 · Windows 11 (10.0.26200, x64)。说明一点:本机 那 1 条失败是 CLI 端到端也补了一条可看的证据: 修复前这里会打印 关于 #114 的收口 已把 PR 描述里的 |
|
补一条: 其中 |
原 blocker 已在 head d1c9830 处理:CLI 模型选择已统一归一化,trace requested/effective 也已分离;旧 REQUEST_CHANGES 失效。
helsome
left a comment
There was a problem hiding this comment.
当前 head 的两个接线 blocker 已处理:normalizeModelSelection() 在 CLI 边界只切第一个 /,显式 provider 优先,并由 CLI→runtime fake→run metadata 测试覆盖;trace 的 model/provider 只写 readback-confirmed 值,请求值单独记录为 requestedModel/requestedProvider,不再冒充 actual。PR 已收窄为 Refs #114,把真实双模型 A/B 留在父 Issue,缺 API key 不作为代码 blocker。验证材料已补 Bun 1.4.2 / Windows、24/24 focused、相邻评测 162 pass / 1 本地既有失败,并且当前 PR CI 的 Typecheck / Focused / advisory full unit / Secret scan 均通过。代码可以批准;当前 mergeable=false,只需基于最新 main 解决冲突并重跑相关基础检查,不要求扩大验收范围。
|
合并后 main 的精确 post-merge 检查发现一个小型集成回归,我已停止继续合并其他 PR。结果 SHA: 准确错误: 这是 #116 与当前 main 的 #117 组合后出现的直接类型回归,不是 API key/环境问题。最小 follow-up 即让该配置应用失败分支遵守现有 return contract(预期 |
…elsome#113) Review follow-up: execution validity is derived from execution facts and explicit error codes, not from the terminal status alone. Changes: - aggregate.ts: isInfrastructureRun stays true only for not-started runs (spawn/config/credential rejection) or an explicit runtime/process failure; PI_REQUEST_TIMEOUT / PI_RUNTIME_ERROR and other in-run errors remain quality outcomes. New isQualityRun keeps started tool_loop / timeout / budget-exhausted runs in the quality denominator; summary and execution counts share the same classification. - scripts/eval/run.ts: printSummary reuses isQualityRun/isInfrastructureRun (single source of truth) and the CLI provider/model split delegates to normalizeModelSelection, shared with helsome#116 — no second config contract. - model-selection.ts: provider/model shorthand split extracted as a shared helper (only the first `/` segment is the provider). - core/evaluation.ts: document evaluated / infraFailed semantics. Tests (reviewer-requested contrasts): - not-started config failure (missing key) is excluded from quality aggregates; - a run that started and then hit tool_loop / wall-clock timeout stays a valid negative quality result and counts as a failure. Verification: - bun test --isolate packages/shared/src/evaluation/aggregate.test.ts packages/shared/src/evaluation/experiment-service.test.ts scripts/eval -> 63 pass / 0 fail - bun run typecheck -> all packages exit 0 UI impact: no layout or interaction changes.
Reconcile the review follow-up with the CLI/effective-config work that landed on main (helsome#114/helsome#116, helsome#115/helsome#117). Conflict resolutions: - core/evaluation.ts: keep both `execution` (helsome#113 validity) and `effectiveConfig` (helsome#114 readback). - experiment-service.ts: keep the helsome#113 evaluation gating, the helsome#116 effective-config readback, and the helsome#117 bounded teardown; the helsome#114 config-apply failure path now stamps `execution: 'not-started'` and returns `runtimeUnusable`, so a config failure stays infra-invalid instead of being measured as a zero score. - experiment-service.test.ts: keep both the relay-header test and the helsome#115 teardown suite; update the helsome#114 CONFIG_APPLY_FAILED expectation to the helsome#113 semantics (validity `invalid`, passRate null). - scripts/eval/run.ts: keep the live preflight (helsome#113) and the effective-config summary (helsome#114); a single normalizeModelSelection import, with thinkingLevel from the CLI. Verification: - bun test --isolate -> 1539 pass / 8 skip / 0 fail - bun run typecheck -> all packages exit 0 UI impact: no layout or interaction changes.
Refs #114
问题回顾
评测记录写了某个 model/provider,不代表运行时真的用了它。
run.ts只把 CLI 参数放进ExperimentConfig,ExperimentService从不把它们应用到 Pi/LLM runtime,也没有任何生效值校验 —— "标签是模型 A、实际跑默认模型" 的无效横向比较就这样静默产生。改动
1. 配置真正应用到运行时(每个 case 启动前)
ExperimentService.runCase在startRun之前调用新的applyRequestedConfig:model + provider齐备 → 经ExperimentLlmControl.setModel()应用,随后getState()回读校验必须完全一致;应用失败或回读不一致 → 该 run 以CONFIG_APPLY_FAILED记为 failed(绝不静默回退到 runtime 残留的模型上执行)。thinkingLevel→setThinkingLevel()应用 + 回读校验,语义同上。strategyId;fixture/local 模式下的一切)→ 显式记为unapplied并给出原因,绝不冒充已生效。2. requested / effective 分离
packages/core:新增EffectiveRuntimeConfig(readback 确认的 model/provider/thinking +unapplied列表 +confirmedAt)与UnappliedConfigItem;EvaluationRun增加可选effectiveConfig。旧记录缺失该字段 = 历史未知,不反填推测值。model/provider仅在有回读依据时写入;无回读依据(控制面不存在、或该维度被记为unapplied)时保持 unknown,绝不回退成 requested 标签。请求值单独命名记录为requestedModel/requestedProvider(只进 metadata,不生成model:/provider:tag,也不进 generation span 的 model 字段)。--model provider/model-id拆成 provider + 纯 modelId 后再进入ExperimentConfig(此前只推断 provider、前缀仍留在 model 里,导致setModel('prov', 'prov/model-id'))。只按第一个/切分,openrouter/anthropic/claude-sonnet-4-5保留anthropic/claude-sonnet-4-5作为 modelId;显式--provider优先。该段抽出为normalizeModelSelection()(packages/shared/src/evaluation/model-selection.ts),fix(eval): separate live execution validity from quality and propagat… #122 可直接复用同一段,不必各自维护一份。evalCLI 新增:每 run 打印requested vs effective摘要行(含 unapplied 原因与CONFIG_APPLY_FAILED),并新增--thinking接线到config.thinkingLevel。ExperimentKernel.getLlmApi放宽为ExperimentLlmControl(LlmRuntimeApi的结构子集,setModel/setThinkingLevel可选),对现有实现向后兼容。3. 隔离
每个 case 都重新应用当前实验的请求配置并回读 —— 连续两个实验无法互相串用配置(有专门测试覆盖)。
验证
按 issue 的验证标准逐条对照(runtime fake 全部为可观测调用序列 + 状态可注入的 FakeKernel):
startRun前应用 — 测试断言setModel调用序在startRun之前mismatchSetModel注入,run 记为 failed 且startedRuns === 0failSetModel注入,错误信息含所请求的模型标识setModel调用序列断言为[provA/m1, provA/m2],两个 run 的 effectiveConfig 各自正确setModel:provA/m1(不是provA/provA/m1),且 run 的effectiveConfig与实验metadata.providerConfiguration都是{ provider: 'provA', model: 'm1' }/只切第一个、显式--provider优先、无/与provA/不改写effectiveConfig.model保持 undefined)requestedModel/requestedProvider有值、model/provider键缺失,且不生成带 model 的 generation span;有回读时model/provider才写入effectiveConfig为可选字段,测试中旧形态 run 正常持久化/读取)验收材料(环境与命令)
环境:Bun 1.4.2 · Windows 11 (10.0.26200, x64)。本机
node_modules因符号链接权限问题未完整安装,bun run typecheck(tsc)无法本地执行,类型检查以 CI 为准。关于那一条失败:把本 PR 触及的
langfuse/metadata.ts改动临时还原后,同一条用例仍以完全相同的方式失败(backend.lastErrorDetails为 undefined,即本机沙箱下 mock 的 500 响应没有走到错误分支),因此判定为本地环境问题而非本 PR 引入;该用例在 CI 为绿色。bun scripts/eval/run.ts --max-cases 1 --mode fixture端到端通过;再加一个归一化可见证据:即 CLI 传入的
deepseek/deepseek-chat已被拆成 provider=deepseek + modelId=deepseek-chat;修复前这里会显示model=deepseek/deepseek-chat,并把这串前缀原样交给setModel()。--model <provider/model-a>/--model <provider/model-b>的真实 live run,从 run 记录里读requested/effectiveConfig与 run id(CLI 会直接打印这一对照)。因此本 PR 标记为Refs #114,不Closes。与 #122 的关系
#122也包含--model provider/id归一化。本 PR 把它抽成normalizeModelSelection()(packages/shared/src/evaluation/model-selection.ts,语义与原实现一致:只切第一个/,显式 provider 优先),#122 可直接改为调用该函数,避免两套独立实现。effective-config 契约(EffectiveRuntimeConfig/ requested-vs-effective 命名)以本 PR 为主,不要求 #122 再设计一套模型选择。Scope 边界
不重写 AgentKernel、不新建 SDK/Manifest 系统;复用现有
LlmRuntimeApi控制面与 #21 方向的快照要素(effective config + dataset/version + gitSha 均已落在实验/run 记录里)。与 #113 分工不变:#113 检查本轮是否有效执行,本 PR 检查有效执行时到底用了哪套配置。