Origem: D:/Documents/Github/OllaHub Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 0
This commit is contained in:
1 parent
a7cbd1a3b4
commit
861af0bb9f
217 files changed
+39421
-1340
No files matched your search
+42
-41
@@ -3,6 +3,7 @@ import type { ResearchEntry } from '@/hooks/use-deep-research';
|
||||
export interface Chunk {
|
||||
content: string;
|
||||
score: number;
|
||||
relevanceScore?: number;
|
||||
sourceIndex: number;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
@@ -45,17 +46,17 @@ export function summarizeMarkdown(content: string, maxLen: number = 2000): strin
|
||||
// Dividir por headings (###, ##, #)
|
||||
const headingRegex = /^(#{1,3})\s+(.+)$/gm;
|
||||
const sections: Array<{ level: number; title: string; content: string; startIndex: number }> = [];
|
||||
|
||||
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
const contentLines = content.split('\n');
|
||||
|
||||
|
||||
// Encontrar todas as seções
|
||||
while ((match = headingRegex.exec(content)) !== null) {
|
||||
const level = match[1].length;
|
||||
const title = match[2].trim();
|
||||
const startIndex = content.substring(0, match.index).split('\n').length - 1;
|
||||
|
||||
|
||||
// Encontrar conteúdo da seção (até próximo heading ou fim)
|
||||
let endIndex = contentLines.length;
|
||||
const nextHeadingRegex = /^(#{1,3})\s+/gm;
|
||||
@@ -64,24 +65,24 @@ export function summarizeMarkdown(content: string, maxLen: number = 2000): strin
|
||||
if (nextMatch) {
|
||||
endIndex = content.substring(0, nextMatch.index).split('\n').length - 1;
|
||||
}
|
||||
|
||||
|
||||
const sectionContent = contentLines.slice(startIndex + 1, endIndex).join('\n').trim();
|
||||
|
||||
|
||||
sections.push({
|
||||
level,
|
||||
title,
|
||||
content: sectionContent,
|
||||
startIndex,
|
||||
});
|
||||
|
||||
|
||||
lastIndex = endIndex;
|
||||
}
|
||||
|
||||
|
||||
// Se não encontrou headings, dividir por parágrafos
|
||||
if (sections.length === 0) {
|
||||
const paragraphs = content.split(/\n\s*\n/).filter(p => p.trim().length > 0);
|
||||
let summary = '';
|
||||
|
||||
|
||||
for (const para of paragraphs) {
|
||||
if (summary.length + para.length + 2 <= maxLen) {
|
||||
summary += para.trim() + '\n\n';
|
||||
@@ -94,17 +95,17 @@ export function summarizeMarkdown(content: string, maxLen: number = 2000): strin
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return summary.trim() || content.substring(0, maxLen);
|
||||
}
|
||||
|
||||
|
||||
// Construir sumário priorizando seções de nível superior
|
||||
let summary = '';
|
||||
const sortedSections = sections.sort((a, b) => a.level - b.level);
|
||||
|
||||
|
||||
for (const section of sortedSections) {
|
||||
const sectionText = `## ${section.title}\n\n${section.content}\n\n`;
|
||||
|
||||
|
||||
if (summary.length + sectionText.length <= maxLen) {
|
||||
summary += sectionText;
|
||||
} else {
|
||||
@@ -117,7 +118,7 @@ export function summarizeMarkdown(content: string, maxLen: number = 2000): strin
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return summary.trim() || content.substring(0, maxLen);
|
||||
}
|
||||
|
||||
@@ -138,18 +139,18 @@ export function chunkBySections(
|
||||
|
||||
const chunks: Chunk[] = [];
|
||||
const lines = markdown.split('\n');
|
||||
|
||||
|
||||
// Dividir por headings primeiro
|
||||
const headingRegex = /^(#{1,3})\s+(.+)$/;
|
||||
let currentChunk: string[] = [];
|
||||
let currentHeading = '';
|
||||
let startIndex = 0;
|
||||
let chunkIndex = 0;
|
||||
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const headingMatch = line.match(headingRegex);
|
||||
|
||||
|
||||
if (headingMatch) {
|
||||
// Se já temos um chunk, salvá-lo
|
||||
if (currentChunk.length > 0) {
|
||||
@@ -165,14 +166,14 @@ export function chunkBySections(
|
||||
chunkIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Iniciar novo chunk
|
||||
currentHeading = line;
|
||||
currentChunk = [line];
|
||||
startIndex = i;
|
||||
} else {
|
||||
currentChunk.push(line);
|
||||
|
||||
|
||||
// Se chunk ficou muito grande, dividir por parágrafos
|
||||
const currentContent = currentChunk.join('\n');
|
||||
if (currentContent.length > maxChunkSize) {
|
||||
@@ -188,14 +189,14 @@ export function chunkBySections(
|
||||
});
|
||||
chunkIndex++;
|
||||
}
|
||||
|
||||
|
||||
// Iniciar novo chunk com a linha atual
|
||||
currentChunk = [currentHeading, line].filter(Boolean);
|
||||
startIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Adicionar último chunk
|
||||
if (currentChunk.length > 0) {
|
||||
const chunkContent = currentChunk.join('\n');
|
||||
@@ -209,16 +210,16 @@ export function chunkBySections(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Se não encontrou headings, dividir por parágrafos
|
||||
if (chunks.length === 0) {
|
||||
const paragraphs = markdown.split(/\n\s*\n/).filter(p => p.trim().length > 0);
|
||||
let currentChunkContent = '';
|
||||
let paraStartIndex = 0;
|
||||
|
||||
|
||||
for (let i = 0; i < paragraphs.length; i++) {
|
||||
const para = paragraphs[i].trim();
|
||||
|
||||
|
||||
if (currentChunkContent.length + para.length + 2 <= maxChunkSize) {
|
||||
currentChunkContent += (currentChunkContent ? '\n\n' : '') + para;
|
||||
} else {
|
||||
@@ -232,13 +233,13 @@ export function chunkBySections(
|
||||
endIndex: i - 1,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Iniciar novo chunk
|
||||
currentChunkContent = para;
|
||||
paraStartIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Adicionar último chunk
|
||||
if (currentChunkContent.length > 0) {
|
||||
chunks.push({
|
||||
@@ -250,7 +251,7 @@ export function chunkBySections(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
@@ -267,7 +268,7 @@ export function extractKeyFacts(markdown: string): string[] {
|
||||
|
||||
const facts: string[] = [];
|
||||
const normalized = markdown.toLowerCase();
|
||||
|
||||
|
||||
// Extrair datas (formato brasileiro: DD/MM/YYYY ou DD-MM-YYYY)
|
||||
const dateRegex = /\b(\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4})\b/g;
|
||||
const dates = [...markdown.matchAll(dateRegex)];
|
||||
@@ -276,7 +277,7 @@ export function extractKeyFacts(markdown: string): string[] {
|
||||
facts.push(match[0]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Extrair anos (4 dígitos entre 1900-2100)
|
||||
const yearRegex = /\b(19\d{2}|20[0-2]\d)\b/g;
|
||||
const years = [...markdown.matchAll(yearRegex)];
|
||||
@@ -285,7 +286,7 @@ export function extractKeyFacts(markdown: string): string[] {
|
||||
facts.push(match[0]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Extrair números relevantes (valores, estatísticas)
|
||||
// Padrão: número seguido de unidade ou contexto
|
||||
const numberPatterns = [
|
||||
@@ -293,7 +294,7 @@ export function extractKeyFacts(markdown: string): string[] {
|
||||
/\bR\$\s*(\d{1,3}(?:\.\d{3})*(?:,\d+)?)/gi,
|
||||
/\b(\d{1,3}(?:\.\d{3})*(?:,\d+)?)\s*%/g,
|
||||
];
|
||||
|
||||
|
||||
numberPatterns.forEach(pattern => {
|
||||
const matches = [...markdown.matchAll(pattern)];
|
||||
matches.forEach(match => {
|
||||
@@ -303,13 +304,13 @@ export function extractKeyFacts(markdown: string): string[] {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Extrair locais (padrões comuns: "em [Local]", "de [Local]", "para [Local]")
|
||||
const locationPatterns = [
|
||||
/\b(?:em|de|para|no|na|nos|nas)\s+([A-ZÁÉÍÓÚÂÊÔ][a-záéíóúâêôãõç]+(?:\s+[A-ZÁÉÍÓÚÂÊÔ][a-záéíóúâêôãõç]+)*)/g,
|
||||
/\b([A-ZÁÉÍÓÚÂÊÔ][a-záéíóúâêôãõç]+\s+(?:de\s+)?[A-ZÁÉÍÓÚÂÊÔ][a-záéíóúâêôãõç]+)\b/g,
|
||||
];
|
||||
|
||||
|
||||
locationPatterns.forEach(pattern => {
|
||||
const matches = [...markdown.matchAll(pattern)];
|
||||
matches.forEach(match => {
|
||||
@@ -323,7 +324,7 @@ export function extractKeyFacts(markdown: string): string[] {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Limitar a 20 fatos mais relevantes
|
||||
return facts.slice(0, 20);
|
||||
}
|
||||
@@ -342,7 +343,7 @@ export function processContent(
|
||||
options: ProcessingOptions = {}
|
||||
): ProcessedContent {
|
||||
const opts = { ...DEFAULT_OPTIONS, ...options };
|
||||
|
||||
|
||||
if (!content || content.length === 0) {
|
||||
return {
|
||||
original: content,
|
||||
@@ -351,30 +352,30 @@ export function processContent(
|
||||
totalTokens: 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Sumarizar se muito longo
|
||||
let processedContent = content;
|
||||
let summarized: string | undefined;
|
||||
|
||||
|
||||
if (content.length > opts.summarizeThreshold) {
|
||||
summarized = summarizeMarkdown(content, opts.maxSummaryLength);
|
||||
processedContent = summarized;
|
||||
}
|
||||
|
||||
|
||||
// Extrair fatos-chave
|
||||
const keyFacts = extractKeyFacts(content);
|
||||
|
||||
|
||||
// Fazer chunking
|
||||
const chunks = chunkBySections(processedContent, opts.maxChunkSize);
|
||||
|
||||
|
||||
// Atribuir sourceIndex aos chunks (todos do mesmo source por enquanto)
|
||||
chunks.forEach((chunk, idx) => {
|
||||
chunk.sourceIndex = 0;
|
||||
});
|
||||
|
||||
|
||||
// Calcular tokens totais (aproximação: 1 token ≈ 4 caracteres)
|
||||
const totalTokens = Math.ceil(processedContent.length / 4);
|
||||
|
||||
|
||||
return {
|
||||
original: content,
|
||||
summarized,
|
||||
|
||||
+20
-15
@@ -163,6 +163,7 @@ wsl --set-default-version 2`,
|
||||
problem: 'CPU cores insuficientes',
|
||||
os: 'windows',
|
||||
description: 'Recomenda-se pelo menos 2 cores de CPU para melhor desempenho.',
|
||||
links: [],
|
||||
commands: [
|
||||
{
|
||||
title: 'Verificar número de cores lógicos',
|
||||
@@ -358,6 +359,7 @@ sudo apt install linux-generic`,
|
||||
problem: 'RAM insuficiente',
|
||||
os: 'linux',
|
||||
description: 'Sistema requer no mínimo 2GB de RAM para executar Docker.',
|
||||
links: [],
|
||||
commands: [
|
||||
{
|
||||
title: 'Verificar processos consumindo mais memória',
|
||||
@@ -381,6 +383,7 @@ sudo apt install linux-generic`,
|
||||
problem: 'Espaço em disco insuficiente',
|
||||
os: 'linux',
|
||||
description: 'É necessário pelo menos 10GB de espaço livre em disco.',
|
||||
links: [],
|
||||
commands: [
|
||||
{
|
||||
title: 'Verificar espaço em disco',
|
||||
@@ -404,6 +407,7 @@ sudo apt install linux-generic`,
|
||||
problem: 'CPU cores insuficientes',
|
||||
os: 'linux',
|
||||
description: 'Recomenda-se pelo menos 2 cores de CPU para melhor desempenho.',
|
||||
links: [],
|
||||
commands: [
|
||||
{
|
||||
title: 'Verificar número de cores',
|
||||
@@ -541,6 +545,7 @@ eval "$(/opt/homebrew/bin/brew shellenv)"`,
|
||||
problem: 'CPU cores insuficientes',
|
||||
os: 'macos',
|
||||
description: 'Recomenda-se pelo menos 2 cores de CPU para melhor desempenho.',
|
||||
links: [],
|
||||
commands: [
|
||||
{
|
||||
title: 'Verificar número de cores',
|
||||
@@ -577,13 +582,13 @@ export function findProblemSolution(problemText: string, os: string): ProblemSol
|
||||
|
||||
// Buscar por matching exato primeiro
|
||||
let solution = PROBLEM_SOLUTIONS.find(s => {
|
||||
const matchesOs = s.os === 'all' || s.os === normalizedOs ||
|
||||
(normalizedOs === 'mac' && s.os === 'macos') ||
|
||||
(normalizedOs === 'windows' && s.os === 'windows') ||
|
||||
(normalizedOs === 'linux' && s.os === 'linux');
|
||||
|
||||
const matchesOs = s.os === 'all' || s.os === normalizedOs ||
|
||||
(normalizedOs === 'mac' && s.os === 'macos') ||
|
||||
(normalizedOs === 'windows' && s.os === 'windows') ||
|
||||
(normalizedOs === 'linux' && s.os === 'linux');
|
||||
|
||||
if (!matchesOs) return false;
|
||||
|
||||
|
||||
const problemLower = s.problem.toLowerCase();
|
||||
return normalizedProblem.includes(problemLower) || problemLower.includes(normalizedProblem);
|
||||
});
|
||||
@@ -595,15 +600,15 @@ export function findProblemSolution(problemText: string, os: string): ProblemSol
|
||||
'virtualmachineplatform': 'windows-vm-platform',
|
||||
'wsl não habilitado': 'windows-wsl-not-enabled',
|
||||
'wsl 2 não configurado': 'windows-wsl2-not-configured',
|
||||
'ram insuficiente': normalizedOs === 'windows' ? 'windows-ram-insufficient' :
|
||||
normalizedOs === 'mac' || normalizedOs === 'macos' ? 'macos-ram-insufficient' :
|
||||
'linux-ram-insufficient',
|
||||
'espaço em disco': normalizedOs === 'windows' ? 'windows-disk-insufficient' :
|
||||
normalizedOs === 'mac' || normalizedOs === 'macos' ? 'macos-disk-insufficient' :
|
||||
'linux-disk-insufficient',
|
||||
'cpu cores': normalizedOs === 'windows' ? 'windows-cpu-cores' :
|
||||
normalizedOs === 'mac' || normalizedOs === 'macos' ? 'macos-cpu-cores' :
|
||||
'linux-cpu-cores',
|
||||
'ram insuficiente': normalizedOs === 'windows' ? 'windows-ram-insufficient' :
|
||||
normalizedOs === 'mac' || normalizedOs === 'macos' ? 'macos-ram-insufficient' :
|
||||
'linux-ram-insufficient',
|
||||
'espaço em disco': normalizedOs === 'windows' ? 'windows-disk-insufficient' :
|
||||
normalizedOs === 'mac' || normalizedOs === 'macos' ? 'macos-disk-insufficient' :
|
||||
'linux-disk-insufficient',
|
||||
'cpu cores': normalizedOs === 'windows' ? 'windows-cpu-cores' :
|
||||
normalizedOs === 'mac' || normalizedOs === 'macos' ? 'macos-cpu-cores' :
|
||||
'linux-cpu-cores',
|
||||
'kernel': 'linux-kernel-old',
|
||||
'pacote curl': 'linux-curl-missing',
|
||||
'pacote ca-certificates': 'linux-ca-certificates-missing',
|
||||
|
||||
@@ -81,16 +81,12 @@ export function condenseKnowledgeBase(
|
||||
options: CondensationOptions = {}
|
||||
): CondensationResult {
|
||||
const condensationStart = Date.now();
|
||||
|
||||
|
||||
chatLog.info(`\n[KnowledgeBase] ========== CONDENSATION START ==========`);
|
||||
chatLog.info(`[KnowledgeBase] Query: "${query}"`);
|
||||
chatLog.info(`[KnowledgeBase] Entries: ${entries.length}`);
|
||||
chatLog.info(`[KnowledgeBase] Options:`, {
|
||||
maxTokens: options.maxTokens || DEFAULT_OPTIONS.maxTokens,
|
||||
minRelevanceScore: options.minRelevanceScore || DEFAULT_OPTIONS.minRelevanceScore,
|
||||
autoSummarize: options.autoSummarize ?? DEFAULT_OPTIONS.autoSummarize,
|
||||
});
|
||||
|
||||
chatLog.info(`[KnowledgeBase] Options: maxTokens=${options.maxTokens || DEFAULT_OPTIONS.maxTokens}, minRelevanceScore=${options.minRelevanceScore || DEFAULT_OPTIONS.minRelevanceScore}, autoSummarize=${options.autoSummarize ?? DEFAULT_OPTIONS.autoSummarize}`);
|
||||
|
||||
const opts = { ...DEFAULT_OPTIONS, ...options };
|
||||
|
||||
if (entries.length === 0) {
|
||||
@@ -110,7 +106,7 @@ export function condenseKnowledgeBase(
|
||||
// Calcular tokens originais
|
||||
const originalContent = entries.map(e => e.content).join('\n\n');
|
||||
const originalTokens = estimateTokens(originalContent);
|
||||
|
||||
|
||||
chatLog.info(`[KnowledgeBase] Original content: ${originalContent.length} chars`);
|
||||
chatLog.info(`[KnowledgeBase] Original tokens (estimated): ${originalTokens}`);
|
||||
|
||||
@@ -123,14 +119,14 @@ export function condenseKnowledgeBase(
|
||||
}> = [];
|
||||
|
||||
chatLog.info(`[KnowledgeBase] Processing ${entries.length} entries into chunks...`);
|
||||
|
||||
|
||||
entries.forEach((entry, entryIndex) => {
|
||||
const entryStart = Date.now();
|
||||
chatLog.info(`\n[KnowledgeBase] --- Processing Entry ${entryIndex + 1}/${entries.length} ---`);
|
||||
chatLog.info(`[KnowledgeBase] Title: ${entry.title}`);
|
||||
chatLog.info(`[KnowledgeBase] URL: ${entry.sourceUrl}`);
|
||||
chatLog.info(`[KnowledgeBase] Content length: ${entry.content.length} chars`);
|
||||
|
||||
|
||||
const processed = processContent(entry.content, query, {
|
||||
summarizeThreshold: opts.summarizeThreshold,
|
||||
});
|
||||
@@ -145,7 +141,7 @@ export function condenseKnowledgeBase(
|
||||
|
||||
const entryDuration = Date.now() - entryStart;
|
||||
chatLog.info(`[KnowledgeBase] Generated ${chunksWithSource.length} chunks in ${entryDuration}ms`);
|
||||
|
||||
|
||||
// Log detalhes dos chunks
|
||||
if (chunksWithSource.length > 0) {
|
||||
chunksWithSource.slice(0, 3).forEach((chunk, chunkIdx) => {
|
||||
@@ -164,7 +160,7 @@ export function condenseKnowledgeBase(
|
||||
|
||||
allChunks.push(...chunksWithSource);
|
||||
});
|
||||
|
||||
|
||||
chatLog.info(`[KnowledgeBase] Total chunks generated: ${allChunks.length}`);
|
||||
|
||||
// Calcular tokens disponíveis (reservar 20% para margem)
|
||||
@@ -175,7 +171,7 @@ export function condenseKnowledgeBase(
|
||||
// Selecionar chunks mais relevantes (usando contexto se disponível)
|
||||
const selectionStart = Date.now();
|
||||
chatLog.info(`[KnowledgeBase] Selecting most relevant chunks...`);
|
||||
|
||||
|
||||
let selectedChunks = scoreAndSelectChunks(
|
||||
allChunks,
|
||||
query,
|
||||
@@ -183,10 +179,10 @@ export function condenseKnowledgeBase(
|
||||
opts.minRelevanceScore,
|
||||
opts.context
|
||||
);
|
||||
|
||||
|
||||
const selectionDuration = Date.now() - selectionStart;
|
||||
chatLog.info(`[KnowledgeBase] Selected ${selectedChunks.length} chunks from ${allChunks.length} total in ${selectionDuration}ms`);
|
||||
|
||||
|
||||
// Log top chunks selecionados
|
||||
if (selectedChunks.length > 0) {
|
||||
chatLog.info(`[KnowledgeBase] Top 5 selected chunks:`);
|
||||
@@ -208,7 +204,7 @@ export function condenseKnowledgeBase(
|
||||
} else {
|
||||
// Construir contexto a partir dos chunks selecionados
|
||||
const chunksBySource = new Map<number, Chunk[]>();
|
||||
|
||||
|
||||
selectedChunks.forEach(chunk => {
|
||||
if (!chunksBySource.has(chunk.sourceIndex)) {
|
||||
chunksBySource.set(chunk.sourceIndex, []);
|
||||
@@ -264,14 +260,14 @@ export function condenseKnowledgeBase(
|
||||
chatLog.info(`[KnowledgeBase] Compression ratio: ${(compressionRatio * 100).toFixed(1)}%`);
|
||||
chatLog.info(`[KnowledgeBase] Chunks used: ${selectedChunks.length} / ${allChunks.length}`);
|
||||
chatLog.info(`[KnowledgeBase] Final context length: ${context.length} chars`);
|
||||
|
||||
|
||||
// Log estatísticas por fonte
|
||||
chatLog.info(`[KnowledgeBase] Chunks per source:`);
|
||||
entries.forEach((entry, idx) => {
|
||||
const chunksUsed = selectedChunks.filter(c => c.sourceIndex === idx).length;
|
||||
chatLog.info(` ${entry.title}: ${chunksUsed} chunks`);
|
||||
});
|
||||
|
||||
|
||||
chatLog.info(`[KnowledgeBase] ============================================\n`);
|
||||
|
||||
return {
|
||||
@@ -327,7 +323,7 @@ export function validateCondensedContext(
|
||||
const normalizedContext = result.context.toLowerCase();
|
||||
const queryWords = normalizedQuery.split(/\s+/).filter(w => w.length > 3);
|
||||
const matchingWords = queryWords.filter(word => normalizedContext.includes(word));
|
||||
|
||||
|
||||
if (matchingWords.length < queryWords.length * 0.5) {
|
||||
warnings.push('Poucas palavras-chave da query aparecem no contexto');
|
||||
}
|
||||
|
||||
+50
-54
@@ -109,9 +109,9 @@ Query expandida:`;
|
||||
num_predict: 100,
|
||||
},
|
||||
}))
|
||||
.replace(/^["']|["']$/g, '')
|
||||
.replace(/\.$/, '')
|
||||
.trim();
|
||||
.replace(/^["']|["']$/g, '')
|
||||
.replace(/\.$/, '')
|
||||
.trim();
|
||||
|
||||
const sanitized = expanded
|
||||
.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
||||
@@ -143,41 +143,41 @@ export function calculateRelevanceScoreForFallback(
|
||||
): number {
|
||||
const queryLower = query.toLowerCase();
|
||||
const queryWords = queryLower.split(/\s+/).filter(w => w.length > 2);
|
||||
|
||||
|
||||
// Extrair texto para análise
|
||||
const title = (result.title || '').toLowerCase();
|
||||
const content = (result.markdown || '').toLowerCase();
|
||||
const combinedText = `${title} ${content}`;
|
||||
|
||||
|
||||
// Contar matches de palavras-chave
|
||||
let matches = 0;
|
||||
const totalWords = queryWords.length;
|
||||
|
||||
|
||||
for (const word of queryWords) {
|
||||
if (combinedText.includes(word)) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Score base: proporção de palavras encontradas
|
||||
let score = matches / totalWords;
|
||||
|
||||
|
||||
// Bônus: título tem mais peso
|
||||
const titleMatches = queryWords.filter(w => title.includes(w)).length;
|
||||
if (titleMatches > 0) {
|
||||
score += (titleMatches / totalWords) * 0.3; // 30% de bônus
|
||||
}
|
||||
|
||||
|
||||
// Bônus: conteúdo não vazio
|
||||
if (content.length > 100) {
|
||||
score += 0.1;
|
||||
}
|
||||
|
||||
|
||||
// Penalidade: conteúdo muito curto
|
||||
if (content.length < 50) {
|
||||
score *= 0.5;
|
||||
}
|
||||
|
||||
|
||||
// Normalizar para 0-1
|
||||
return Math.min(1, Math.max(0, score));
|
||||
}
|
||||
@@ -193,16 +193,16 @@ export function areResultsRelevant(
|
||||
if (results.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Calcular scores de relevância
|
||||
const scores = results.map(r => calculateRelevanceScoreForFallback(r, query));
|
||||
|
||||
|
||||
// Verificar se pelo menos um resultado tem score suficiente
|
||||
const maxScore = Math.max(...scores);
|
||||
const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
|
||||
|
||||
|
||||
chatLog.info(`Relevance scores - Max: ${maxScore.toFixed(2)}, Avg: ${avgScore.toFixed(2)}, Min required: ${minScore}`);
|
||||
|
||||
|
||||
// Considerar relevante se:
|
||||
// 1. Pelo menos um resultado tem score alto, OU
|
||||
// 2. Média dos scores é razoável
|
||||
@@ -283,15 +283,15 @@ export async function executeProgressiveSearch(
|
||||
const seenUrls = new Set<string>();
|
||||
const queryStartTime = Date.now();
|
||||
const maxQueryTimeout = finalConfig.maxTimeoutPerQuery || TIMEOUT_CONFIG.maxTimeoutPerQuery;
|
||||
|
||||
|
||||
chatLog.info(`\n=== PROGRESSIVE SEARCH START ===`);
|
||||
chatLog.info(`Original Query: "${originalQuery}"`);
|
||||
chatLog.info(`Config: ${JSON.stringify({ ...finalConfig, context: finalConfig.context ? 'present' : 'none', enrichedQueries: finalConfig.enrichedQueries ? 'present' : 'none' })}`);
|
||||
chatLog.info(`Timeout config: initial=${finalConfig.initialTimeout}ms, max per query=${maxQueryTimeout}ms, max per round=${finalConfig.maxTimeoutPerRound}ms`);
|
||||
|
||||
|
||||
// Se usar semantic search, criar rounds baseados em estratégias
|
||||
let rounds: Array<{ round: number; config: RoundConfig }> = [];
|
||||
|
||||
|
||||
if (finalConfig.useSemanticSearch && finalConfig.enrichedQueries) {
|
||||
const semanticRounds = createSemanticRounds(finalConfig.enrichedQueries, originalQuery);
|
||||
rounds = semanticRounds.map((roundConfig, idx) => ({
|
||||
@@ -312,10 +312,10 @@ export async function executeProgressiveSearch(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Limitar número de rounds
|
||||
rounds = rounds.slice(0, finalConfig.maxRounds);
|
||||
|
||||
|
||||
for (const { round, config: roundConfig } of rounds) {
|
||||
// Verificar timeout total da query
|
||||
const elapsedTime = Date.now() - queryStartTime;
|
||||
@@ -323,63 +323,59 @@ export async function executeProgressiveSearch(
|
||||
chatLog.warn(`[Round ${round}] Timeout total da query atingido (${elapsedTime}ms >= ${maxQueryTimeout}ms), parando busca`);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Calcular timeout adaptativo para este round
|
||||
const roundTimeout = calculateAdaptiveTimeout(round, finalConfig.initialTimeout || TIMEOUT_CONFIG.initialTimeout);
|
||||
const maxRoundTimeout = finalConfig.maxTimeoutPerRound || TIMEOUT_CONFIG.maxTimeoutPerRound;
|
||||
const actualRoundTimeout = Math.min(roundTimeout, maxRoundTimeout);
|
||||
|
||||
|
||||
chatLog.info(`\n--- Round ${round}/${rounds.length} [${roundConfig.strategy}] ---`);
|
||||
chatLog.info(`Round timeout: ${actualRoundTimeout}ms (adaptativo: ${roundTimeout}ms, máximo: ${maxRoundTimeout}ms)`);
|
||||
|
||||
|
||||
const roundStartTime = Date.now();
|
||||
const roundResults: ScrapedContent[] = [];
|
||||
const roundQueries: string[] = [];
|
||||
|
||||
|
||||
// Executar todas as queries do round em paralelo com timeout por round
|
||||
const queryPromises = roundConfig.queries.map(async (query) => {
|
||||
// Expandir query se necessário (modo tradicional)
|
||||
const finalQuery = finalConfig.enableQueryExpansion && !finalConfig.useSemanticSearch
|
||||
? await expandQuery(query, model, round)
|
||||
: query;
|
||||
|
||||
|
||||
try {
|
||||
// Passar round para searchFn para timeout adaptativo
|
||||
const results = await searchFn(finalQuery, finalConfig.maxResultsPerRound, round);
|
||||
return { query: finalQuery, results };
|
||||
<<<<<<< HEAD
|
||||
} catch (err) {
|
||||
chatLog.warn(`[Round ${round}] Query "${finalQuery}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
=======
|
||||
} catch (error) {
|
||||
chatLog.warn(`[Round ${round}] Query "${finalQuery}" failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
|
||||
return { query: finalQuery, results: [] };
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Adicionar timeout total para o round
|
||||
const roundPromise = Promise.all(queryPromises);
|
||||
type QueryResult = { query: string; results: ScrapedContent[] };
|
||||
const timeoutPromise = new Promise<QueryResult[]>((_, reject) => {
|
||||
setTimeout(() => reject(new Error(`Round ${round} timeout após ${actualRoundTimeout}ms`)), actualRoundTimeout);
|
||||
});
|
||||
|
||||
|
||||
let queryResults: QueryResult[];
|
||||
try {
|
||||
queryResults = await Promise.race([roundPromise, timeoutPromise]);
|
||||
} catch (_err) {
|
||||
chatLog.warn(`[Round ${round}] Timeout do round após ${actualRoundTimeout}ms, usando resultados parciais`);
|
||||
// Tentar obter resultados parciais das promises que já completaram
|
||||
queryResults = await Promise.allSettled(queryPromises).then(results =>
|
||||
queryResults = await Promise.allSettled(queryPromises).then(results =>
|
||||
results.map(r => r.status === 'fulfilled' ? r.value : { query: '', results: [] })
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Agregar resultados de todas as queries do round
|
||||
for (const { query, results } of queryResults) {
|
||||
roundQueries.push(query);
|
||||
|
||||
|
||||
// Filtrar duplicatas
|
||||
const uniqueResults = results.filter(r => {
|
||||
if (seenUrls.has(r.url)) {
|
||||
@@ -388,14 +384,14 @@ export async function executeProgressiveSearch(
|
||||
seenUrls.add(r.url);
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
roundResults.push(...uniqueResults);
|
||||
}
|
||||
|
||||
|
||||
const roundDuration = Date.now() - roundStartTime;
|
||||
|
||||
|
||||
chatLog.info(`[Round ${round}] Executed ${roundQueries.length} queries, found ${roundResults.length} unique results in ${roundDuration}ms`);
|
||||
|
||||
|
||||
// Log detalhado se houver resultados
|
||||
if (roundResults.length > 0) {
|
||||
roundResults.slice(0, 3).forEach((result, idx) => {
|
||||
@@ -405,7 +401,7 @@ export async function executeProgressiveSearch(
|
||||
chatLog.info(` ... and ${roundResults.length - 3} more results`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calcular relevância usando contexto se disponível
|
||||
let semanticSimilarity: ((ctx: QueryContext, text: string) => number) | null = null;
|
||||
if (finalConfig.context) {
|
||||
@@ -414,13 +410,13 @@ export async function executeProgressiveSearch(
|
||||
}
|
||||
const relevanceScore = roundResults.length > 0
|
||||
? roundResults.reduce((sum, r) => {
|
||||
if (semanticSimilarity && finalConfig.context) {
|
||||
return sum + semanticSimilarity(finalConfig.context, r.markdown || r.title);
|
||||
}
|
||||
return sum + calculateRelevanceScoreForFallback(r, originalQuery);
|
||||
}, 0) / roundResults.length
|
||||
if (semanticSimilarity && finalConfig.context) {
|
||||
return sum + semanticSimilarity(finalConfig.context, r.markdown || r.title);
|
||||
}
|
||||
return sum + calculateRelevanceScoreForFallback(r, originalQuery);
|
||||
}, 0) / roundResults.length
|
||||
: 0;
|
||||
|
||||
|
||||
// Registrar tentativa
|
||||
const attempt: FallbackAttempt = {
|
||||
round,
|
||||
@@ -433,19 +429,19 @@ export async function executeProgressiveSearch(
|
||||
duration: roundDuration,
|
||||
};
|
||||
attempts.push(attempt);
|
||||
|
||||
|
||||
allResults.push(...roundResults);
|
||||
totalResultsAnalyzed += roundResults.length;
|
||||
|
||||
|
||||
// Verificar early exit: resultados suficientes com alta relevância
|
||||
const minResultsForEarlyExit = finalConfig.minResultsForEarlyExit || 5;
|
||||
const minRelevanceForEarlyExit = finalConfig.minRelevanceForEarlyExit || 0.7;
|
||||
|
||||
|
||||
if (roundResults.length >= minResultsForEarlyExit && relevanceScore >= minRelevanceForEarlyExit) {
|
||||
chatLog.info(`[Round ${round}] 🎯 Early exit: resultados suficientes encontrados!`);
|
||||
chatLog.info(`[Round ${round}] Results: ${roundResults.length} (mínimo: ${minResultsForEarlyExit}), Relevance: ${relevanceScore.toFixed(2)} (mínimo: ${minRelevanceForEarlyExit})`);
|
||||
chatLog.info(`[Round ${round}] Total results: ${allResults.length}, Total analyzed: ${totalResultsAnalyzed}`);
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
knowledgeBaseContext: '', // Será preenchido pelo caller
|
||||
@@ -456,23 +452,23 @@ export async function executeProgressiveSearch(
|
||||
finalQuery: roundQueries.join(' | '),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Verificar se atingiu limite de resultados
|
||||
if (totalResultsAnalyzed >= finalConfig.maxTotalResults) {
|
||||
chatLog.warn(`[Round ${round}] ⚠️ Reached max total results limit (${finalConfig.maxTotalResults})`);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Se não encontrou resultados relevantes, continuar para próxima rodada
|
||||
chatLog.warn(`[Round ${round}] ⚠️ Did not find relevant results (min score: ${finalConfig.minRelevanceScore}), continuing...`);
|
||||
}
|
||||
|
||||
|
||||
// Se chegou aqui, não encontrou resultados relevantes
|
||||
chatLog.warn(`\n⚠️ PROGRESSIVE SEARCH FAILED`);
|
||||
chatLog.warn(`Total rounds: ${attempts.length}`);
|
||||
chatLog.warn(`Total results analyzed: ${totalResultsAnalyzed}`);
|
||||
chatLog.warn(`Using internal knowledge fallback`);
|
||||
|
||||
|
||||
return {
|
||||
success: false,
|
||||
knowledgeBaseContext: '',
|
||||
|
||||
Reference in new issue
Block a user