feat: implement AI-driven knowledge extraction pipeline for company documentation
All checks were successful
On Push to Main / test (push) Successful in 30s
On Push to Main / publish (push) Successful in 59s
On Push to Main / deploy-dev (push) Successful in 1m32s

This commit is contained in:
RaymondVerhoef
2026-05-20 09:35:57 +02:00
parent caaf2b9eba
commit 2752fb95d9

View File

@@ -84,6 +84,23 @@ export async function analyzeHandbookDelta(fileContent, filePath) {
throw error; throw error;
} }
} }
function chunkText(text, maxChunkSize = 4000) {
const paragraphs = text.split(/\n+/);
const chunks = [];
let currentChunk = '';
for (const para of paragraphs) {
if ((currentChunk + '\n' + para).length > maxChunkSize) {
if (currentChunk) chunks.push(currentChunk.trim());
currentChunk = para;
} else {
currentChunk = currentChunk ? currentChunk + '\n' + para : para;
}
}
if (currentChunk) chunks.push(currentChunk.trim());
return chunks;
}
export async function processSourceText(textContent, sourceName) { export async function processSourceText(textContent, sourceName) {
// Deduplicate: skip if a source with the same name was already successfully processed // Deduplicate: skip if a source with the same name was already successfully processed
const existing = await db.getSources(); const existing = await db.getSources();
@@ -98,23 +115,48 @@ export async function processSourceText(textContent, sourceName) {
const sourceId = rec.id; const sourceId = rec.id;
try { try {
const responseText = await anthropicApi.generateContent(SYSTEM_PROMPT, `Analyze the following text:\n\n${textContent}`); const chunks = chunkText(textContent, 4000);
console.log('[Pipeline] Raw AI response:', responseText); console.log(`[Pipeline] Split "${sourceName}" into ${chunks.length} chunks for processing.`);
let extractedData; let allExtractedTopics = [];
try { let allExtractedRelations = [];
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
const jsonStr = jsonMatch ? jsonMatch[0] : responseText; for (let i = 0; i < chunks.length; i++) {
extractedData = JSON.parse(jsonStr); if (i > 0) {
} catch (e) { console.log(`[Pipeline] Pacing delay (12s) to prevent rate limits before chunk ${i + 1}/${chunks.length}...`);
console.error('[Pipeline] AI returned non-JSON response:', responseText?.substring(0, 500)); await new Promise(r => setTimeout(r, 12000));
throw new Error(`AI response was not valid JSON. The model responded with: "${responseText?.substring(0, 120)}..."`); }
console.log(`[Pipeline] Processing chunk ${i + 1}/${chunks.length} (${chunks[i].length} chars)...`);
const responseText = await anthropicApi.generateContent(
SYSTEM_PROMPT,
`Analyze this part of the document (${i + 1}/${chunks.length}):\n\n${chunks[i]}`
);
console.log(`[Pipeline] Raw AI response for chunk ${i + 1}:`, responseText);
let extractedData;
try {
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
const jsonStr = jsonMatch ? jsonMatch[0] : responseText;
extractedData = JSON.parse(jsonStr);
} catch (e) {
console.error(`[Pipeline] AI returned non-JSON response for chunk ${i + 1}:`, responseText?.substring(0, 500));
throw new Error(`AI response for chunk ${i + 1} was not valid JSON.`);
}
if (extractedData.topics && Array.isArray(extractedData.topics)) {
allExtractedTopics.push(...extractedData.topics);
}
if (extractedData.relations && Array.isArray(extractedData.relations)) {
allExtractedRelations.push(...extractedData.relations);
}
} }
await mergeKnowledgeGraph(extractedData); // Merge everything together
await mergeKnowledgeGraph({ topics: allExtractedTopics, relations: allExtractedRelations });
await db.updateSourceStatus(sourceId, 'completed'); await db.updateSourceStatus(sourceId, 'completed');
return { success: true, data: extractedData }; return { success: true, data: { topics: allExtractedTopics, relations: allExtractedRelations } };
} catch (error) { } catch (error) {
await db.updateSourceStatus(sourceId, 'failed', error.message); await db.updateSourceStatus(sourceId, 'failed', error.message);