使用Doclang构建AI原生文档平台
深入探讨为DocLang——为LLM构建的AI原生标记语言——设计端到端演练场的技术细节。
AI模型价格对比 | AI工具导航 | ONNX模型库 | Vibe Coding教程 | PLC在线仿真器 | Tripo 3D | Meshy AI | ElevenLabs | KlingAI | ArtSpace | Phot.AI | InVideo
随着大型语言模型(LLM)应用的成熟,一个根本性的摩擦点仍然存在:传统文档格式从未为分词器设计过。
- PDF是为精确的视觉打印布局而构建的。
- DOCX是为人类桌面编辑器而构建的。
- HTML充斥着显示标签,结构化元素如表格的代币成本高达5倍。
DocLang是一个由Linux基金会AI与数据(由IBM、NVIDIA、Red Hat、ABBYY和HumanSignal支持)管理的开放文档标准。DocLang将结构化文档清晰地映射到LLM代币,同时在明确的XML标记表示中保留精确的几何形状、语义层次、代码块和公式。
1、什么是Doclang?
摘自Doclang网站。

DocLang是用于非结构化内容的AI原生标记格式——文档、图像、音频和视频 alike。它从底层围绕一个单一原则设计:清晰地映射到LLM代币。这使得DocLang成为使用现代AI读取、写入和推理现实世界内容的最高效方式。
世界的知识存在于为渲染或播放而非理解而设计的格式中——PDF、HTML、Word、LaTeX、MP3、MP4。当你将它们交给AI系统时,你会丢失结构、语义、布局、时间,或同时丢失所有内容。而AI确实流利使用的格式也不够好:Markdown无法描述复杂内容,HTML浪费代币,LaTeX模棱两可,而且它们都不能处理音频或视频。
DocLang修复了这个问题:
- AI原生 — 与LLM分词器1对齐的受控词汇表,保持提示和输出简短且可预测
- 无损 — 在单一表示中保留结构、语义、布局和几何形状
- 富有表现力 — 对表格、公式、代码、图表、嵌套列表、表单和具有视觉基础的多模态内容的一流支持
- 超越文档 — 自然扩展到音频和视频,具有转录、说话者、时间戳、场景和视听基础的原生基元——因此访谈、讲座录音或电影脚本与PDF生活在相同的表示中
- 明确 — 每个标签只有一个工作,因此相同的内容总是以相同的方式序列化
- 开放 — ISO标准,开放开发,由行业领导者管理
如果你使用LLM和VLM构建现实世界内容,DocLang就是你一直缺少的基底。
2、实现
为了演示摄取、转换、验证和查询DocLang的完整生命周期,Bob创建并连接了DocLang演示应用程序。
┌─────────────────────────────────────────────────────────────────┐
│ 浏览器(UI) │
│ ┌───────────┐ ┌───────────┐ ┌──────────┐ ┌─────────────────┐ │
│ │ Markdown │ │ DocLang │ │ 上传 │ │ 询问Ollama │ │
│ │ → DocLang │ │ 编辑器 │ │ 文档 │ │ (文档问答) │ │
│ └─────┬─────┘ └─────┬─────┘ └────┬─────┘ └────────┬────────┘ │
└────────│─────────────│────────────│───────────────────│─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ Flask API (app.py) │
│ │
│ /api/convert/markdown → markdown_to_doclang() │
│ /api/convert/doclang → doclang_to_html() │
│ /api/validate → validate_doclang() │
│ /api/upload/submit → Docling作业已启动(返回job_id) │
│ /api/upload/status/<id>→ 轮询作业 → doclang + 预览 │
│ /api/ask → Ollama /api/generate │
│ /api/save → output/{name}_{timestamp}.dclg.xml │
│ /api/samples → input/**/*.dclg.xml │
└──────────┬──────────────────────────┬───────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────────┐
│ Docling引擎 │ │ Ollama(本地LLM) │
│ (PDF/DOCX/HTML│ │ granite / 任何 │
│ → DocLang XML)│ │ 已安装模型 │
└─────────────────┘ └──────────────────────┘
3、系统架构与工作流模式
该应用程序构建为一个轻量级、快速的Flask API,支持多标签 单页应用程序(SPA)界面。它无缝地将客户端交互与重型文档解析引擎和本地LLM桥接起来。
3.1 高级系统架构

DocLang演示应用程序被设计为一个解耦的多层系统,专为低延迟和平稳文档处理而设计。其核心是一个轻量级Flask REST API,协调交互式单页应用程序(SPA)前端、本地文件存储和专门的下游服务工作者之间的请求。为了在繁重的文档转换期间保持UI高度响应,长时间运行的提取任务被卸载到由IBM Docling引擎支持的异步线程池工作者,而基于基础的自然语言查询则直接由本地Ollama LLM实例驱动。

3.2 文档处理与摄取管道


在底层,应用程序平衡了实时用户反馈与繁重的计算任务。该架构基于在Python 3.10+上运行的快速Flask后端,提供直观的多标签单页应用程序。当解析大型PDF时,应用程序不是锁定用户界面,而是将前端UI事件与繁重的后台作业解耦——将文档提取交给后台线程,本地AI推断通过本地API调用交给Ollama。

