Office-Dokumente für RAG
Retrieval-Augmented-Generation-Pipelines stehen und fallen mit der Ingest-Qualität. Office Oxide gibt dir drei Outputs (Text, Markdown, strukturierte IR), die sauber auf die drei Dinge passen, die ein RAG-Pipeline braucht: Body fürs Embedding, Struktur fürs Chunking, Metadaten für Zitate.
Output wählen
| Ziel | Nimm |
|---|---|
| Günstigste Embeddings, niedrigste Token-Kosten | plain_text() |
| Strukturerhaltende Chunks (bestes Retrieval-Quality) | to_markdown() |
| Chunk + Zitat per Section/Folie/Zelle | to_ir() |
Für die meisten Projekte ist to_markdown() der Sweet Spot: bewahrt Überschriften (natürliche Chunk-Grenzen), Tabellen bleiben abfragbar, klein genug zum Embedden ohne Token-Explosion.
Heading-bewusstes Chunking aus Markdown
Der Markdown-Output nutzt # / ## / ### für Quell-Überschriften. Wenn du dort schneidest, bekommst du semantisch kohärente Chunks “kostenlos”.
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-basiertes Chunking für Zitate-Genauigkeit
Wenn du im retrievten Kontext Folie 3 oder Sheet “Q4 Forecast” zitieren willst — geh durch die IR. Jede Section trägt den natürlichen Locator:
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"Folie {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),
})
Jetzt haben deine retrievten Chunks einen präzisen Locator (slide:3 / sheet:Q4 Forecast / section:2) für Zitate.
LangChain-Integration
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")
Wirf das wie üblich in Chroma.from_documents(docs, embedder) (oder einen beliebigen Vectorstore).
LlamaIndex-Integration
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})]
Für Section-weise Nodes nimm das IR-Pattern oben und übergib jeden Chunk als separates LIDoc.
Tabellen — der schwierige Teil
LLMs gehen mit kleinen Tabellen in Markdown gut um. Große Tabellen (50+ Zeilen) lieber zusammenfassen oder paginieren:
def summarize_table(rows: list[list[str]]) -> str:
headers = rows[0]
body = rows[1:]
return f"Tabelle mit Spalten {headers} und {len(body)} Zeilen. Sample: {body[:3]}"
Für Dashboards (XLSX) lieber Sheet-Zusammenfassungen extrahieren statt voller Zell-Dumps — das LLM profitiert mehr von „Sheet ‘Q4’ summiert Umsatz $4,2M über 12 Regionen" als von 5.000 Zellwerten.
Performance & Kosten
| Op | Zeit pro Datei (DOCX, Median) | Anmerkungen |
|---|---|---|
plain_text() |
0,8 ms | am günstigsten |
to_markdown() |
~1,5 ms | für RAG empfohlen |
to_ir() |
~1,2 ms | wenn Struktur gebraucht wird |
Ein Million-Dokument-Korpus extrahiert in ~25 Minuten Single-Thread, ~3 Minuten auf 8 Cores. Der dominierende Kostenblock in deiner RAG-Pipeline werden die Embedding-API-Calls sein, nicht das Office-Parsing.
Siehe auch
- Markdown-Extraktion — vollständige Output-Spec
- Strukturierte IR — Schema für zitate-bewusstes Chunking
- Batch-Verarbeitung — Parallelisierungs-Patterns