Skip to content

fix(eval): separate live execution validity from quality and propagat… - #122

Open
suzhiguo7 wants to merge 3 commits into
helsome:mainfrom
suzhiguo7:fix/eval-live-validity-113
Open

suzhiguo7 wants to merge 3 commits into
helsome:mainfrom
suzhiguo7:fix/eval-live-validity-113

Conversation

@suzhiguo7

Copy link
Copy Markdown

修复 Live Nightly 的"全部运行失败仍显示成功"与评测有效性缺陷。

原始基线(6405b7b)

Run 35074861257 / artifact 10437259480:86/86 run 失败(1×PI_HEALTH_TIMEOUT + 85×PI_RUNTIME_ERROR: No API key found for the selected model),0 次 tool call;passRate=0 但 composite=0.596(未执行 run 的规则指标幻影满分);Actions conclusion=success。

改动说明

  • Live preflight(新增 scripts/eval/preflight.ts):逐项检查 Pi runtime 健康、模型可用、one-token 凭证探针、LongBridge 数据源、judge 就绪;只输出状态(敏感信息 redact),失败 exit 2 且不跑任何 case。
  • 执行有效性 vs 质量分离ExperimentSummary 增加 validity(valid/invalid/inconclusive)、execution(requested/started/evaluated/infraFailed/skipped)、validityReasons;质量指标只聚合 completed run;无有效 run 时 composite/passRate 为 null;有效负向 case 仍判 fail;invalid/inconclusive 实验拒绝 seed baseline。
  • 失败传播:CLI 退出码 0(有效通过)/1(质量回归或用法错误)/2(无效或不可结论)/3(取消);nightly 移除 continue-on-errorset -o pipefail 保留真实退出码,失败仍上传 summary/JSON/log。
  • 配套修复--model provider/id 归一化并显式 setModel(此前 live 不会把所选模型下发给 Pi);cold-start health timeout 5s→60s;空 answer 凭证探针不再误报 "Connection verified.";judge 支持额外 HTTP header(FINAGENT_JUDGE_HEADERS,供中转使用)。

关联 Issue

Closes #113

测试报告

环境

  • Bun:1.3.14
  • OS:Windows 10(本地验证;CI 为 ubuntu-latest)

实际执行命令与结果

bun run typecheck
→ @finagent/core / i18n / shared / ui / electron 全部 exit 0

bun test --isolate
→ Ran 1475 tests across 163 files: 1467 pass / 8 skip / 0 fail
  (8 skip:7 条需要 gitignore 的真实账户 fixture,1 条为 Windows symlink 权限;均非失败)

bun run eval:smoke -- --mode fixture --baseline folio-agent-v1 --out artifacts/eval-smoke.json
→ validity: valid · GATE: PASS (folio-agent-v1) · exit 0

# 与 workflow 相同的 `... | tee ...` 管道(Git Bash,真实 CLI,用 Pi 缺失制造底层失败)
FINAGENT_PI_COMMAND=pi-missing-xyz bun run eval:full -- --mode live --model go/deepseek-v4.1-flash --preflight-only --out artifacts/eval-pipefail-demo.json 2>&1 | tee artifacts/eval-pipefail-demo.log
→ 不加 set -o pipefail:管道 exit 0(CLI 本身 exit 2)——旧假绿机制复现
→ 加 set -o pipefail:管道 exit 2
→ 失败产物仍写出:artifacts/eval-pipefail-demo.json(exitCode=2, preflight.ok=false)

# 真实 live smoke(真实模型 + Folio 工具 + 真实 LongBridge 数据)
bun run eval:full -- --mode live --model go/deepseek-v4.1-flash --max-cases 1 --out artifacts/eval-live-smoke-judge.json
→ PREFLIGHT: READY(Pi/模型目录/凭证探针/LongBridge 数据源/judge 全 ok)
→ validity: valid · execution: requested=1 started=1 evaluated=1 infraFailed=0 skipped=0 · exit 0
→ case fv1-market-001 "What is Apple trading at right now?"
→ run fa6db2ce-f03c-422e-b7fb-eddf7b3635c9(exp-1789646764609-6dfc9e)
→ tools: get_quote(call_00_ET_lph7e4nq6vj7CDj7FYAN8578)success · get_market_status(call_01_ET_ihdiPhKoypVu9z8Fg48c2750)success
→ 真实返回: AAPL.US $332.41, +1.07 (+0.32%), open $332.53, prevClose $331.34, high/low $335.48/$330.70, volume 35,981,000
→ judge 启用时真实评分: groundedness=1.0, financial_reasoning=0.95, decision_usefulness=0.85
→ judge 未启用时 summary 明确打印未测指标(groundedness 等),不声称通过修复 Live Nightly 的"全部运行失败仍显示成功"与评测有效性缺陷。