4、关键实现亮点
将DocLang规范从概念转化为生产就绪的Web应用程序需要解决跨多线程、模式转换和本地LLM编排的关键现实世界边缘情况。app.py中的核心后端实现桥接了底层系统硬件约束与高级解析逻辑。下面,我们分解了应用程序实现的三个基础组件,详细介绍了我们如何解决macOS硬件线程崩溃、创建高效的客户端Markdown转换器,以及将DocLang直接连接到本地Ollama推断。
4.1 解决macOS Metal / PyTorch MPS线程崩溃
在macOS上的后台工作者线程中执行深度学习模型(如Docling的布局和OCR模型)时,PyTorch尝试通过Apple Metal(MPS)分派操作。在非主线程中,这会引发不可恢复的C级中止:MTLCompiler: compileRequest ... Crashing instead.
Bob在app.py中通过在任何导入发生之前强制PyTorch MPS回退,并将Docling显式固定到CPU线程池,从而干净地解决了这个问题:
import os
# 必须在导入torch或docling之前配置
os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1")
from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.datamodel.pipeline_options import PdfPipelineOptions, AcceleratorOptions, AcceleratorDevice
def _make_converter():
"""构建固定到CPU的DocumentConverter,以确保macOS上的线程安全执行。"""
accel = AcceleratorOptions(num_threads=4, device=AcceleratorDevice.CPU)
opts = PdfPipelineOptions()
opts.accelerator_options = accel
return DocumentConverter(
format_options={
"pdf": PdfFormatOption(pipeline_options=opts),
}
)
4.2 手动Markdown到DocLang XML解析器
对于不需要调用重型神经模型的快速客户端转换,app.py具有自定义的Markdown到DocLang解析器。它将标准Markdown元素转换为原生DocLang标签:
def markdown_to_doclang(md_text: str) -> str:
"""将简化的Markdown转换为有效的DocLang XML标记。"""
lines = md_text.strip().splitlines()
elements: list[str] = []
i = 0
while i < len(lines):
line = lines[i]
stripped = line.strip()
if stripped.startswith("### "):
elements.append(f' <heading level="3">{stripped[4:]}</heading>')
elif stripped.startswith("## "):
elements.append(f' <heading level="2">{stripped[3:]}</heading>')
elif stripped.startswith("# "):
elements.append(f' <heading level="1">{stripped[2:]}</heading>')
elif stripped.startswith("```"):
lang = stripped[3:].strip() or "text"
code_lines: list[str] = []
i += 1
while i < len(lines) and not lines[i].strip().startswith("```"):
code_lines.append(lines[i])
i += 1
code_body = "\n".join(code_lines)
elements.append(
f' <code>\n <label value="{lang}"/>\n'
f' <content><![CDATA[\n{code_body}\n ]]></content>\n </code>'
)
elif stripped.startswith("- "):
list_items: list[str] = []
while i < len(lines) and lines[i].strip().startswith("- "):
list_items.append(f" <item><text>{lines[i].strip()[2:]}</text></item>")
i += 1
elements.append(" <list>\n" + "\n".join(list_items) + "\n </list>")
continue
elif stripped:
elements.append(f" <text>{stripped}</text>")
i += 1
return "<doclang>\n" + "\n".join(elements) + "\n</doclang>"
4.3 使用Ollama的本地LLM问答集成
DocLang简洁的XML表示使其最适合直接包含在系统提示中。应用程序将用户查询和DocLang标记直接路由到本地Ollama模型:
def ask_ollama(question: str, context_dclg: str) -> str:
"""使用本地Ollama实例对解析的DocLang XML执行基于基础的问答。"""
prompt = textwrap.dedent(f"""
You are a helpful assistant. The following is a document in DocLang format
(an AI-native XML document standard). Answer the user's question using only
the information present in the document.
<document>
{context_dclg}
</document>
Question: {question}
Answer:
""").strip()
resp = requests.post(
f"{OLLAMA_BASE_URL}/api/generate",
json={"model": OLLAMA_MODEL, "prompt": prompt, "stream": False},
timeout=60,
)
return resp.json().get("response", "").strip()
5、token效率基准测试
与传统HTML表示相比,DocLang大幅削减了上下文窗口消耗——在包含复杂表格、标题和代码示例的标准文档上实现了约64.2%的代币节省。

6、自动化测试套件
为了确保后台任务队列、文件IO和API验证的可靠性,Bob使用tests/test_app.py中的pytest设计了单元测试套件:
class TestAPIRoutes:
def test_api_convert_markdown(self, client):
resp = client.post("/api/convert/markdown", json={"markdown": "# Hello\n\nWorld."})
assert resp.status_code == 200
data = resp.get_json()
assert "doclang" in data
assert "html_preview" in data
assert "tokens" in data
def test_api_upload_submit_creates_job(self, client):
from io import BytesIO
data = {"file": (BytesIO(b"<html><body>Hello</body></html>"), "test.html")}
resp = client.post("/api/upload/submit", data=data, content_type="multipart/form-data")
assert resp.status_code == 200
body = resp.get_json()
assert "job_id" in body
# 轮询异步状态
job_resp = client.get(f"/api/upload/status/{body['job_id']}")
assert job_resp.status_code == 200
7、结束语
Bob设计的DocLang演示应用程序为将下一代AI原生文档格式集成到企业软件工作流中提供了完整的蓝图。
通过将IBM Docling的多格式提取与DocLang的代币优化XML模式和Ollama的本地LLM推理配对,开发人员可以构建隐私优先、闪电般快速的文档智能管道。
原文链接: Building an AI-Native Document Platform with Doclang
汇智网翻译整理,转载请标明出处