# aduc_framework/engineers/prompt_engine.py # # Copyright (C) August 4, 2025 Carlos Rodrigues dos Santos # # Versão 3.0.0 (Provider-Aware Prompt Translator) # # O PromptEngine atua como o tradutor entre a lógica de tarefa agnóstica # do Composer e o formato de prompt específico exigido por um LLM. # Esta versão é ciente do provedor (Llama, Gemini, etc.) e aplica os # templates de formatação apenas quando necessário (ex: para o Llama), # usando um prompt genérico direto para outros (ex: Gemini). import logging from pathlib import Path from typing import Dict logger = logging.getLogger(__name__) class PromptEngine: """ O PromptEngine traduz prompts genéricos para o formato específico do modelo de linguagem alvo. Ele seleciona o template correto com base no provedor de LLM ativo e na presença de imagens na requisição. """ def __init__(self): """ Inicializa o PromptEngine em um estado "em espera". O provedor e os templates são carregados posteriormente pelo Composer. """ self.provider: str | None = None self.model_map_name: str = "llama_3_2_vision" # Padrão para Llama self.template_with_image: str | None = None self.template_text_only: str | None = None logger.info("PromptEngine inicializado (aguardando configuração do provedor pelo Composer).") def _load_model_template(self, map_name: str, template_file: str) -> str: """ Carrega um arquivo de template de um mapa de modelo específico. """ map_path = Path(__file__).resolve().parent.parent / "prompts" / "model_maps" / map_name / template_file if not map_path.is_file(): raise FileNotFoundError(f"Template de modelo '{template_file}' não encontrado no mapa '{map_name}' em: {map_path}") with open(map_path, 'r', encoding='utf-8') as f: return f.read() def set_provider(self, provider: str): """ Configura o provedor de LLM ativo (ex: 'llama_multimodal', 'gemini') e carrega os templates de formatação específicos, se necessário. Este método é chamado pelo Composer durante sua inicialização. """ if self.provider == provider: return # Evita recarregamentos desnecessários self.provider = provider logger.info(f"PromptEngine: Provedor de LLM definido como '{self.provider}'.") # Carrega os templates específicos do Llama apenas se ele for o provedor. # Para o Gemini, nenhum template é necessário, então os atributos permanecem None. if self.provider == 'llama_multimodal': logger.info(f"Carregando templates para o mapa de modelo '{self.model_map_name}'...") self.template_with_image = self._load_model_template(self.model_map_name, "image_template.txt") self.template_text_only = self._load_model_template(self.model_map_name, "text_template.txt") else: self.template_with_image = None self.template_text_only = None def translate(self, generic_prompt_content: str, has_image: bool) -> str: """ Envolve o conteúdo do prompt genérico com o template específico do modelo, mas somente se o provedor ativo (Llama) exigir. Para provedores como o Gemini, retorna o prompt genérico sem modificação. Args: generic_prompt_content (str): O conteúdo do prompt agnóstico gerado pelo Composer. has_image (bool): Sinaliza se a tarefa atual inclui um contexto visual. Returns: str: O prompt final, formatado e pronto para ser enviado ao LLM. """ if self.provider is None: raise RuntimeError("PromptEngine: O provedor não foi definido. Chame set_provider() antes de usar.") # --- LÓGICA CONDICIONAL --- # Se o provedor for Gemini, ele usa o "prompt padrão genérico" sem modificação. if self.provider == 'gemini': logger.debug("PROMPT ENGINE (Gemini): Retornando prompt genérico sem aplicar template.") return generic_prompt_content # A lógica original do Llama é executada para o provedor padrão ('llama_multimodal') logger.debug(f"--- PROMPT ENGINE ({self.provider}): INICIANDO TRADUÇÃO (Imagem: {has_image}) ---") template = self.template_with_image if has_image else self.template_text_only if not template: raise RuntimeError(f"PromptEngine: Template para o provedor '{self.provider}' não foi carregado corretamente.") try: # Insere o conteúdo genérico dentro do template específico do modelo final_prompt = template.format(generic_prompt_content=generic_prompt_content) logger.debug(f"--- PROMPT ENGINE ({self.provider}): TRADUÇÃO CONCLUÍDA ---") return final_prompt except KeyError as e: logger.error(f"PROMPT ENGINE: Erro de chave durante a tradução! Chave não encontrada: {e}", exc_info=True) logger.error("Verifique se o template do modelo contém apenas o placeholder '{generic_prompt_content}'.") raise e # --- Instância Singleton --- # A instância é criada vazia e será configurada dinamicamente pelo Composer. prompt_engine_singleton = PromptEngine()