TL;DR: Different pipelines into one index. Each corpus has its own semantic unit (clause, thread, table), so build a per-source ingestion adapter that emits a common schema, and prove the split with per-corpus recall@10.
How to approach it
The trap is proposing one chunker. The principle to state up front: chunk at the semantic unit a user's question targets, and that unit differs per corpus, a clause, a conversation, a table. Propose a per-source ingestion adapter feeding a common index schema.
A strong answer
Contracts: questions target clauses ("what's the termination notice period in the ACME MSA?"). Chunk by clause/section using the numbering structure (11, 11.1, 11.2), keeping each chunk self-contained with a metadata header: document name, parties, effective date, section path. Use parent-child retrieval: embed at clause level, but be able to return the full section, since clauses cross-reference ("as defined in Section 3"). Definitions sections deserve special handling: either retrieve them alongside any clause that uses defined terms, or inline expand. Never split mid-clause; legal users lose trust instantly when a retrieved chunk starts mid-sentence.
Slack threads: the message is too small (context-free, "yes that works" embeds to noise) and the channel is too big. The semantic unit is the thread or conversation episode: chunk by thread, or for unthreaded channels, sessionize by time gap (more than 30 min silence starts a new episode). Preserve speaker names and timestamps in the text; prepend channel and topic. Quality varies wildly, so consider an LLM pass that titles or summarizes each thread for embedding while retrieval returns the raw thread. Mind ACLs: private channels carry permissions that must travel with the chunk.
PDF tables: the failure mode is layout-blind text extraction shredding rows into word salad. Run layout-aware parsing (Docling, unstructured.io, Azure Document Intelligence, or a vision-LLM pass) to detect tables, then extract each table as its own chunk serialized as Markdown, with caption and surrounding paragraph attached. For wide or long tables, also generate row-level renderings ("Region: EMEA; Q3 revenue: $4.2M") so point lookups can match a single row. Numeric questions over tables often deserve a different tool entirely: text-to-SQL over extracted tables beats embedding them.
The table failure is worth seeing at actual size, because it looks abstract until it costs a demo. Take a two-row revenue table. Layout-blind extraction reads it in visual order and emits:
Region Q2 Q3 EMEA $3.8M $4.2M APAC $2.1M $2.6M
Ask "what was Q3 revenue for EMEA?" and there is nothing in that string binding $4.2M to either EMEA or Q3; the model, handed the blob, must guess which number belongs to which cell, and it guesses fluently. The row-per-record rendering makes the binding explicit:
Region: EMEA | Q2 revenue: $3.8M | Q3 revenue: $4.2M
Region: APAC | Q2 revenue: $2.1M | Q3 revenue: $2.6M
Now the EMEA row is its own retrievable unit and the answer is unambiguous inside a single chunk. Every layout-aware parser named above exists to get from the first rendering to the second; being able to state the transformation, not just the vendor list, is what shows you understand the problem.
All three feed one index schema: {text, embedding, source_type, doc_id, section_path, acl, timestamp}, uniform retrieval with specialized ingestion. Prove the design with a per-corpus golden set: measure recall@10 separately for contract, Slack, and table questions; expect the naive baseline to fail worst on tables (often <40% recall) and the specialized pipeline to recover 30+ points there.
The three corpora side by side:
| Corpus | Semantic unit | Chunking strategy | Parsing note |
|---|---|---|---|
| Contracts | Clause | Chunk by clause/section number, parent-child to return the full section | Never split mid-clause; give definitions sections special handling |
| Slack threads | Thread or conversation episode | Chunk by thread, or sessionize unthreaded channels on 30-min gaps | Keep speaker names and timestamps; carry ACLs from private channels |
| PDFs with tables | Table | Each table its own chunk serialized as Markdown, plus row-level renderings | Layout-aware parse (Docling, unstructured.io, Azure Document Intelligence, vision-LLM); consider text-to-SQL |
What interviewers probe next
"Which corpus do you build first?" Whichever the pilot's real questions hit; check the query log or ask the customer. "Scanned PDFs?" OCR first; vision models handle layout but cost roughly 10x, so route by document type. "How do you handle a 400-page contract amendment chain?" Amendment-aware metadata and effective-date filtering, or you'll retrieve superseded terms.
Common mistakes
One chunker for everything. Ignoring tables until a customer demo asks "what was Q3 revenue?" and the system reads shredded cells. Dropping speaker/timestamp metadata from Slack so answers can't be attributed. And never proposing the eval split: without per-corpus recall numbers you can't show the specialized pipelines earned their complexity.
Key takeaways
- One index schema, three ingestion adapters: the semantic unit is a clause, a thread, and a table respectively.
- Tables are the watched detail: parse layout, serialize to Markdown plus row-level records, and consider text-to-SQL.
- Carry ACLs and effective dates as metadata, and prove the split with per-corpus recall, not a blended number.
