feat: sync runtime search and schema quality updates from app repo

- port retrieval, validation, and eval improvements relevant to os
- align prompts and dimensions with the flat single-agent model
- replace the old eval suite with the focused core scenarios

Generated with Codex
This commit is contained in:
“BeeRad”
2026-03-15 14:55:45 +11:00
parent 053c163e31
commit 4c75df101f
57 changed files with 1809 additions and 534 deletions
+14 -79
View File
@@ -1,7 +1,4 @@
import { getSQLiteClient } from './sqlite-client';
import { openai as openaiProvider } from '@ai-sdk/openai';
import { generateText } from 'ai';
import { hasValidOpenAiKey } from '../storage/apiKeys';
export interface Dimension {
name: string;
@@ -18,85 +15,23 @@ export interface LockedDimension {
export class DimensionService {
/**
* Get all locked (priority) dimensions with their descriptions
* Legacy compatibility shim. Dimensions are now flat, so there is no locked subset.
*/
static async getLockedDimensions(): Promise<LockedDimension[]> {
const sqlite = getSQLiteClient();
const result = sqlite.query(`
WITH dimension_counts AS (
SELECT nd.dimension, COUNT(*) AS count
FROM node_dimensions nd
GROUP BY nd.dimension
)
SELECT
d.name,
d.description,
COALESCE(dc.count, 0) AS count
FROM dimensions d
LEFT JOIN dimension_counts dc ON dc.dimension = d.name
WHERE d.is_priority = 1
ORDER BY d.name ASC
`);
return result.rows.map((row: any) => ({
name: row.name,
description: row.description,
count: Number(row.count)
}));
return [];
}
/**
* Automatically assign locked dimensions + suggest keyword dimensions
* Returns { locked: string[], keywords: string[] }
*
* IMPORTANT: Returns empty result immediately if no valid API key is configured.
* This prevents slow node creation when OpenAI is unavailable.
* Automatic special-dimension assignment has been removed. Callers must provide dimensions explicitly.
*/
static async assignDimensions(nodeData: {
title: string;
notes?: string;
content?: string;
link?: string;
description?: string;
}): Promise<{ locked: string[]; keywords: string[] }> {
// Fast path: skip AI if no valid API key
if (!hasValidOpenAiKey()) {
console.log(`[DimensionAssignment] No valid OpenAI key, skipping for: "${nodeData.title}"`);
return { locked: [], keywords: [] };
}
try {
const lockedDimensions = await this.getLockedDimensions();
if (lockedDimensions.length === 0) {
console.log('[DimensionAssignment] No locked dimensions available');
return { locked: [], keywords: [] };
}
const prompt = this.buildAssignmentPrompt(nodeData, lockedDimensions);
console.log(`[DimensionAssignment] Processing: "${nodeData.title}"`);
const response = await generateText({
model: openaiProvider('gpt-4o-mini'),
prompt,
maxOutputTokens: 300, // Increased to accommodate more dimensions
temperature: 0.1,
});
console.log(`[DimensionAssignment] AI Response:\n${response.text}`);
const result = this.parseAssignmentResponse(response.text, lockedDimensions);
console.log(`[DimensionAssignment] Locked: ${result.locked.join(', ') || 'none'}`);
console.log(`[DimensionAssignment] Keywords: ${result.keywords.join(', ') || 'none'}`);
return result;
} catch (error) {
console.error('[DimensionAssignment] Error:', error);
return { locked: [], keywords: [] };
}
console.log(`[DimensionAssignment] Skipped for "${nodeData.title}" — flat dimensions require explicit assignment.`);
return { locked: [], keywords: [] };
}
/**
@@ -153,7 +88,7 @@ export class DimensionService {
}
/**
* Build AI prompt for dimension assignment (locked dimensions only)
* Legacy no-op prompt builder retained only for backward compatibility.
*/
private static buildAssignmentPrompt(
nodeData: { title: string; notes?: string; link?: string; description?: string },
@@ -162,13 +97,13 @@ export class DimensionService {
// Use description as primary context, content as fallback
let nodeContextSection: string;
if (nodeData.description) {
const contentPreview = nodeData.notes?.slice(0, 500) || '';
const notesPreview = nodeData.notes?.slice(0, 500) || '';
nodeContextSection = `DESCRIPTION: ${nodeData.description}
NOTES PREVIEW: ${contentPreview}${nodeData.notes && nodeData.notes.length > 500 ? '...' : ''}`;
NOTES PREVIEW: ${notesPreview}${nodeData.notes && nodeData.notes.length > 500 ? '...' : ''}`;
} else {
const contentPreview = nodeData.notes?.slice(0, 2000) || '';
nodeContextSection = `NOTES: ${contentPreview}${nodeData.notes && nodeData.notes.length > 2000 ? '...' : ''}`;
const notesPreview = nodeData.notes?.slice(0, 2000) || '';
nodeContextSection = `NOTES: ${notesPreview}${nodeData.notes && nodeData.notes.length > 2000 ? '...' : ''}`;
}
// Include ALL locked dimensions, using fallback text for those without descriptions
@@ -181,7 +116,7 @@ NOTES PREVIEW: ${contentPreview}${nodeData.notes && nodeData.notes.length > 500
})
.join('\n---\n');
return `You are categorizing a knowledge node into locked dimensions.
return `Dimensions are now flat categories with no locked subset.
=== NODE TO CATEGORIZE ===
Title: ${nodeData.title}
@@ -203,7 +138,7 @@ LOCKED:
}
/**
* Parse AI response and extract locked dimensions
* Legacy no-op parser retained only for backward compatibility.
*/
private static parseAssignmentResponse(
response: string,
@@ -251,4 +186,4 @@ LOCKED:
}
}
export const dimensionService = new DimensionService();
export const dimensionService = new DimensionService();