Files
ra-h-os/app/api/health/vectors/route.ts
T
“BeeRad” 733d1c3407 Initial commit: RA-H Open Source Edition
Local-first knowledge management system with BYO API keys.

Features:
- 3-panel UI (Nodes | Focus | Helpers)
- SQLite + sqlite-vec for vector search
- Agent system (Easy/Hard mode orchestrators)
- Content extraction (YouTube, PDF, web)
- Integrate workflow for connection discovery
- Dimension system with auto-assignment

Tech stack:
- Next.js 15 + TypeScript + Tailwind CSS
- Anthropic (Claude) + OpenAI (GPT) via Vercel AI SDK

Setup:
  npm install && npm rebuild better-sqlite3
  scripts/dev/bootstrap-local.sh
  npm run dev

MIT License
2025-12-15 16:14:28 +11:00

120 lines
3.7 KiB
TypeScript

import { NextResponse } from 'next/server';
import { getSQLiteClient } from '@/services/database/sqlite-client';
import { chunkService } from '@/services/database/chunks';
export async function GET() {
try {
const sqlite = getSQLiteClient();
// Test basic database connection
const connectionTest = await sqlite.testConnection();
if (!connectionTest) {
return NextResponse.json({
status: 'error',
message: 'Database connection failed',
details: null
});
}
// Check if vector extension is loaded
const vectorExtensionTest = await sqlite.checkVectorExtension();
let vectorStats = null;
let chunkStats = null;
let vectorHealth = 'unknown';
try {
// Get chunk counts
const totalChunks = await chunkService.getChunkCount();
const chunksWithoutEmbeddings = await chunkService.getChunksWithoutEmbeddings();
const vectorizedCount = totalChunks - chunksWithoutEmbeddings.length;
chunkStats = {
total_chunks: totalChunks,
vectorized_chunks: vectorizedCount,
missing_embeddings: chunksWithoutEmbeddings.length,
coverage_percentage: totalChunks > 0 ? Math.round((vectorizedCount / totalChunks) * 100) : 0
};
// Test vector table health by attempting a simple query
if (vectorExtensionTest) {
try {
const result = sqlite.query('SELECT COUNT(*) as count FROM vec_chunks');
const vecCount = Number(result.rows[0].count);
vectorStats = {
vec_chunks_count: vecCount,
matches_chunk_embeddings: vecCount === vectorizedCount
};
vectorHealth = vecCount === vectorizedCount ? 'healthy' : 'inconsistent';
} catch (vecError: any) {
vectorHealth = 'corrupted';
vectorStats = {
error: vecError.message,
suggestion: 'Vector table may be corrupted and need recreation'
};
}
} else {
vectorHealth = 'extension_unavailable';
}
} catch (error: any) {
return NextResponse.json({
status: 'error',
message: 'Failed to collect vector statistics',
details: error.message
});
}
return NextResponse.json({
status: 'success',
data: {
database_connected: connectionTest,
vector_extension_loaded: vectorExtensionTest,
vector_health: vectorHealth,
chunk_stats: chunkStats,
vector_stats: vectorStats,
recommendations: generateRecommendations(vectorHealth, chunkStats, vectorStats)
}
});
} catch (error: any) {
console.error('Vector health check failed:', error);
return NextResponse.json({
status: 'error',
message: 'Health check failed',
details: error.message
});
}
}
function generateRecommendations(
vectorHealth: string,
chunkStats: any,
vectorStats: any
): string[] {
const recommendations: string[] = [];
if (vectorHealth === 'corrupted') {
recommendations.push('Vector tables are corrupted - restart the application to trigger automatic healing');
}
if (vectorHealth === 'extension_unavailable') {
recommendations.push('Vector extension not loaded - check sqlite-vec installation');
}
if (chunkStats && chunkStats.coverage_percentage < 95) {
recommendations.push(`${chunkStats.missing_embeddings} chunks missing embeddings - consider running embedding generation`);
}
if (vectorStats && !vectorStats.matches_chunk_embeddings) {
recommendations.push('Vector count does not match chunk embeddings - database inconsistency detected');
}
if (recommendations.length === 0) {
recommendations.push('Vector search system is healthy');
}
return recommendations;
}