wip: snapshot das alterações não commitadas (2026-10-20)
release.yml (push) Invalid workflow file

Origem: D:/Documents/Github/OllaHub
Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 0
This commit is contained in:
evandro committed 2026-10-20 00:49:46 -03:00
1 parent a7cbd1a3b4
commit 861af0bb9f
217 files changed
+39421 -1340

No files matched your search

+42 -41
View File
@@ -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
View File
@@ -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',
+15 -19
View File
@@ -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
View File
@@ -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: '',