| """ |
| CAMADA L1 — Tábua de Conceitos (Aristóteles: Categorias) |
| ========================================================= |
| Mapeia cada termo do prompt a relações semânticas fixas: |
| - Sinonímia : mesma denotação |
| - Antonímia : oposição semântica direta |
| - Hiponímia : relação específico → geral |
| - Homonímia : mesma forma, sentidos distintos |
| - Paronímia : semelhança formal, sentidos distintos |
| |
| As relações são BINÁRIAS nesta camada — elimina a necessidade de |
| defuzzificação posterior na camada L3. |
| """ |
|
|
| from __future__ import annotations |
| from dataclasses import dataclass, field |
| from typing import Dict, List, Optional |
| import re |
| import json |
| import os |
| from knowledge_base import get_domain_knowledge_base |
|
|
|
|
| @dataclass |
| class ConceptNode: |
| """Um conceito na tábua, com todas as suas relações.""" |
| term: str |
| definition: str = "" |
| synonyms: List[str] = field(default_factory=list) |
| antonyms: List[str] = field(default_factory=list) |
| hyponyms: List[str] = field(default_factory=list) |
| hypernyms: List[str] = field(default_factory=list) |
| homonyms: Dict[str, str] = field(default_factory=dict) |
| paronyms: List[str] = field(default_factory=list) |
| domain: str = "geral" |
| application_context: str = "" |
| canonical_source: str = "" |
| canonical_context: Dict[str, str] = field(default_factory=dict) |
|
|
|
|
| class ConceptTable: |
| """ |
| Tábua de conceitos fixos. Em produção seria alimentada por um |
| dicionário / ontologia formal (WordNet-PT, OpenWordNet-PT, etc.). |
| Aqui usamos um conjunto seminal suficiente para demonstrar todas |
| as camadas do modelo. |
| """ |
|
|
| def __init__(self) -> None: |
| self._table: Dict[str, ConceptNode] = {} |
| |
| self._build_seed_table() |
| |
| self._load_external_concepts() |
|
|
| |
| |
| |
|
|
| def get(self, term: str) -> Optional[ConceptNode]: |
| return self._table.get(self._normalize(term)) |
|
|
| def extract_concepts(self, text: str, llm_context: Optional[str] = None, domain: str = "geral", config: Optional[Dict] = None) -> List[ConceptNode]: |
| """Extrai e retorna os nós de todos os termos encontrados no texto.""" |
| tokens = re.findall(r"[a-záàãâéêíóôõúüçA-ZÁÀÃÂÉÊÍÓÔÕÚÜÇ]+", text) |
| seen, result = set(), [] |
| for tok in tokens: |
| key = self._normalize(tok) |
| if key not in seen: |
| node = self._table.get(key) |
| if node: |
| seen.add(key) |
| result.append(self._clone_node(node)) |
|
|
| if result: |
| self._enrich_concepts_with_application_context(result, text, llm_context, domain, config) |
| combined_text = f"{llm_context.strip()} {text}" if llm_context else text |
| result = [ |
| node for node in result |
| if LogicLMSymbolicSolver.is_context_compatible(node, combined_text) |
| ] |
| return result |
|
|
| def add(self, node: ConceptNode) -> None: |
| self._table[self._normalize(node.term)] = node |
|
|
| def relation_type(self, term_a: str, term_b: str) -> str: |
| """Retorna o tipo de relação semântica entre dois termos.""" |
| a = self._normalize(term_a) |
| b = self._normalize(term_b) |
| node_a = self._table.get(a) |
| if not node_a: |
| return "desconhecida" |
| if b in [self._normalize(s) for s in node_a.synonyms]: |
| return "sinonímia" |
| if b in [self._normalize(s) for s in node_a.antonyms]: |
| return "antonímia" |
| if b in [self._normalize(s) for s in node_a.hyponyms]: |
| return "hiponímia" |
| if b in [self._normalize(s) for s in node_a.hypernyms]: |
| return "hiperonímia" |
| if b in [self._normalize(s) for s in node_a.paronyms]: |
| return "paronímia" |
| if b in [self._normalize(k) for k in node_a.homonyms]: |
| return "homonímia" |
| return "sem_relação_direta" |
|
|
| |
| |
| |
|
|
| def _clone_node(self, node: ConceptNode) -> ConceptNode: |
| return ConceptNode( |
| term=node.term, |
| definition=node.definition, |
| synonyms=list(node.synonyms), |
| antonyms=list(node.antonyms), |
| hyponyms=list(node.hyponyms), |
| hypernyms=list(node.hypernyms), |
| homonyms=dict(node.homonyms), |
| paronyms=list(node.paronyms), |
| domain=node.domain, |
| application_context="", |
| canonical_source=node.canonical_source, |
| canonical_context=dict(node.canonical_context), |
| ) |
|
|
| def _enrich_concepts_with_application_context( |
| self, |
| concepts: List[ConceptNode], |
| prompt: str, |
| llm_context: Optional[str] = None, |
| domain: str = "geral", |
| config: Optional[Dict] = None, |
| ) -> None: |
| """Aplica o solver simbólico Logic-LM para adicionar contexto de uso aos conceitos.""" |
| LogicLMSymbolicSolver.enrich(concepts, prompt, llm_context, domain, config) |
|
|
| def _build_seed_table(self) -> None: |
| entries = [ |
| ConceptNode( |
| term="quente", |
| definition="Que possui temperatura alta.", |
| synonyms=["aquecido", "cálido", "morno", "tépido"], |
| antonyms=["frio", "gelado", "fresco"], |
| hypernyms=["temperatura"], |
| hyponyms=["escaldante", "ardente"], |
| domain="físico", |
| canonical_source="Newton - Philosophiae Naturalis Principia Mathematica - Livro I", |
| ), |
| ConceptNode( |
| term="frio", |
| definition="Que possui temperatura baixa.", |
| synonyms=["gelado", "fresco", "frígido"], |
| antonyms=["quente", "aquecido", "cálido"], |
| hypernyms=["temperatura"], |
| hyponyms=["congelado", "glacial"], |
| domain="físico", |
| canonical_source="Newton - Philosophiae Naturalis Principia Mathematica - Livro I", |
| ), |
| ConceptNode( |
| term="morno", |
| definition="Entre quente e frio; tépido.", |
| synonyms=["tépido", "ameno"], |
| antonyms=["escaldante", "glacial"], |
| hypernyms=["temperatura", "quente", "frio"], |
| hyponyms=[], |
| domain="físico", |
| canonical_source="Galen - De Temperamentis - Seção 3", |
| ), |
| ConceptNode( |
| term="temperatura", |
| definition="Grandeza física que mede o grau de calor de um corpo.", |
| synonyms=["calor", "grau"], |
| antonyms=[], |
| hypernyms=["grandeza_física"], |
| hyponyms=["quente", "frio", "morno"], |
| domain="físico", |
| canonical_source="Galileu - Discorsi e Dimostrazioni Matematiche - Seção 2", |
| ), |
| ConceptNode( |
| term="água", |
| definition="Substância H2O, geralmente em estado líquido.", |
| synonyms=["H2O", "líquido"], |
| antonyms=[], |
| hypernyms=["substância", "fluido"], |
| hyponyms=["vapor", "gelo"], |
| domain="físico", |
| canonical_source="Newton - Opticks - Definição 19", |
| ), |
| ConceptNode( |
| term="verdadeiro", |
| definition="Que está de acordo com os fatos ou a realidade.", |
| synonyms=["correto", "real", "factual"], |
| antonyms=["falso", "incorreto", "fictício"], |
| hypernyms=["valor_lógico"], |
| domain="lógica", |
| canonical_source="Aristóteles - Metafísica - Livro Gamma", |
| canonical_context={ |
| "lógica_clássica": "Aristóteles - Metafísica: valor de verdade binário, NÃO lógica paraconsistente", |
| "epistemologia": "Platão - Teeteto: correspondência com realidade, NÃO coerência pura" |
| } |
| ), |
| ConceptNode( |
| term="falso", |
| definition="Que não corresponde aos fatos ou à realidade.", |
| synonyms=["incorreto", "errado", "fictício"], |
| antonyms=["verdadeiro", "correto", "real"], |
| hypernyms=["valor_lógico"], |
| domain="lógica", |
| canonical_source="Aristóteles - Metafísica - Livro Gamma", |
| canonical_context={ |
| "lógica_clássica": "Aristóteles - Metafísica: negação do verdadeiro, NÃO dialética hegeliana" |
| } |
| ), |
| ConceptNode( |
| term="banco", |
| definition="Móvel para sentar; instituição financeira; repositório de dados.", |
| synonyms=[], |
| antonyms=[], |
| hypernyms=[], |
| homonyms={ |
| "assento": "móvel para sentar", |
| "financeiro": "instituição financeira", |
| "dados": "repositório de dados", |
| }, |
| domain="geral", |
| ), |
| ConceptNode( |
| term="eminente", |
| definition="Pessoa ilustre ou notável.", |
| synonyms=["ilustre", "notável"], |
| antonyms=[], |
| paronyms=["iminente"], |
| domain="geral", |
| ), |
| ConceptNode( |
| term="iminente", |
| definition="Que está prestes a acontecer.", |
| synonyms=["próximo", "imediato"], |
| antonyms=[], |
| paronyms=["eminente"], |
| domain="geral", |
| ), |
| ConceptNode( |
| term="inteligência", |
| definition="Capacidade de compreender, raciocinar e resolver problemas.", |
| synonyms=["cognição", "raciocínio", "entendimento"], |
| antonyms=["ignorância", "estupidez"], |
| hypernyms=["capacidade_mental"], |
| domain="cognitivo", |
| ), |
| ConceptNode( |
| term="conhecimento", |
| definition="Ato ou efeito de conhecer; saber, ciência, erudição.", |
| synonyms=["saber", "ciência", "erudição"], |
| antonyms=["ignorância", "desconhecimento"], |
| hypernyms=["epistemologia"], |
| domain="filosófico", |
| canonical_context={ |
| "epistemologia": "Platão - Teeteto: justificação verdadeira, NÃO opinião infundada", |
| "kantiano": "Kant - Crítica da Razão Pura: a priori vs a posteriori, NÃO empirismo puro" |
| } |
| ), |
| ConceptNode( |
| term="verdade", |
| definition="Conformidade entre o que se diz e o que é.", |
| synonyms=["veracidade", "factualidade", "realidade"], |
| antonyms=["mentira", "falsidade", "ilusão"], |
| hypernyms=["epistemologia"], |
| domain="filosófico", |
| canonical_context={ |
| "platônico": "Platão - República: ideias eternas, NÃO relativismo", |
| "aristotélico": "Aristóteles - Metafísica: correspondência, NÃO coerência" |
| } |
| ), |
| ConceptNode( |
| term="síntese regulativa", |
| definition="Princípio que orienta o conhecimento sem constituí-lo.", |
| synonyms=["regulativo", "orientador"], |
| antonyms=[], |
| hypernyms=["epistemologia", "kantismo"], |
| domain="filosófico", |
| canonical_source="Kant - Crítica da Razão Pura", |
| canonical_context={ |
| "kantismo": "Kant, CRP: princípio regulativo do conhecimento, NÃO Russell" |
| } |
| ), |
| ] |
| for node in entries: |
| self.add(node) |
|
|
| @staticmethod |
| def _normalize(term: str) -> str: |
| return term.strip().lower() |
|
|
| |
| |
| |
|
|
| def _load_external_concepts(self) -> None: |
| """ |
| Carrega conceitos adicionais de um banco gerado a partir do |
| dicionário em inglês (arquivo JSON se existir). |
| |
| Formato esperado (lista de objetos): |
| { |
| "term": "abacus", |
| "definition": "Frame with beads for calculating...", |
| "synonyms": [], |
| "antonyms": [], |
| "hyponyms": [], |
| "hypernyms": [], |
| "domain": "geral" |
| } |
| """ |
| base_dir = os.path.dirname(__file__) or "." |
| json_path = os.path.join(base_dir, "data", "concepts_en.json") |
| if not os.path.exists(json_path): |
| return |
| try: |
| with open(json_path, "r", encoding="utf-8") as f: |
| items = json.load(f) |
| except Exception: |
| return |
|
|
| for item in items: |
| term = item.get("term") |
| if not term: |
| continue |
| node = ConceptNode( |
| term=term, |
| definition=item.get("definition", ""), |
| synonyms=item.get("synonyms", []), |
| antonyms=item.get("antonyms", []), |
| hyponyms=item.get("hyponyms", []), |
| hypernyms=item.get("hypernyms", []), |
| homonyms=item.get("homonyms", {}), |
| paronyms=item.get("paronyms", []), |
| domain=item.get("domain", "geral"), |
| application_context="", |
| canonical_source=item.get("canonical_source", ""), |
| ) |
| key = self._normalize(term) |
| if key not in self._table: |
| self._table[key] = node |
|
|
|
|
| class LogicLMSymbolicSolver: |
| """Processador simbólico inspirado em LLM-Symbolic Solver Logic-LM. |
| |
| Este módulo faz uma pesquisa contextual nos parâmetros de entrada da |
| LLM base da IA Doninha e acrescenta à definição dos conceitos uma nota |
| de aplicação prática para o prompt atual. |
| """ |
|
|
| CONTEXTUAL_KEYWORDS = { |
| "físico": ["temperatura", "calor", "energia", "massa", "volume"], |
| "lógica": ["verdade", "falso", "proposição", "argumento", "inferencia", "inferência"], |
| "cognitivo": ["raciocínio", "inteligência", "compreender", "resolver", "pensar"], |
| "filosófico": ["verdade", "conhecimento", "epistemologia", "realidade", "ética"], |
| "geral": ["aplicação", "uso", "contexto", "pergunta", "problema"], |
| } |
|
|
| @classmethod |
| def enrich( |
| cls, |
| concepts: List[ConceptNode], |
| prompt: str, |
| llm_context: Optional[str] = None, |
| domain: str = "geral", |
| config: Optional[Dict] = None, |
| ) -> List[ConceptNode]: |
| text = prompt.strip() |
| if llm_context: |
| text = f"{llm_context.strip()} {text}" |
| lower_text = text.lower() |
|
|
| |
| kb = get_domain_knowledge_base(domain, config, query_for_rag=text) |
|
|
| for node in concepts: |
| node.application_context = cls._infer_application_context(node, lower_text, concepts, kb) |
| return concepts |
|
|
| @classmethod |
| def _infer_application_context( |
| cls, |
| node: ConceptNode, |
| text: str, |
| concepts: List[ConceptNode], |
| kb: Dict[str, float], |
| ) -> str: |
| base_context = "" |
| if node.term.lower() in text: |
| if not cls.is_context_compatible(node, text): |
| return "" |
| relation = cls._infer_relation(node, text, concepts) |
| if relation: |
| base_context = relation |
| else: |
| base_context = cls._default_context(node) |
|
|
| |
| relevant_terms = [term for term, score in kb.items() if term.lower() in text.lower() and score > 0.5] |
| if relevant_terms: |
| kb_context = f" Contexto de conhecimento: {', '.join(relevant_terms[:3])}." |
| base_context += kb_context |
|
|
| return base_context |
|
|
| @classmethod |
| def is_context_compatible( |
| cls, |
| node: ConceptNode, |
| text: str, |
| ) -> bool: |
| if not node.canonical_source: |
| return True |
| lower_text = text.lower() |
| if node.term.lower() in lower_text: |
| return True |
| if node.domain: |
| domain_keywords = cls.CONTEXTUAL_KEYWORDS.get(node.domain, []) |
| if any(keyword in lower_text for keyword in domain_keywords): |
| return True |
| canonical_keywords = cls._extract_source_keywords(node.canonical_source) |
| if any(keyword in lower_text for keyword in canonical_keywords): |
| return True |
| if node.application_context and any( |
| part in lower_text for part in cls._tokenize(node.application_context) |
| ): |
| return True |
|
|
| |
| if node.canonical_context: |
| return cls._check_canonical_context_compatibility(node, text) |
| return False |
|
|
| @classmethod |
| def _check_canonical_context_compatibility( |
| cls, |
| node: ConceptNode, |
| text: str, |
| ) -> bool: |
| """Verifica se o contexto canônico do conceito é compatível com o texto atual.""" |
| lower_text = text.lower() |
| for context_key, context_value in node.canonical_context.items(): |
| |
| if "NÃO" in context_value.upper(): |
| |
| not_parts = context_value.upper().split("NÃO")[1:] |
| for not_part in not_parts: |
| prohibited_terms = cls._extract_prohibited_terms(not_part.strip()) |
| if any(term in lower_text for term in prohibited_terms): |
| |
| cls._generate_canonical_alert(node, context_key, context_value, text) |
| return False |
| |
| elif ":" in context_value: |
| required_terms = cls._extract_required_terms(context_value) |
| if any(term in lower_text for term in required_terms): |
| return True |
| return True |
|
|
| @classmethod |
| def _extract_prohibited_terms(cls, not_part: str) -> List[str]: |
| """Extrai termos proibidos de uma parte 'NÃO ...'.""" |
| |
| terms = re.split(r'[,\s]+ou[\s]+|[,;]', not_part) |
| return [term.strip().lower() for term in terms if term.strip()] |
|
|
| @classmethod |
| def _extract_required_terms(cls, context_value: str) -> List[str]: |
| """Extrai termos requeridos do contexto canônico.""" |
| |
| parts = context_value.split(":") |
| if len(parts) > 1: |
| description = parts[1].strip() |
| terms = re.findall(r"[a-záàãâéêíóôõúüçA-ZÁÀÃÂÉÊÍÓÔÕÚÜÇ]+", description) |
| return [term.lower() for term in terms if len(term) > 3] |
| return [] |
|
|
| @classmethod |
| def _generate_canonical_alert( |
| cls, |
| node: ConceptNode, |
| context_key: str, |
| context_value: str, |
| text: str, |
| ) -> None: |
| """Gera um alerta de incompatibilidade canônica para ser passado ao L7.""" |
| |
| |
| if not hasattr(cls, '_canonical_alerts'): |
| cls._canonical_alerts = [] |
| alert = { |
| 'concept': node.term, |
| 'canonical_context': f"{context_key}: {context_value}", |
| 'incompatible_usage': text[:100] + "..." if len(text) > 100 else text, |
| 'alert_type': 'canonical_incompatibility' |
| } |
| cls._canonical_alerts.append(alert) |
|
|
| @classmethod |
| def get_canonical_alerts(cls) -> List[Dict]: |
| """Retorna e limpa os alertas canônicos gerados.""" |
| if not hasattr(cls, '_canonical_alerts'): |
| cls._canonical_alerts = [] |
| alerts = cls._canonical_alerts[:] |
| cls._canonical_alerts.clear() |
| return alerts |
|
|
| @classmethod |
| def _extract_source_keywords(cls, source: str) -> List[str]: |
| return [ |
| token for token in re.findall(r"[a-záàãâéêíóôõúüçA-ZÁÀÃÂÉÊÍÓÔÕÚÜÇ]+", source.lower()) |
| if len(token) > 3 |
| ] |
|
|
| @classmethod |
| def _tokenize(cls, text: str) -> List[str]: |
| return [token for token in re.findall(r"[a-záàãâéêíóôõúüçA-ZÁÀÃÂÉÊÍÓÔÕÚÜÇ]+", text.lower()) if len(token) > 3] |
|
|
| @classmethod |
| def _infer_relation( |
| cls, |
| node: ConceptNode, |
| text: str, |
| concepts: List[ConceptNode], |
| ) -> str: |
| domain_keywords = cls.CONTEXTUAL_KEYWORDS.get(node.domain, []) |
| for keyword in domain_keywords: |
| if keyword in text: |
| return cls._build_context_sentence(node, keyword) |
|
|
| related = cls._related_concepts(node, concepts, text) |
| if related: |
| return cls._build_related_context(node, related) |
|
|
| return "" |
|
|
| @classmethod |
| def _related_concepts( |
| cls, |
| node: ConceptNode, |
| concepts: List[ConceptNode], |
| text: str, |
| ) -> List[str]: |
| related = [] |
| for other in concepts: |
| if other.term == node.term: |
| continue |
| if other.term.lower() in text: |
| related.append(other.term) |
| return related |
|
|
| @classmethod |
| def _build_context_sentence(cls, node: ConceptNode, keyword: str) -> str: |
| return ( |
| f"No contexto da pergunta, '{node.term}' é aplicado como um conceito de {node.domain}" |
| f" relacionado a '{keyword}', indicando como o prompt utiliza seu significado prático." |
| ) |
|
|
| @classmethod |
| def _build_related_context(cls, node: ConceptNode, related: List[str]) -> str: |
| related_terms = ", ".join(related[:3]) |
| return ( |
| f"Neste caso, '{node.term}' aparece em conjunto com {related_terms}," |
| f" o que sugere seu papel prático na análise do prompt." |
| ) |
|
|
| @classmethod |
| def _default_context(cls, node: ConceptNode) -> str: |
| return ( |
| f"No contexto atual, '{node.term}' representa {node.definition.lower()}" |
| f" e serve como um conceito relevante para o problema expresso no prompt." |
| ) |
|
|
| @classmethod |
| def summarize_application_context(cls, concepts: List[ConceptNode]) -> str: |
| parts = [node.application_context for node in concepts if node.application_context] |
| return " ".join(parts) |
|
|
| @staticmethod |
| def _normalize_text(text: str) -> str: |
| return " ".join(text.split()).strip() |