mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 08:26:21 +02:00
770 lines
32 KiB
Markdown
770 lines
32 KiB
Markdown
|
|
---
|
|||
|
|
layout: default
|
|||
|
|
title: "Извлечение знаний из онтологий - Фаза 2, рефакторинг"
|
|||
|
|
parent: "Russian (Beta)"
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Извлечение знаний из онтологий - Фаза 2, рефакторинг
|
|||
|
|
|
|||
|
|
> **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.
|
|||
|
|
|
|||
|
|
**Статус**: Черновик
|
|||
|
|
**Автор**: Аналитическая сессия 2025-12-03
|
|||
|
|
**Связанные**: `ontology.md`, `ontorag.md`
|
|||
|
|
|
|||
|
|
## Обзор
|
|||
|
|
|
|||
|
|
Этот документ выявляет несоответствия в текущей системе извлечения знаний на основе онтологий и предлагает рефакторинг для повышения производительности LLM и снижения потери информации.
|
|||
|
|
|
|||
|
|
## Текущая реализация
|
|||
|
|
|
|||
|
|
### Как это работает сейчас
|
|||
|
|
|
|||
|
|
1. **Загрузка онтологии** (`ontology_loader.py`)
|
|||
|
|
Загружает JSON-файл онтологии с ключами, такими как `"fo/Recipe"`, `"fo/Food"`, `"fo/produces"`
|
|||
|
|
Идентификаторы классов включают префикс пространства имен в самом ключе
|
|||
|
|
Пример из `food.ontology`:
|
|||
|
|
```json
|
|||
|
|
"classes": {
|
|||
|
|
"fo/Recipe": {
|
|||
|
|
"uri": "http://purl.org/ontology/fo/Recipe",
|
|||
|
|
"rdfs:comment": "A Recipe is a combination..."
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **Построение запроса** (`extract.py:299-307`, `ontology-prompt.md`)
|
|||
|
|
Шаблон получает словари `classes`, `object_properties`, `datatype_properties`
|
|||
|
|
Шаблон выполняет итерацию: `{% for class_id, class_def in classes.items() %}`
|
|||
|
|
LLM видит: `**fo/Recipe**: A Recipe is a combination...`
|
|||
|
|
Пример формата вывода показывает:
|
|||
|
|
```json
|
|||
|
|
{"subject": "recipe:cornish-pasty", "predicate": "rdf:type", "object": "Recipe"}
|
|||
|
|
{"subject": "recipe:cornish-pasty", "predicate": "has_ingredient", "object": "ingredient:flour"}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **Разбор ответа** (`extract.py:382-428`)
|
|||
|
|
Ожидается массив JSON: `[{"subject": "...", "predicate": "...", "object": "..."}]`
|
|||
|
|
Проверка на соответствие подмножеству онтологии
|
|||
|
|
Расширение URI с помощью `expand_uri()` (extract.py:473-521)
|
|||
|
|
|
|||
|
|
4. **Расширение URI** (`extract.py:473-521`)
|
|||
|
|
Проверяет, присутствует ли значение в словаре `ontology_subset.classes`
|
|||
|
|
Если найдено, извлекает URI из определения класса
|
|||
|
|
Если не найдено, создает URI: `f"https://trustgraph.ai/ontology/{ontology_id}#{value}"`
|
|||
|
|
|
|||
|
|
### Пример потока данных
|
|||
|
|
|
|||
|
|
**JSON онтологии → Загрузчик → Запрос:**
|
|||
|
|
```
|
|||
|
|
"fo/Recipe" → classes["fo/Recipe"] → LLM sees "**fo/Recipe**"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Большая языковая модель → Парсер → Вывод:**
|
|||
|
|
```
|
|||
|
|
"Recipe" → not in classes["fo/Recipe"] → constructs URI → LOSES original URI
|
|||
|
|
"fo/Recipe" → found in classes → uses original URI → PRESERVES URI
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Выявленные проблемы
|
|||
|
|
|
|||
|
|
### 1. **Несоответствие примеров в запросе**
|
|||
|
|
|
|||
|
|
**Проблема**: Шаблон запроса показывает идентификаторы классов с префиксами (`fo/Recipe`), но пример вывода использует имена классов без префиксов (`Recipe`).
|
|||
|
|
|
|||
|
|
**Местоположение**: `ontology-prompt.md:5-52`
|
|||
|
|
|
|||
|
|
```markdown
|
|||
|
|
## Ontology Classes:
|
|||
|
|
- **fo/Recipe**: A Recipe is...
|
|||
|
|
|
|||
|
|
## Example Output:
|
|||
|
|
{"subject": "recipe:cornish-pasty", "predicate": "rdf:type", "object": "Recipe"}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Влияние**: LLM получает противоречивые сигналы о том, какой формат использовать.
|
|||
|
|
|
|||
|
|
### 2. **Потеря информации при расширении URI**
|
|||
|
|
|
|||
|
|
**Проблема**: Когда LLM возвращает имена классов без префикса, следуя примеру, `expand_uri()` не может найти их в словаре онтологии и создает резервные URI, теряя исходные правильные URI.
|
|||
|
|
|
|||
|
|
**Местоположение**: `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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Влияние:**
|
|||
|
|
Исходный URI: `http://purl.org/ontology/fo/Recipe`
|
|||
|
|
Сформированный URI: `https://trustgraph.ai/ontology/food#Recipe`
|
|||
|
|
Семантическое значение потеряно, нарушается совместимость.
|
|||
|
|
|
|||
|
|
### 3. **Неоднозначный формат экземпляра сущности**
|
|||
|
|
|
|||
|
|
**Проблема:** Отсутствуют четкие указания относительно формата URI экземпляра сущности.
|
|||
|
|
|
|||
|
|
**Примеры в запросе:**
|
|||
|
|
`"recipe:cornish-pasty"` (префикс, похожий на пространство имен)
|
|||
|
|
`"ingredient:flour"` (другой префикс)
|
|||
|
|
|
|||
|
|
**Фактическое поведение** (extract.py:517-520):
|
|||
|
|
```python
|
|||
|
|
# Treat as entity instance - construct unique URI
|
|||
|
|
normalized = value.replace(" ", "-").lower()
|
|||
|
|
return f"https://trustgraph.ai/{ontology_id}/{normalized}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Влияние**: LLM должна угадать соглашение о префиксах без контекста онтологии.
|
|||
|
|
|
|||
|
|
### 4. **Отсутствие рекомендаций по префиксам пространств имен**
|
|||
|
|
|
|||
|
|
**Проблема**: JSON-файл онтологии содержит определения пространств имен (строки 10-25 в food.ontology):
|
|||
|
|
```json
|
|||
|
|
"namespaces": {
|
|||
|
|
"fo": "http://purl.org/ontology/fo/",
|
|||
|
|
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Но эти данные никогда не передаются в языковую модель. Языковая модель не знает:
|
|||
|
|
Что означает "fo"
|
|||
|
|
Какой префикс использовать для сущностей
|
|||
|
|
К каким элементам относится какое пространство имен
|
|||
|
|
|
|||
|
|
### 5. **Метки, не используемые в запросе**
|
|||
|
|
|
|||
|
|
**Проблема**: У каждого класса есть поля `rdfs:label` (например, `{"value": "Recipe", "lang": "en-gb"}`), но шаблон запроса их не использует.
|
|||
|
|
|
|||
|
|
**Текущая ситуация**: Отображаются только `class_id` и `comment`
|
|||
|
|
```jinja
|
|||
|
|
- **{{class_id}}**{% if class_def.comment %}: {{class_def.comment}}{% endif %}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Доступно, но не используется**:
|
|||
|
|
```python
|
|||
|
|
"rdfs:label": [{"value": "Recipe", "lang": "en-gb"}]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Влияние**: Может предоставить удобочитаемые имена наряду с техническими идентификаторами.
|
|||
|
|
|
|||
|
|
## Предлагаемые решения
|
|||
|
|
|
|||
|
|
### Вариант A: Нормализация до идентификаторов без префиксов
|
|||
|
|
|
|||
|
|
**Подход**: Удалять префиксы из идентификаторов классов перед отображением LLM.
|
|||
|
|
|
|||
|
|
**Изменения**:
|
|||
|
|
1. Изменить `build_extraction_variables()` для преобразования ключей:
|
|||
|
|
```python
|
|||
|
|
classes_for_prompt = {
|
|||
|
|
k.split('/')[-1]: v # "fo/Recipe" → "Recipe"
|
|||
|
|
for k, v in ontology_subset.classes.items()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. Обновить пример запроса, чтобы он соответствовал (уже использует имена без префиксов).
|
|||
|
|
|
|||
|
|
3. Изменить `expand_uri()` для обработки обоих форматов:
|
|||
|
|
```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']
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Преимущества:**
|
|||
|
|
Более понятный и читаемый для человека.
|
|||
|
|
Соответствует существующим примерам запросов.
|
|||
|
|
Большие языковые модели (LLM) лучше работают с более простыми токенами.
|
|||
|
|
|
|||
|
|
**Недостатки:**
|
|||
|
|
Конфликты имен классов, если несколько онтологий имеют одинаковое имя класса.
|
|||
|
|
Потеря информации о пространстве имен.
|
|||
|
|
Требуется логика обработки исключений для поиска.
|
|||
|
|
|
|||
|
|
### Вариант B: Использовать полные префиксные идентификаторы последовательно
|
|||
|
|
|
|||
|
|
**Подход:** Обновить примеры для использования префиксных идентификаторов, соответствующих тем, которые показаны в списке классов.
|
|||
|
|
|
|||
|
|
**Изменения:**
|
|||
|
|
1. Обновить пример запроса (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. Добавьте объяснение пространства имен в запрос:
|
|||
|
|
```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. Оставьте `expand_uri()` без изменений (это работает правильно, когда найдены совпадения).
|
|||
|
|
|
|||
|
|
**Преимущества:**
|
|||
|
|
Согласованность входных и выходных данных.
|
|||
|
|
Отсутствие потери информации.
|
|||
|
|
Сохраняет семантику пространства имен.
|
|||
|
|
Работает с несколькими онтологиями.
|
|||
|
|
|
|||
|
|
**Недостатки:**
|
|||
|
|
Более многословные токены для LLM.
|
|||
|
|
Требует от LLM отслеживания префиксов.
|
|||
|
|
|
|||
|
|
### Вариант C: Гибридный - Отображать и метку, и идентификатор.
|
|||
|
|
|
|||
|
|
**Подход:** Улучшить запрос, чтобы отображать как читаемые человеком метки, так и технические идентификаторы.
|
|||
|
|
|
|||
|
|
**Изменения:**
|
|||
|
|
1. Обновить шаблон запроса:
|
|||
|
|
```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 %}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Пример вывода:
|
|||
|
|
```markdown
|
|||
|
|
- **fo/Recipe** (label: "Recipe"): A Recipe is a combination...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. Инструкции по обновлению:
|
|||
|
|
```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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Преимущества:**
|
|||
|
|
Наиболее понятный формат для больших языковых моделей (LLM).
|
|||
|
|
Сохраняет всю информацию.
|
|||
|
|
Явно указывает, что использовать.
|
|||
|
|
|
|||
|
|
**Недостатки:**
|
|||
|
|
Более длинный запрос.
|
|||
|
|
Более сложный шаблон.
|
|||
|
|
|
|||
|
|
## Реализованный подход
|
|||
|
|
|
|||
|
|
**Упрощенный формат "Сущность-Отношение-Атрибут"** - полностью заменяет старый формат на основе троек.
|
|||
|
|
|
|||
|
|
Новый подход был выбран, потому что:
|
|||
|
|
|
|||
|
|
1. **Отсутствие потери информации:** Оригинальные URI сохраняются корректно.
|
|||
|
|
2. **Более простая логика:** Не требуется преобразование, прямые запросы к словарям работают.
|
|||
|
|
3. **Безопасность пространств имен:** Обрабатывает несколько онтологий без конфликтов.
|
|||
|
|
4. **Семантическая корректность:** Сохраняет семантику RDF/OWL.
|
|||
|
|
|
|||
|
|
## Реализация завершена
|
|||
|
|
|
|||
|
|
### Что было создано:
|
|||
|
|
|
|||
|
|
1. **Новый шаблон запроса** (`prompts/ontology-extract-v2.txt`)
|
|||
|
|
✅ Четкие разделы: Типы сущностей, Отношения, Атрибуты.
|
|||
|
|
✅ Пример использования полных идентификаторов типов (`fo/Recipe`, `fo/has_ingredient`).
|
|||
|
|
✅ Инструкции по использованию точных идентификаторов из схемы.
|
|||
|
|
✅ Новый формат JSON с массивами сущностей/отношений/атрибутов.
|
|||
|
|
|
|||
|
|
2. **Нормализация сущностей** (`entity_normalizer.py`)
|
|||
|
|
✅ `normalize_entity_name()` - Преобразует имена в формат, безопасный для URI.
|
|||
|
|
✅ `normalize_type_identifier()` - Обрабатывает слеши в типах (`fo/Recipe` → `fo-recipe`).
|
|||
|
|
✅ `build_entity_uri()` - Создает уникальные URI, используя кортеж (имя, тип).
|
|||
|
|
✅ `EntityRegistry` - Отслеживает сущности для исключения дубликатов.
|
|||
|
|
|
|||
|
|
3. **JSON-парсер** (`simplified_parser.py`)
|
|||
|
|
✅ Парсит новый формат: `{entities: [...], relationships: [...], attributes: [...]}`
|
|||
|
|
✅ Поддерживает имена полей в формате kebab-case и snake_case.
|
|||
|
|
✅ Возвращает структурированные классы данных.
|
|||
|
|
✅ Корректная обработка ошибок с ведением журнала.
|
|||
|
|
|
|||
|
|
4. **Тройной преобразователь** (`triple_converter.py`)
|
|||
|
|
✅ `convert_entity()` - Автоматически генерирует тройки типа + метки.
|
|||
|
|
✅ `convert_relationship()` - Соединяет URI сущностей через свойства.
|
|||
|
|
✅ `convert_attribute()` - Добавляет литеральные значения.
|
|||
|
|
✅ Выполняет поиск полных URI из определений онтологии.
|
|||
|
|
|
|||
|
|
5. **Обновленный основной процессор** (`extract.py`)
|
|||
|
|
✅ Удален старый код извлечения на основе троек.
|
|||
|
|
✅ Добавлен метод `extract_with_simplified_format()`.
|
|||
|
|
✅ Теперь использует только новый упрощенный формат.
|
|||
|
|
✅ Вызывает запрос с идентификатором `extract-with-ontologies-v2`.
|
|||
|
|
|
|||
|
|
## Тестовые примеры
|
|||
|
|
|
|||
|
|
### Тест 1: Сохранение 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"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Тест 2: Конфликт между несколькими онтологиями
|
|||
|
|
```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"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Тест 3: Формат экземпляра сущности
|
|||
|
|
```python
|
|||
|
|
# Given prompt with food ontology
|
|||
|
|
# LLM should create instances like
|
|||
|
|
{"subject": "recipe:cornish-pasty"} # Namespace-style
|
|||
|
|
{"subject": "food:beef"} # Consistent prefix
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Открытые вопросы
|
|||
|
|
|
|||
|
|
1. **Следует ли экземплярам сущностей использовать префиксы пространств имен?**
|
|||
|
|
Сейчас: `"recipe:cornish-pasty"` (произвольно)
|
|||
|
|
Альтернатива: Использовать префикс онтологии `"fo:cornish-pasty"`?
|
|||
|
|
Альтернатива: Без префикса, расширить в URI `"cornish-pasty"` → полный URI?
|
|||
|
|
|
|||
|
|
2. **Как обрабатывать область определения/область значений в запросе?**
|
|||
|
|
В настоящее время отображается: `(Recipe → Food)`
|
|||
|
|
Должно ли быть: `(fo/Recipe → fo/Food)`?
|
|||
|
|
|
|||
|
|
3. **Следует ли нам проверять ограничения области определения/области значений?**
|
|||
|
|
TODO комментарий в extract.py:470
|
|||
|
|
Это позволило бы выявлять больше ошибок, но было бы сложнее.
|
|||
|
|
|
|||
|
|
4. **Что касается обратных свойств и эквивалентностей?**
|
|||
|
|
В онтологии есть `owl:inverseOf`, `owl:equivalentClass`
|
|||
|
|
В настоящее время не используются при извлечении.
|
|||
|
|
Следует ли их использовать?
|
|||
|
|
|
|||
|
|
## Показатели успеха
|
|||
|
|
|
|||
|
|
✅ Отсутствие потери информации об URI (100% сохранение исходных URI).
|
|||
|
|
✅ Формат вывода LLM соответствует формату входных данных.
|
|||
|
|
✅ Отсутствие неоднозначных примеров в запросе.
|
|||
|
|
✅ Тесты проходят с использованием нескольких онтологий.
|
|||
|
|
✅ Улучшенное качество извлечения (измеряется процентом допустимых троек).
|
|||
|
|
|
|||
|
|
## Альтернативный подход: Упрощенный формат извлечения
|
|||
|
|
|
|||
|
|
### Философия
|
|||
|
|
|
|||
|
|
Вместо того, чтобы просить LLM понимать семантику RDF/OWL, попросите его делать то, что он умеет хорошо: **находить сущности и отношения в тексте**.
|
|||
|
|
|
|||
|
|
Пусть код занимается построением URI, преобразованием в RDF и формальностями семантической паутины.
|
|||
|
|
|
|||
|
|
### Пример: Классификация сущностей
|
|||
|
|
|
|||
|
|
**Исходный текст:**
|
|||
|
|
```
|
|||
|
|
Cornish pasty is a traditional British pastry filled with meat and vegetables.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Схема онтологии (показана 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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Что возвращает большая языковая модель (простой JSON):**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"entities": [
|
|||
|
|
{
|
|||
|
|
"entity": "Cornish pasty",
|
|||
|
|
"type": "Recipe"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Что генерирует код (тройки 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)
|
|||
|
|
)
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Преимущества
|
|||
|
|
|
|||
|
|
1. **LLM (большая языковая модель) не должна:**
|
|||
|
|
Понимать синтаксис URI
|
|||
|
|
Придумывать префиксы идентификаторов (`recipe:`, `ingredient:`)
|
|||
|
|
Знать о `rdf:type` или `rdfs:label`
|
|||
|
|
Конструировать идентификаторы семантической паутины
|
|||
|
|
|
|||
|
|
2. **LLM просто должна:**
|
|||
|
|
Находить сущности в тексте
|
|||
|
|
Сопоставлять их с классами онтологии
|
|||
|
|
Извлекать отношения и атрибуты
|
|||
|
|
|
|||
|
|
3. **Код обрабатывает:**
|
|||
|
|
Нормализацию и построение URI
|
|||
|
|
Генерацию триплетов RDF
|
|||
|
|
Автоматическое присвоение меток
|
|||
|
|
Управление пространствами имен
|
|||
|
|
|
|||
|
|
### Почему это работает лучше
|
|||
|
|
|
|||
|
|
**Более простой запрос** = меньше путаницы = меньше ошибок
|
|||
|
|
**Согласованные идентификаторы** = код контролирует правила нормализации
|
|||
|
|
**Автоматически сгенерированные метки** = нет отсутствующих триплетов rdfs:label
|
|||
|
|
**LLM фокусируется на извлечении** = на том, что она действительно хороша
|
|||
|
|
|
|||
|
|
### Пример: Отношения между сущностями
|
|||
|
|
|
|||
|
|
**Исходный текст:**
|
|||
|
|
```
|
|||
|
|
Cornish pasty is a traditional British pastry filled with beef and potatoes.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Схема онтологии (показана 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)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Что возвращает большая языковая модель (простой JSON):**
|
|||
|
|
```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"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Что генерирует код (тройки 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)
|
|||
|
|
)
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Основные моменты:**
|
|||
|
|
LLM возвращает имена сущностей на естественном языке: `"Cornish pasty"`, `"beef"`, `"potatoes"`
|
|||
|
|
LLM включает типы для устранения неоднозначности: `subject-type`, `object-type`
|
|||
|
|
LLM использует имя отношения из схемы: `"has_ingredient"`
|
|||
|
|
Код генерирует согласованные идентификаторы, используя (имя, тип): `("Cornish pasty", "Recipe")` → `recipe-cornish-pasty`
|
|||
|
|
Код ищет URI отношения в онтологии: `fo/has_ingredient` → полный URI
|
|||
|
|
Одна и та же (имя, тип) всегда получает один и тот же URI (дедупликация)
|
|||
|
|
|
|||
|
|
### Пример: Разрешение неоднозначности имени сущности
|
|||
|
|
|
|||
|
|
**Проблема:** Одно и то же имя может относиться к разным типам сущностей.
|
|||
|
|
|
|||
|
|
**Реальный пример:**
|
|||
|
|
```
|
|||
|
|
"Cornish pasty" can be:
|
|||
|
|
- A Recipe (instructions for making it)
|
|||
|
|
- A Food (the dish itself)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Как это обрабатывается:**
|
|||
|
|
|
|||
|
|
LLM возвращает оба элемента как отдельные сущности:
|
|||
|
|
```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"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Разрешение кода:**
|
|||
|
|
```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
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Почему это работает:**
|
|||
|
|
Тип включен во ВСЕ ссылки (сущности, отношения, атрибуты).
|
|||
|
|
Код использует кортеж `(name, type)` в качестве ключа поиска.
|
|||
|
|
Отсутствие неоднозначности, отсутствие конфликтов.
|
|||
|
|
|
|||
|
|
### Пример: Атрибуты сущности
|
|||
|
|
|
|||
|
|
**Исходный текст:**
|
|||
|
|
```
|
|||
|
|
This Cornish pasty recipe serves 4-6 people and takes 45 minutes to prepare.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Схема онтологии (показана 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)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Что возвращает большая языковая модель (простой JSON):**
|
|||
|
|
```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"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Что генерирует код (тройки 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!
|
|||
|
|
)
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Основные моменты:**
|
|||
|
|
LLM извлекает строковые значения: `"4-6 people"`, `"45 minutes"`
|
|||
|
|
LLM включает тип сущности для устранения неоднозначности: `entity-type`
|
|||
|
|
LLM использует имя атрибута из схемы: `"serves"`, `"preparation_time"`
|
|||
|
|
Код ищет URI атрибута из свойств типа данных онтологии
|
|||
|
|
**Объект является строковым значением** (`is_uri=False`), а не ссылкой URI
|
|||
|
|
Значения остаются в виде обычного текста, нормализация не требуется
|
|||
|
|
|
|||
|
|
**Различия с отношениями:**
|
|||
|
|
Отношения: и субъект, и объект являются сущностями (URI)
|
|||
|
|
Атрибуты: субъект является сущностью (URI), объект является строковым значением (строка/число)
|
|||
|
|
|
|||
|
|
### Полный пример: Сущности + Отношения + Атрибуты
|
|||
|
|
|
|||
|
|
**Исходный текст:**
|
|||
|
|
```
|
|||
|
|
Cornish pasty is a savory pastry filled with beef and potatoes.
|
|||
|
|
This recipe serves 4 people.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Что возвращает большая языковая модель:**
|
|||
|
|
```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"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Результат:** Сгенерировано 11 тройных наборов RDF:
|
|||
|
|
3 тройных набора, определяющих тип сущности (rdf:type)
|
|||
|
|
3 тройных набора, определяющих метку сущности (rdfs:label) - автоматически
|
|||
|
|
2 тройных набора, описывающих отношения (has_ingredient)
|
|||
|
|
1 тройной набор, описывающий атрибут (serves)
|
|||
|
|
|
|||
|
|
Все это получено из простых, естественных текстовых извлечений с помощью LLM!
|
|||
|
|
|
|||
|
|
## Ссылки
|
|||
|
|
|
|||
|
|
Текущая реализация: `trustgraph-flow/trustgraph/extract/kg/ontology/extract.py`
|
|||
|
|
Шаблон запроса: `ontology-prompt.md`
|
|||
|
|
Тестовые примеры: `tests/unit/test_extract/test_ontology/`
|
|||
|
|
Пример онтологии: `e2e/test-data/food.ontology`
|