mirror of
https://github.com/cline/cline.git
synced 2026-09-15 13:02:17 +08:00
* cleaning up a bit * cleaning up some more * readme * added max limit * making it portable * ignore * committing plans for now * strealit hooked up, multi model runs, better db torage * docs * VALID attempts * logging * more stability * strategy * cleaning deps * docs * streamlit dashboard work * dashboard showing bad cases * better parallelization pt1 * global worker pool for even better more robust parallelization * bumping up default max parallel requests from 20 -> 80 * better devx * better docs * docs * better devx * better docs * better presentation * dark mode * removed unused import --------- Co-authored-by: Cline Evaluation <cline@example.com>
349 lines
10 KiB
TypeScript
349 lines
10 KiB
TypeScript
import { DatabaseClient } from './client';
|
|
import {
|
|
SystemPrompt,
|
|
ProcessingFunctions,
|
|
FileRecord,
|
|
BenchmarkRun,
|
|
Case,
|
|
Result,
|
|
CreateSystemPromptInput,
|
|
CreateProcessingFunctionsInput,
|
|
CreateFileInput,
|
|
CreateBenchmarkRunInput,
|
|
CreateCaseInput,
|
|
CreateResultInput
|
|
} from './types';
|
|
|
|
const db = DatabaseClient.getInstance();
|
|
|
|
// System Prompts Operations
|
|
export async function upsertSystemPrompt(input: CreateSystemPromptInput): Promise<string> {
|
|
const hash = DatabaseClient.generateHash(input.content);
|
|
|
|
const stmt = db.getDatabase().prepare(`
|
|
INSERT OR IGNORE INTO system_prompts (hash, name, content)
|
|
VALUES (?, ?, ?)
|
|
`);
|
|
|
|
stmt.run(hash, input.name, input.content);
|
|
return hash;
|
|
}
|
|
|
|
export async function getSystemPromptByHash(hash: string): Promise<SystemPrompt | null> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM system_prompts WHERE hash = ?
|
|
`);
|
|
|
|
const result = stmt.get(hash) as SystemPrompt | undefined;
|
|
return result || null;
|
|
}
|
|
|
|
// Processing Functions Operations
|
|
export async function upsertProcessingFunctions(input: CreateProcessingFunctionsInput): Promise<string> {
|
|
const hash = DatabaseClient.generateHash(input.parsing_function + input.diff_edit_function);
|
|
|
|
const stmt = db.getDatabase().prepare(`
|
|
INSERT OR IGNORE INTO processing_functions (hash, name, parsing_function, diff_edit_function)
|
|
VALUES (?, ?, ?, ?)
|
|
`);
|
|
|
|
stmt.run(hash, input.name, input.parsing_function, input.diff_edit_function);
|
|
return hash;
|
|
}
|
|
|
|
export async function getProcessingFunctionsByHash(hash: string): Promise<ProcessingFunctions | null> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM processing_functions WHERE hash = ?
|
|
`);
|
|
|
|
const result = stmt.get(hash) as ProcessingFunctions | undefined;
|
|
return result || null;
|
|
}
|
|
|
|
// Files Operations
|
|
export async function upsertFile(input: CreateFileInput): Promise<string> {
|
|
const hash = DatabaseClient.generateHash(input.content);
|
|
|
|
const stmt = db.getDatabase().prepare(`
|
|
INSERT OR IGNORE INTO files (hash, filepath, content, tokens)
|
|
VALUES (?, ?, ?, ?)
|
|
`);
|
|
|
|
stmt.run(hash, input.filepath, input.content, input.tokens || null);
|
|
return hash;
|
|
}
|
|
|
|
export async function getFileByHash(hash: string): Promise<FileRecord | null> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM files WHERE hash = ?
|
|
`);
|
|
|
|
const result = stmt.get(hash) as FileRecord | undefined;
|
|
return result || null;
|
|
}
|
|
|
|
// Benchmark Runs Operations
|
|
export async function createBenchmarkRun(input: CreateBenchmarkRunInput): Promise<string> {
|
|
const runId = DatabaseClient.generateId();
|
|
|
|
const stmt = db.getDatabase().prepare(`
|
|
INSERT INTO runs (run_id, description, system_prompt_hash)
|
|
VALUES (?, ?, ?)
|
|
`);
|
|
|
|
stmt.run(runId, input.description || null, input.system_prompt_hash);
|
|
return runId;
|
|
}
|
|
|
|
export async function getBenchmarkRun(runId: string): Promise<BenchmarkRun | null> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM runs WHERE run_id = ?
|
|
`);
|
|
|
|
const result = stmt.get(runId) as BenchmarkRun | undefined;
|
|
return result || null;
|
|
}
|
|
|
|
export async function getAllBenchmarkRuns(): Promise<BenchmarkRun[]> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM runs ORDER BY created_at DESC
|
|
`);
|
|
|
|
return stmt.all() as BenchmarkRun[];
|
|
}
|
|
|
|
// Cases Operations
|
|
export async function createCase(input: CreateCaseInput): Promise<string> {
|
|
const caseId = DatabaseClient.generateId();
|
|
|
|
const stmt = db.getDatabase().prepare(`
|
|
INSERT INTO cases (case_id, run_id, description, system_prompt_hash, task_id, tokens_in_context, file_hash)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
stmt.run(
|
|
caseId,
|
|
input.run_id,
|
|
input.description,
|
|
input.system_prompt_hash,
|
|
input.task_id,
|
|
input.tokens_in_context,
|
|
input.file_hash || null
|
|
);
|
|
|
|
return caseId;
|
|
}
|
|
|
|
export async function getCasesByRun(runId: string): Promise<Case[]> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM cases WHERE run_id = ? ORDER BY created_at
|
|
`);
|
|
|
|
return stmt.all(runId) as Case[];
|
|
}
|
|
|
|
export async function getCaseById(caseId: string): Promise<Case | null> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM cases WHERE case_id = ?
|
|
`);
|
|
|
|
const result = stmt.get(caseId) as Case | undefined;
|
|
return result || null;
|
|
}
|
|
|
|
// Results Operations
|
|
export async function insertResult(input: CreateResultInput): Promise<string> {
|
|
const resultId = DatabaseClient.generateId();
|
|
|
|
const stmt = db.getDatabase().prepare(`
|
|
INSERT INTO results (
|
|
result_id, run_id, case_id, model_id, processing_functions_hash,
|
|
succeeded, error_enum, num_edits, num_lines_deleted, num_lines_added,
|
|
time_to_first_token_ms, time_to_first_edit_ms, time_round_trip_ms,
|
|
cost_usd, completion_tokens, raw_model_output, file_edited_hash,
|
|
parsed_tool_call_json
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
stmt.run(
|
|
resultId,
|
|
input.run_id,
|
|
input.case_id,
|
|
input.model_id,
|
|
input.processing_functions_hash,
|
|
input.succeeded ? 1 : 0, // Convert boolean to integer
|
|
input.error_enum || null,
|
|
input.num_edits || null,
|
|
input.num_lines_deleted || null,
|
|
input.num_lines_added || null,
|
|
input.time_to_first_token_ms || null,
|
|
input.time_to_first_edit_ms || null,
|
|
input.time_round_trip_ms || null,
|
|
input.cost_usd || null,
|
|
input.completion_tokens || null,
|
|
input.raw_model_output || null,
|
|
input.file_edited_hash || null,
|
|
input.parsed_tool_call_json || null
|
|
);
|
|
|
|
return resultId;
|
|
}
|
|
|
|
export async function getResultsByRun(runId: string): Promise<Result[]> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM results WHERE run_id = ? ORDER BY created_at
|
|
`);
|
|
|
|
return stmt.all(runId) as Result[];
|
|
}
|
|
|
|
export async function getResultsByCase(caseId: string): Promise<Result[]> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM results WHERE case_id = ? ORDER BY created_at
|
|
`);
|
|
|
|
return stmt.all(caseId) as Result[];
|
|
}
|
|
|
|
export async function getResultById(resultId: string): Promise<Result | null> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM results WHERE result_id = ?
|
|
`);
|
|
|
|
const result = stmt.get(resultId) as Result | undefined;
|
|
return result || null;
|
|
}
|
|
|
|
// Batch operations for performance
|
|
export async function insertResultsBatch(inputs: CreateResultInput[]): Promise<string[]> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
INSERT INTO results (
|
|
result_id, run_id, case_id, model_id, processing_functions_hash,
|
|
succeeded, error_enum, num_edits, num_lines_deleted, num_lines_added,
|
|
time_to_first_token_ms, time_to_first_edit_ms, time_round_trip_ms,
|
|
cost_usd, completion_tokens, raw_model_output, file_edited_hash,
|
|
parsed_tool_call_json
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
return db.transaction(() => {
|
|
const resultIds: string[] = [];
|
|
|
|
for (const input of inputs) {
|
|
const resultId = DatabaseClient.generateId();
|
|
|
|
stmt.run(
|
|
resultId,
|
|
input.run_id,
|
|
input.case_id,
|
|
input.model_id,
|
|
input.processing_functions_hash,
|
|
input.succeeded ? 1 : 0, // Convert boolean to integer
|
|
input.error_enum || null,
|
|
input.num_edits || null,
|
|
input.num_lines_deleted || null,
|
|
input.num_lines_added || null,
|
|
input.time_to_first_token_ms || null,
|
|
input.time_to_first_edit_ms || null,
|
|
input.time_round_trip_ms || null,
|
|
input.cost_usd || null,
|
|
input.completion_tokens || null,
|
|
input.raw_model_output || null,
|
|
input.file_edited_hash || null,
|
|
input.parsed_tool_call_json || null
|
|
);
|
|
|
|
resultIds.push(resultId);
|
|
}
|
|
|
|
return resultIds;
|
|
});
|
|
}
|
|
|
|
export async function createCasesBatch(inputs: CreateCaseInput[]): Promise<string[]> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
INSERT INTO cases (case_id, run_id, description, system_prompt_hash, task_id, tokens_in_context)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
return db.transaction(() => {
|
|
const caseIds: string[] = [];
|
|
|
|
for (const input of inputs) {
|
|
const caseId = DatabaseClient.generateId();
|
|
|
|
stmt.run(
|
|
caseId,
|
|
input.run_id,
|
|
input.description,
|
|
input.system_prompt_hash,
|
|
input.task_id,
|
|
input.tokens_in_context
|
|
);
|
|
|
|
caseIds.push(caseId);
|
|
}
|
|
|
|
return caseIds;
|
|
});
|
|
}
|
|
|
|
// Utility functions
|
|
export async function getRunStats(runId: string): Promise<{
|
|
total_cases: number;
|
|
total_results: number;
|
|
success_rate: number;
|
|
avg_cost: number;
|
|
avg_latency: number;
|
|
}> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT
|
|
COUNT(DISTINCT c.case_id) as total_cases,
|
|
COUNT(r.result_id) as total_results,
|
|
AVG(CASE WHEN r.succeeded THEN 1.0 ELSE 0.0 END) as success_rate,
|
|
AVG(r.cost_usd) as avg_cost,
|
|
AVG(r.time_round_trip_ms) as avg_latency
|
|
FROM cases c
|
|
LEFT JOIN results r ON c.case_id = r.case_id
|
|
WHERE c.run_id = ?
|
|
`);
|
|
|
|
const result = stmt.get(runId) as any;
|
|
return {
|
|
total_cases: result.total_cases || 0,
|
|
total_results: result.total_results || 0,
|
|
success_rate: result.success_rate || 0,
|
|
avg_cost: result.avg_cost || 0,
|
|
avg_latency: result.avg_latency || 0
|
|
};
|
|
}
|
|
|
|
// Count valid attempts for a specific case and model
|
|
export async function getValidAttemptCount(caseId: string, modelId: string): Promise<number> {
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT COUNT(*) as count
|
|
FROM results
|
|
WHERE case_id = ?
|
|
AND model_id = ?
|
|
AND error_enum NOT IN (1, 6, 7) -- Exclude: no_tool_calls, wrong_tool_call, wrong_file_edited
|
|
`);
|
|
|
|
const result = stmt.get(caseId, modelId) as { count: number };
|
|
return result.count;
|
|
}
|
|
|
|
// Get valid results for a specific case and model (for analysis)
|
|
export async function getValidResults(caseId: string, modelId: string, limit?: number): Promise<Result[]> {
|
|
const limitClause = limit ? `LIMIT ${limit}` : '';
|
|
const stmt = db.getDatabase().prepare(`
|
|
SELECT * FROM results
|
|
WHERE case_id = ?
|
|
AND model_id = ?
|
|
AND error_enum NOT IN (1, 6, 7) -- Only valid attempts
|
|
ORDER BY created_at
|
|
${limitClause}
|
|
`);
|
|
|
|
return stmt.all(caseId, modelId) as Result[];
|
|
}
|