A developer building a LangChain agent or RAG pipeline that reads XLSX files needs clean, structured content from each sheet, not flat row-by-row text that loses column context. With openpyxl, you get cell values row by row with no association to their column headers. With pandas, the headers survive on the DataFrame, but you still decide how to serialise rows into text, how to handle multiple sheets, and where chunk boundaries fall, so the content that reaches LangChain has no structural anchors for retrieval. For an agent reasoning over a financial model, a product catalogue, or a data export, a row of bare values without column names or sheet context is not a useful retrieval unit. EnConvert’s Ingest endpoint takes the XLSX upload, converts the spreadsheet into Markdown, and returns JSONL chunks where each table stays whole rather than being split mid-row, with headings_path and section metadata preserved, so XLSX content is ready for embedding and retrieval through one endpoint.
How to Ingest XLSX into LangChain with EnConvert
Use EnConvert to ingest XLSX files into LangChain as structured chunks where tables stay whole, ready to load as Document objects and add to a vector store
Get API keyExample
python
import json
import os
import time
import requests
from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
ENCONVERT_API_KEY = os.environ["ENCONVERT_API_KEY"]
XLSX_PATH = "/path/to/your/financial-model.xlsx"
# Step 1: Upload the XLSX file (multipart/form-data, field name "files")
with open(XLSX_PATH, "rb") as xlsx_file:
upload = requests.post(
"https://api.enconvert.com/v2/ingest/files",
headers={"X-API-Key": ENCONVERT_API_KEY},
files={
"files": (
os.path.basename(XLSX_PATH),
xlsx_file,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
timeout=120,
)
upload.raise_for_status()
job_id = upload.json()["job_id"]
# Step 2: Poll until the ingest job finishes
while True:
status_response = requests.get(
f"https://api.enconvert.com/v2/ingest/{job_id}",
headers={"X-API-Key": ENCONVERT_API_KEY},
timeout=60,
)
status_response.raise_for_status()
job = status_response.json()
if job["status"] == "completed":
break
if job["status"] in ("failed", "canceled"):
raise RuntimeError(
f"Ingest job {job_id} {job['status']}: {job.get('error_message')}"
)
time.sleep(5)
# Step 3: Download the JSONL output, one chunk per line.
# output_url is a signed URL (valid 15 minutes). Plain GET, no API key.
jsonl_response = requests.get(job["output_url"], timeout=60)
jsonl_response.raise_for_status()
chunks = [
json.loads(line)
for line in jsonl_response.content.splitlines()
if line.strip()
]
# Step 4: Turn each chunk into a LangChain Document.
# For an XLSX file, each worksheet becomes a section named after the sheet.
documents = [
Document(
id=chunk["id"],
page_content=chunk["content"],
metadata=chunk["metadata"],
)
for chunk in chunks
]
print("Sections:", sorted({doc.metadata["section"] for doc in documents}))
# Step 5: Embed the Documents into a vector store
# (InMemoryVectorStore needs numpy installed for cosine search)
embeddings = OpenAIEmbeddings(
model="text-embedding-3-large",
api_key=os.environ["OPENAI_API_KEY"],
)
vector_store = InMemoryVectorStore(embeddings)
vector_store.add_documents(documents)
# Step 6: Search only inside one section (sheet)
SECTION = "Income Statement" # replace with a sheet name from your own workbook
results = vector_store.similarity_search(
"What is the projected revenue growth?",
k=3,
filter=lambda doc: doc.metadata.get("section") == SECTION,
)
for doc in results:
print(doc.metadata["headings_path"], doc.page_content[:200])
What you get back
The Ingest endpoint converts the XLSX file to Markdown and returns JSONL chunks where each table stays whole, with headings_path and section metadata on every chunk so retrieval can be filtered by section. Reading the same file with openpyxl or pandas and serialising rows as plain text discards table structure, produces no meaningful chunk boundaries, and populates no LangChain metadata automatically.