## 原始基线(6405b7b)
Run 35074861257 / artifact 10437259480:86/86 run 失败(1×PI_HEALTH_TIMEOUT + 85×PI_RUNTIME_ERROR: No API key found for the selected model),0 次 tool call;passRate=0 但 composite=0.596(未执行 run 的规则指标幻影满分);Actions conclusion=success。

## 改动说明
- **Live preflight**(新增 `scripts/eval/preflight.ts`):逐项检查 Pi runtime 健康、模型可用、one-token 凭证探针、LongBridge 数据源、judge 就绪;只输出状态(敏感信息 redact),失败 exit 2 且不跑任何 case。
- **执行有效性 vs 质量分离**:`ExperimentSummary` 增加 `validity`(valid/invalid/inconclusive)、`execution`(requested/started/evaluated/infraFailed/skipped)、`validityReasons`;质量指标只聚合 completed run;无有效 run 时 composite/passRate 为 null;有效负向 case 仍判 fail;invalid/inconclusive 实验拒绝 seed baseline。
- **失败传播**:CLI 退出码 0(有效通过)/1(质量回归或用法错误)/2(无效或不可结论)/3(取消);nightly 移除 `continue-on-error`,`set -o pipefail` 保留真实退出码,失败仍上传 summary/JSON/log。
- **配套修复**:`--model provider/id` 归一化并显式 `setModel`(此前 live 不会把所选模型下发给 Pi);cold-start health timeout 5s→60s;空 answer 凭证探针不再误报 "Connection verified.";judge 支持额外 HTTP header(`FINAGENT_JUDGE_HEADERS`,供中转使用)。

## 关联 Issue
Closes #113

## 测试报告(正式审核前必填)

### 环境

- Bun:1.3.14
- OS:Windows 10(本地验证;CI 为 ubuntu-latest)

### 实际执行命令与结果

```text
bun run typecheck
→ @finagent/core / i18n / shared / ui / electron 全部 exit 0

bun test --isolate
→ Ran 1475 tests across 163 files: 1467 pass / 8 skip / 0 fail
  (8 skip 为环境性 skip:Windows 符号链接权限、需要真实 LongBridge 账户 fixture)

bun run eval:smoke -- --mode fixture --baseline folio-agent-v1 --out artifacts/eval-smoke.json
→ validity: valid · GATE: PASS (folio-agent-v1) · exit 0

# 与 workflow 相同的 `... | tee ...` 管道(Git Bash,真实 CLI,用 Pi 缺失制造底层失败)
FINAGENT_PI_COMMAND=pi-missing-xyz bun run eval:full -- --mode live --model go/deepseek-v4.1-flash --preflight-only --out artifacts/eval-pipefail-demo.json 2>&1 | tee artifacts/eval-pipefail-demo.log
→ 不加 set -o pipefail:管道 exit 0(CLI 本身 exit 2)——旧假绿机制复现
→ 加 set -o pipefail:管道 exit 2
→ 失败产物仍写出:artifacts/eval-pipefail-demo.json(exitCode=2, preflight.ok=false)

# 真实 live smoke(真实模型 + Folio 工具 + 真实 LongBridge 数据)
bun run eval:full -- --mode live --model go/deepseek-v4.1-flash --max-cases 1 --out artifacts/eval-live-smoke-judge.json
→ PREFLIGHT: READY(Pi/模型目录/凭证探针/LongBridge 数据源/judge 全 ok)
→ validity: valid · execution: requested=1 started=1 evaluated=1 infraFailed=0 skipped=0 · exit 0
→ case fv1-market-001 "What is Apple trading at right now?"
→ run fa6db2ce-f03c-422e-b7fb-eddf7b3635c9(exp-1789646764609-6dfc9e)
→ tools: get_quote(call_00_ET_lph7e4nq6vj7CDj7FYAN8578)success · get_market_status(call_01_ET_ihdiPhKoypVu9z8Fg48c2750)success
→ 真实返回: AAPL.US $332.41, +1.07 (+0.32%), open $332.53, prevClose $331.34, high/low $335.48/$330.70, volume 35,981,000
→ judge 启用时真实评分: groundedness=1.0, financial_reasoning=0.95, decision_usefulness=0.85
→ judge 未启用时 summary 明确打印未测指标(groundedness 等),不声称通过

…e failures (helsome#113)

Add a live preflight (Pi health, model availability, one-token credential probe, LongBridge data source, judge readiness) that reports status only and exits 2 instead of running an unmeasurable suite. Split execution validity from quality: quality metrics aggregate completed runs only; invalid/inconclusive experiments expose no headline composite, cannot gate, and cannot seed a baseline. The CLI now exits 0/1/2/3, and the nightly workflow drops continue-on-error and keeps the real exit code through the tee pipeline with set -o pipefail, while still uploading summary/JSON/log on failure.

Verified: bun run typecheck; bun test --isolate (1467 pass, 0 fail); fixture smoke gate PASS (exit 0); live smoke with a real model + get_quote + LongBridge data (validity=valid, exit 0). Closes helsome#113.

@helsome helsome 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.

真实 live smoke、Bun/OS/命令与1467 pass/8 skip报告已提供,认可这次不是只跑fixture。缺模型Key属于配置未就绪,不是Runtime代码损坏;本PR按“如实表达环境未就绪/测量是否有效”的改进审,不要求扩成新评测平台。

当前核心 blocker 在 aggregate.ts:isInfrastructureRun 把所有 status=failed/timeout 都算 infra,而 validResults 只保留 completed。这会把“模型已经正常开始、发生工具循环/预算耗尽或任务超时”也剔出质量分母,抹掉真实Agent失败。请按执行事实/明确错误原因区分未开始的配置失败和已执行的任务失败,而不是仅看终态。补两个对照:无Key未开始→不测质量;正常开始后tool_loop/timeout→仍是有效的负向质量结果并计入失败。无需再跑完整真实suite,受控ExperimentService集成+aggregate测试足够。

#116正在负责requested/effective模型接线,这里的CLI provider/model拆分请协调复用,别再扩另一套配置契约。另请补“无可见UI变化”的说明;若 testModel 空回答的新message会显示在设置连接测试界面,附该失败态一张截图即可,不要求视频。

RXQ6 pushed a commit to RXQ6/folio that referenced this pull request Sep 18, 2026
…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`.
…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.
@suzhiguo7

