tei-mcp v0.3: кодировать в TEI, не переписывая источник

Когда я впервые писал о tei-mcp, цель была одна: отучить ИИ-ассистентов галлюцинировать разметку TEI. Заземление на схему решило часть проблемы: имея прямой, инструментальный доступ к спецификации P5, модели больше не нужно гадать, что означает элемент и какие атрибуты он принимает. Результат проходит валидацию.
Но у галлюцинации в кодировании TEI два лица, и схема ловит лишь одно. Валидация по спецификации говорит, что разметка корректна. О тексте, который эта разметка обёртывает, она не говорит ничего. А именно там — в самом тексте — живут самые разрушительные галлюцинации. Сборка по закреплённым отрезкам (span-locked composition), главная новинка v0.3, придумана специально, чтобы их не допускать.
Содержание
Галлюцинация, которую схема поймать не может
Попросите модель закодировать французское письмо XVI века — и нередко
получите документ TEI безупречного вида. Заголовок заполнен, теги <persName>
расставлены правильно, <dateline> оформлен как положено. Прогоните его через
validate_document — он пройдёт.
А потом сравните основной текст с источником.
mesme превратилось в même. Запятая переехала. luy тихо осовременили до
lui. Фразу, которую трудно было разобрать в рукописи, «исправили» во что-то
более гладкое. Ни одного из этих изменений никто не просил. Ни одно не помечено.
Документ валиден по схеме и тихо неверен.
Для архивного процесса — где закодированный текст становится постоянной записью, на которую опираются последующие читатели, поисковые индексы и цитаты, — это самый важный вид сбоя. Кривой тег раздражает. Осовремененное написание, которого никто не замечает пять лет, — это порча.
Сборка по закреплённым отрезкам
Новый выпуск (v0.3) содержит механизм предотвращения галлюцинаций, нацеленный ровно на этот сбой. Цель конструкции — сделать галлюцинации в основном тексте невозможными по самому устройству, а не просто маловероятными.
Идея проста: модель никогда не набирает основной текст.
Вместо этого процесс выглядит так:
- Модель вызывает
get_source("letter_001")и получает исходный простой текст в виде неизменяемой строки. - Для каждого тега, который она хочет поставить, она вызывает
tag_span("letter_001", start, end, element_path, attrs), регистрируя элемент TEI на диапазоне символов источника. - Закончив, она вызывает
compose("letter_001"). Сервер переплетает записанные теги с исходным простым текстом, отрисовывает итоговый TEI и затем проверяет байт за байтом, что плоское текстовое содержимое полученного документа равно источнику.
Если байты совпадают, документ возвращается. Если нет — если теги модели
каким-то образом подразумевают основной текст, отличающийся от источника хотя бы
на один символ, — compose() выбрасывает исключение вместо того, чтобы вернуть
испорченный документ.
В этом процессе нет пути, на котором модель выдала бы документ TEI с основным
текстом, отличным от источника. Инвариант механический, а не поведенческий.
Вам не нужно верить, что модель не станет галлюцинировать; нужно верить лишь
проверке == между двумя байтовыми строками.
Что это такое — и чем это не является
Сборка по закреплённым отрезкам дополняет заземление на схему, а не заменяет
его. Инструменты заземления (validate_document, lookup_element,
valid_children и остальные из первоначальных шестнадцати) помогают модели
выдавать валидный TEI. Сборка по закреплённым отрезкам гарантирует, что
основной текст внутри этого TEI верен источнику. Пригодный к внедрению процесс
кодирования должен удовлетворять обеим осям, и теперь обе покрывает один сервер.
Но и волшебной таблеткой от всего это не назовёшь. compose() пока не проверяет,
допустимы ли зарегистрированные теги по загруженной ODD-настройке — это следующий
шаг. Записанные теги живут в памяти процесса и не переживают перезапуск. А
исходные файлы должны быть доступны для чтения оттуда, где запущен сервер. Всё
это решаемо; ничто из этого не подрывает основной инвариант.
Почему это важно и за пределами TEI
Этот приём обобщается. Всякий раз, когда модель просят аннотировать, преобразовать или обернуть фрагмент текста — и всякий раз, когда целостность исходного текста важнее, чем способность модели его «улучшить», — годится решение той же формы. Не просите модель перепечатывать текст. Просите её выдать инструкции над текстом, а применять их пусть будет детерминированный сборщик под инвариантом равенства.
Для цифровых изданий в частности это меняет то, что можно ответственно поручить модели. Кодирование вдруг становится задачей, которую можно делегировать, не сверяя вручную каждый результат с источником. Машина идёт скучным путём; редактор проверяет разметку, а не орфографию.
Как обновиться
Если tei-mcp у вас уже установлен:
uvx tei-mcp@latest
Или с нуля:
pip install tei-mcp
Чтобы пользоваться сборкой по закреплённым отрезкам, укажите серверу каталог с исходными текстовыми файлами:
export TEI_MCP_SPAN_SOURCE_ROOT=/path/to/sources
uvx tei-mcp
Имя каждого файла без расширения становится идентификатором документа
(letter_001.txt → letter_001).
Исходный код, полная документация и заметки о конструкции инварианта: github.com/Pantagrueliste/tei-mcp
