Skip to main content

nl2prolog

Русский / English natural language → Prolog facts & queries.
Детерминированный парсер для Q&A и KB building в доменах с нормативной и научной лексикой.

Python 3.9+ License: MIT QA Benchmark


Что это

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)

Source distribution for nl2prolog 1.0.2
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)

Table of built distributions (wheels) for nl2prolog 1.0.2
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

1.0.2 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page