Skip to content

feat(eval,cli): describe metrics in prose and restructure eval output - #3453

Merged
kwakayama merged 1 commit into
mainfrom
feat/eval-metric-labels
Aug 7, 2026
Merged

kwakayama merged 1 commit into
mainfrom
feat/eval-metric-labels

Conversation

@kwakayama

@kwakayama kwakayama commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

What

veryfront eval printed one line per metric keyed by factory name, so agent.calledTool never said which tool the eval asserted on. It also put the ● glyph on every line, including report paths, which made nothing stand out.

Before

  ● Eval eval:assistant
  ● Target: agent:assistant
  ● Result: 1/1 passed (100%)
  ●   agent.calledTool: 1/1 passed (100%)
  ●   agent.noFailedTools: 1/1 passed (100%)
  ●   judge.rubric: 1/1 passed (100%)
  ● Report directory: .veryfront/evals/20260807_081251961_b01ba68d70d06f57/001-assistant
  ● Eval suite: 1/1 passed
  ● Report directory: .veryfront/evals/20260807_081251961_b01ba68d70d06f57
  ● Suite report: .veryfront/evals/20260807_081251961_b01ba68d70d06f57/report.md

After

  Eval:   Assistant smoke test
  Target: agent:assistant
  Result: 1/1 passed (100%)

  ● Agent called tool "calculator": 1/1 passed (100%)
  ● Agent had no failed tool calls: 1/1 passed (100%)
  ● LLM as a judge passed: 1/1 passed (100%)

  Eval suite: 1/1 passed
  Report: .veryfront/evals/20260807_100517129_69ecbeee6f8fb0aa/report.md

Where the label comes from

