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

+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: '',