Copy link
Copy Markdown
Author

已按 review 处理(commit 4d1605e):

  • aggregate.ts:not-started(spawn/config/credential)或明确 runtime/process 错误才算 infra;已开始后的 tool_loop / timeout / 预算耗尽视为有效负向质量结果,进入质量分母(isQualityRun)。
  • run.ts:summary 分类统一复用 isQualityRun/isInfrastructureRun;CLI provider/model 拆分改用 normalizeModelSelection,与 fix(eval): apply experiment config to the runtime and record requested/effective (#114) #116 共用,不再另立契约。
  • 对照测试:无 Key 未开始 → 不计质量;已开始 tool_loop/timeout → 计为失败并进入质量分母。
  • 本地:bun test aggregate + experiment-service + scripts/eval → 63 pass / 0 fail;bun run typecheck 全部通过。

已解决与 main 的冲突(merge commit b85946d,已推送)。

处理方式:

验证:bun test --isolate -> 1539 pass / 8 skip / 0 fail;bun run typecheck 全部包通过。
UI impact: no layout or interaction changes.

@helsome
helsome dismissed their stale review September 19, 2026 00:09

旧 review 的 aggregate/配置复用 blocker 已由当前 head b85946d 处理,撤销过期阻塞;下面按当前最终 diff 提交新的单一 blocker。

@helsome helsome 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.

旧 blocker 已解除:当前 head 已按执行事实区分 not-started 基础设施未就绪与已开始后的 tool_loop/timeout/预算耗尽,相关负向结果会进入质量分母;CLI 也复用了 #116 的 normalizeModelSelection。并且这条分支已经补上 CONFIG_APPLY_FAILED 早退返回的 runtimeUnusable: false,正好修复当前 main 的 TS2741 类型回归。

现在只剩一个新的、具体的 workflow blocker:.github/workflows/eval-nightly.yml 移除了 continue-on-error,而本 PR 自己的 docs/EVALUATION-CI.md 又明确说明 Nightly runner 当前不会安装/认证 LongBridge,因此 preflight 在现有 CI 环境会按设计返回 invalid/exit 2。这样合并后每日 Nightly 会因为一个已知的环境前置条件缺失而永久红灯。缺模型 Key / Provider 凭证 / 数据源配置未就绪应如实显示为“本轮不可测量”,但不应被包装成代码回归。

请做最小调整即可:保留 set -o pipefail、真实 CLI exit code、steps.eval-full.outcome、summary 和 artifact 的可见性,但让这种 report-only Nightly 的已知 env-invalid 状态不把整个 workflow 判为仓库失败。最简单可以恢复 eval step 的 continue-on-error: true;或者等价地捕获 exit code并在 summary 中明确区分 invalid/inconclusive 与 quality regression。同步调整对应 workflow test/docs。无需新评测框架,也无需再跑完整 live suite;focused workflow/pipefail 测试 + typecheck/现有全量报告即可。

另外,testProvider 空回答的新 message 如果确实会显示在设置连接测试界面,请补一张该失败态截图;如果不会进入可见 UI,请在 PR 中明确说明即可。

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.

[P0][Eval Reliability] 修复 Live Nightly 全部运行失败仍显示成功,并增加运行有效性闸门

2 participants