The parameter that makes an assertion specific lives in the config the metric factory captured, so the label is built there rather than reconstructed in the CLI:

  • src/eval/metric-labels.ts (new) maps a metric name plus its config to prose for all built-in metrics — agent.calledTool → Agent called tool "calculator", ops.cost → Cost stayed under $0.05, knowledge.recallAtK → Knowledge recall@2. Unknown names return undefined so callers fall back to the raw name instead of guessing. Long parameters (the scaffold's regexes) elide at 40 chars so a metric line stays on one row.
  • createMetric is the single choke point every metric funnels through, so the label attaches there and rides on EvalMetricResult.label — a field that already existed and was unused.
  • EvalMetricSummary.label carries it into the JSON report, not just the terminal.
  • Suite children now carry the definition name, so Eval: heads the block with Assistant smoke test rather than the generated id.

Known limitation

Two calledTool assertions in one eval share a summary row keyed name:family:severity. No single label describes both, so it is dropped there and the row falls back to agent.calledTool.

The summary key is deliberately unchanged: src/eval/baseline.ts:12 derives the same key independently, so splitting rows would silently drop data in baseline comparisons.

Other output changes

  • Report directory and Report markdown collapse into one Report: pointing at the markdown.
  • --report is relabeled Report JSON:. That flag writes the JSON summary, not markdown, and previously shared the Report: label with the markdown line.
  • Per-child Report directory lines are gone from suite output; the directory is the report path's parent.

Testing

  • src/eval/ + cli/commands/eval/ — 19 passed, 209 steps, 0 failed
  • extensions/ext-eval-report-mlflow/ (the only other consumer of these types) — passing
  • deno lint, deno fmt --check, lint:module-boundaries, lint:dependency-boundaries, lint:cli-boundary, lint:barrel-jsdoc, lint:core-deps, docs:api-reference:check — all passing

New tests cover the label formatter (including fallback and elision paths), label propagation into the summary, the conflict-drop rule, and the rendered CLI lines. Writing the formatter test caught a prototype-pollution bug in the lookup table: a metric named toString returned Object.prototype.toString as its label. Fixed with a null-prototype object.

The regenerated docs/api-reference/veryfront/eval.md contains only source-link line shifts from these edits.

Summary by CodeRabbit

  • New Features

    • Added clearer, human-readable labels for evaluation metrics, including configured parameters where applicable.
    • Evaluation reports now show metric markers, evaluation names, aligned output, and artifact paths.
    • Suite reports identify each evaluation by name and ID.
    • Metric labels are preserved in summaries when results are consistent.
  • Bug Fixes

    • Conflicting metric labels are no longer incorrectly represented in aggregated summaries.
  • Documentation

    • Updated evaluation API reference links to match the current source locations.

`veryfront eval` printed one line per metric keyed by factory name, so
`agent.calledTool` never said which tool the eval asserted on. That
parameter lives in the config the metric factory captured, so the label is
built there rather than reconstructed in the CLI.

- `src/eval/metric-labels.ts` maps a metric name plus its config to prose
  for all built-in metrics, and returns undefined for unknown names so
  callers fall back to the raw name instead of guessing.
- `createMetric` is the single choke point every metric funnels through,
  so the label attaches there and rides on the existing but unused
  `EvalMetricResult.label` field into `EvalMetricSummary`. The JSON report
  carries it too, not just the terminal.
- Metrics sharing a summary row disagree on their label when they assert
  on different tools. The label is dropped in that case rather than
  crediting the row to whichever ran first. The summary key is left alone
  because baseline.ts derives the same key independently, so splitting
  rows would silently drop data in baseline comparisons.

Output is restructured to match: header lines are column-aligned and
glyph-free, `●` now marks metric assertions and nothing else, and artifact
paths are dimmed. `Report directory` and `Report markdown` collapse into
one `Report:`. The `--report` path is relabeled `Report JSON:` because
that flag writes JSON, not markdown, and previously shared the `Report:`
label with the markdown line.
@kwakayama
kwakayama requested a review from kojiwakayama as a code owner August 7, 2026 10:47
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Eval metrics now derive human-readable labels, preserve them through report aggregation, and render them in revised single, suite, and comparison CLI output. Tests cover label formatting, aggregation conflicts, report names, and artifact paths.

Changes

Eval reporting

Layer / File(s) Summary
Metric label generation and propagation
src/eval/types.ts, src/eval/metric-labels.ts, src/eval/metrics.ts, src/eval/*test.ts
Built-in metric configurations produce human-readable labels. Evaluator-provided labels take precedence.
Summary label aggregation
src/eval/report.ts, src/eval/report.test.ts, docs/api-reference/veryfront/eval.md
Summary rows retain consistent labels and omit labels when merged results conflict. API source links now match current declarations.
Suite child report names
src/eval/run-report.ts, src/eval/run-report.test.ts
Suite child report hints include each evaluation definition name.
Formatted eval CLI output
cli/commands/eval/command.ts, cli/commands/eval/command.test.ts
Single, suite, and comparison output uses aligned headers, prose metric lines, revised report labels, and dimmed artifact paths.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: kojiwakayama

Sequence Diagram(s)

sequenceDiagram
  participant EvalRunner
  participant MetricFormatter
  participant EvalReport
  participant EvalCLI
  EvalRunner->>MetricFormatter: formatEvalMetricLabel(name, config)
  MetricFormatter-->>EvalRunner: human-readable label
  EvalRunner->>EvalReport: metric result with label
  EvalReport-->>EvalCLI: summary rows and artifact details
  EvalCLI-->>EvalCLI: print formatted eval output
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: prose metric descriptions and restructured eval CLI output.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/eval-metric-labels

Comment @coderabbitai help to get the list of available commands.

@kwakayama
kwakayama enabled auto-merge August 7, 2026 10:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/commands/eval/command.ts`:
- Around line 808-810: Update printLine and its callers in the suite,
single-eval, and model-comparison paths to respect the active --quiet mode by
routing output through the established quiet-aware helper or suppressing
rendering when quiet is enabled. Preserve normal human-readable output
otherwise, and add coverage verifying veryfront eval --quiet emits no report
output.

In `@src/eval/metric-labels.ts`:
- Around line 61-73: Update the metric label cases in the label-rendering
function, specifically agent.calledTool, agent.notCalledTool, and
agent.toolCallCount, to pass configured tool names through elide() before
interpolation. Add a focused test using a long tool name that verifies the
rendered labels remain elided and do not wrap CLI output.
- Around line 51-59: Update the answer.contains and answer.regex branches in the
metric-label formatter to always return generic labels without including
configured text or regex patterns, removing the elide-based interpolation.
Adjust the corresponding metric-label tests to stop asserting that raw patterns
appear in output.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5be1fbbb-2b5b-4938-8b15-a0598649b701

📥 Commits

Reviewing files that changed from the base of the PR and between c7b48a0 and 2fedfdf.

📒 Files selected for processing (13)
  • cli/commands/eval/command.test.ts
  • cli/commands/eval/command.ts
  • docs/api-reference/veryfront/eval.md
  • src/eval/metric-labels.test.ts
  • src/eval/metric-labels.ts
  • src/eval/metrics.test.ts
  • src/eval/metrics.ts
  • src/eval/report.test.ts
  • src/eval/report.ts
  • src/eval/run-report.test.ts
  • src/eval/run-report.ts
  • src/eval/runner.test.ts
  • src/eval/types.ts

Comment on lines +808 to +810
function printLine(text: string): void {
console.log(` ${text}`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve --quiet behavior.

printLine() writes directly to console.log. The suite, single-eval, and model-comparison paths now use this helper. veryfront eval --quiet can emit human-readable report output because this path bypasses the logger.

Use a quiet-aware output helper, or return before rendering human output when quiet mode is active. Add coverage for veryfront eval --quiet.

As per coding guidelines, "Keep CLI global flags and exit codes consistent: --json/-j, --output/-o, --yes/-y, --quiet/-q...".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/commands/eval/command.ts` around lines 808 - 810, Update printLine and
its callers in the suite, single-eval, and model-comparison paths to respect the
active --quiet mode by routing output through the established quiet-aware helper
or suppressing rendering when quiet is enabled. Preserve normal human-readable
output otherwise, and add coverage verifying veryfront eval --quiet emits no
report output.

Source: Coding guidelines

Comment thread src/eval/metric-labels.ts
Comment on lines +51 to +59
case "answer.contains": {
const text = readString(config, "text");
return text ? `Answer contained "${elide(text)}"` : "Answer contained the expected text";
}
case "answer.regex": {
const pattern = readString(config, "pattern");
return pattern
? `Answer matched pattern ${elide(pattern)}`
: "Answer matched the expected pattern";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not emit configured answer text or regex patterns.

Lines 53 and 57 put raw metric configuration into report and CLI labels. Expected answers and patterns can contain customer data or secrets. Elision only shortens the value. It does not redact the value.

Use a generic label for these metrics. Update src/eval/metric-labels.test.ts to remove the assertion that requires the raw pattern in output.

Proposed fix
     case "answer.contains": {
-      const text = readString(config, "text");
-      return text ? `Answer contained "${elide(text)}"` : "Answer contained the expected text";
+      return "Answer contained the expected text";
     }
     case "answer.regex": {
-      const pattern = readString(config, "pattern");
-      return pattern
-        ? `Answer matched pattern ${elide(pattern)}`
-        : "Answer matched the expected pattern";
+      return "Answer matched the expected pattern";
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case "answer.contains": {
const text = readString(config, "text");
return text ? `Answer contained "${elide(text)}"` : "Answer contained the expected text";
}
case "answer.regex": {
const pattern = readString(config, "pattern");
return pattern
? `Answer matched pattern ${elide(pattern)}`
: "Answer matched the expected pattern";
case "answer.contains": {
return "Answer contained the expected text";
}
case "answer.regex": {
return "Answer matched the expected pattern";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/eval/metric-labels.ts` around lines 51 - 59, Update the answer.contains
and answer.regex branches in the metric-label formatter to always return generic
labels without including configured text or regex patterns, removing the
elide-based interpolation. Adjust the corresponding metric-label tests to stop
asserting that raw patterns appear in output.

Source: Coding guidelines

Comment thread src/eval/metric-labels.ts
Comment on lines +61 to +73
case "agent.calledTool": {
const tool = readString(config, "tool");
return tool ? `Agent called tool "${tool}"` : "Agent called the expected tool";
}
case "agent.notCalledTool": {
const tool = readString(config, "tool");
return tool ? `Agent did not call tool "${tool}"` : "Agent avoided the excluded tool";
}
case "agent.toolCallCount": {
const tool = readString(config, "tool");
return tool
? `Agent call count for tool "${tool}" was in range`
: "Agent tool call count was in range";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Elide configured tool names.

MAX_PARAM_LENGTH applies to rendered metric parameters, but lines 63, 67, and 72 render tool without elide. A long configured tool name can wrap the CLI output.

Apply elide(tool) at each interpolation. Add a focused long-tool test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/eval/metric-labels.ts` around lines 61 - 73, Update the metric label
cases in the label-rendering function, specifically agent.calledTool,
agent.notCalledTool, and agent.toolCallCount, to pass configured tool names
through elide() before interpolation. Add a focused test using a long tool name
that verifies the rendered labels remain elided and do not wrap CLI output.

Source: Coding guidelines

@kwakayama
kwakayama added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 375412a Aug 7, 2026
31 checks passed
@kwakayama
kwakayama deleted the feat/eval-metric-labels branch August 7, 2026 11:07
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.

1 participant