Historico de quizzes e inicio de atualização da IA para leitura de pdfs de matemática (incompleto)

This commit is contained in:
2026-05-20 01:32:37 +01:00
parent 80ed2b1346
commit 98dcd621c7
12 changed files with 1539 additions and 271 deletions

View File

@@ -86,11 +86,13 @@ class MaterialsRAGService {
/// RAG CHUNK RETRIEVAL - Versão correta
/// Busca chunks relevantes dos PDFs com base na query do usuário
/// Se [selectedMaterialIds] for fornecido e não vazio, filtra apenas esses materiais
/// Se [filterTableData] for true, remove dados de tabelas/gráficos do conteúdo
static Future<String> getRelevantChunks({
required String userQuery,
int maxMaterials = 5,
int maxChunks = 5,
List<String>? selectedMaterialIds,
bool filterTableData = false,
}) async {
try {
final user = _auth.currentUser;
@@ -187,7 +189,7 @@ class MaterialsRAGService {
// PDFs pequenos: enviar texto completo (formulários, notas, etc.)
// PDFs grandes: keyword window search para não sobrecarregar o modelo
final String context;
String context;
if (fullText.length <= 10000) {
context = fullText;
Logger.info(
@@ -202,6 +204,13 @@ class MaterialsRAGService {
context = windows.join('\n\n---\n\n');
Logger.info('Large PDF — keyword windows: ${windows.length}');
}
// Filter table data if requested (for math subjects)
if (filterTableData) {
context = _filterTableData(context);
Logger.info('Filtered table data from content');
}
if (context.isNotEmpty) {
contextBuffer.writeln('\n[MATERIAL: $fileName]');
contextBuffer.writeln(context);
@@ -409,11 +418,6 @@ class MaterialsRAGService {
int maxWindows, {
int windowSize = 1200,
}) {
if (text.isEmpty || userQuery.isEmpty) {
// Sem query — devolver início do texto
return [text.length > windowSize ? text.substring(0, windowSize) : text];
}
// Extrair keywords: palavras com >3 chars + nomes próprios (palavras com maiúscula, >2 chars)
// Os nomes próprios são invariantes entre línguas (ex: "Claire", "Rae", "François")
final properNouns = RegExp(
@@ -645,4 +649,36 @@ class MaterialsRAGService {
_chunksCache.clear();
Logger.info('Materials chunks cache cleared');
}
/// Filter out table data from text (for math subjects)
/// Removes lines that look like tabular data with multiple numbers
static String _filterTableData(String text) {
final lines = text.split('\n');
final filtered = <String>[];
for (final line in lines) {
final trimmed = line.trim();
// Skip lines that look like table data
// Pattern: multiple numbers separated by spaces/tabs
final numberPattern = RegExp(r'\d+\s+\d+');
final matches = numberPattern.allMatches(trimmed);
// If a line has 2+ number pairs separated by spaces, it's likely table data
if (matches.length >= 2) {
continue;
}
// Skip lines with specific date patterns (table data)
if (RegExp(r'\d{1,2}/\d{1,2}/\d{4}').hasMatch(trimmed) &&
RegExp(r'\d+').allMatches(trimmed).length > 2) {
continue;
}
// Keep the line
filtered.add(line);
}
return filtered.join('\n');
}
}

View File

@@ -29,7 +29,7 @@ class RAGAIService {
// PASSO 2 — ADICIONAR SYSTEM MESSAGE DO VICO (SEMPRE PRIMEIRO)
messages.add({
'role': 'system',
'content': '''Tu és "Vico", o Assistente IA oficial do Teach it.
'content': r'''Tu és "Vico", o Assistente IA oficial do Learn It.
Nunca referes o nome do modelo.
Nunca dizes que és Qwen ou OpenAI.
@@ -37,7 +37,17 @@ Respondes sempre como o Vico.
Tens personalidade confiante, motivadora e orgulhosa.
Ajudas o aluno segundo o método de ensino presente nos materiais do professor.
Usas formatação Markdown clara e organizada.''',
Usas formatação Markdown clara e organizada.
IMPORTANTE: NUNCA uses LaTeX ou símbolos como $ ou $$ para fórmulas matemáticas.
Usa apenas texto normal e caracteres Unicode para símbolos matemáticos (ex: x², ³, ¹⁄², π, √).
IMPORTANTE - RESPOSTAS COMPLETAS:
- NUNCA termines respostas com dois pontos (:).
- NUNCA deixes respostas incompletas como "A função é: " ou "Calculamos o denominador: ".
- SEMPRE completa as frases e fornece a resposta completa.
- Se precisares de explicar um cálculo, explica-o completamente com o resultado final.
- Se precisares de definir algo, fornece a definição completa.''',
});
// PASSO 3 — BUSCAR MEMÓRIA DA CONVERSA NA Cloud Firestore
@@ -54,8 +64,8 @@ Usas formatação Markdown clara e organizada.''',
// PASSO 4 — BUSCAR PDFs DO PROFESSOR NO Firebase Storage (RAG CHUNK RETRIEVAL)
final pdfContext = await MaterialsRAGService.getRelevantChunks(
userQuery: userQuery,
maxMaterials: 5,
maxChunks: 5,
maxMaterials: 10,
maxChunks: 20,
);
if (pdfContext.isNotEmpty) {
messages.add({
@@ -178,7 +188,7 @@ Usas formatação Markdown clara e organizada.''',
/// System message for Vico identity (for legacy calls)
static const String _systemMessage =
'''Tu és "Vico", o Assistente IA oficial do Teach it.
r'''Tu és "Vico", o Assistente IA oficial do Learn It.
Nunca referes o nome do modelo.
Nunca dizes que és Qwen ou OpenAI.
@@ -186,7 +196,17 @@ Respondes sempre como o Vico.
Tens personalidade confiante, motivadora e orgulhosa.
Ajudas o aluno segundo o método de ensino presente nos materiais do professor.
Usas formatação clara e organizada.''';
Usas formatação clara e organizada.
IMPORTANTE: NUNCA uses LaTeX ou símbolos como $ ou $$ para fórmulas matemáticas.
Usa apenas texto normal e caracteres Unicode para símbolos matemáticos (ex: x², ³, ¹⁄², π, √).
IMPORTANTE - RESPOSTAS COMPLETAS:
- NUNCA termines respostas com dois pontos (:).
- NUNCA deixes respostas incompletas como "A função é: " ou "Calculamos o denominador: ".
- SEMPRE completa as frases e fornece a resposta completa.
- Se precisares de explicar um cálculo, explica-o completamente com o resultado final.
- Se precisares de definir algo, fornece a definição completa.''';
/// Call Ollama API with complete messages array
static Future<String> _callOllamaAPIWithMessages(
@@ -215,7 +235,10 @@ Usas formatação clara e organizada.''';
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
final message = responseData['message'];
final content = message?['content'] ?? '';
var content = message?['content'] ?? '';
// Post-process to remove LaTeX symbols
content = _removeLaTeXSymbols(content);
Logger.info('Ollama API response received');
return content.trim();
@@ -431,15 +454,8 @@ Usas formatação clara e organizada.''';
final response = await http.get(url).timeout(Duration(seconds: 10));
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
final models = responseData['models'] as List? ?? [];
final hasModel = models.any(
(model) => (model['name'] as String? ?? '').contains('qwen3-coder'),
);
Logger.info('Ollama service available, model found: $hasModel');
return hasModel;
Logger.info('Ollama service available');
return true;
} else {
Logger.warning(
'Ollama service returned status: ${response.statusCode}',
@@ -477,18 +493,228 @@ Usas formatação clara e organizada.''';
}
}
/// Remove LaTeX symbols from AI response
static String _removeLaTeXSymbols(String text) {
// Remove patterns like $$...$$ (display math)
var cleaned = text.replaceAll(RegExp(r'\$\$[^$]*\$\$'), '');
// Remove patterns like $...$ (inline math) - be more careful
// Only remove when properly closed
cleaned = cleaned.replaceAllMapped(
RegExp(r'\$[^$\n]+?\$'),
(match) => match.group(0)!.replaceAll(r'$', ''),
);
// Remove any remaining standalone $ symbols
cleaned = cleaned.replaceAll(RegExp(r'(?<!\\)\$'), '');
return cleaned;
}
/// Gerar quiz a partir de um prompt com contexto PDF embutido — sem histórico de conversa
static Future<String> generateQuiz(String prompt) async {
static Future<String> generateQuiz(
String prompt, {
bool isMathematics = false,
}) async {
final systemPrompt = isMathematics
? _getMathematicsSystemPrompt()
: _getTextBasedSystemPrompt();
final messages = <Map<String, String>>[
{
'role': 'system',
'content':
'És um assistente educativo especializado em criar quizzes pedagógicos. '
'Cria sempre perguntas claras, baseadas exclusivamente no contexto fornecido.',
},
{'role': 'system', 'content': systemPrompt},
{'role': 'user', 'content': prompt},
];
return await _callOllamaAPIWithMessages(messages);
final raw = await _callOllamaAPIWithMessages(messages);
// Filter out table questions for mathematics
if (isMathematics) {
return _filterTableQuestions(raw);
}
return raw;
}
/// Filter out questions that reference tables, graphs, or specific dates
static String _filterTableQuestions(String json) {
try {
final List<dynamic> questions = jsonDecode(json);
final List<Map<String, dynamic>> filtered = [];
// Keywords that indicate table/graph dependence
final tableKeywords = [
'tabela',
'gráfico',
'dia 1/',
'início de',
'final de',
'tendência',
'evolução',
'ao longo do tempo',
'percentagem no início',
'percentagem no final',
'ano foi de',
'dia específico',
'data específica',
];
for (final q in questions) {
if (q is Map<String, dynamic>) {
final questionText = (q['q'] as String? ?? '').toLowerCase();
// Check if question contains table keywords
final hasTableKeyword = tableKeywords.any(
(keyword) => questionText.contains(keyword.toLowerCase()),
);
// Skip questions with table keywords
if (!hasTableKeyword) {
filtered.add(q);
}
}
}
// If filtered list is empty, return original to avoid empty response
if (filtered.isEmpty) {
Logger.warning(
'All questions filtered out as table questions, returning original',
);
return json;
}
return jsonEncode(filtered);
} catch (e) {
Logger.error('Error filtering table questions: $e');
return json;
}
}
/// System prompt for mathematics quizzes
static String _getMathematicsSystemPrompt() {
return '''És um assistente educativo especializado em criar EXERCÍCIOS DE MATEMÁTICA.
REGRAS CRÍTICAS:
1. ANALISA TODO O CONTEÚDO fornecido:
- Lê TODO o documento do início ao fim
- Identifica TODOS os tópicos e tipos de exercícios presentes
- NÃO te limites apenas aos primeiros exercícios
- Cria perguntas sobre TODOS os tópicos encontrados no documento
2. MANTÉM o NÍVEL DE DIFICULDADE da ficha original:
- Analisa a complexidade dos exercícios na ficha
- Cria exercícios com o MESMO nível de dificuldade
- NÃO faças perguntas mais avançadas do que o que está na ficha
- Se a ficha tem exercícios simples, cria exercícios simples
- Se a ficha tem exercícios complexos, cria exercícios complexos
3. ESTRICTAMENTE PROIBIDO criar perguntas que envolvam tabelas ou gráficos:
- NUNCA faças perguntas que dependam de dados de tabelas
- NUNCA faças perguntas que dependam de dados de gráficos
- NUNCA faças perguntas sobre "tendência" ou "evolução ao longo do tempo"
- NUNCA faças perguntas com datas específicas (ex: "dia 1/1/2017", "início de 2017")
- NUNCA faças perguntas sobre "percentagem no início" ou "percentagem no final"
- Se a ficha tem exercícios com tabelas, adapta-os para usar valores diretamente no texto
- Fornece os dados necessários diretamente no texto da pergunta
4. Cria perguntas COMPLETAMENTE INDEPENDENTES:
- CADA pergunta deve ser respondida SEM depender de outras perguntas
- NUNCA faças referências à "pergunta anterior" ou "pergunta seguinte"
- NUNCA uses resultados de perguntas anteriores em novas perguntas
- CADA pergunta deve ter TODOS os dados necessários no seu enunciado
- O aluno deve conseguir responder a qualquer pergunta independentemente da ordem
5. Usa VALORES DIFERENTES do conteúdo:
- Os valores numéricos nas perguntas podem ser DIFERENTES dos que estão no PDF
- Usa valores que mantenham o mesmo tipo de problema mas com números diferentes
- Exemplo: se o PDF tem "um prisma de 5cm", podes usar "um prisma de 7cm"
- O importante é manter a ESTRUTURA e TIPO de problema, não os valores exatos
6. EXEMPLOS DE PERGUNTAS PROIBIDAS (NÃO faças estas):
- "Qual é a percentagem da área afetada pela vespa no início do dia 1/1/2017?" (PROIBIDO - depende de tabela)
- "Com o passar do tempo, a área afetada tende para:" (PROIBIDO - depende de gráfico/evolução)
- "Em que ano a produção foi de X toneladas?" (PROIBIDO - depende de tabela)
- "Usando o resultado da pergunta anterior, calcule..." (PROIBIDO - depende de pergunta anterior)
- "Considerando o valor calculado acima, determine..." (PROIBIDO - depende de pergunta anterior)
7. EXEMPLOS DE PERGUNTAS PERMITIDAS (faz estas):
- "Um prisma tem base quadrada com 5cm de lado e altura 12cm. Qual é o volume?" (PERMITIDO - dados diretos, independente)
- "Calcule a área de um círculo com raio 7cm." (PERMITIDO - dados diretos, independente)
- "Resolva a equação 2x + 5 = 15." (PERMITIDO - dados diretos, independente)
- "Uma esfera tem raio de 3 metros. Determine o seu volume." (PERMITIDO - dados diretos, independente)
8. ANALISA OS EXEMPLOS DE EXERCÍCIOS no contexto fornecido:
- Identifica os TIPOS de problemas que aparecem na ficha (ex: determinar volume de sólidos, planos que decompõem prismas, equações, frações, etc.)
- Repara na COMPLEXIDADE e estrutura dos exercícios originais
- Gera exercícios NOVOS que seguem a MESMA estrutura e complexidade mas com VALORES DIFERENTES
9. NÃO copies perguntas do conteúdo fornecido
10. Usa valores diferentes mas mantém o TIPO e ESTRUTURA do problema
11. INCLUI TODOS OS DADOS NECESSÁRIOS no texto da pergunta:
- Fornece TODOS os valores numéricos necessários
- Exemplo: "Um prisma tem base quadrada com 5cm de lado e altura 12cm. Qual é o volume?"
- NUNCA faças perguntas que dependam de dados que não forneces no texto
12. Na explicação, usa texto normal, SEM LaTeX:
- Escreve "a fração é 15/44" em vez de "\\frac{15}{44}"
- Escreve "raiz quadrada de 25" em vez de "\\sqrt{25}"
- Escreve "x ao quadrado" em vez de "x^2"
- Apenas usa LaTeX na própria pergunta se for estritamente necessário para a notação matemática
13. Cria exercícios variados sobre TODOS os tópicos:
- Diferentes valores numéricos
- Diferentes contextos quando aplicável
- Mesmo TIPO e complexidade de problema matemático
- VARIADOS entre todos os tópicos do documento
14. EXEMPLOS DE TIPOS DE EXERCÍCIOS DE MATEMÁTICA:
- Determinar volumes de sólidos (prismas, pirâmides, cilindros, cones, esferas)
- Planos que decompõem sólidos em partes geometricamente iguais
- Equações lineares e quadráticas
- Sistemas de equações
- Funções e gráficos
- Geometria analítica
- Trigonometria
- Probabilidade e estatística
- Cálculo de áreas e perímetros
- Frações e números racionais
- Potências e raízes
FORMATO JSON:
[{"q":"Pergunta com dados completos incluídos","opts":["A) opção","B) opção","C) opção","D) opção"],"ans":0,"exp":"Explicação em texto normal sem LaTeX"}]
ans é o índice (0-3) da opção correcta.''';
}
/// System prompt for text-based subject quizzes
static String _getTextBasedSystemPrompt() {
return '''És um assistente educativo especializado em criar quizzes pedagógicos.
REGRAS CRÍTICAS:
1. Cria perguntas de compreensão, análise e síntese
2. Baseia-te nos conceitos e temas do conteúdo
3. Evita perguntas de cópia direta
4. Foca em entender e aplicar os conceitos
5. INCLUI CONTEXTO SUFICIENTE EM CADA PERGUNTA:
- Cada pergunta deve ser compreensível por si só
- Fornece contexto necessário sobre o assunto da pergunta
- Exemplo PROIBIDO: "Em que ano a vespa asiática afetou mais de 50% da área?" (sem contexto)
- Exemplo PERMITIDO: "De acordo com o estudo sobre a vespa asiática em Portugal, em que ano esta espécie afetou mais de 50% da área de distribuição?"
- Exemplo PROIBIDO: "Qual foi a principal causa?" (sem contexto)
- Exemplo PERMITIDO: "Qual foi a principal causa da extinção do dodo, segundo o texto?"
6. EVITA perguntas que dependam de dados específicos não mencionados:
- NUNCA faças perguntas sobre "ano X" ou "data X" sem especificar de que ano/data se trata
- NUNCA faças perguntas sobre "porcentagem X" sem explicar o contexto
- NUNCA faças perguntas que dependam de tabelas ou gráficos
7. EXEMPLOS DE PERGUNTAS BEM FORMULADAS:
- "De acordo com o texto sobre a vespa asiática, qual é o principal impacto desta espécie na biodiversidade portuguesa?"
- "O estudo sobre a mudança climática menciona que a temperatura média aumentou. Qual foi a principal causa mencionada?"
- "Segundo o documento sobre a história de Portugal, qual foi o resultado da Batalha de Aljubarrota?"
FORMATO JSON:
[{"q":"Pergunta com contexto suficiente","opts":["A) opção","B) opção","C) opção","D) opção"],"ans":0,"exp":"Explicação"}]
ans é o índice (0-3) da opção correcta.''';
}
/// Test the service with a simple query
@@ -644,7 +870,7 @@ Usas formatação clara e organizada.''';
messages.add({
'role': 'system',
'content':
'''Tu és "Vico", o Assistente IA oficial do Teach it — uma plataforma educativa portuguesa.
r'''Tu és "Vico", o Assistente IA oficial do Learn It — uma plataforma educativa portuguesa.
Nunca referes o nome do modelo de linguagem.
Nunca dizes que és Qwen, OpenAI ou qualquer outro modelo.
@@ -653,11 +879,29 @@ Respondes sempre como o Vico.
Tens personalidade simpática, confiante e motivadora.
Podes responder normalmente a saudações, agradecimentos e conversa casual — sê natural e amigável.
IMPORTANTE: NUNCA uses LaTeX ou símbolos como $ ou $$ para fórmulas matemáticas.
Usa apenas texto normal e caracteres Unicode para símbolos matemáticos (ex: x², ³, ¹⁄², π, √).
REGRAS CRÍTICAS PARA PERGUNTAS EDUCATIVAS:
- Quando te for fornecido contexto de materiais do professor (indicado com [MATERIAL: ...]), responde EXCLUSIVAMENTE com base nesse conteúdo.
- NÃO inventes factos educativos, NÃO uses conhecimento externo sobre matérias escolares.
- Se a resposta educativa não estiver no contexto fornecido, diz claramente: "Não encontrei essa informação no material disponível."
- Para conversa casual e saudações não precisas de contexto — responde livremente com a tua personalidade.''',
- Para conversa casual e saudações não precisas de contexto — responde livremente com a tua personalidade.
IMPORTANTE - COMO TRATAR MATERIAIS SELECIONADOS:
- Quando o aluno selecionar materiais (PDFs, fichas, exames), ASSUME que o aluno quer ajuda com esses materiais.
- NUNCA perguntes "que ficha pretendo resolver" ou "o que pretendo resolver".
- NUNCA perguntes "em que posso ajudar" quando materiais estão selecionados.
- ASSUME automaticamente que o aluno quer explicação, resolução ou ajuda com os materiais selecionados.
- Analisa os materiais selecionados e oferece ajuda proativamente: "Vejo que selecionaste [nome do material]. Como posso ajudar com este conteúdo?"
- Se a pergunta do aluno for vaga (ex: "ajuda"), usa os materiais selecionados para oferecer ajuda específica sobre o conteúdo.
IMPORTANTE - RESPOSTAS COMPLETAS:
- NUNCA termines respostas com dois pontos (:).
- NUNCA deixes respostas incompletas como "A função é: " ou "Calculamos o denominador: ".
- SEMPRE completa as frases e fornece a resposta completa.
- Se precisares de explicar um cálculo, explica-o completamente com o resultado final.
- Se precisares de definir algo, fornece a definição completa.''',
});
// PASSO 3 — BUSCAR MEMÓRIA DA CONVERSA NA Cloud Firestore (máx 4 para poupar heap)
@@ -696,8 +940,8 @@ REGRAS CRÍTICAS PARA PERGUNTAS EDUCATIVAS:
} else {
pdfContext = await MaterialsRAGService.getRelevantChunks(
userQuery: userQuery,
maxMaterials: 5,
maxChunks: 5,
maxMaterials: 10,
maxChunks: 20,
selectedMaterialIds: selectedMaterialIds,
);
if (pdfContext.isNotEmpty) {

View File

@@ -63,7 +63,7 @@ class RAGService {
/// System message for Vico identity - ALWAYS first in every conversation
static const String _systemMessage =
'''Tu és "Vico", o Assistente IA oficial do Teach it.
'''Tu és "Vico", o Assistente IA oficial do Learn It.
Nunca referes o nome do modelo.
Nunca dizes que és Qwen ou OpenAI.
@@ -71,7 +71,17 @@ Respondes sempre como o Vico.
Tens personalidade confiante, motivadora e orgulhosa.
Ajudas o aluno segundo o método de ensino presente nos materiais do professor.
Usas formatação clara e organizada.''';
Usas formatação clara e organizada.
IMPORTANTE: NUNCA uses LaTeX ou símbolos como \$ ou \$\$ para fórmulas matemáticas.
Usa apenas texto normal e caracteres Unicode para símbolos matemáticos (ex: x², ³, ¹⁄², π, √).
IMPORTANTE - RESPOSTAS COMPLETAS:
- NUNCA termines respostas com dois pontos (:).
- NUNCA deixes respostas incompletas como "A função é: " ou "Calculamos o denominador: ".
- SEMPRE completa as frases e fornece a resposta completa.
- Se precisares de explicar um cálculo, explica-o completamente com o resultado final.
- Se precisares de definir algo, fornece a definição completa.''';
/// Process a user query through RAG pipeline
static Future<RAGResponse> processQuery({
@@ -89,6 +99,10 @@ Usas formatação clara e organizada.''';
'Processing RAG query: "${userQuery.substring(0, 50)}..." in ${mode.name} mode',
);
// Detect if subject is math
final isMathSubject = _isMathSubject(subject);
Logger.info('Subject: $subject, Is math: $isMathSubject');
// 1. Generate embedding for user query
final queryEmbedding = VectorService.generateEmbedding(userQuery);
@@ -109,8 +123,13 @@ Usas formatação clara e organizada.''';
return _createNoContentResponse(userQuery, mode);
}
// 3. Build context window
final context = _buildContextWindow(relevantChunks, userQuery, mode);
// 3. Build context window with math-specific filtering
final context = _buildContextWindow(
relevantChunks,
userQuery,
mode,
isMathSubject: isMathSubject,
);
// 4. Generate response (this will be handled by RAGAIService)
final response = await _generateResponse(
@@ -134,8 +153,9 @@ Usas formatação clara e organizada.''';
static String _buildContextWindow(
List<ContentChunk> chunks,
String userQuery,
TutorMode mode,
) {
TutorMode mode, {
bool isMathSubject = false,
}) {
try {
final contextBuilder = StringBuffer();
@@ -147,6 +167,13 @@ Usas formatação clara e organizada.''';
for (int i = 0; i < sortedChunks.length; i++) {
final chunk = sortedChunks[i];
var chunkText = chunk.text;
// Filter table data for math subjects
if (isMathSubject) {
chunkText = _filterTableData(chunkText);
}
contextBuilder.writeln('--- Fonte ${i + 1} ---');
contextBuilder.writeln('Disciplina: ${chunk.subject}');
contextBuilder.writeln('Conceito: ${chunk.concept}');
@@ -158,12 +185,30 @@ Usas formatação clara e organizada.''';
if (chunk.pageNumber != null) {
contextBuilder.writeln('Página: ${chunk.pageNumber}');
}
contextBuilder.writeln('\nConteúdo:\n${chunk.text}\n');
contextBuilder.writeln('\nConteúdo:\n$chunkText\n');
}
// Add mode-specific instructions
contextBuilder.writeln('\n=== INSTRUÇÕES DE TUTORIA ===');
contextBuilder.writeln('Modo: ${_getModeInstructions(mode)}');
// Add math-specific instructions if applicable
if (isMathSubject) {
contextBuilder.writeln('\n=== INSTRUÇÕES PARA MATEMÁTICA ===');
contextBuilder.writeln('Para notações matemáticas:');
contextBuilder.writeln(
r'- NUNCA use LaTeX ou símbolos como $ ou $$ para fórmulas',
);
contextBuilder.writeln(
'- Use apenas texto normal e caracteres Unicode (ex: x², ³, ¹⁄², π, √)',
);
contextBuilder.writeln(
'- Preserve a notação matemática original quando possível',
);
contextBuilder.writeln('- Explique passo a passo os cálculos');
contextBuilder.writeln('- Use exemplos numéricos concretos');
}
contextBuilder.writeln('Pergunta do Aluno: $userQuery\n');
final contextText = contextBuilder.toString();
@@ -241,7 +286,11 @@ Usas formatação clara e organizada.''';
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
final answer = responseData['message']['content'] as String;
var answer = responseData['message']['content'] as String;
// Post-process to remove LaTeX symbols
answer = _removeLaTeXSymbols(answer);
final confidence = _calculateConfidence(sources);
final relatedConcepts = _extractRelatedConcepts(sources);
@@ -344,7 +393,9 @@ $query
- Use linguagem clara e educacional
- Adapte a resposta ao nível do aluno
- Forneça exemplos quando possível
- Seja conciso mas completo''';
- Seja conciso mas completo
- NUNCA use LaTeX ou símbolos como \$ ou \$\$ para fórmulas
- Use apenas texto normal e caracteres Unicode para símbolos matemáticos''';
}
/// Calculate relevance score
@@ -408,6 +459,77 @@ $query
return concepts.toList()..sort();
}
/// Detect if subject is math-related
static bool _isMathSubject(String? subject) {
if (subject == null) return false;
final lowerSubject = subject.toLowerCase();
final mathKeywords = [
'matemática',
'math',
'matematica',
'álgebra',
'algebra',
'geometria',
'cálculo',
'calculo',
'estatística',
'estatistica',
'funções',
'funcoes',
'equações',
'equacoes',
'números',
'numeros',
];
return mathKeywords.any((keyword) => lowerSubject.contains(keyword));
}
/// Filter out table data from text (for math subjects)
/// Removes lines that look like tabular data with multiple numbers
static String _filterTableData(String text) {
final lines = text.split('\n');
final filtered = <String>[];
for (final line in lines) {
final trimmed = line.trim();
// Skip lines that look like table data
// Pattern: multiple numbers separated by spaces/tabs
final numberPattern = RegExp(r'\d+\s+\d+');
final matches = numberPattern.allMatches(trimmed);
// If a line has 2+ number pairs separated by spaces, it's likely table data
if (matches.length >= 2) {
continue;
}
// Skip lines with specific date patterns (table data)
if (RegExp(r'\d{1,2}/\d{1,2}/\d{4}').hasMatch(trimmed) &&
RegExp(r'\d+').allMatches(trimmed).length > 2) {
continue;
}
// Keep the line
filtered.add(line);
}
return filtered.join('\n');
}
/// Remove LaTeX symbols from AI response
static String _removeLaTeXSymbols(String text) {
// Remove patterns like $...$ and $$...$$
var cleaned = text.replaceAll(RegExp(r'\$\$[^$]+\$\$'), '');
cleaned = cleaned.replaceAll(RegExp(r'\$[^$]+\$'), '');
// Also remove standalone $ symbols
cleaned = cleaned.replaceAll(r'$', r'\$');
return cleaned;
}
/// Create response for no content found
static RAGResponse _createNoContentResponse(String query, TutorMode mode) {
return RAGResponse(