tei-mcp v0.3:编码TEI,不动源文本一字

我第一次介绍tei-mcp时,目标是阻止AI助手幻觉出TEI标记。模式锚定解决了一部分问题:有了对P5规范的直接、基于工具的访问,模型不必再猜一个元素是什么意思、能带哪些属性。输出能通过验证。
但在TEI编码里,幻觉有两副面孔,模式只抓得住一副。依规范验证,只能告诉您标记是否合式;至于标记包裹的文本,它一言不发。而破坏性更大的幻觉,恰恰就藏在文本本身里。v0.3的头号新功能——区间锁定合成(span-locked composition)——正是专为防范这类幻觉而设计的。
目录
模式抓不住的幻觉
让模型编码一封十六世纪的法文信,您往往会拿到一份看上去无可挑剔的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
每个文件的主名即为其文档ID(letter_001.txt →
letter_001)。
源代码、完整文档以及该不变量的设计说明: github.com/Pantagrueliste/tei-mcp
