Files
OllaHub/lib/contextual-analyzer.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

277 lines
9.2 KiB
TypeScript

import { chatLog } from './terminal-logger';
/**
* Tipos de intenção da query
*/
export type QueryIntent =
| 'factual' // Busca por fatos específicos
| 'comparison' // Comparação entre entidades
| 'how-to' // Instruções ou tutoriais
| 'definition' // Definição de conceito
| 'list' // Listagem de itens
| 'opinion' // Opiniões ou análises
| 'temporal' // Informações temporais (quando, histórico)
| 'causal' // Causa e efeito (por que, como funciona)
| 'unknown'; // Não identificado
/**
* Entidade extraída da query
*/
export interface ExtractedEntity {
type: 'person' | 'location' | 'date' | 'concept' | 'organization' | 'event' | 'product' | 'other';
value: string;
confidence: number; // 0-1
}
/**
* Relacionamento entre entidades
*/
export interface EntityRelationship {
from: string;
to: string;
relation: string; // ex: "localizado em", "criado por", "relacionado com"
confidence: number;
}
/**
* Contexto temporal extraído
*/
export interface TemporalContext {
period?: string; // ex: "2024", "últimos 5 anos"
date?: string; // Data específica
relative?: string; // ex: "recente", "atual", "histórico"
}
/**
* Contexto geográfico extraído
*/
export interface GeographicContext {
location?: string; // Cidade, estado, país
region?: string; // Região, continente
coordinates?: { lat: number; lng: number };
}
/**
* Análise contextual completa da query
*/
export interface QueryContext {
originalQuery: string;
intent: QueryIntent;
entities: ExtractedEntity[];
topics: string[]; // Tópicos principais identificados
relationships: EntityRelationship[]; // Relacionamentos entre entidades
implicitQuestions: string[]; // Perguntas implícitas na query
temporalContext?: TemporalContext;
geographicContext?: GeographicContext;
keywords: string[]; // Palavras-chave importantes
synonyms: string[]; // Sinônimos relevantes
domain?: string; // Domínio (ex: "acadêmico", "tecnologia", "saúde")
complexity: 'simple' | 'moderate' | 'complex';
}
/**
* Prompt para análise contextual usando LLM
*/
const CONTEXTUAL_ANALYSIS_PROMPT = `Você é um especialista em análise semântica e extração de contexto de queries de busca.
Analise a query abaixo e extraia TODAS as informações contextuais relevantes.
Query: "{{query}}"
## INSTRUÇÕES
1. **INTENÇÃO**: Identifique o tipo de intenção (factual, comparison, how-to, definition, list, opinion, temporal, causal)
2. **ENTIDADES**: Extraia todas as entidades mencionadas (pessoas, lugares, datas, conceitos, organizações, eventos, produtos)
3. **TÓPICOS**: Identifique os tópicos principais abordados
4. **RELACIONAMENTOS**: Identifique relacionamentos implícitos entre entidades
5. **PERGUNTAS IMPLÍCITAS**: Quais perguntas relacionadas estão implícitas?
6. **CONTEXTO TEMPORAL**: Há referências a tempo? (datas, períodos, "recente", "atual", "histórico")
7. **CONTEXTO GEOGRÁFICO**: Há referências a localização? (cidades, estados, países, regiões)
8. **PALAVRAS-CHAVE**: Identifique palavras-chave importantes (remover stop words)
9. **SINÔNIMOS**: Liste sinônimos relevantes para os termos principais
10. **DOMÍNIO**: Identifique o domínio da query (acadêmico, tecnologia, saúde, negócios, etc.)
11. **COMPLEXIDADE**: Avalie a complexidade (simple, moderate, complex)
## FORMATO DE SAÍDA
Retorne APENAS um JSON válido no seguinte formato:
{
"intent": "list",
"entities": [
{"type": "concept", "value": "resultados acadêmicos", "confidence": 0.9},
{"type": "concept", "value": "prêmios", "confidence": 0.9}
],
"topics": ["pesquisa científica", "reconhecimento acadêmico", "premiações"],
"relationships": [
{"from": "resultados acadêmicos", "to": "prêmios", "relation": "recebem", "confidence": 0.8}
],
"implicitQuestions": [
"Quais foram os prêmios acadêmicos mais recentes?",
"Quem recebeu prêmios acadêmicos?"
],
"temporalContext": {"relative": "recente", "period": "2024"},
"geographicContext": null,
"keywords": ["resultados", "acadêmicos", "premiados", "últimos"],
"synonyms": ["pesquisas", "estudos", "trabalhos científicos", "reconhecimento", "distinções"],
"domain": "acadêmico",
"complexity": "moderate"
}
IMPORTANTE:
- Retorne APENAS o JSON, sem markdown, sem explicações
- Use null para campos não aplicáveis
- Confidence deve ser entre 0 e 1
- Preserve nomes próprios e termos técnicos na forma original`;
/**
* Analisa o contexto de uma query usando LLM
*
* @param query - Query original do usuário
* @param model - Modelo LLM a ser usado
* @returns Análise contextual completa
*/
export async function analyzeQueryContext(
query: string,
model: string
): Promise<QueryContext> {
if (!query || !query.trim()) {
return {
originalQuery: query,
intent: 'unknown',
entities: [],
topics: [],
relationships: [],
implicitQuestions: [],
keywords: [],
synonyms: [],
complexity: 'simple',
};
}
chatLog.info(`[ContextualAnalysis] Analisando contexto da query: "${query}"`);
try {
const prompt = CONTEXTUAL_ANALYSIS_PROMPT.replace('{{query}}', query);
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.3, // Baixa temperatura para análise mais determinística
num_predict: 800, // Espaço suficiente para JSON completo
},
}),
});
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 analysis = JSON.parse(jsonText) as Partial<QueryContext>;
// Validar e preencher campos obrigatórios
const context: QueryContext = {
originalQuery: query,
intent: (analysis.intent as QueryIntent) || 'unknown',
entities: analysis.entities || [],
topics: analysis.topics || [],
relationships: analysis.relationships || [],
implicitQuestions: analysis.implicitQuestions || [],
temporalContext: analysis.temporalContext,
geographicContext: analysis.geographicContext,
keywords: analysis.keywords || [],
synonyms: analysis.synonyms || [],
domain: analysis.domain,
complexity: analysis.complexity || 'moderate',
};
chatLog.success(`[ContextualAnalysis] Contexto extraído: ${context.intent}, ${context.entities.length} entidades, ${context.topics.length} tópicos`);
return context;
} catch (error) {
chatLog.error(`[ContextualAnalysis] Erro ao analisar contexto:`, error);
// Fallback: análise básica usando heurísticas
return createFallbackContext(query);
}
}
/**
* Cria contexto básico usando heurísticas quando a análise LLM falha
*/
function createFallbackContext(query: string): QueryContext {
const lowerQuery = query.toLowerCase();
// Detectar intenção básica
let intent: QueryIntent = 'factual';
if (lowerQuery.includes('como') || lowerQuery.includes('tutorial')) {
intent = 'how-to';
} else if (lowerQuery.includes('o que é') || lowerQuery.includes('definição')) {
intent = 'definition';
} else if (lowerQuery.includes('quais') || lowerQuery.includes('lista')) {
intent = 'list';
} else if (lowerQuery.includes('comparar') || lowerQuery.includes('vs')) {
intent = 'comparison';
} else if (lowerQuery.includes('por que') || lowerQuery.includes('causa')) {
intent = 'causal';
}
// Extrair palavras-chave básicas
const stopWords = new Set([
'o', 'a', 'os', 'as', 'um', 'uma', 'de', 'da', 'do', 'das', 'dos',
'em', 'no', 'na', 'nos', 'nas', 'para', 'por', 'com', 'sem',
'que', 'qual', 'quais', 'onde', 'quando', 'como', 'porque',
]);
const keywords = query
.toLowerCase()
.split(/\s+/)
.filter(word => word.length > 2 && !stopWords.has(word));
// Detectar contexto temporal
let temporalContext: TemporalContext | undefined;
if (lowerQuery.includes('último') || lowerQuery.includes('recente') || lowerQuery.includes('atual')) {
temporalContext = { relative: 'recente' };
} else if (lowerQuery.includes('2024') || lowerQuery.includes('2023')) {
const yearMatch = query.match(/\b(20\d{2})\b/);
if (yearMatch) {
temporalContext = { period: yearMatch[1] };
}
}
return {
originalQuery: query,
intent,
entities: [],
topics: keywords.slice(0, 5), // Usar keywords como tópicos
relationships: [],
implicitQuestions: [],
temporalContext,
keywords,
synonyms: [],
complexity: keywords.length > 5 ? 'complex' : keywords.length > 2 ? 'moderate' : 'simple',
};
}