Files
OllaHub/lib/query-enricher.ts
Evandro Fonseca Junior e5f062040c feat: enhance UI and functionality across chat and settings
- Added custom scrollbar styles for improved aesthetics and usability across the interface.
- Implemented responsive adjustments for chat sidebar and input components to enhance mobile experience.
- Integrated tooltip components in select elements for better user guidance and information display.
- Updated chat input and message components for improved styling and functionality.
- Enhanced settings page with semantic query expansion options and improved model selection UI.
- Refactored sidebar list to include better date formatting and tooltip support for session titles.
- Introduced GPU stats monitoring in the system panel for better hardware insights.
2025-11-26 19:04:46 -03:00

295 lines
9.9 KiB
TypeScript

import type { QueryContext } from './contextual-analyzer';
import { chatLog } from './terminal-logger';
/**
* Queries enriquecidas por categoria
*/
export interface EnrichedQueries {
literal: string[]; // Queries literais (original + variações diretas)
semantic: string[]; // Variantes semânticas (sinônimos, reformulações)
related: string[]; // Perguntas relacionadas
expanded: string[]; // Expansões temáticas (autores, eventos, tipos)
contextual: string[]; // Queries com contexto adicional (datas, locais)
}
/**
* Prompt para enriquecimento de queries usando LLM
*/
const QUERY_ENRICHMENT_PROMPT = `Você é um especialista em otimização de queries de busca e expansão semântica.
Com base na query original e no contexto analisado, gere múltiplas variantes de queries enriquecidas para maximizar a cobertura de resultados relevantes.
## QUERY ORIGINAL
"{{originalQuery}}"
## CONTEXTO ANALISADO
- Intenção: {{intent}}
- Tópicos: {{topics}}
- Entidades: {{entities}}
- Contexto Temporal: {{temporalContext}}
- Contexto Geográfico: {{geographicContext}}
- Palavras-chave: {{keywords}}
- Sinônimos: {{synonyms}}
- Domínio: {{domain}}
## INSTRUÇÕES
Gere queries enriquecidas nas seguintes categorias:
1. **LITERAL**: Variações diretas da query original (preservar termos principais)
- Ex: "resultados acadêmicos premiados" → ["resultados academicos premiados", "ultimos resultados academicos premiados"]
2. **SEMANTIC**: Variantes semânticas usando sinônimos e reformulações
- Ex: "resultados acadêmicos" → "pesquisas científicas", "estudos acadêmicos", "trabalhos científicos"
- Manter o significado, mas usar termos alternativos
3. **RELATED**: Perguntas relacionadas e FAQs
- Ex: "premios academicos 2024", "conferencias academicas premiadas", "descobertas cientificas premiadas"
- Explorar aspectos adjacentes do tópico
4. **EXPANDED**: Expansões temáticas específicas
- Ex: "Nobel Prize 2024", "Turing Award winners", "Fields Medal recentes"
- Incluir tipos específicos, categorias, exemplos concretos
5. **CONTEXTUAL**: Queries com contexto adicional (datas, locais, domínios)
- Ex: "resultados academicos brasileiros premiados 2024", "pesquisas premiadas universidades"
- Combinar tópico principal com contexto temporal/geográfico
## REGRAS CRÍTICAS
- **PRESERVAR ENTIDADES**: Se a query menciona "Garuva", TODAS as queries devem mencionar "Garuva"
- **NÃO TRADUZIR**: Preservar nomes próprios, termos técnicos na forma original
- **OTIMIZAR PARA BUSCA**: Usar termos que aparecem em resultados reais
- **COBERTURA AMPLA**: Cada categoria deve explorar diferentes ângulos
- **QUALIDADE > QUANTIDADE**: 3-5 queries por categoria é suficiente
- **RELEVÂNCIA**: Todas as queries devem ser relevantes para responder a query original
## FORMATO DE SAÍDA
Retorne APENAS um JSON válido no seguinte formato:
{
"literal": ["query literal 1", "query literal 2"],
"semantic": ["variante semantica 1", "variante semantica 2"],
"related": ["pergunta relacionada 1", "pergunta relacionada 2"],
"expanded": ["expansao tematica 1", "expansao tematica 2"],
"contextual": ["query contextual 1", "query contextual 2"]
}
IMPORTANTE:
- Retorne APENAS o JSON, sem markdown, sem explicações
- Cada array deve ter 3-5 queries
- Queries devem ser em português se a query original for em português
- Preservar termos técnicos e nomes próprios`;
/**
* Enriquece queries baseado no contexto analisado
*
* @param originalQuery - Query original do usuário
* @param context - Contexto analisado da query
* @param model - Modelo LLM a ser usado
* @returns Queries enriquecidas por categoria
*/
export async function enrichQueries(
originalQuery: string,
context: QueryContext,
model: string
): Promise<EnrichedQueries> {
chatLog.info(`[QueryEnricher] Enriquecendo queries para: "${originalQuery}"`);
try {
// Preparar contexto para o prompt
const entitiesStr = context.entities.map(e => `${e.type}: ${e.value}`).join(', ') || 'nenhuma';
const temporalStr = context.temporalContext
? JSON.stringify(context.temporalContext)
: 'nenhum';
const geographicStr = context.geographicContext
? JSON.stringify(context.geographicContext)
: 'nenhum';
const prompt = QUERY_ENRICHMENT_PROMPT
.replace('{{originalQuery}}', originalQuery)
.replace('{{intent}}', context.intent)
.replace('{{topics}}', context.topics.join(', ') || 'nenhum')
.replace('{{entities}}', entitiesStr)
.replace('{{temporalContext}}', temporalStr)
.replace('{{geographicContext}}', geographicStr)
.replace('{{keywords}}', context.keywords.join(', ') || 'nenhuma')
.replace('{{synonyms}}', context.synonyms.join(', ') || 'nenhum')
.replace('{{domain}}', context.domain || 'geral');
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt,
stream: false,
options: {
temperature: 0.5, // Temperatura média para criatividade controlada
num_predict: 1000, // Espaço suficiente para múltiplas queries
},
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
const rawResponse = (data.response || '').trim();
// Limpar resposta (remover markdown code blocks se houver)
let jsonText = rawResponse
.replace(/^```json\s*/i, '')
.replace(/^```\s*/i, '')
.replace(/```\s*$/i, '')
.trim();
// Tentar extrair JSON se houver texto antes/depois
const jsonMatch = jsonText.match(/\{[\s\S]*\}/);
if (jsonMatch) {
jsonText = jsonMatch[0];
}
const enriched = JSON.parse(jsonText) as Partial<EnrichedQueries>;
// Validar e preencher campos
const result: EnrichedQueries = {
literal: enriched.literal || [originalQuery],
semantic: enriched.semantic || [],
related: enriched.related || [],
expanded: enriched.expanded || [],
contextual: enriched.contextual || [],
};
// Garantir que sempre há pelo menos a query original
if (result.literal.length === 0) {
result.literal = [originalQuery];
}
// Log estatísticas
const totalQueries =
result.literal.length +
result.semantic.length +
result.related.length +
result.expanded.length +
result.contextual.length;
chatLog.success(
`[QueryEnricher] ${totalQueries} queries geradas: ` +
`${result.literal.length} literal, ${result.semantic.length} semântica, ` +
`${result.related.length} relacionadas, ${result.expanded.length} expandidas, ` +
`${result.contextual.length} contextuais`
);
return result;
} catch (error) {
chatLog.error(`[QueryEnricher] Erro ao enriquecer queries:`, error);
// Fallback: usar apenas query original
return createFallbackEnrichment(originalQuery, context);
}
}
/**
* Cria enriquecimento básico usando heurísticas quando a análise LLM falha
*/
function createFallbackEnrichment(
originalQuery: string,
context: QueryContext
): EnrichedQueries {
const literal = [originalQuery];
// Adicionar variações básicas usando sinônimos do contexto
const semantic: string[] = [];
if (context.synonyms.length > 0) {
// Substituir algumas palavras por sinônimos
let semanticQuery = originalQuery;
context.synonyms.slice(0, 2).forEach(synonym => {
// Tentar substituir palavra-chave por sinônimo
const keywords = context.keywords;
if (keywords.length > 0) {
const keyword = keywords[0];
semanticQuery = semanticQuery.replace(
new RegExp(keyword, 'gi'),
synonym
);
if (semanticQuery !== originalQuery) {
semantic.push(semanticQuery);
}
}
});
}
// Adicionar contexto temporal se disponível
const contextual: string[] = [];
if (context.temporalContext?.period) {
contextual.push(`${originalQuery} ${context.temporalContext.period}`);
}
if (context.temporalContext?.relative === 'recente') {
contextual.push(`${originalQuery} recente`);
}
// Adicionar contexto geográfico se disponível
if (context.geographicContext?.location) {
contextual.push(`${originalQuery} ${context.geographicContext.location}`);
}
return {
literal,
semantic: semantic.length > 0 ? semantic : [],
related: [],
expanded: [],
contextual,
};
}
/**
* Combina todas as queries enriquecidas em uma lista única para busca
*
* @param enriched - Queries enriquecidas
* @param strategy - Estratégia de combinação
* @returns Lista de queries para buscar
*/
export function combineEnrichedQueries(
enriched: EnrichedQueries,
strategy: 'all' | 'prioritize-literal' | 'prioritize-semantic' = 'prioritize-literal'
): string[] {
const queries: string[] = [];
switch (strategy) {
case 'all':
queries.push(...enriched.literal);
queries.push(...enriched.semantic);
queries.push(...enriched.related);
queries.push(...enriched.expanded);
queries.push(...enriched.contextual);
break;
case 'prioritize-literal':
queries.push(...enriched.literal);
queries.push(...enriched.semantic);
queries.push(...enriched.contextual);
queries.push(...enriched.related);
queries.push(...enriched.expanded);
break;
case 'prioritize-semantic':
queries.push(...enriched.semantic);
queries.push(...enriched.literal);
queries.push(...enriched.contextual);
queries.push(...enriched.related);
queries.push(...enriched.expanded);
break;
}
// Remover duplicatas mantendo ordem
const unique = Array.from(new Set(queries.map(q => q.toLowerCase())))
.map(lower => queries.find(q => q.toLowerCase() === lower)!)
.filter(Boolean);
return unique;
}