Doninha-Artificial-Inteligence / l1_concept_table.py
0danielfonseca's picture
Upload 38 files
b034037 verified
"""
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) # mais específicos
hypernyms: List[str] = field(default_factory=list) # mais gerais
homonyms: Dict[str, str] = field(default_factory=dict) # sentido → definição
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) # Verificação de atribuição canônica
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] = {}
# Tábua seminal em português
self._build_seed_table()
# Banco de conceitos em inglês aprendido de dicionário externo (se existir)
self._load_external_concepts()
# ------------------------------------------------------------------ #
# API pública #
# ------------------------------------------------------------------ #
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"
# ------------------------------------------------------------------ #
# Construção da tábua seminal #
# ------------------------------------------------------------------ #
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()
# ------------------------------------------------------------------ #
# Carregamento de conceitos externos (ex.: dicionário em inglês) #
# ------------------------------------------------------------------ #
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()
# Carrega KB específico do domínio
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)
# Enriquece com termos relevantes do KB do domínio
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
# Verificação de atribuição canônica
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():
# Verifica se o contexto canônico contém indicações de incompatibilidade
if "NÃO" in context_value.upper():
# Extrai termos proibidos (após "NÃO")
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):
# Incompatível - gera alerta para L7
cls._generate_canonical_alert(node, context_key, context_value, text)
return False
# Verifica se o contexto canônico requer termos específicos
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 # Compatível por padrão se não há restrições específicas
@classmethod
def _extract_prohibited_terms(cls, not_part: str) -> List[str]:
"""Extrai termos proibidos de uma parte 'NÃO ...'."""
# Remove pontuação e divide por vírgulas ou 'ou'
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."""
# Assume formato "Fonte: descrição, termos requeridos"
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."""
# Armazena o alerta em uma variável global ou estrutura compartilhada
# Por simplicidade, vamos usar um dicionário global para alertas
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()