fix(web-probe): report turn timing jumps

This commit is contained in:
Codex
2026-06-20 18:43:32 +00:00
parent 6bb3365ab3
commit 3022bf9016
@@ -1259,6 +1259,7 @@ function buildSampleMetrics(samples, control) {
});
const turnTiming = buildTurnTimingTable(samples, timeline);
const turnCells = turnTiming.rows.flatMap((row) => Object.values(row.cells || {}));
const turnTimingNonMonotonic = Array.isArray(turnTiming.nonMonotonic) ? turnTiming.nonMonotonic : [];
const withTotal = timeline.filter((item) => item.totalElapsedSeconds !== null).length;
const withRecent = timeline.filter((item) => item.recentUpdateSeconds !== null).length;
const diagnostics = timeline.filter((item) => item.diagnosticSeen).length;
@@ -1274,11 +1275,15 @@ function buildSampleMetrics(samples, control) {
turnColumns: turnTiming.columns.length,
turnTimingRows: turnTiming.rows.length,
turnCellsWithTotalElapsed: turnCells.filter((item) => item.totalElapsedSeconds !== null).length,
turnCellsWithRecentUpdate: turnCells.filter((item) => item.recentUpdateSeconds !== null).length
turnCellsWithRecentUpdate: turnCells.filter((item) => item.recentUpdateSeconds !== null).length,
turnTimingNonMonotonicCount: turnTimingNonMonotonic.length,
turnTimingTotalElapsedDecreaseCount: turnTimingNonMonotonic.filter((item) => item.metric === "totalElapsedSeconds").length,
turnTimingRecentUpdateDecreaseCount: turnTimingNonMonotonic.filter((item) => item.metric === "recentUpdateSeconds").length
},
rounds,
turnColumns: turnTiming.columns,
turnTimingTable: turnTiming.rows,
turnTimingNonMonotonic,
timeline
};
}
@@ -1338,7 +1343,73 @@ function buildTurnTimingTable(samples, timeline) {
cells
});
}
return { columns, rows };
return { columns, rows, nonMonotonic: detectTurnTimingNonMonotonic(columns, rows) };
}
function detectTurnTimingNonMonotonic(columns, rows) {
const events = [];
for (const column of columns) {
const previousByMetric = new Map();
for (const row of rows) {
const cell = row.cells?.[column.id];
if (!cell) continue;
for (const metric of ["totalElapsedSeconds", "recentUpdateSeconds"]) {
const value = cell[metric];
if (value === null || value === undefined || !Number.isFinite(Number(value))) continue;
const current = Number(value);
const previous = previousByMetric.get(metric);
if (previous && current < previous.value) {
events.push({
columnId: column.id,
columnLabel: column.label,
metric,
fromSeq: previous.seq,
fromTs: previous.ts,
fromValue: previous.value,
toSeq: row.seq ?? null,
toTs: row.ts ?? null,
toValue: current,
delta: current - previous.value,
traceId: cell.traceId ?? column.traceId ?? null,
messageId: cell.messageId ?? column.messageId ?? null,
promptIndex: cell.promptIndex ?? row.promptIndex ?? null,
source: cell.source ?? column.source ?? null,
valuesRedacted: true
});
}
if (previous && metric === "recentUpdateSeconds") {
const elapsedMs = Date.parse(String(row.ts ?? "")) - Date.parse(String(previous.ts ?? ""));
const elapsedSeconds = Number.isFinite(elapsedMs) && elapsedMs >= 0 ? elapsedMs / 1000 : null;
const allowedIncrease = elapsedSeconds === null ? 10 : Math.max(10, elapsedSeconds + 5);
const increase = current - previous.value;
if (increase > allowedIncrease) {
events.push({
columnId: column.id,
columnLabel: column.label,
metric: "recentUpdateSeconds",
anomaly: "jump",
fromSeq: previous.seq,
fromTs: previous.ts,
fromValue: previous.value,
toSeq: row.seq ?? null,
toTs: row.ts ?? null,
toValue: current,
delta: increase,
sampleDeltaSeconds: elapsedSeconds,
allowedIncreaseSeconds: allowedIncrease,
traceId: cell.traceId ?? column.traceId ?? null,
messageId: cell.messageId ?? column.messageId ?? null,
promptIndex: cell.promptIndex ?? row.promptIndex ?? null,
source: cell.source ?? column.source ?? null,
valuesRedacted: true
});
}
}
previousByMetric.set(metric, { value: current, seq: row.seq ?? null, ts: row.ts ?? null });
}
}
}
return events;
}
function turnMetricItems(sample, timelineItem) {
@@ -1610,7 +1681,11 @@ function renderTurnTimingTable(sampleMetrics) {
lines.push("| " + cells.map(escapeMarkdownCell).join(" | ") + " |");
}
const columnLines = columns.map((column) => "- " + column.label + ": source=" + (column.source || "-") + " prompt=" + (column.promptIndex ?? "-") + " lastPrompt=" + (column.lastPromptIndex ?? "-") + " firstSeq=" + (column.firstSeq ?? "-") + " lastSeq=" + (column.lastSeq ?? "-") + " traceId=" + (column.traceId || "-") + " messageId=" + (column.messageId || "-")).join("\n");
return lines.join("\n") + "\n\n列说明:\n" + columnLines;
const nonMonotonic = Array.isArray(sampleMetrics?.turnTimingNonMonotonic) ? sampleMetrics.turnTimingNonMonotonic : [];
const nonMonotonicLines = nonMonotonic.length > 0
? nonMonotonic.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " " + item.metric + (item.anomaly ? " " + item.anomaly : "") + " " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " allowed=" + (item.allowedIncreaseSeconds ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n")
: "- 未观察到数值下降事件。";
return lines.join("\n") + "\n\n列说明:\n" + columnLines + "\n\n非单调/跳增事件(仅报表暴露,不做下游 repair):\n" + nonMonotonicLines;
}
function formatMetricCell(value) {