tudo sobre a memoria da ia, formatação, memória e conhecimento de pdfs, junto da inserção de pdfs

This commit is contained in:
2026-05-14 00:13:29 +01:00
parent ad400a9c37
commit 55ec2521cf
14 changed files with 1483 additions and 97 deletions

View File

@@ -2,6 +2,8 @@ import 'dart:convert';
import 'package:http/http.dart' as http;
import '../utils/logger.dart';
import 'rag_service.dart';
import 'chat_memory_service.dart';
import 'materials_rag_service.dart';
import '../models/content_chunk.dart';
/// Service for RAG-enhanced AI communication using Ollama API
@@ -11,7 +13,7 @@ class RAGAIService {
static const int _timeoutSeconds = 60;
static const int _maxTokens = 4000;
/// Generate AI response with RAG context
/// Generate AI response with RAG context, conversation memory, and teacher materials
static Future<RAGResponse> generateRAGResponse({
required String userQuery,
required String context,
@@ -21,13 +23,70 @@ class RAGAIService {
try {
Logger.info('Generating RAG response with ${sources.length} sources');
// 1. Build the prompt with context
final prompt = _buildRAGPrompt(userQuery, context, mode);
// PASSO 1 — Criar a lista messages vazia
List<Map<String, String>> messages = [];
// 2. Call Ollama API
final response = await _callOllamaAPI(prompt);
// PASSO 2 — ADICIONAR SYSTEM MESSAGE DO GOAT (SEMPRE PRIMEIRO)
messages.add({
'role': 'system',
'content': '''Tu és "O GOAT", o Assistente IA oficial do Teach it.
// 3. Process response and create RAGResponse
Nunca referes o nome do modelo.
Nunca dizes que és Qwen ou OpenAI.
Respondes sempre como o GOAT.
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.''',
});
// PASSO 3 — BUSCAR MEMÓRIA DA CONVERSA NA Cloud Firestore
final conversationHistory = await ChatMemoryService.getRecentMessages(limit: 20);
for (final msg in conversationHistory) {
messages.add({
'role': msg['role'] as String,
'content': msg['content'] as String,
});
}
// PASSO 4 — BUSCAR PDFs DO PROFESSOR NO Firebase Storage (RAG CHUNK RETRIEVAL)
final pdfContext = await MaterialsRAGService.getRelevantChunks(
userQuery: userQuery,
maxMaterials: 5,
maxChunks: 5,
);
if (pdfContext.isNotEmpty) {
messages.add({
'role': 'system',
'content': pdfContext, // Já vem formatado com [CHUNK 1], [CHUNK 2], etc.
});
}
// PASSO 5 — SÓ AGORA adicionar a pergunta do user
messages.add({
'role': 'user',
'content': userQuery,
});
// Log do tamanho do array para verificação
Logger.info('Built messages array with ${messages.length} messages for API');
// Save user message to Firestore (after building the messages array)
await ChatMemoryService.saveMessage(
role: 'user',
content: userQuery,
);
// Call Ollama API with complete messages array
final response = await _callOllamaAPIWithMessages(messages);
// Save AI response to memory
await ChatMemoryService.saveMessage(
role: 'assistant',
content: response,
);
// Process response and create RAGResponse
final ragResponse = _createRAGResponse(
query: userQuery,
aiResponse: response,
@@ -43,10 +102,11 @@ class RAGAIService {
}
}
/// Build RAG-enhanced prompt for Ollama
/// Build RAG-enhanced prompt for Ollama with teacher materials
static String _buildRAGPrompt(
String userQuery,
String context,
String materialsContext,
TutorMode mode,
) {
final promptBuilder = StringBuffer();
@@ -63,6 +123,13 @@ class RAGAIService {
);
promptBuilder.writeln('Seja claro, paciente e educativo.\n');
// Add teacher materials (PDFs) if available
if (materialsContext.isNotEmpty) {
promptBuilder.writeln('=== MATERIAL DO PROFESSOR ===');
promptBuilder.writeln(materialsContext);
promptBuilder.writeln('\n=== FIM DO MATERIAL DO PROFESSOR ===\n');
}
// Add context
promptBuilder.writeln('=== CONTEÚDO EDUCACIONAL DISPONÍVEL ===');
promptBuilder.writeln(context);
@@ -113,18 +180,29 @@ class RAGAIService {
return promptBuilder.toString();
}
/// Call Ollama API
static Future<String> _callOllamaAPI(String prompt) async {
/// System message for O GOAT identity (for legacy calls)
static const String _systemMessage = '''Tu és "O GOAT", o Assistente IA oficial do Teach it.
Nunca referes o nome do modelo.
Nunca dizes que és Qwen ou OpenAI.
Respondes sempre como o GOAT.
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.''';
/// Call Ollama API with complete messages array
static Future<String> _callOllamaAPIWithMessages(
List<Map<String, String>> messages,
) async {
try {
Logger.info('Calling Ollama API with model: $_model');
Logger.info('Calling Ollama API with ${messages.length} messages');
final url = Uri.parse(_baseUrl);
final requestBody = {
'model': _model,
'messages': [
{'role': 'user', 'content': prompt},
],
'messages': messages,
'stream': false,
'options': {'temperature': 0.7, 'top_p': 0.9, 'max_tokens': _maxTokens},
};
@@ -153,6 +231,14 @@ class RAGAIService {
}
}
/// Legacy: Call Ollama API with single prompt (for backward compatibility)
static Future<String> _callOllamaAPI(String prompt) async {
return _callOllamaAPIWithMessages([
{'role': 'system', 'content': _systemMessage},
{'role': 'user', 'content': prompt},
]);
}
/// Create RAGResponse from AI response
static RAGResponse _createRAGResponse({
required String query,
@@ -405,4 +491,72 @@ class RAGAIService {
return 'Service test failed: $e';
}
}
/// Simple ask method for chat UI - uses conversation memory, teacher PDFs, and O GOAT identity
static Future<String> ask(String userQuery) async {
Logger.info('USING RAG AI SERVICE');
// PASSO 1 — Criar a lista messages vazia
List<Map<String, String>> messages = [];
// PASSO 2 — ADICIONAR SYSTEM MESSAGE DO GOAT (SEMPRE PRIMEIRO)
messages.add({
'role': 'system',
'content': '''Tu és "O GOAT", o Assistente IA oficial do Teach it.
Nunca referes o nome do modelo.
Nunca dizes que és Qwen ou OpenAI.
Respondes sempre como o GOAT.
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.''',
});
// PASSO 3 — BUSCAR MEMÓRIA DA CONVERSA NA Cloud Firestore
final conversationHistory = await ChatMemoryService.getRecentMessages(limit: 20);
for (final msg in conversationHistory) {
messages.add({
'role': msg['role'] as String,
'content': msg['content'] as String,
});
}
// Log de confirmação de ordem do histórico
if (messages.length > 1) {
Logger.info('History order fixed. First message: ${messages[1]}');
}
// PASSO 4 — BUSCAR PDFs DO PROFESSOR NO Firebase Storage (RAG CHUNK RETRIEVAL)
final pdfContext = await MaterialsRAGService.getRelevantChunks(
userQuery: userQuery,
maxMaterials: 5,
maxChunks: 5,
);
if (pdfContext.isNotEmpty) {
messages.add({
'role': 'system',
'content': pdfContext, // Já vem formatado com [CHUNK 1], [CHUNK 2], etc.
});
}
// PASSO 5 — SÓ AGORA adicionar a pergunta do user
messages.add({
'role': 'user',
'content': userQuery,
});
Logger.info('USING RAG AI SERVICE - Built messages array with ${messages.length} messages');
// Save user message to Firestore
await ChatMemoryService.saveMessage(role: 'user', content: userQuery);
// Call API
final response = await _callOllamaAPIWithMessages(messages);
// Save AI response to memory
await ChatMemoryService.saveMessage(role: 'assistant', content: response);
return response;
}
}