feat: 完善两阶段 Agent 自进化 Pipeline、Skills 与 Benchmark (#129)

This commit is contained in:
Jingzhe Xu authored and GitHub committed 2026-07-30 15:59:58 +08:00
1 parent 3bd0a7208a
commit 081fe2d172
34 files changed
+2093 -763

No files matched your search

@@ -1,32 +1,17 @@
/**
* Metric switching and per-model series grouping for the Benchmark center chart (pure functions,
* easy to unit test): the chart can switch between the score / cost / duration metrics (sharing
* the same time axis), and is grouped into series by each evaluation's (provider, modelId) — one
* color per series, legend by model. score is always present;
* cost / durationMs are optional — missing values are **skipped points**: neither drawn nor
* connected, so the line breaks at the gap (lineSegments splits value-bearing indices into
* contiguous segments).
* Score-only data helpers and per-runtime series grouping for the Benchmark center chart.
* Series share one time axis and use each Evaluation's authoritative stored Score.
*/
export type BenchmarkMetric = "score" | "cost" | "duration";
export const BENCHMARK_METRICS: readonly BenchmarkMetric[] = ["score", "cost", "duration"];
/** Minimal evaluation shape needed to read a metric (BenchmarkEvaluation is a superset). */
/** Minimal Evaluation shape needed to read Score (BenchmarkEvaluation is a superset). */
export interface MetricSourceLike {
score: number;
cost?: number;
durationMs?: number;
}
/** Each evaluation's value under the selected metric; missing (cost / durationMs not recorded) is null (skipped point). */
export function metricValues(
evaluations: readonly MetricSourceLike[],
metric: BenchmarkMetric,
): (number | null)[] {
/** Each Evaluation's Score; non-finite malformed values become chart gaps defensively. */
export function scoreValues(evaluations: readonly MetricSourceLike[]): (number | null)[] {
return evaluations.map((e) => {
const v = metric === "score" ? e.score : metric === "cost" ? e.cost : e.durationMs;
return typeof v === "number" && Number.isFinite(v) ? v : null;
return typeof e.score === "number" && Number.isFinite(e.score) ? e.score : null;
});
}
@@ -63,37 +48,36 @@ export function metricMax(values: readonly (number | null)[]): number {
/** Minimal evaluation shape needed for series grouping (BenchmarkEvaluation is a superset). */
export interface ModelRefLike {
provider?: string;
modelId?: string;
thinkingLevel?: string;
}
/** One chart series: the set of evaluations sharing the same (provider, modelId) (older records with no model tag are grouped into a single series). */
/** One chart series: the set of evaluations sharing the same (modelId, thinkingLevel). */
export interface EvaluationSeries {
/** Grouping key (internal grouping only, not used as an id; empty string for untagged model). */
key: string;
provider?: string;
modelId?: string;
thinkingLevel?: string;
/** Matching evaluation indices: global time-axis positions, shared across all series on the same x-axis. */
indices: number[];
}
/**
* Groups evaluations into series by the model they carry: models are ordered by first
* appearance (color is picked from SERIES_COLORS by series index, so color follows the model and
* doesn't change with filtering); older records with no model tag are grouped into a trailing
* unnamed series (shown in gray, labeled "untagged model").
* Groups evaluations into series by model ID and thinking level, deliberately ignoring provider.
* Runtime combinations are ordered by first appearance (color is picked from SERIES_COLORS by
* series index); defensive records with no model tag are grouped into a trailing unnamed series.
*/
export function modelSeries(evaluations: readonly ModelRefLike[]): EvaluationSeries[] {
const map = new Map<string, EvaluationSeries>();
evaluations.forEach((e, index) => {
const labeled = e.modelId !== undefined && e.modelId !== "";
const key = labeled ? `${e.provider ?? ""}\u0000${e.modelId}` : "";
const key = labeled ? `${e.modelId}\u0000${e.thinkingLevel ?? ""}` : "";
let series = map.get(key);
if (!series) {
series = {
key,
...(labeled && e.provider !== undefined ? { provider: e.provider } : {}),
...(labeled && e.modelId !== undefined ? { modelId: e.modelId } : {}),
...(labeled && e.thinkingLevel !== undefined ? { thinkingLevel: e.thinkingLevel } : {}),
indices: [],
};
map.set(key, series);
@@ -104,12 +88,11 @@ export function modelSeries(evaluations: readonly ModelRefLike[]): EvaluationSer
return [...all.filter((x) => x.key !== ""), ...all.filter((x) => x.key === "")];
}
/** A series' value sequence under the selected metric: indices outside this series are null (skipped point), keeping the global time axis. */
/** A series' Score sequence; indices outside this series are null, keeping the global time axis. */
export function seriesValues(
evaluations: readonly (MetricSourceLike & ModelRefLike)[],
series: EvaluationSeries,
metric: BenchmarkMetric,
): (number | null)[] {
const own = new Set(series.indices);
return metricValues(evaluations, metric).map((v, i) => (own.has(i) ? v : null));
return scoreValues(evaluations).map((v, i) => (own.has(i) ? v : null));
}