nl2prolog
Русский / English natural language → Prolog facts & queries.
Детерминированный парсер для Q&A и KB building в доменах с нормативной и научной лексикой.
Что это
nl2prolog — не «ещё один NER» и не «семантический парсер». Это детерминированный преобразователь русского (и английского) текста в программу на Prolog: edge/3, attribute/3, node/3, alias/2.
Решает две задачи:
| Use case | Что делает | Пример |
|---|---|---|
| Q&A | NL-вопрос → Prolog-запрос → Agent.stream() |
Кто директор X? → edge(X, 'x', 'директор') |
| KB building | NL-текст → Prolog-факты для графа знаний | X основан в 2015 году. → attribute('x', 'год_основания', '2015') |
Ключевой принцип: парсер отвечает только за синтаксис. Семантика (KB-правила, алиасы, метонимия) — слой выше.
Зачем
Если вам нужно:
- ✅ Воспроизводимый парсинг нормативных и научных текстов (одно и то же на входе → одно и то же на выходе)
- ✅ Аудит — видно, какие правила сработали, какие термины отфильтрованы, почему
- ✅ Прозрачная архитектура — синтаксис, диалог, KB-семантика разделены
- ✅ Расширение под домен без форка — класс-атрибуты для всех списков
- ✅ Детерминированный Q&A поверх PyAgentProlog
Если вам нужно:
- ❌ «Просто спросить LLM» — используйте LLM
- ❌ «Извлечь вообще все сущности» — используйте NER-модели (Natasha NER, spaCy)
- ❌ «Понимать смысл, а не структуру» — используйте semantic parsing / LLM
nl2prolog — про точность и повторяемость там, где цена ошибки высока (нормативка, compliance, научные обзоры).
Установка
# Минимальная (только pymorphy3, EN-fallback)
pip install nl2prolog
# Русский (Natasha — рекомендовано)
pip install "nl2prolog[ru]"
# Русский + PyAgentProlog (исполнение запросов)
pip install "nl2prolog[ru,prolog]"
# Всё: ru + en + prolog + dev
pip install "nl2prolog[full,dev]"
Extra Что даёт Обязательность (core) pymorphy3 — морфология RU ✅ обязательно ru natasha — сегментация, UD, NER для RU ⭐ сильно рекомендовано en spacy + en_core_web_sm опционально prolog PyAgentProlog — исполнение запросов опционально
Без natasha парсер деградирует до pymorphy3-fallback: не собирает compound-сущности, хуже размечает роли. Для production установите nl2prolog[ru].
Проверка:
from nl2prolog import runtime_info
print(runtime_info())
# {'version': '1.0.0', 'natasha': True, 'pymorphy3': True,
# 'spacy_ru': False, 'spacy_en': False, 'prolog_native': True}
Быстрый старт
Q&A — вопрос → Prolog-запрос
from nl2prolog import parse_query
q = parse_query("Кто директор технопарка Саратов?")
print(q)
# edge(X, 'технопарк_саратов', 'директор')
Запрос — это готовая цель для Prolog:
?- edge(X, 'технопарк_саратов', 'директор').
X = 'иванов_иван'.
KB building — текст → факты
from nl2prolog import parse_facts
facts = parse_facts("Технопарк Саратов основан в 2015 году.")
print(facts)
# edge('технопарк_саратов', '2015', 'основать').
# node('технопарк_саратов', _, concept).
Полный API — parse()
Когда нужны и факты, и запрос:
from nl2prolog import TextToProlog
t2p = TextToProlog()
facts, query = t2p.parse(
"Кто директор технопарка Саратов?",
strict=True, # без fallback-субъекта «я»
facts_from_questions=False, # не тащить факты из вопроса в KB
)
print(query) # edge(X, 'технопарк_саратов', 'директор')
print(facts) # node('технопарк_саратов', _, concept). …
Диалоговый слой
DialogTurn — coreference и эллипсис. Парсер stateless по дизайну, состояние живёт здесь.
from nl2prolog import TextToProlog
from nl2prolog.dialog import DialogTurn
t2p = TextToProlog()
dlg = DialogTurn(t2p=t2p)
# Turn 1: обычный запрос — контекст обновляется автоматически
# (после успешного исполнения через dlg.ask)
dlg.last_entity = "иванов_иван"
dlg.last_query = "edge(X, 'технопарк_саратов', 'директор')"
# Turn 2: «А сколько ему лет?» — coreference
q, ok, src = dlg.resolve("А сколько ему лет?")
print(q) # attribute('иванов_иван', 'возраст', X)
print(ok) # True
# Turn 3: «А у Казани?» — эллипсис
dlg.last_query = "attribute('технопарк_москва', 'площадь', X)"
q, ok, src = dlg.resolve("А у Казани?")
print(q) # attribute('технопарк_казань', 'площадь', X)
Без контекста resolve() возвращает ("", False, "") — вызывающий код падает в обычный парсер.
Публичный API
import nl2prolog
# Высокоуровневые удобства
nl2prolog.parse_query(nl, strict=True) # → str (Prolog query)
nl2prolog.parse_facts(text, strict=False) # → str (Prolog facts)
nl2prolog.runtime_info() # → dict
# Класс
nl2prolog.TextToProlog()
.parse(text, **kwargs) # → (facts, query)
.convert(text, mode="graph"|"query"|…) # → str
.convert_answer(text, question_condition) # → str
.extract_conditions(text) # → list[Term]
.extract_query(text) # → list[Term]
# Диалог
nl2prolog.DialogTurn(t2p=…, agent_factory=…)
.ask(g, nl) # → (answer_dict, status)
.answer(g, nl) # → (facts_str, status)
.resolve(nl) # → (query, is_resolved, source)
.yes(g) / .no(g)
.reset() / .reset_context()
# Термы
nl2prolog.Term(functor, args)
nl2prolog.Variable(name)
Параметры parse()
Параметр Default Назначение strict False Не подставлять «я» вместо пропущенного субъекта facts_from_questions True Извлекать факты и из вопросов (для Q&A — False) log_filtered False Заполнить t2p.filtered_atoms (аудит фильтрации) link_components False Добавить alias/2 для UI-группировки lang "auto" "ru" / "en" / автоопределение
Архитектура
nl2prolog разделяет слои — это ключевая идея, дающая обобщаемость.
NL-вопрос
│
├─ dialog.py: coref / ellipsis (состояние диалога)
│ если resolved → готовый query
│
└─ text_to_prolog.py: parse
├─ grammar frames (что/сколько/когда/есть/какие)
├─ UD-syntax (compound, role-triple, wh-handler)
└─ 3 уровня фильтрации
│
▼
edge / attribute / node / alias
│
▼
(ваш слой)
├─ KB-aliases (FIO, аббревиатуры)
├─ KB-rules (located_in, transitive closure)
├─ Query lift (edge → rule invocation)
└─ Agent (PyAgentProlog)
Слой Отвечает за Не отвечает за text_to_prolog синтаксис, грамматические фреймы, compound семантику, правила, диалог dialog.py coreference, эллипсис, контекст извлечение фактов KB (ваш) правила вывода, типизацию, алиасы разбор NL Agent исполнение разбор NL Правило: если знание можно записать как kb.pl-факт или как контекст в dialog.py — оно не должно быть в парсере.
Расширение под домен
Все списки — леммы, а не словоформы. Расширение без форка:
from nl2prolog import TextToProlog
# Научный домен
TextToProlog.ATTR_LABEL_NOUNS |= {
'метрика', 'выборка', 'гипотеза', 'оценка',
}
# Отсечь ложные NER-срабатывания
TextToProlog.NER_ORG_BLACKLIST |= {
'регламент', 'постановление', 'протокол',
}
# Защитить короткие термины от фильтра
TextToProlog.SHORT_PROTECTED |= {
'оки', 'окио', 'мвт', 'гкал',
}
# Убрать adjectival noise — через noise_ru.txt
# (файл в каталоге пакета, одна лемма на строку)
Класс-атрибут Что расширяет NER_ORG_BLACKLIST абстрактные существительные → concept вместо org/person ATTR_LABEL_NOUNS лейблы («наименование», «площадь») → атрибуты, не узлы ATTR_ADJ_WHITELIST Rel-прилагательные, которые парсер сохраняет SHORT_PROTECTED короткие (<5) осмысленные термины (аббревиатуры, единицы) SKIP_LEFTOVER_NOUNS служебные существительные, не идущие в граф GENERIC_ADJ fallback для generic-ADJ, если POS не сработал
Файл noise_ru.txt — расширение ATTR_ADJ_WHITELIST без правки кода:
# noise_ru.txt (леммы)
эмпирический
теоретический
экспериментальный
статистический
Метрики
Эталонный Q&A-корпус (33 пары + 2 диалога, домен «технопарки»):
Метрика Значение Цель Query extraction 32/33 = 97.0% ≥ 70% ✅ Answer accuracy 33/33 = 100.0% ≥ 85% ✅ Dialog turns 6/6 = 100.0% ≥ 50% ✅
По категориям (query ✓):
Категория ✓ who, where, when, how_many, what, which 18/18 yes_no (FIO через KB-alias) 4/4 transitive (KB-rule lift) 2/2 list (grammar frame + lift) 1/1 dialog (coref + ellipsis) 6/6 alias (metonymy) 1/2 Единственный провал — Кто директор технопарка в РФ? → ожидается технопарк_москва. Это metonymy («столичный технопарк»), решается на уровне KB / world knowledge, не в парсере.
Что не делает
Явные ограничения, чтобы не было неожиданностей:
Не решает Почему Куда Coreference вне диалога парсер stateless DialogTurn Метонимия (РФ → москва) world knowledge KB-alias / embeddings Связность графа = 1 NL-парсинг даёт лес компонентов норма для KB OCR-фрагменты (управа из «уп равляющая») pymorphy не отличает от слов pre-processing Семантическое сходство вне NLP-слоя metrics.py / embeddings Многоязычность ≠ RU+EN только RU и EN — Дискурс (эти темы, этот опыт) вне синтаксиса dialog.py / LLM
Разработка
git clone https://github.com/you/nl2prolog.git
cd nl2prolog
pip install -e ".[full,dev]"
# Тесты (быстрые)
pytest -v -m "not slow"
# Полный прогон (включая corpus)
pytest -v
# Линтеры
ruff check src/ tests/
mypy src/nl2prolog/
Структура репозитория
nl2prolog/
├── pyproject.toml
├── README.md
├── LICENSE
├── CHANGELOG.md
├── src/nl2prolog/
│ ├── __init__.py
│ ├── _version.py
│ ├── _terms.py # Term/Variable (fallback без PyAgentProlog)
│ ├── text_to_prolog.py # парсер
│ ├── dialog.py # coreference + ellipsis
│ ├── noise.py # POS-фильтрация
│ ├── noise_ru.txt
│ ├── noise_en.txt
│ └── py.typed
└── tests/
├── conftest.py
├── test_noise.py
├── test_text_to_prolog_smoke.py
├── test_dialog.py
└── data/reg_001.txt
Совместимость
Компонент Версия Python 3.9+ pymorphy3 ≥ 2.0 natasha (опц.) ≥ 1.6 spacy (опц., EN) ≥ 3.7 PyAgentProlog (опц.) любая стабильная
Только Linux/macOS/Windows, Python с поддержкой pymorphy3 (CPython, PyPy ≥ 3.9).
Лицензия
MIT
Ссылки
Issues / вопросы: https://github.com/binom0x/nl2prolog/issues Changelog: CHANGELOG.md PyAgentProlog: https://github.com/binom0x/PyAgentProlog (если используете)
Что дальше
Пакет в состоянии 1.0.0 — стабильный QA-слой. Roadmap: v1.1 — рефакторинг монолита text_to_prolog.py → 9 модулей (~30 мин/модуль) v1.2 — публичный KnowledgeGraph API v1.3 — интеграции: integrations.pyagentprolog, integrations.gradio v2.0 — CLI: nl2prolog facts input.txt, nl2prolog query "…"
См. CHANGELOG.md для деталей.
Metadata
Release files for nl2prolog 1.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| nl2prolog-1.0.2.tar.gz | 64.5 kB | Without provenance | Details for nl2prolog-1.0.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| nl2prolog-1.0.2-py3-none-any.whl | Python 3 | none | any | Details for nl2prolog-1.0.2-py3-none-any.whl |
Total release size: 118.8 kB
Release files / nl2prolog-1.0.2.tar.gz
| Download URL | nl2prolog-1.0.2.tar.gz |
|---|---|
| Size | 64.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b856fec59ff1a39530c0f3669fc36878e33893fa801d1d4394ceccf9c3273ae6
|
|
BLAKE2b-256 checksum How to use checksums |
5853d11f69a880979d35d9708b817f62b29a19f358905648f655a5ed9a053a48
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / nl2prolog-1.0.2-py3-none-any.whl
| Download URL | nl2prolog-1.0.2-py3-none-any.whl |
|---|---|
| Size | 54.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a305b672c7c8f82cdc77fef6d618e481b4d77bdb827c03a018897d550fd98314
|
|
BLAKE2b-256 checksum How to use checksums |
d142cec9799af416723df8fbddcf079080d1e21a5b19e0737521ffd5d8cf788f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Security for nl2prolog 1.0.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.