trustgraph/docs/tech-specs/ontology-extract-phase-2.pt.md
Alex Jenkins 8954fa3ad7 Feat: TrustGraph i18n & Documentation Translation Updates (#781)
Native CLI i18n: The TrustGraph CLI has built-in translation support
that dynamically loads language strings. You can test and use
different languages by simply passing the --lang flag (e.g., --lang
es for Spanish, --lang ru for Russian) or by configuring your
environment's LANG variable.

Automated Docs Translations: This PR introduces autonomously
translated Markdown documentation into several target languages,
including Spanish, Swahili, Portuguese, Turkish, Hindi, Hebrew,
Arabic, Simplified Chinese, and Russian.
2026-04-14 12:08:32 +01:00

24 KiB

layout title parent
default Extração de Conhecimento de Ontologias - Fase 2 de Refatoração Portuguese (Beta)

Extração de Conhecimento de Ontologias - Fase 2 de Refatoração

Beta Translation: This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.

Status: Rascunho Autor: Sessão de Análise 2025-12-03 Relacionado: ontology.md, ontorag.md

Visão Geral

Este documento identifica inconsistências no sistema atual de extração de conhecimento baseado em ontologias e propõe uma refatoração para melhorar o desempenho do LLM e reduzir a perda de informações.

Implementação Atual

Como Funciona Atualmente

  1. Carregamento da Ontologia (ontology_loader.py) Carrega o arquivo JSON da ontologia com chaves como "fo/Recipe", "fo/Food", "fo/produces" Os IDs das classes incluem o prefixo do namespace na própria chave Exemplo de food.ontology:

    "classes": {
      "fo/Recipe": {
        "uri": "http://purl.org/ontology/fo/Recipe",
        "rdfs:comment": "A Recipe is a combination..."
      }
    }
    
  2. Construção do Prompt (extract.py:299-307, ontology-prompt.md) O modelo recebe os dicionários classes, object_properties, datatype_properties O modelo itera: {% for class_id, class_def in classes.items() %} O LLM vê: **fo/Recipe**: A Recipe is a combination... O formato de saída de exemplo mostra:

    {"subject": "recipe:cornish-pasty", "predicate": "rdf:type", "object": "Recipe"}
    {"subject": "recipe:cornish-pasty", "predicate": "has_ingredient", "object": "ingredient:flour"}
    
  3. Análise da Resposta (extract.py:382-428) Espera um array JSON: [{"subject": "...", "predicate": "...", "object": "..."}] Valida em relação a um subconjunto da ontologia Expande URIs via expand_uri() (extract.py:473-521)

  4. Expansão de URIs (extract.py:473-521) Verifica se o valor está no dicionário ontology_subset.classes Se encontrado, extrai o URI da definição da classe Se não encontrado, constrói o URI: f"https://trustgraph.ai/ontology/{ontology_id}#{value}"

Exemplo de Fluxo de Dados

JSON da Ontologia → Loader → Prompt:

"fo/Recipe" → classes["fo/Recipe"] → LLM sees "**fo/Recipe**"

LLM → Parser → Output:

"Recipe" → not in classes["fo/Recipe"] → constructs URI → LOSES original URI
"fo/Recipe" → found in classes → uses original URI → PRESERVES URI

Problemas Identificados

1. Exemplos Inconsistentes no Prompt

Problema: O modelo de prompt mostra IDs de classe com prefixos (fo/Recipe), mas a saída de exemplo usa nomes de classe sem prefixo (Recipe).

Localização: ontology-prompt.md:5-52

## Ontology Classes:
- **fo/Recipe**: A Recipe is...

## Example Output:
{"subject": "recipe:cornish-pasty", "predicate": "rdf:type", "object": "Recipe"}

Impacto: O LLM recebe sinais conflitantes sobre qual formato usar.

2. Perda de Informação na Expansão de URIs

Problema: Quando o LLM retorna nomes de classe sem prefixo, seguindo o exemplo, expand_uri() não consegue encontrá-los no dicionário de ontologias e constrói URIs de fallback, perdendo os URIs originais corretos.

Localização: extract.py:494-500

if value in ontology_subset.classes:  # Looks for "Recipe"
    class_def = ontology_subset.classes[value]  # But key is "fo/Recipe"
    if isinstance(class_def, dict) and 'uri' in class_def:
        return class_def['uri']  # Never reached!
return f"https://trustgraph.ai/ontology/{ontology_id}#{value}"  # Fallback

Impacto: URI original: http://purl.org/ontology/fo/Recipe URI construído: https://trustgraph.ai/ontology/food#Recipe Perda de significado semântico, quebra a interoperabilidade

3. Formato Ambíguo de Instância de Entidade

Problema: Não há orientação clara sobre o formato do URI da instância de entidade.

Exemplos no prompt: "recipe:cornish-pasty" (prefixo semelhante a um namespace) "ingredient:flour" (prefixo diferente)

Comportamento real (extract.py:517-520):

# Treat as entity instance - construct unique URI
normalized = value.replace(" ", "-").lower()
return f"https://trustgraph.ai/{ontology_id}/{normalized}"

Impacto: O modelo de linguagem deve adivinhar a convenção de prefixo sem contexto ontológico.

4. Sem Orientação de Prefixo de Namespace

Problema: O JSON da ontologia contém definições de namespace (linha 10-25 em food.ontology):

"namespaces": {
  "fo": "http://purl.org/ontology/fo/",
  "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
  ...
}

Mas estas informações nunca são transmitidas para o LLM. O LLM não sabe: O que "fo" significa Qual prefixo usar para entidades Qual namespace se aplica a quais elementos

5. Rótulos Não Utilizados no Prompt

Problema: Cada classe tem campos rdfs:label (por exemplo, {"value": "Recipe", "lang": "en-gb"}), mas o modelo de prompt não os utiliza.

Atual: Mostra apenas class_id e comment

- **{{class_id}}**{% if class_def.comment %}: {{class_def.comment}}{% endif %}

Disponível, mas não utilizado:

"rdfs:label": [{"value": "Recipe", "lang": "en-gb"}]

Impacto: Poderia fornecer nomes legíveis para humanos, juntamente com IDs técnicos.

Soluções Propostas

Opção A: Normalizar para IDs sem Prefixo

Abordagem: Remover os prefixos dos IDs de classe antes de exibi-los para o LLM.

Alterações:

  1. Modificar build_extraction_variables() para transformar chaves:

    classes_for_prompt = {
        k.split('/')[-1]: v  # "fo/Recipe" → "Recipe"
        for k, v in ontology_subset.classes.items()
    }
    
  2. Atualizar o exemplo de prompt para corresponder (já usa nomes sem prefixo).

  3. Modificar expand_uri() para lidar com ambos os formatos:

    # Try exact match first
    if value in ontology_subset.classes:
        return ontology_subset.classes[value]['uri']
    
    # Try with prefix
    for prefix in ['fo/', 'rdf:', 'rdfs:']:
        prefixed = f"{prefix}{value}"
        if prefixed in ontology_subset.classes:
            return ontology_subset.classes[prefixed]['uri']
    

Prós: Mais limpo, mais legível para humanos Compatível com exemplos de prompts existentes Modelos de linguagem grandes (LLMs) funcionam melhor com tokens mais simples

Contras: Colisões de nomes de classe se múltiplas ontologias tiverem o mesmo nome de classe Perde informações de namespace Requer lógica de fallback para pesquisas

Opção B: Usar IDs com Prefixo Completo Consistentemente

Abordagem: Atualizar exemplos para usar IDs com prefixo correspondentes ao que é mostrado na lista de classes.

Mudanças:

  1. Atualizar exemplo de prompt (ontology-prompt.md:46-52):

    [
      {"subject": "recipe:cornish-pasty", "predicate": "rdf:type", "object": "fo/Recipe"},
      {"subject": "recipe:cornish-pasty", "predicate": "rdfs:label", "object": "Cornish Pasty"},
      {"subject": "recipe:cornish-pasty", "predicate": "fo/produces", "object": "food:cornish-pasty"},
      {"subject": "food:cornish-pasty", "predicate": "rdf:type", "object": "fo/Food"}
    ]
    
  2. Adicionar explicação do namespace ao prompt:

    ## Namespace Prefixes:
    - **fo/**: Food Ontology (http://purl.org/ontology/fo/)
    - **rdf:**: RDF Schema
    - **rdfs:**: RDF Schema
    
    Use these prefixes exactly as shown when referencing classes and properties.
    
  3. Mantenha expand_uri() como está (funciona corretamente quando as correspondências são encontradas).

Prós: Consistência entre entrada e saída. Sem perda de informação. Preserva a semântica do namespace. Funciona com múltiplas ontologias.

Contras: Tokens mais verbosos para o LLM. Requer que o LLM rastreie os prefixos.

Opção C: Híbrida - Mostrar Tanto o Rótulo quanto o ID

Abordagem: Aprimorar o prompt para mostrar tanto os rótulos legíveis por humanos quanto os IDs técnicos.

Alterações:

  1. Atualizar o modelo do prompt:

    {% for class_id, class_def in classes.items() %}
    - **{{class_id}}** (label: "{{class_def.labels[0].value if class_def.labels else class_id}}"){% if class_def.comment %}: {{class_def.comment}}{% endif %}
    {% endfor %}
    

    Exemplo de saída:

    - **fo/Recipe** (label: "Recipe"): A Recipe is a combination...
    
  2. Instruções de atualização:

    When referencing classes:
    - Use the full prefixed ID (e.g., "fo/Recipe") in JSON output
    - The label (e.g., "Recipe") is for human understanding only
    

Prós: Mais claro para LLM Preserva todas as informações Explícito sobre o que usar

Contras: Prompt mais longo Modelo mais complexo

Abordagem Implementada

Formato Simplificado de Entidade-Relacionamento-Atributo - substitui completamente o formato antigo baseado em triplas.

A nova abordagem foi escolhida porque:

  1. Nenhuma Perda de Informação: URIs originais preservados corretamente
  2. Lógica Mais Simples: Nenhuma transformação necessária, pesquisas diretas em dicionários funcionam
  3. Segurança de Namespace: Lida com múltiplas ontologias sem colisões
  4. Correção Semântica: Mantém a semântica RDF/OWL

Implementação Completa

O Que Foi Construído:

  1. Novo Modelo de Prompt (prompts/ontology-extract-v2.txt) Seções claras: Tipos de Entidade, Relacionamentos, Atributos Exemplo usando identificadores de tipo completos (fo/Recipe, fo/has_ingredient) Instruções para usar identificadores exatos do esquema Novo formato JSON com arrays de entidades/relacionamentos/atributos

  2. Normalização de Entidade (entity_normalizer.py) normalize_entity_name() - Converte nomes para formato seguro para URI normalize_type_identifier() - Lida com barras em tipos (fo/Recipefo-recipe) build_entity_uri() - Cria URIs únicos usando a tupla (nome, tipo) EntityRegistry - Rastreia entidades para desduplicação

  3. Analisador JSON (simplified_parser.py) Analisa o novo formato: {entities: [...], relationships: [...], attributes: [...]} Suporta nomes de campos em kebab-case e snake_case Retorna dataclasses estruturadas Tratamento de erros elegante com registro

  4. Conversor de Triplas (triple_converter.py) convert_entity() - Gera automaticamente triplas de tipo + rótulo convert_relationship() - Conecta URIs de entidade via propriedades convert_attribute() - Adiciona valores literais Consulta URIs completos a partir de definições de ontologia

  5. Processador Principal Atualizado (extract.py) Removeu o código antigo de extração baseado em triplas Adicionado método extract_with_simplified_format() Agora usa exclusivamente o novo formato simplificado Chama o prompt com o ID extract-with-ontologies-v2

Casos de Teste

Teste 1: Preservação de URI

# Given ontology class
classes = {"fo/Recipe": {"uri": "http://purl.org/ontology/fo/Recipe", ...}}

# When LLM returns
llm_output = {"subject": "x", "predicate": "rdf:type", "object": "fo/Recipe"}

# Then expanded URI should be
assert expanded == "http://purl.org/ontology/fo/Recipe"
# Not: "https://trustgraph.ai/ontology/food#Recipe"

Teste 2: Colisão Multi-Ontologia

# Given two ontologies
ont1 = {"fo/Recipe": {...}}
ont2 = {"cooking/Recipe": {...}}

# LLM should use full prefix to disambiguate
llm_output = {"object": "fo/Recipe"}  # Not just "Recipe"

Teste 3: Formato de Instância de Entidade

# Given prompt with food ontology
# LLM should create instances like
{"subject": "recipe:cornish-pasty"}  # Namespace-style
{"subject": "food:beef"}              # Consistent prefix

Perguntas Abertas

  1. As instâncias de entidades devem usar prefixos de namespace? Atual: "recipe:cornish-pasty" (arbitrário) Alternativa: Usar o prefixo da ontologia "fo:cornish-pasty"? Alternativa: Sem prefixo, expandir no URI "cornish-pasty" → URI completo?

  2. Como lidar com o domínio/alcance no prompt? Atualmente mostra: (Recipe → Food) Deveria ser: (fo/Recipe → fo/Food)?

  3. Devemos validar as restrições de domínio/alcance? TODO: comentário em extract.py:470 Detectaria mais erros, mas seria mais complexo

  4. O que dizer das propriedades inversas e equivalências? A ontologia tem owl:inverseOf, owl:equivalentClass Não está atualmente sendo usado na extração Deveria estar?

Métricas de Sucesso

Perda zero de informações de URI (100% de preservação dos URIs originais) O formato de saída do LLM corresponde ao formato de entrada Nenhum exemplo ambíguo no prompt Os testes passam com múltiplas ontologias Qualidade de extração aprimorada (medida pela porcentagem de triplas válidas)

Abordagem Alternativa: Formato de Extração Simplificado

Filosofia

Em vez de pedir ao LLM para entender a semântica RDF/OWL, peça para ele fazer o que ele faz de melhor: encontrar entidades e relacionamentos no texto.

Deixe que o código lide com a construção de URIs, a conversão RDF e as formalidades da web semântica.

Exemplo: Classificação de Entidades

Texto de Entrada:

Cornish pasty is a traditional British pastry filled with meat and vegetables.

Esquema de Ontologia (mostrado ao LLM):

## Entity Types:
- Recipe: A recipe is a combination of ingredients and a method
- Food: A food is something that can be eaten
- Ingredient: An ingredient combines a quantity and a food

O que o LLM retorna (JSON simples):

{
  "entities": [
    {
      "entity": "Cornish pasty",
      "type": "Recipe"
    }
  ]
}

O que o Código Produz (Triplas RDF):

# 1. Normalize entity name + type to ID (type prevents collisions)
entity_id = "recipe-cornish-pasty"  # normalize("Cornish pasty", "Recipe")
entity_uri = "https://trustgraph.ai/food/recipe-cornish-pasty"

# Note: Same name, different type = different URI
# "Cornish pasty" (Recipe) → recipe-cornish-pasty
# "Cornish pasty" (Food) → food-cornish-pasty

# 2. Generate triples
triples = [
    # Type triple
    Triple(
        s=Value(value=entity_uri, is_uri=True),
        p=Value(value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type", is_uri=True),
        o=Value(value="http://purl.org/ontology/fo/Recipe", is_uri=True)
    ),
    # Label triple (automatic)
    Triple(
        s=Value(value=entity_uri, is_uri=True),
        p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True),
        o=Value(value="Cornish pasty", is_uri=False)
    )
]

Benefícios

  1. O LLM não precisa de: Entender a sintaxe de URI Inventar prefixos de identificadores (recipe:, ingredient:) Saber sobre rdf:type ou rdfs:label Construir identificadores da web semântica

  2. O LLM precisa apenas de: Encontrar entidades no texto Mapeá-las para classes de ontologia Extrair relacionamentos e atributos

  3. O código lida com: Normalização e construção de URI Geração de triplas RDF Atribuição automática de rótulos Gerenciamento de namespaces

Por que Isso Funciona Melhor

Prompt mais simples = menos confusão = menos erros IDs consistentes = o código controla as regras de normalização Rótulos gerados automaticamente = sem triplas rdfs:label ausentes O LLM se concentra na extração = o que ele realmente faz bem

Exemplo: Relacionamentos de Entidades

Texto de entrada:

Cornish pasty is a traditional British pastry filled with beef and potatoes.

Esquema de Ontologia (mostrado ao LLM):

## Entity Types:
- Recipe: A recipe is a combination of ingredients and a method
- Food: A food is something that can be eaten
- Ingredient: An ingredient combines a quantity and a food

## Relationships:
- has_ingredient: Relates a recipe to an ingredient it uses (Recipe → Ingredient)
- food: Relates an ingredient to the food that is required (Ingredient → Food)

O que o LLM retorna (JSON simples):

{
  "entities": [
    {
      "entity": "Cornish pasty",
      "type": "Recipe"
    },
    {
      "entity": "beef",
      "type": "Food"
    },
    {
      "entity": "potatoes",
      "type": "Food"
    }
  ],
  "relationships": [
    {
      "subject": "Cornish pasty",
      "subject-type": "Recipe",
      "relation": "has_ingredient",
      "object": "beef",
      "object-type": "Food"
    },
    {
      "subject": "Cornish pasty",
      "subject-type": "Recipe",
      "relation": "has_ingredient",
      "object": "potatoes",
      "object-type": "Food"
    }
  ]
}

O que o Código Produz (Triplas RDF):

# Normalize entity names to URIs
cornish_pasty_uri = "https://trustgraph.ai/food/cornish-pasty"
beef_uri = "https://trustgraph.ai/food/beef"
potatoes_uri = "https://trustgraph.ai/food/potatoes"

# Look up relation URI from ontology
has_ingredient_uri = "http://purl.org/ontology/fo/ingredients"  # from fo/has_ingredient

triples = [
    # Entity type triples (as before)
    Triple(s=cornish_pasty_uri, p=rdf_type, o="http://purl.org/ontology/fo/Recipe"),
    Triple(s=cornish_pasty_uri, p=rdfs_label, o="Cornish pasty"),

    Triple(s=beef_uri, p=rdf_type, o="http://purl.org/ontology/fo/Food"),
    Triple(s=beef_uri, p=rdfs_label, o="beef"),

    Triple(s=potatoes_uri, p=rdf_type, o="http://purl.org/ontology/fo/Food"),
    Triple(s=potatoes_uri, p=rdfs_label, o="potatoes"),

    # Relationship triples
    Triple(
        s=Value(value=cornish_pasty_uri, is_uri=True),
        p=Value(value=has_ingredient_uri, is_uri=True),
        o=Value(value=beef_uri, is_uri=True)
    ),
    Triple(
        s=Value(value=cornish_pasty_uri, is_uri=True),
        p=Value(value=has_ingredient_uri, is_uri=True),
        o=Value(value=potatoes_uri, is_uri=True)
    )
]

Pontos-chave: O LLM retorna nomes de entidades em linguagem natural: "Cornish pasty", "beef", "potatoes" O LLM inclui tipos para disambiguar: subject-type, object-type O LLM usa o nome da relação do esquema: "has_ingredient" O código deriva IDs consistentes usando (nome, tipo): ("Cornish pasty", "Recipe")recipe-cornish-pasty O código pesquisa o URI da relação na ontologia: fo/has_ingredient → URI completo A mesma tupla (nome, tipo) sempre obtém o mesmo URI (desduplicação)

Exemplo: Desambiguação de Nomes de Entidades

Problema: O mesmo nome pode se referir a diferentes tipos de entidade.

Caso real:

"Cornish pasty" can be:
- A Recipe (instructions for making it)
- A Food (the dish itself)

Como é tratado:

O LLM retorna ambos como entidades separadas:

{
  "entities": [
    {"entity": "Cornish pasty", "type": "Recipe"},
    {"entity": "Cornish pasty", "type": "Food"}
  ],
  "relationships": [
    {
      "subject": "Cornish pasty",
      "subject-type": "Recipe",
      "relation": "produces",
      "object": "Cornish pasty",
      "object-type": "Food"
    }
  ]
}

Resolução de Código:

# Different types → different URIs
recipe_uri = normalize("Cornish pasty", "Recipe")
# → "https://trustgraph.ai/food/recipe-cornish-pasty"

food_uri = normalize("Cornish pasty", "Food")
# → "https://trustgraph.ai/food/food-cornish-pasty"

# Relationship connects them correctly
triple = Triple(
    s=recipe_uri,  # The Recipe
    p="http://purl.org/ontology/fo/produces",
    o=food_uri     # The Food
)

Por que isso funciona: O tipo está incluído em TODAS as referências (entidades, relacionamentos, atributos). O código usa a tupla (name, type) como chave de pesquisa. Sem ambiguidades, sem colisões.

Exemplo: Atributos de Entidade

Texto de entrada:

This Cornish pasty recipe serves 4-6 people and takes 45 minutes to prepare.

Esquema de Ontologia (mostrado ao LLM):

## Entity Types:
- Recipe: A recipe is a combination of ingredients and a method

## Attributes:
- serves: Indicates what the recipe is intended to serve (Recipe → text)
- preparation_time: Time needed to prepare the recipe (Recipe → text)

O que o LLM retorna (JSON simples):

{
  "entities": [
    {
      "entity": "Cornish pasty recipe",
      "type": "Recipe"
    }
  ],
  "attributes": [
    {
      "entity": "Cornish pasty recipe",
      "entity-type": "Recipe",
      "attribute": "serves",
      "value": "4-6 people"
    },
    {
      "entity": "Cornish pasty recipe",
      "entity-type": "Recipe",
      "attribute": "preparation_time",
      "value": "45 minutes"
    }
  ]
}

O que o Código Produz (Triplas RDF):

# Normalize entity name to URI
recipe_uri = "https://trustgraph.ai/food/cornish-pasty-recipe"

# Look up attribute URIs from ontology
serves_uri = "http://purl.org/ontology/fo/serves"  # from fo/serves
prep_time_uri = "http://purl.org/ontology/fo/preparation_time"  # from fo/preparation_time

triples = [
    # Entity type triple
    Triple(
        s=Value(value=recipe_uri, is_uri=True),
        p=Value(value=rdf_type, is_uri=True),
        o=Value(value="http://purl.org/ontology/fo/Recipe", is_uri=True)
    ),

    # Label triple (automatic)
    Triple(
        s=Value(value=recipe_uri, is_uri=True),
        p=Value(value=rdfs_label, is_uri=True),
        o=Value(value="Cornish pasty recipe", is_uri=False)
    ),

    # Attribute triples (objects are literals, not URIs)
    Triple(
        s=Value(value=recipe_uri, is_uri=True),
        p=Value(value=serves_uri, is_uri=True),
        o=Value(value="4-6 people", is_uri=False)  # Literal value!
    ),
    Triple(
        s=Value(value=recipe_uri, is_uri=True),
        p=Value(value=prep_time_uri, is_uri=True),
        o=Value(value="45 minutes", is_uri=False)  # Literal value!
    )
]

Pontos-chave: O LLM extrai valores literais: "4-6 people", "45 minutes" O LLM inclui o tipo de entidade para desambiguação: entity-type O LLM usa o nome do atributo do esquema: "serves", "preparation_time" O código pesquisa o URI do atributo a partir das propriedades do tipo de dados da ontologia O objeto é literal (is_uri=False), não uma referência de URI Os valores permanecem como texto natural, sem necessidade de normalização

Diferença em relação a Relacionamentos: Relacionamentos: tanto o sujeito quanto o objeto são entidades (URIs) Atributos: o sujeito é uma entidade (URI), o objeto é um valor literal (string/número)

Exemplo Completo: Entidades + Relacionamentos + Atributos

Texto de Entrada:

Cornish pasty is a savory pastry filled with beef and potatoes.
This recipe serves 4 people.

O que o LLM retorna:

{
  "entities": [
    {
      "entity": "Cornish pasty",
      "type": "Recipe"
    },
    {
      "entity": "beef",
      "type": "Food"
    },
    {
      "entity": "potatoes",
      "type": "Food"
    }
  ],
  "relationships": [
    {
      "subject": "Cornish pasty",
      "subject-type": "Recipe",
      "relation": "has_ingredient",
      "object": "beef",
      "object-type": "Food"
    },
    {
      "subject": "Cornish pasty",
      "subject-type": "Recipe",
      "relation": "has_ingredient",
      "object": "potatoes",
      "object-type": "Food"
    }
  ],
  "attributes": [
    {
      "entity": "Cornish pasty",
      "entity-type": "Recipe",
      "attribute": "serves",
      "value": "4 people"
    }
  ]
}

Resultado: 11 triplas RDF geradas: 3 triplas de tipo de entidade (rdf:type) 3 triplas de rótulo de entidade (rdfs:label) - automático 2 triplas de relacionamento (has_ingredient) 1 tripla de atributo (serves)

Tudo isso a partir de extrações simples e em linguagem natural pelo LLM!

Referências

Implementação atual: trustgraph-flow/trustgraph/extract/kg/ontology/extract.py Modelo de prompt: ontology-prompt.md Casos de teste: tests/unit/test_extract/test_ontology/ Ontologia de exemplo: e2e/test-data/food.ontology