Documentos Office para RAG
Pipelines de Retrieval-augmented Generation vivem e morrem pela qualidade da ingestão. Office Oxide te dá três saídas (texto, Markdown, IR estruturado) que mapeiam direto nas três coisas que um pipeline de RAG precisa: corpo para embeddings, estrutura para chunking, metadados para citações.
Escolha sua saída
| Objetivo | Use |
|---|---|
| Embeddings mais baratos, menor custo de token | plain_text() |
| Chunks que preservam estrutura (melhor qualidade de retrieval) | to_markdown() |
| Chunk + citação por section/slide/célula | to_ir() |
Para a maioria dos projetos, to_markdown() é o ponto doce: preserva títulos (limites naturais de chunk), mantém tabelas consultáveis e é pequeno o bastante para embedar sem explodir tokens.
Chunking ciente de título a partir do Markdown
A saída Markdown usa # / ## / ### para títulos da fonte. Cortar por aí dá chunks semanticamente coesos “de graça”.
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", " "))
Chunking baseado em IR para precisão de citação
Se você precisa citar slide 3 ou planilha “Q4 Forecast” no contexto recuperado, percorra o IR. Cada section traz o locator natural:
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"Slide {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),
})
Agora seus chunks recuperados têm um locator preciso para citação (slide:3 / sheet:Q4 Forecast / section:2).
Integração com 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")
Joga em Chroma.from_documents(docs, embedder) (ou qualquer vectorstore) como sempre.
Integração com 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})]
Para nodes por section, use o padrão IR acima e passe cada chunk como um LIDoc separado.
Tabelas — a parte difícil
LLMs lidam bem com tabelas pequenas em Markdown. Tabelas grandes (50+ linhas) ficam melhor resumidas ou paginadas:
def summarize_table(rows: list[list[str]]) -> str:
headers = rows[0]
body = rows[1:]
return f"Tabela com colunas {headers} e {len(body)} linhas. Amostra: {body[:3]}"
Para dashboards (XLSX), considere extrair sumários por planilha em vez de dumps de células — o LLM tira mais proveito de “Planilha ‘Q4’ totaliza receita de $4.2M em 12 regiões” do que de 5.000 valores de célula.
Performance e custo
| Op | Tempo por arquivo (DOCX, mediana) | Notas |
|---|---|---|
plain_text() |
0,8 ms | mais barato |
to_markdown() |
~1,5 ms | recomendado para RAG |
to_ir() |
~1,2 ms | quando você precisa de estrutura |
Um corpus de um milhão de documentos extrai em ~25 minutos single-thread, ~3 minutos em 8 cores. O custo dominante no seu pipeline RAG vai ser as chamadas à API de embedding, não o parsing do Office.
Veja também
- Extração Markdown — spec completa de saída
- IR estruturado — schema para chunking ciente de citação
- Processamento em lote — padrões de paralelismo