sync: bug fixes + eval system from private repo
- Fix chunk_status: pass chunk_status/chunk/metadata to context builder - Fix vector search: scope by node_id BEFORE similarity search - Add eval logging system (RAH_EVALS_LOG=1) - Add eval dashboard at /evals - Add vitest for testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
e15f223ed8
commit
2f2ef10ec9
@@ -0,0 +1,141 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export type EvalChatRow = {
|
||||
id: number;
|
||||
ts: string;
|
||||
trace_id: string;
|
||||
span_id: string | null;
|
||||
helper_name: string | null;
|
||||
model: string | null;
|
||||
prompt_version: string | null;
|
||||
system_message: string | null;
|
||||
user_message: string | null;
|
||||
assistant_message: string | null;
|
||||
input_tokens: number | null;
|
||||
output_tokens: number | null;
|
||||
total_tokens: number | null;
|
||||
cache_write_tokens: number | null;
|
||||
cache_read_tokens: number | null;
|
||||
cache_hit: number | null;
|
||||
cache_savings_pct: number | null;
|
||||
estimated_cost_usd: number | null;
|
||||
provider: string | null;
|
||||
mode: string | null;
|
||||
workflow_key: string | null;
|
||||
workflow_node_id: number | null;
|
||||
latency_ms: number | null;
|
||||
success: number | null;
|
||||
error: string | null;
|
||||
dataset_id: string | null;
|
||||
scenario_id: string | null;
|
||||
};
|
||||
|
||||
export type EvalToolCallRow = {
|
||||
id: number;
|
||||
ts: string;
|
||||
trace_id: string;
|
||||
span_id: string | null;
|
||||
parent_span_id: string | null;
|
||||
helper_name: string | null;
|
||||
tool_name: string;
|
||||
args_json: string | null;
|
||||
result_json: string | null;
|
||||
success: number | null;
|
||||
latency_ms: number | null;
|
||||
error: string | null;
|
||||
dataset_id: string | null;
|
||||
scenario_id: string | null;
|
||||
};
|
||||
|
||||
export type EvalTrace = {
|
||||
chat: EvalChatRow;
|
||||
toolCalls: EvalToolCallRow[];
|
||||
comment: string | null;
|
||||
};
|
||||
|
||||
const LOG_DB_PATH = path.join(process.cwd(), 'logs', 'evals.sqlite');
|
||||
|
||||
function ensureCommentSchema(db: Database.Database) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS eval_comments (
|
||||
trace_id TEXT PRIMARY KEY,
|
||||
scenario_id TEXT,
|
||||
comment TEXT,
|
||||
updated_at TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_eval_comments_scenario ON eval_comments(scenario_id);
|
||||
`);
|
||||
}
|
||||
|
||||
export function upsertEvalComment(traceId: string, scenarioId: string | null, comment: string) {
|
||||
if (!fs.existsSync(LOG_DB_PATH)) {
|
||||
return;
|
||||
}
|
||||
const db = new Database(LOG_DB_PATH);
|
||||
ensureCommentSchema(db);
|
||||
const now = new Date().toISOString();
|
||||
db.prepare(`
|
||||
INSERT INTO eval_comments (trace_id, scenario_id, comment, updated_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(trace_id) DO UPDATE SET
|
||||
comment = excluded.comment,
|
||||
updated_at = excluded.updated_at,
|
||||
scenario_id = excluded.scenario_id
|
||||
`).run(traceId, scenarioId, comment, now);
|
||||
}
|
||||
|
||||
export function loadEvalTraces(limit = 25): EvalTrace[] {
|
||||
if (!fs.existsSync(LOG_DB_PATH)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const db = new Database(LOG_DB_PATH, { readonly: true, fileMustExist: true });
|
||||
const chats = db.prepare(`
|
||||
SELECT *
|
||||
FROM llm_chats
|
||||
ORDER BY ts DESC
|
||||
LIMIT ?
|
||||
`).all(limit) as EvalChatRow[];
|
||||
|
||||
if (chats.length === 0) return [];
|
||||
|
||||
const traceIds = Array.from(new Set(chats.map(chat => chat.trace_id)));
|
||||
const placeholders = traceIds.map(() => '?').join(', ');
|
||||
const toolCalls = db.prepare(`
|
||||
SELECT *
|
||||
FROM tool_calls
|
||||
WHERE trace_id IN (${placeholders})
|
||||
ORDER BY ts ASC
|
||||
`).all(...traceIds) as EvalToolCallRow[];
|
||||
|
||||
const toolCallsByTrace = new Map<string, EvalToolCallRow[]>();
|
||||
toolCalls.forEach(call => {
|
||||
const list = toolCallsByTrace.get(call.trace_id) || [];
|
||||
list.push(call);
|
||||
toolCallsByTrace.set(call.trace_id, list);
|
||||
});
|
||||
|
||||
const commentsByTrace = new Map<string, string | null>();
|
||||
if (placeholders.length > 0) {
|
||||
try {
|
||||
const commentRows = db.prepare(`
|
||||
SELECT trace_id, comment
|
||||
FROM eval_comments
|
||||
WHERE trace_id IN (${placeholders})
|
||||
`).all(...traceIds) as Array<{ trace_id: string; comment: string | null }>;
|
||||
commentRows.forEach(row => {
|
||||
commentsByTrace.set(row.trace_id, row.comment ?? null);
|
||||
});
|
||||
} catch {
|
||||
// Comments table doesn't exist yet in readonly mode.
|
||||
}
|
||||
}
|
||||
|
||||
return chats.map(chat => ({
|
||||
chat,
|
||||
toolCalls: toolCallsByTrace.get(chat.trace_id) || [],
|
||||
comment: commentsByTrace.get(chat.trace_id) ?? null,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user