mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 08:26:21 +02:00
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.
769 lines
24 KiB
Markdown
769 lines
24 KiB
Markdown
---
|
|
layout: default
|
|
title: "Extração de Conhecimento de Ontologias - Fase 2 de Refatoração"
|
|
parent: "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`:
|
|
```json
|
|
"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:
|
|
```json
|
|
{"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`
|
|
|
|
```markdown
|
|
## 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`
|
|
|
|
```python
|
|
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):
|
|
```python
|
|
# 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):
|
|
```json
|
|
"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`
|
|
```jinja
|
|
- **{{class_id}}**{% if class_def.comment %}: {{class_def.comment}}{% endif %}
|
|
```
|
|
|
|
**Disponível, mas não utilizado**:
|
|
```python
|
|
"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:
|
|
```python
|
|
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:
|
|
```python
|
|
# 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):
|
|
```json
|
|
[
|
|
{"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:
|
|
```markdown
|
|
## 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:
|
|
```jinja
|
|
{% 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:
|
|
```markdown
|
|
- **fo/Recipe** (label: "Recipe"): A Recipe is a combination...
|
|
```
|
|
|
|
2. Instruções de atualização:
|
|
```markdown
|
|
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/Recipe` → `fo-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
|
|
```python
|
|
# 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
|
|
```python
|
|
# 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
|
|
```python
|
|
# 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):**
|
|
```markdown
|
|
## 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):**
|
|
```json
|
|
{
|
|
"entities": [
|
|
{
|
|
"entity": "Cornish pasty",
|
|
"type": "Recipe"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**O que o Código Produz (Triplas RDF):**
|
|
```python
|
|
# 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):**
|
|
```markdown
|
|
## 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):**
|
|
```json
|
|
{
|
|
"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):**
|
|
```python
|
|
# 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:
|
|
```json
|
|
{
|
|
"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:**
|
|
```python
|
|
# 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):**
|
|
```markdown
|
|
## 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):**
|
|
```json
|
|
{
|
|
"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):**
|
|
```python
|
|
# 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:**
|
|
```json
|
|
{
|
|
"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`
|