mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 16:36: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
25 KiB
Markdown
769 lines
25 KiB
Markdown
---
|
|
layout: default
|
|
title: "Extracción de Conocimiento Ontológico - Fase 2, Refactorización"
|
|
parent: "Spanish (Beta)"
|
|
---
|
|
|
|
# Extracción de Conocimiento Ontológico - Fase 2, Refactorización
|
|
|
|
> **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.
|
|
|
|
**Estado**: Borrador
|
|
**Autor**: Sesión de Análisis 2025-12-03
|
|
**Relacionado**: `ontology.md`, `ontorag.md`
|
|
|
|
## Resumen
|
|
|
|
Este documento identifica inconsistencias en el sistema actual de extracción de conocimiento basado en ontologías y propone una refactorización para mejorar el rendimiento de los LLM y reducir la pérdida de información.
|
|
|
|
## Implementación Actual
|
|
|
|
### Cómo Funciona Actualmente
|
|
|
|
1. **Carga de la Ontología** (`ontology_loader.py`)
|
|
Carga el archivo JSON de la ontología con claves como `"fo/Recipe"`, `"fo/Food"`, `"fo/produces"`
|
|
Los ID de las clases incluyen el prefijo del espacio de nombres en la clave.
|
|
Ejemplo de `food.ontology`:
|
|
```json
|
|
"classes": {
|
|
"fo/Recipe": {
|
|
"uri": "http://purl.org/ontology/fo/Recipe",
|
|
"rdfs:comment": "A Recipe is a combination..."
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Construcción del prompt** (`extract.py:299-307`, `ontology-prompt.md`)
|
|
La plantilla recibe diccionarios `classes`, `object_properties`, `datatype_properties`
|
|
La plantilla itera: `{% for class_id, class_def in classes.items() %}`
|
|
El LLM ve: `**fo/Recipe**: A Recipe is a combination...`
|
|
El formato de salida de ejemplo muestra:
|
|
```json
|
|
{"subject": "recipe:cornish-pasty", "predicate": "rdf:type", "object": "Recipe"}
|
|
{"subject": "recipe:cornish-pasty", "predicate": "has_ingredient", "object": "ingredient:flour"}
|
|
```
|
|
|
|
3. **Análisis de la respuesta** (`extract.py:382-428`)
|
|
Espera un array JSON: `[{"subject": "...", "predicate": "...", "object": "..."}]`
|
|
Valida contra un subconjunto de la ontología
|
|
Expande los URIs mediante `expand_uri()` (extract.py:473-521)
|
|
|
|
4. **Expansión de URIs** (`extract.py:473-521`)
|
|
Comprueba si el valor está en el diccionario `ontology_subset.classes`
|
|
Si se encuentra, extrae el URI de la definición de la clase
|
|
Si no se encuentra, construye el URI: `f"https://trustgraph.ai/ontology/{ontology_id}#{value}"`
|
|
|
|
### Ejemplo de flujo de datos
|
|
|
|
**JSON de la ontología → Loader → Prompt:**
|
|
```
|
|
"fo/Recipe" → classes["fo/Recipe"] → LLM sees "**fo/Recipe**"
|
|
```
|
|
|
|
**LLM → Analizador → Salida:**
|
|
```
|
|
"Recipe" → not in classes["fo/Recipe"] → constructs URI → LOSES original URI
|
|
"fo/Recipe" → found in classes → uses original URI → PRESERVES URI
|
|
```
|
|
|
|
## Problemas Identificados
|
|
|
|
### 1. **Ejemplos Inconsistentes en la Instrucción**
|
|
|
|
**Problema**: La plantilla de la instrucción muestra ID de clase con prefijos (`fo/Recipe`) pero la salida de ejemplo utiliza nombres de clase sin prefijos (`Recipe`).
|
|
|
|
**Ubicación**: `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**: El modelo de lenguaje (LLM) recibe señales contradictorias sobre qué formato utilizar.
|
|
|
|
### 2. **Pérdida de información en la expansión de URI**
|
|
|
|
**Problema**: Cuando el LLM devuelve nombres de clase sin prefijo, siguiendo el ejemplo, `expand_uri()` no puede encontrarlos en el diccionario de ontología y construye URI de respaldo, perdiendo los URI originales correctos.
|
|
|
|
**Ubicación**: `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 construido: `https://trustgraph.ai/ontology/food#Recipe`
|
|
Significado semántico perdido, interrumpe la interoperabilidad.
|
|
|
|
### 3. **Formato ambiguo de instancia de entidad**
|
|
|
|
**Problema:** No hay una guía clara sobre el formato de la URI de la instancia de entidad.
|
|
|
|
**Ejemplos en la solicitud:**
|
|
`"recipe:cornish-pasty"` (prefijo similar a un espacio de nombres)
|
|
`"ingredient:flour"` (prefijo diferente)
|
|
|
|
**Comportamiento 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**: El modelo de lenguaje debe adivinar la convención de prefijos sin contexto ontológico.
|
|
|
|
### 4. **Sin Guía de Prefijos de Espacio de Nombres**
|
|
|
|
**Problema**: El archivo JSON de la ontología contiene definiciones de espacios de nombres (líneas 10-25 en food.ontology):
|
|
```json
|
|
"namespaces": {
|
|
"fo": "http://purl.org/ontology/fo/",
|
|
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
...
|
|
}
|
|
```
|
|
|
|
Pero estas líneas nunca se muestran al LLM. El LLM no sabe:
|
|
Qué significa "fo"
|
|
Qué prefijo usar para las entidades
|
|
A qué espacio de nombres se aplica a qué elementos
|
|
|
|
### 5. **Etiquetas No Utilizadas en el Prompt**
|
|
|
|
**Problema**: Cada clase tiene campos `rdfs:label` (por ejemplo, `{"value": "Recipe", "lang": "en-gb"}`), pero la plantilla del prompt no los utiliza.
|
|
|
|
**Actual**: Muestra solo `class_id` y `comment`
|
|
```jinja
|
|
- **{{class_id}}**{% if class_def.comment %}: {{class_def.comment}}{% endif %}
|
|
```
|
|
|
|
**Disponible pero no utilizado**:
|
|
```python
|
|
"rdfs:label": [{"value": "Recipe", "lang": "en-gb"}]
|
|
```
|
|
|
|
**Impacto**: Podría proporcionar nombres legibles por humanos junto con identificadores técnicos.
|
|
|
|
## Soluciones propuestas
|
|
|
|
### Opción A: Normalizar a identificadores sin prefijos
|
|
|
|
**Enfoque**: Eliminar los prefijos de los identificadores de clase antes de mostrarlos al LLM.
|
|
|
|
**Cambios**:
|
|
1. Modificar `build_extraction_variables()` para transformar las claves:
|
|
```python
|
|
classes_for_prompt = {
|
|
k.split('/')[-1]: v # "fo/Recipe" → "Recipe"
|
|
for k, v in ontology_subset.classes.items()
|
|
}
|
|
```
|
|
|
|
2. Actualizar el ejemplo de la instrucción para que coincida (ya utiliza nombres sin prefijos).
|
|
|
|
3. Modificar `expand_uri()` para que gestione ambos 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']
|
|
```
|
|
|
|
**Ventajas:**
|
|
Más limpio, más legible para los humanos.
|
|
Coincide con ejemplos de prompts existentes.
|
|
Los LLM funcionan mejor con tokens más simples.
|
|
|
|
**Desventajas:**
|
|
Colisiones de nombres de clase si múltiples ontologías tienen el mismo nombre de clase.
|
|
Pierde la información del espacio de nombres.
|
|
Requiere lógica de respaldo para las búsquedas.
|
|
|
|
### Opción B: Utilizar IDs con Prefijos Completos de Forma Consistente
|
|
|
|
**Enfoque:** Actualizar los ejemplos para utilizar IDs con prefijos que coincidan con lo que se muestra en la lista de clases.
|
|
|
|
**Cambios:**
|
|
1. Actualizar el ejemplo del 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. Agregar una explicación del espacio de nombres a la instrucción:
|
|
```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. Mantener `expand_uri()` tal cual (funciona correctamente cuando se encuentran coincidencias).
|
|
|
|
**Ventajas**:
|
|
Consistencia entre entrada y salida.
|
|
Sin pérdida de información.
|
|
Preserva la semántica del espacio de nombres.
|
|
Funciona con múltiples ontologías.
|
|
|
|
**Desventajas**:
|
|
Tokens más verbosos para el LLM.
|
|
Requiere que el LLM rastree los prefijos.
|
|
|
|
### Opción C: Híbrida: Mostrar tanto la etiqueta como el ID.
|
|
|
|
**Enfoque**: Mejorar el prompt para mostrar tanto las etiquetas legibles por humanos como los ID técnicos.
|
|
|
|
**Cambios**:
|
|
1. Actualizar la plantilla del 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 %}
|
|
```
|
|
|
|
Ejemplo de salida:
|
|
```markdown
|
|
- **fo/Recipe** (label: "Recipe"): A Recipe is a combination...
|
|
```
|
|
|
|
2. Instrucciones de actualización:
|
|
```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
|
|
```
|
|
|
|
**Ventajas**:
|
|
Más claro para los modelos de lenguaje (LLM).
|
|
Preserva toda la información.
|
|
Explícito sobre qué usar.
|
|
|
|
**Desventajas**:
|
|
Requiere un prompt más largo.
|
|
Plantilla más compleja.
|
|
|
|
## Enfoque Implementado
|
|
|
|
**Formato Simplificado de Entidad-Relación-Atributo** - reemplaza completamente el formato basado en triples anterior.
|
|
|
|
El nuevo enfoque se eligió porque:
|
|
|
|
1. **Sin Pérdida de Información**: Los URI originales se conservan correctamente.
|
|
2. **Lógica Más Simple**: No se necesita transformación, las búsquedas directas en diccionarios funcionan.
|
|
3. **Seguridad de Espacios de Nombres**: Maneja múltiples ontologías sin colisiones.
|
|
4. **Corrección Semántica**: Mantiene la semántica RDF/OWL.
|
|
|
|
## Implementación Completada
|
|
|
|
### Lo que se Construyó:
|
|
|
|
1. **Nueva Plantilla de Prompt** (`prompts/ontology-extract-v2.txt`)
|
|
✅ Secciones claras: Tipos de Entidad, Relaciones, Atributos.
|
|
✅ Ejemplo utilizando identificadores de tipo completos (`fo/Recipe`, `fo/has_ingredient`).
|
|
✅ Instrucciones para usar los identificadores exactos del esquema.
|
|
✅ Nuevo formato JSON con matrices de entidades/relaciones/atributos.
|
|
|
|
2. **Normalización de Entidades** (`entity_normalizer.py`)
|
|
✅ `normalize_entity_name()` - Convierte los nombres a un formato seguro para URI.
|
|
✅ `normalize_type_identifier()` - Maneja las barras diagonales en los tipos (`fo/Recipe` → `fo-recipe`).
|
|
✅ `build_entity_uri()` - Crea URI únicos utilizando la tupla (nombre, tipo).
|
|
✅ `EntityRegistry` - Realiza un seguimiento de las entidades para la eliminación de duplicados.
|
|
|
|
3. **Analizador JSON** (`simplified_parser.py`)
|
|
✅ Analiza el nuevo formato: `{entities: [...], relationships: [...], attributes: [...]}`
|
|
✅ Admite nombres de campo en formato kebab-case y snake_case.
|
|
✅ Devuelve clases de datos estructuradas.
|
|
✅ Manejo de errores con registro.
|
|
|
|
4. **Convertidor de Triples** (`triple_converter.py`)
|
|
✅ `convert_entity()` - Genera automáticamente triples de tipo + etiqueta.
|
|
✅ `convert_relationship()` - Conecta los URI de las entidades a través de propiedades.
|
|
✅ `convert_attribute()` - Agrega valores literales.
|
|
✅ Busca URI completos a partir de las definiciones de la ontología.
|
|
|
|
5. **Procesador Principal Actualizado** (`extract.py`)
|
|
✅ Se eliminó el código antiguo de extracción basado en triples.
|
|
✅ Se agregó el método `extract_with_simplified_format()`.
|
|
✅ Ahora utiliza exclusivamente el nuevo formato simplificado.
|
|
✅ Llama al indicador con el ID `extract-with-ontologies-v2`.
|
|
|
|
## Casos de Prueba
|
|
|
|
### Prueba 1: Preservación 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"
|
|
```
|
|
|
|
### Prueba 2: Colisión Multi-Ontología
|
|
```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"
|
|
```
|
|
|
|
### Prueba 3: Formato de Instancia de Entidad
|
|
```python
|
|
# Given prompt with food ontology
|
|
# LLM should create instances like
|
|
{"subject": "recipe:cornish-pasty"} # Namespace-style
|
|
{"subject": "food:beef"} # Consistent prefix
|
|
```
|
|
|
|
## Preguntas Abiertas
|
|
|
|
1. **¿Deben las instancias de entidades usar prefijos de espacio de nombres?**
|
|
Actual: `"recipe:cornish-pasty"` (arbitrario)
|
|
Alternativa: ¿Usar prefijo de ontología `"fo:cornish-pasty"`?
|
|
Alternativa: Sin prefijo, expandir en URI `"cornish-pasty"` → URI completa?
|
|
|
|
2. **¿Cómo manejar el dominio/rango en el prompt?**
|
|
Actualmente muestra: `(Recipe → Food)`
|
|
¿Debería ser: `(fo/Recipe → fo/Food)`?
|
|
|
|
3. **¿Debemos validar las restricciones de dominio/rango?**
|
|
TODO comentario en extract.py:470
|
|
Detectaría más errores pero sería más complejo
|
|
|
|
4. **¿Qué tal las propiedades inversas y las equivalencias?**
|
|
La ontología tiene `owl:inverseOf`, `owl:equivalentClass`
|
|
Actualmente no se utilizan en la extracción
|
|
¿Deberían usarse?
|
|
|
|
## Métricas de Éxito
|
|
|
|
✅ Pérdida de información de URI cero (100% de preservación de los URI originales)
|
|
✅ El formato de salida del LLM coincide con el formato de entrada
|
|
✅ No hay ejemplos ambiguos en el prompt
|
|
✅ Las pruebas pasan con múltiples ontologías
|
|
✅ Calidad de extracción mejorada (medida por el porcentaje de triples válidos)
|
|
|
|
## Enfoque Alternativo: Formato de Extracción Simplificado
|
|
|
|
### Filosofía
|
|
|
|
En lugar de pedirle al LLM que comprenda la semántica de RDF/OWL, pídele que haga lo que hace bien: **encontrar entidades y relaciones en el texto**.
|
|
|
|
Deje que el código se encargue de la construcción de URI, la conversión de RDF y las formalidades de la web semántica.
|
|
|
|
### Ejemplo: Clasificación de Entidades
|
|
|
|
**Texto de entrada:**
|
|
```
|
|
Cornish pasty is a traditional British pastry filled with meat and vegetables.
|
|
```
|
|
|
|
**Esquema de Ontología (mostrado al 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
|
|
```
|
|
|
|
**Lo que el LLM devuelve (JSON simple):**
|
|
```json
|
|
{
|
|
"entities": [
|
|
{
|
|
"entity": "Cornish pasty",
|
|
"type": "Recipe"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**¿Qué código produce (triples 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)
|
|
)
|
|
]
|
|
```
|
|
|
|
### Beneficios
|
|
|
|
1. **El LLM no necesita:**
|
|
Entender la sintaxis de URI
|
|
Inventar prefijos de identificadores (`recipe:`, `ingredient:`)
|
|
Conocer `rdf:type` o `rdfs:label`
|
|
Construir identificadores de la web semántica
|
|
|
|
2. **El LLM solo necesita:**
|
|
Encontrar entidades en el texto
|
|
Mapearlas a clases de ontología
|
|
Extraer relaciones y atributos
|
|
|
|
3. **El código se encarga de:**
|
|
Normalización y construcción de URI
|
|
Generación de triples RDF
|
|
Asignación automática de etiquetas
|
|
Gestión de espacios de nombres
|
|
|
|
### ¿Por qué esto funciona mejor?
|
|
|
|
**Indicación más simple** = menos confusión = menos errores
|
|
**IDs consistentes** = el código controla las reglas de normalización
|
|
**Etiquetas generadas automáticamente** = no faltan triples rdfs:label
|
|
**El LLM se centra en la extracción** = en lo que realmente es bueno
|
|
|
|
### Ejemplo: Relaciones de Entidades
|
|
|
|
**Texto de entrada:**
|
|
```
|
|
Cornish pasty is a traditional British pastry filled with beef and potatoes.
|
|
```
|
|
|
|
**Esquema de Ontología (mostrado al 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)
|
|
```
|
|
|
|
**Lo que el LLM devuelve (JSON simple):**
|
|
```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"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**¿Qué código produce (triples 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)
|
|
)
|
|
]
|
|
```
|
|
|
|
**Puntos clave:**
|
|
El modelo de lenguaje (LLM) devuelve nombres de entidades en lenguaje natural: `"Cornish pasty"`, `"beef"`, `"potatoes"`
|
|
El LLM incluye tipos para disambiguar: `subject-type`, `object-type`
|
|
El LLM utiliza el nombre de la relación del esquema: `"has_ingredient"`
|
|
El código deriva IDs consistentes utilizando (nombre, tipo): `("Cornish pasty", "Recipe")` → `recipe-cornish-pasty`
|
|
El código busca el URI de la relación en la ontología: `fo/has_ingredient` → URI completo
|
|
La misma tupla (nombre, tipo) siempre obtiene el mismo URI (desduplicación)
|
|
|
|
### Ejemplo: Disambiguación del nombre de la entidad
|
|
|
|
**Problema:** El mismo nombre puede referirse a diferentes tipos de entidad.
|
|
|
|
**Caso real:**
|
|
```
|
|
"Cornish pasty" can be:
|
|
- A Recipe (instructions for making it)
|
|
- A Food (the dish itself)
|
|
```
|
|
|
|
**Cómo se gestiona:**
|
|
|
|
El modelo de lenguaje grande (LLM) devuelve 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"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Resolución 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 qué funciona esto?:**
|
|
El tipo se incluye en TODAS las referencias (entidades, relaciones, atributos).
|
|
El código utiliza la tupla `(name, type)` como clave de búsqueda.
|
|
No hay ambigüedad, no hay colisiones.
|
|
|
|
### Ejemplo: Atributos de Entidad
|
|
|
|
**Texto de entrada:**
|
|
```
|
|
This Cornish pasty recipe serves 4-6 people and takes 45 minutes to prepare.
|
|
```
|
|
|
|
**Esquema de Ontología (mostrado al 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)
|
|
```
|
|
|
|
**Lo que el LLM devuelve (JSON simple):**
|
|
```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"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**¿Qué código produce (triples 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!
|
|
)
|
|
]
|
|
```
|
|
|
|
**Puntos Clave:**
|
|
El LLM extrae valores literales: `"4-6 people"`, `"45 minutes"`
|
|
El LLM incluye el tipo de entidad para la desambiguación: `entity-type`
|
|
El LLM utiliza el nombre del atributo del esquema: `"serves"`, `"preparation_time"`
|
|
El código busca el URI del atributo de las propiedades del tipo de datos de la ontología
|
|
**El objeto es literal** (`is_uri=False`), no una referencia de URI
|
|
Los valores permanecen como texto natural, no se necesita normalización
|
|
|
|
**Diferencia con las Relaciones:**
|
|
Relaciones: tanto el sujeto como el objeto son entidades (URIs)
|
|
Atributos: el sujeto es una entidad (URI), el objeto es un valor literal (cadena/número)
|
|
|
|
### Ejemplo Completo: Entidades + Relaciones + Atributos
|
|
|
|
**Texto de Entrada:**
|
|
```
|
|
Cornish pasty is a savory pastry filled with beef and potatoes.
|
|
This recipe serves 4 people.
|
|
```
|
|
|
|
**Lo que el LLM devuelve:**
|
|
```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:** Se generaron 11 triples RDF:
|
|
3 triples de tipo de entidad (rdf:type)
|
|
3 triples de etiqueta de entidad (rdfs:label) - automático
|
|
2 triples de relación (has_ingredient)
|
|
1 triple de atributo (serves)
|
|
|
|
¡Todo proviene de extracciones simples y en lenguaje natural realizadas por el LLM!
|
|
|
|
## Referencias
|
|
|
|
Implementación actual: `trustgraph-flow/trustgraph/extract/kg/ontology/extract.py`
|
|
Plantilla de prompt: `ontology-prompt.md`
|
|
Casos de prueba: `tests/unit/test_extract/test_ontology/`
|
|
Ontología de ejemplo: `e2e/test-data/food.ontology`
|