Office 文档用于 RAG
检索增强生成(RAG)流水线的成败取决于摄取质量。Office Oxide 给你三种输出(纯文本、Markdown、结构化 IR),它们恰好对应 RAG 流水线需要的三件事:用于嵌入的正文、用于分块的结构、用于引用的元数据。
选择输出
| 目标 | 使用 |
|---|---|
| 嵌入最便宜、token 成本最低 | plain_text() |
| 结构保留分块(检索质量最好) | to_markdown() |
| 按 section/幻灯片/单元格分块并引用 | to_ir() |
对大多数项目来说 to_markdown() 是甜蜜点:保留标题(自然分块边界)、表格保持可查询、足够小到能在不爆 token 的情况下嵌入。
基于 Markdown 的标题感知分块
Markdown 输出对源标题使用 # / ## / ###。在那里切分能「免费」获得语义连贯的块。
from office_oxide import Document
def chunk_by_heading(md: str, level: int = 2):
chunks, current = [], []
for line in md.splitlines():
if line.startswith("#" * level + " "):
if current:
chunks.append("\n".join(current))
current = [line]
else:
current.append(line)
if current:
chunks.append("\n".join(current))
return chunks
with Document.open("report.docx") as doc:
md = doc.to_markdown()
chunks = chunk_by_heading(md, level=2)
for c in chunks:
print(len(c), c[:60].replace("\n", " "))
基于 IR 的分块以保证引用准确
如果你需要在检索到的上下文中引用 幻灯片 3 或 「Q4 Forecast」工作表,请走 IR。每个 section 都带有天然定位符:
from office_oxide import Document
with Document.open("deck.pptx") as doc:
ir = doc.to_ir()
chunks = []
for i, section in enumerate(ir["sections"], 1):
title = section.get("title") or f"幻灯片 {i}"
body = []
for el in section["elements"]:
if el["kind"] == "Heading":
body.append("# " + el["text"])
elif el["kind"] == "Paragraph":
body.append(" ".join(r["text"] for r in el["runs"]))
elif el["kind"] == "Table":
for row in el["rows"]:
body.append(" | ".join(row))
chunks.append({
"source": "deck.pptx",
"locator": f"slide:{i}",
"title": title,
"text": "\n".join(body),
})
现在检索到的块带有用于引用的精确定位符(slide:3 / sheet:Q4 Forecast / section:2)。
LangChain 集成
from langchain_core.documents import Document as LCDoc
from office_oxide import Document
def load_office(path: str) -> list[LCDoc]:
with Document.open(path) as doc:
ir = doc.to_ir()
out = []
for i, section in enumerate(ir["sections"], 1):
body_lines = []
for el in section["elements"]:
if el["kind"] == "Paragraph":
body_lines.append(" ".join(r["text"] for r in el["runs"]))
elif el["kind"] == "Heading":
body_lines.append(el["text"])
if not body_lines:
continue
out.append(LCDoc(
page_content="\n".join(body_lines),
metadata={
"source": path,
"section_index": i,
"section_title": section.get("title"),
},
))
return out
docs = load_office("report.docx")
像往常一样丢进 Chroma.from_documents(docs, embedder)(或任何向量库)。
LlamaIndex 集成
from llama_index.core import Document as LIDoc
from office_oxide import Document
def load_office(path: str) -> list[LIDoc]:
with Document.open(path) as doc:
md = doc.to_markdown()
return [LIDoc(text=md, metadata={"source": path})]
要按 section 出节点,使用上面的 IR 模式,把每个块作为单独的 LIDoc 传入。
表格 — 难的部分
LLM 处理 Markdown 形式的小表很好。大表(50+ 行)最好做摘要或分页:
def summarize_table(rows: list[list[str]]) -> str:
headers = rows[0]
body = rows[1:]
return f"含列 {headers}、共 {len(body)} 行的表。样例: {body[:3]}"
仪表盘(XLSX)请考虑做 每张工作表的摘要 而不是把所有单元格原样转储 — LLM 从「工作表 ‘Q4’ 在 12 个地区累计收入 $4.2M」中获得的收益比 5,000 个单元格值更大。
性能与成本
| 操作 | 单文件 DOCX 时间(中位) | 备注 |
|---|---|---|
plain_text() |
0.8 ms | 最便宜 |
to_markdown() |
~1.5 ms | RAG 推荐 |
to_ir() |
~1.2 ms | 需要结构时 |
百万级文档语料单线程 ~25 分钟,8 核 ~3 分钟 抽取完成。RAG 流水线里真正主导成本的是嵌入 API 调用,不是 Office 解析。
相关链接
- Markdown 提取 — 完整输出规范
- 结构化 IR — 引用感知分块的 schema
- 批处理 — 并行模式