Why this matters
- PolicyDesk's handbook lived in PDF, the holiday calendar in a sheet, and branch addresses on the intranet. Three loaders, one index.
- Interviewers ask "how would you ingest a database?" to see if you turn rows into text on purpose instead of embedding raw tuples blindly.
- Access control is a source problem. A row the user cannot see must not become a chunk they can retrieve.
- Refresh schedules differ. A policy PDF changes monthly. A rate API changes daily. One cron for everything will be wrong.
One pipeline, many front doors
Sources PolicyDesk actually has
- Local files: PDF, TXT, DOCX, CSV, and spreadsheets on a drive or in object storage.
- Web pages: the public FAQ and the intranet help center, fetched by URL.
- Databases: Postgres for structured policy tables, sometimes Mongo for form submissions.
- APIs: REST endpoints that already speak JSON, including HR systems you do not get a database login for.
- Object storage: S3 or similar for the nightly handbook drop.
- Unstructured dumps: HTML, scanned images, and email. These need extraction, not a naive text split.
Each source becomes documents with page_content and metadata. After that, chunk, embed, and upsert are shared. Do not build a second vector collection per format unless the embedding model or the access domain truly differs.
Loaders, one by one
PDF. PyPDFLoader reads a digital text layer. It will return empty pages for a scan. For scans, OCR first. For multi-column layouts and tables, use a parser that understands layout (the tables lesson). Always keep page number in metadata.
from langchain_community.document_loaders import PyPDFLoader
pages = PyPDFLoader("leave-policy.pdf").load()
print(len(pages), pages[0].metadata.get("page"))
Plain text. TextLoader is enough for markdown and .txt. You still chunk, because a whole wiki export is not one retrieval unit.
CSV and Excel. Each row should become text a human would say, not a comma salad.
from langchain_community.document_loaders import CSVLoader
rows = CSVLoader("holiday-calendar.csv").load()
Prefer a template: "Holiday: Republic Day. Date: 26 January. Offices: all India." Embed that sentence. Keep the date and region as metadata so you can filter "Mumbai office" without hoping the vector notices a column. Spreadsheet loaders (UnstructuredExcelLoader and its cousins) exist for xlsx. Merged cells and multiple sheets still need a look with your eyes.
Web. WebBaseLoader fetches a URL. Strip navigation, cookie banners, and footers before chunking, or every page looks like the site chrome.
from langchain_community.document_loaders import WebBaseLoader
docs = WebBaseLoader("https://intranet.northline.example/holidays").load()
Respect robots, auth, and copyright. An internal crawler with a service account is not a license to index the public internet. Record the URL and the fetch time in metadata so you can expire stale pages.
SQL. Select the columns that belong in the answer. Do not SELECT * from a table of national IDs and embed it.
from langchain_community.utilities import SQLDatabase
db = SQLDatabase.from_uri("postgresql://policy_reader@db/hr")
# Prefer an explicit query that projects safe columns only.
rows = db.run("SELECT name, days, eligibility FROM leave_rules")
Turn each row into a short document, with the primary key in metadata so an update can delete the old point. A database is also a candidate for not using vectors: if the question is "how many days is casual leave?" and the value is a column, a SQL query is the accurate tool. Vectors are for fuzzy wording over text. The text-to-SQL project in the next lesson is that other tool. Use both when the product has both fuzzy policy prose and exact tables.
APIs. Call the endpoint, keep only the fields you will show, and shape them into text plus metadata.
import requests
payload = requests.get(
"https://hr.internal.example/policies/leave",
timeout=10,
).json()
text = payload["summary"]
metadata = {"source": "leave-api", "version": payload["version"]}
Paginate. Time out. Do not embed error bodies ({"error": "unauthorized"}) or you will retrieve them forever.
The rest of this guide is part of Pro
Free covers every fresher guide in full. Pro unlocks Senior, Staff and Architect guides across every track.
Still to come · 3 sections
- Many sources, one collection
- A combined indexing sketch
- Practices per source
Test yourself
Answer these before moving on — recall is what makes it stick.