222 lines
5.7 KiB
TypeScript
222 lines
5.7 KiB
TypeScript
import type { QueryContext } from './contextual-analyzer';
|
|
import type { EnrichedQueries } from './query-enricher';
|
|
import type { FallbackAttempt } from './web-search-fallback';
|
|
import type { ScrapedContent } from '@/services/webSearch';
|
|
import type { UnifiedContext } from './context-unifier';
|
|
|
|
/**
|
|
* Log completo de uma pesquisa
|
|
*/
|
|
export interface SearchLog {
|
|
timestamp: number;
|
|
originalQuery: string;
|
|
contextAnalysis?: QueryContext;
|
|
enrichedQueries?: EnrichedQueries;
|
|
rounds: Array<{
|
|
round: number;
|
|
strategy?: string;
|
|
queries: string[];
|
|
results: Array<{
|
|
query: string;
|
|
engine?: string;
|
|
resultsCount: number;
|
|
duration: number;
|
|
topResults: Array<{
|
|
title: string;
|
|
url: string;
|
|
score?: number;
|
|
}>;
|
|
}>;
|
|
totalResults: number;
|
|
relevanceScore: number;
|
|
}>;
|
|
finalResults: {
|
|
totalSources: number;
|
|
unifiedContext?: UnifiedContext;
|
|
keyFacts?: number;
|
|
contradictions?: number;
|
|
};
|
|
usedFallback: boolean;
|
|
totalDuration: number;
|
|
}
|
|
|
|
/**
|
|
* Armazena logs de pesquisa em memória
|
|
* (Pode ser expandido para salvar em arquivo ou banco de dados)
|
|
*/
|
|
class SearchLogger {
|
|
private logs: SearchLog[] = [];
|
|
private maxLogs = 100; // Manter apenas os últimos 100 logs
|
|
|
|
/**
|
|
* Adiciona um novo log
|
|
*/
|
|
addLog(log: SearchLog): void {
|
|
this.logs.unshift(log); // Adicionar no início
|
|
if (this.logs.length > this.maxLogs) {
|
|
this.logs = this.logs.slice(0, this.maxLogs);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtém todos os logs
|
|
*/
|
|
getLogs(): SearchLog[] {
|
|
return [...this.logs];
|
|
}
|
|
|
|
/**
|
|
* Obtém log por query
|
|
*/
|
|
getLogByQuery(query: string): SearchLog | undefined {
|
|
return this.logs.find(log =>
|
|
log.originalQuery.toLowerCase() === query.toLowerCase()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Limpa todos os logs
|
|
*/
|
|
clearLogs(): void {
|
|
this.logs = [];
|
|
}
|
|
|
|
/**
|
|
* Exporta logs como JSON
|
|
*/
|
|
exportLogs(): string {
|
|
return JSON.stringify(this.logs, null, 2);
|
|
}
|
|
}
|
|
|
|
// Instância singleton
|
|
export const searchLogger = new SearchLogger();
|
|
|
|
/**
|
|
* Cria um log de pesquisa a partir de dados coletados
|
|
*/
|
|
export function createSearchLog(
|
|
originalQuery: string,
|
|
contextAnalysis: QueryContext | null,
|
|
enrichedQueries: EnrichedQueries | null,
|
|
attempts: FallbackAttempt[],
|
|
finalSources: ScrapedContent[],
|
|
unifiedContext: UnifiedContext | null,
|
|
usedFallback: boolean,
|
|
startTime: number
|
|
): SearchLog {
|
|
const endTime = Date.now();
|
|
const totalDuration = endTime - startTime;
|
|
|
|
// Processar rounds dos attempts
|
|
const rounds = attempts.map(attempt => {
|
|
const queryResults = attempt.queries
|
|
? attempt.queries.map(q => ({
|
|
query: q,
|
|
resultsCount: attempt.results.length,
|
|
duration: attempt.duration,
|
|
topResults: attempt.results.slice(0, 3).map(r => ({
|
|
title: r.title,
|
|
url: r.url,
|
|
score: attempt.relevanceScore,
|
|
})),
|
|
}))
|
|
: [{
|
|
query: attempt.query,
|
|
resultsCount: attempt.results.length,
|
|
duration: attempt.duration,
|
|
topResults: attempt.results.slice(0, 3).map(r => ({
|
|
title: r.title,
|
|
url: r.url,
|
|
score: attempt.relevanceScore,
|
|
})),
|
|
}];
|
|
|
|
return {
|
|
round: attempt.round,
|
|
strategy: attempt.strategy,
|
|
queries: attempt.queries || [attempt.query],
|
|
results: queryResults,
|
|
totalResults: attempt.results.length,
|
|
relevanceScore: attempt.relevanceScore,
|
|
};
|
|
});
|
|
|
|
return {
|
|
timestamp: startTime,
|
|
originalQuery,
|
|
contextAnalysis: contextAnalysis || undefined,
|
|
enrichedQueries: enrichedQueries || undefined,
|
|
rounds,
|
|
finalResults: {
|
|
totalSources: finalSources.length,
|
|
unifiedContext: unifiedContext || undefined,
|
|
keyFacts: unifiedContext?.keyFacts.length,
|
|
contradictions: unifiedContext?.contradictions.length,
|
|
},
|
|
usedFallback,
|
|
totalDuration,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Formata log para exibição
|
|
*/
|
|
export function formatSearchLog(log: SearchLog): string {
|
|
const lines: string[] = [];
|
|
|
|
lines.push(`=== SEARCH LOG ===`);
|
|
lines.push(`Timestamp: ${new Date(log.timestamp).toISOString()}`);
|
|
lines.push(`Query: "${log.originalQuery}"`);
|
|
lines.push(`Duration: ${log.totalDuration}ms`);
|
|
lines.push(`Used Fallback: ${log.usedFallback}`);
|
|
lines.push('');
|
|
|
|
if (log.contextAnalysis) {
|
|
lines.push(`--- Context Analysis ---`);
|
|
lines.push(`Intent: ${log.contextAnalysis.intent}`);
|
|
lines.push(`Entities: ${log.contextAnalysis.entities.length}`);
|
|
lines.push(`Topics: ${log.contextAnalysis.topics.join(', ')}`);
|
|
lines.push('');
|
|
}
|
|
|
|
if (log.enrichedQueries) {
|
|
lines.push(`--- Enriched Queries ---`);
|
|
lines.push(`Literal: ${log.enrichedQueries.literal.length}`);
|
|
lines.push(`Semantic: ${log.enrichedQueries.semantic.length}`);
|
|
lines.push(`Related: ${log.enrichedQueries.related.length}`);
|
|
lines.push(`Expanded: ${log.enrichedQueries.expanded.length}`);
|
|
lines.push(`Contextual: ${log.enrichedQueries.contextual.length}`);
|
|
lines.push('');
|
|
}
|
|
|
|
lines.push(`--- Rounds (${log.rounds.length}) ---`);
|
|
log.rounds.forEach((round, idx) => {
|
|
lines.push(`Round ${round.round} [${round.strategy || 'N/A'}]:`);
|
|
lines.push(` Queries: ${round.queries.join(', ')}`);
|
|
lines.push(` Results: ${round.totalResults}`);
|
|
lines.push(` Relevance Score: ${round.relevanceScore.toFixed(2)}`);
|
|
lines.push('');
|
|
});
|
|
|
|
lines.push(`--- Final Results ---`);
|
|
lines.push(`Total Sources: ${log.finalResults.totalSources}`);
|
|
if (log.finalResults.keyFacts) {
|
|
lines.push(`Key Facts: ${log.finalResults.keyFacts}`);
|
|
}
|
|
if (log.finalResults.contradictions) {
|
|
lines.push(`Contradictions: ${log.finalResults.contradictions}`);
|
|
}
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|