Carlexxx
feat: ✨ aBINC 2.2
fb56537
raw
history blame
6.08 kB
# aduc_framework/engineers/neura_link.py
#
# Versão 2.0.0 (Interface Neural com Controle de Memória)
#
# Este componente substitui o antigo 'Composer'. Ele atua como o hub central
# de comunicação com o LLM, abstraindo a formatação de prompts, o envio
# de requisições e o parsing de respostas. Agora inclui um método `reset_memory`
# para dar ao orquestrador controle explícito sobre o ciclo de vida da conversa.
import logging
import json
import re
import yaml
import os
from PIL import Image
from typing import List, Dict, Any, Optional
# --- Importando os managers de LLM de baixo nível e o motor de prompt ---
from ..managers.gemini_manager import gemini_manager_singleton
from ..managers.llama_multimodal_manager import llama_multimodal_manager_singleton
from .prompt_engine import prompt_engine_singleton
logger = logging.getLogger(__name__)
def robust_json_parser(raw_text: str) -> dict:
"""
Analisa um objeto JSON de uma string que pode conter texto extra,
como blocos de código Markdown ou explicações do LLM.
"""
logger.debug(f"Neura_Link(JSON_PARSER): Tentando parsear JSON (primeiros 500 chars):\n---\n{raw_text[:500]}\n---")
match = re.search(r'```json\s*(\{.*?\})\s*```', raw_text, re.DOTALL)
if match:
json_str = match.group(1); logger.debug("JSON explícito encontrado.")
return json.loads(json_str)
try:
start_index = raw_text.find('{'); end_index = raw_text.rfind('}')
if start_index != -1 and end_index != -1 and end_index > start_index:
json_str = raw_text[start_index : end_index + 1]; logger.debug("JSON por delimitadores '{...}' encontrado.")
return json.loads(json_str)
except json.JSONDecodeError: pass
try:
return json.loads(raw_text)
except json.JSONDecodeError as e:
logger.error(f"Falha CRÍTICA no parser de JSON. O LLM retornou um texto inválido. Resposta bruta: \n{raw_text}")
raise ValueError(f"O LLM retornou um formato JSON inválido que não pôde ser corrigido. Erro: {e}")
class NeuraLink:
"""
Neura_Link é a interface de comunicação com a IA. Ele carrega templates,
formata prompts, envia requisições e processa as respostas, com controle
sobre a memória da sessão.
"""
def __init__(self):
logger.info("Neura_Link: Lendo config.yaml para selecionar o LLM Engine...")
with open("config.yaml", 'r') as f: config = yaml.safe_load(f)
self.provider = config.get('specialists', {}).get('llm_engine', {}).get('provider', 'gemini')
if self.provider == 'llama_multimodal':
self.llm_manager = llama_multimodal_manager_singleton
logger.info("Neura_Link: Motor de LLM configurado para usar 'Llama'.")
else:
self.llm_manager = gemini_manager_singleton
logger.info("Neura_Link: Motor de LLM configurado para usar 'Gemini' (padrão).")
prompt_engine_singleton.set_provider(self.provider)
self.task_templates = self._load_task_templates()
logger.info(f"Neura_Link inicializado com {len(self.task_templates)} templates de tarefa.")
def _load_task_templates(self) -> Dict[str, str]:
"""Carrega todos os arquivos .txt do diretório de templates de tarefa."""
templates = {}
try:
template_dir = os.path.join(os.path.dirname(__file__), '..', 'prompts', 'task_templates')
for task_file in os.listdir(template_dir):
if task_file.endswith(".txt"):
task_id = os.path.splitext(task_file)[0]
with open(os.path.join(template_dir, task_file), 'r', encoding='utf-8') as f:
templates[task_id] = f.read()
except FileNotFoundError:
raise FileNotFoundError(f"Diretório de templates de tarefa não encontrado. Verifique a estrutura do projeto.")
return templates
def reset_memory(self):
"""
Envia um comando para o manager de LLM subjacente reiniciar sua
sessão de chat, garantindo um contexto limpo para uma nova operação.
"""
logger.info("Neura_Link: Solicitando reinicialização da memória da sessão de chat.")
# Chama o manager com o gatilho `restart_session` e um prompt vazio.
# O manager está programado para não fazer uma chamada à API se o prompt for vazio.
self.llm_manager.process_turn(prompt_text="", restart_session=True)
def execute_task(self, task_id: str, template_data: Dict[str, Any], image_paths: Optional[List[str]] = None, expected_format: str = "text", use_memory: bool = False) -> Any:
"""
Executa uma única tarefa cognitiva, orquestrando todo o processo.
"""
logger.info(f"Neura_Link: Executando tarefa '{task_id}'...")
generic_template = self.task_templates.get(task_id)
if not generic_template:
raise ValueError(f"Neura_Link: Template para a tarefa '{task_id}' não foi encontrado.")
prompt_content = generic_template
for key, value in template_data.items():
prompt_content = prompt_content.replace(f"{{{key}}}", str(value))
images = [Image.open(p) for p in image_paths] if image_paths else None
final_model_prompt = prompt_engine_singleton.translate(
generic_prompt_content=prompt_content, has_image=bool(images)
)
logger.info(f"Neura_Link: Enviando prompt finalizado para o provedor '{self.provider}'. Use Memory: {use_memory}")
response_raw = self.llm_manager.process_turn(
prompt_text=final_model_prompt,
image_list=images,
use_memory=use_memory
)
logger.info(f"Neura_Link: Resposta bruta recebida do provedor '{self.provider}'.")
if expected_format == "json":
return robust_json_parser(response_raw)
else:
return response_raw.strip()
# --- Instância Singleton ---
neura_link_singleton = NeuraLink()