Skip to content

Commit 10cac13

Browse files
committed
feat: add multi-index query support (queryAll / query_all)
- Add MultiQueryResult class (TS + Python) with combined_context, labels, per-index results, and optional agentic answer - Add static TreeDex.queryAll() (TS, parallel) and TreeDex.query_all() (Python) — queries N indexes, merges context with [Label] separators - Export MultiQueryResult from src/index.ts and treedex/__init__.py - Add 10 new tests in test/core.test.ts and tests/test_core.py covering multi-index queries, custom labels, shared LLM, agentic mode, and validation errors - Add examples/multi_index.py and examples/node/multi-index.ts - Add docs/how-it-works.md — full phase-by-phase pipeline walkthrough - Update docs/api.md, README.md, DOCS.md with MultiQueryResult reference - Fix docs nav_order conflict (how-it-works.md=3, shifted api.md+ up by 1)
1 parent 23bb6d6 commit 10cac13

17 files changed

Lines changed: 901 additions & 10 deletions

DOCS.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,49 @@ Counters are maintained per level. When a new Level 1 appears, all deeper counte
302302
| Build from pages | `TreeDex.from_pages(pages, llm, **opts)` | `await TreeDex.fromPages(pages, llm, opts?)` | From pre-extracted pages |
303303
| Build from tree | `TreeDex.from_tree(tree, pages, llm)` | `TreeDex.fromTree(tree, pages, llm)` | From existing tree (no LLM) |
304304
| Query | `index.query(q, llm=, agentic=)` | `await index.query(q, {llm?, agentic?})` | Retrieve relevant sections |
305+
| **Multi-index query** | **`TreeDex.query_all(indexes, q, ...)`** | **`await TreeDex.queryAll(indexes, q, ...)`** | **Query multiple indexes simultaneously** |
305306
| Save | `index.save(path)` | `await index.save(path)` | Export to JSON |
306307
| Load | `TreeDex.load(path, llm)` | `await TreeDex.load(path, llm)` | Import from JSON |
307308
| Show tree | `index.show_tree()` | `index.showTree()` | Pretty-print |
308309
| Stats | `index.stats()` | `index.stats()` | Return `{total_pages, total_tokens, ...}` |
309310
| Find large | `index.find_large_sections(**opts)` | `index.findLargeSections(opts?)` | Nodes exceeding thresholds |
310311

312+
#### `query_all` / `queryAll` Options
313+
314+
Query multiple TreeDex indexes simultaneously. Indexes are queried in parallel
315+
(Node.js) or sequentially (Python), then results are merged into a single
316+
`MultiQueryResult` with `[Label]` separators between sources.
317+
318+
| Parameter | Python | Node.js | Default | Description |
319+
|-----------|--------|---------|---------|-------------|
320+
| indexes | `list[TreeDex]` | `TreeDex[]` | required | List of indexes to query |
321+
| question | `str` | `string` | required | The user's question |
322+
| llm | `llm=` | `{ llm? }` | `None` | Shared LLM override; falls back to each index's own LLM |
323+
| agentic | `agentic=` | `{ agentic? }` | `False` | Generate a single answer over the combined context |
324+
| labels | `labels=` | `{ labels? }` | `["Document 1", ...]` | Human-readable name per index |
325+
326+
```python
327+
multi = TreeDex.query_all(
328+
[index_a, index_b, index_c],
329+
"What are the safety guidelines?",
330+
labels=["Manual A", "Manual B", "Manual C"],
331+
agentic=True,
332+
)
333+
print(multi.combined_context) # merged context with [Manual A]/[Manual B] headers
334+
print(multi.answer) # single answer over all sources
335+
print(multi.results[0].pages_str) # pages from Manual A
336+
```
337+
338+
```typescript
339+
const multi = await TreeDex.queryAll(
340+
[indexA, indexB, indexC],
341+
"What are the safety guidelines?",
342+
{ labels: ["Manual A", "Manual B", "Manual C"], agentic: true },
343+
);
344+
console.log(multi.combinedContext);
345+
console.log(multi.answer);
346+
```
347+
311348
#### `from_file` Options
312349

313350
| Parameter | Python | Node.js | Default | Description |
@@ -331,6 +368,17 @@ Counters are maintained per level. When a new Level 1 appears, all deeper counte
331368
| Reasoning | `.reasoning` | `.reasoning` | `str` | LLM's selection explanation |
332369
| Answer | `.answer` | `.answer` | `str` | Generated answer (agentic mode only) |
333370

371+
### MultiQueryResult
372+
373+
Returned by `TreeDex.query_all()` / `TreeDex.queryAll()`.
374+
375+
| Property | Python | Node.js | Type | Description |
376+
|----------|--------|---------|------|-------------|
377+
| Per-index results | `.results` | `.results` | `list[QueryResult]` | One result per index, in input order |
378+
| Labels | `.labels` | `.labels` | `list[str]` | Human-readable name for each index |
379+
| Combined context | `.combined_context` | `.combinedContext` | `str` | All contexts merged with `[Label]` headers and `---` separators |
380+
| Answer | `.answer` | `.answer` | `str` | Single answer over all sources (agentic only) |
381+
334382
### Hierarchy Utilities
335383

336384
| Function | Python | Node.js | Description |

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ Use `auto_loader(path)` / `autoLoader(path)` for automatic format detection.
383383
| Create from tree | `TreeDex.from_tree(tree, pages)` | `TreeDex.fromTree(tree, pages)` |
384384
| Query | `index.query(question)` | `await index.query(question)` |
385385
| Agentic query | `index.query(q, agentic=True)` | `await index.query(q, { agentic: true })` |
386+
| **Multi-index query** | **`TreeDex.query_all(indexes, q)`** | **`await TreeDex.queryAll(indexes, q)`** |
386387
| Save | `index.save(path)` | `await index.save(path)` |
387388
| Load | `TreeDex.load(path, llm)` | `await TreeDex.load(path, llm)` |
388389
| Show tree | `index.show_tree()` | `index.showTree()` |
@@ -400,6 +401,41 @@ Use `auto_loader(path)` / `autoLoader(path)` for automatic format detection.
400401
| Reasoning | `.reasoning` | `.reasoning` | LLM's explanation for selection |
401402
| Answer | `.answer` | `.answer` | LLM-generated answer (agentic mode only) |
402403

404+
### `MultiQueryResult`
405+
406+
Returned by `TreeDex.query_all()` / `TreeDex.queryAll()` when querying multiple indexes at once.
407+
408+
| Property | Python | Node.js | Description |
409+
|----------|--------|---------|-------------|
410+
| Per-index results | `.results` | `.results` | One `QueryResult` per index, in input order |
411+
| Labels | `.labels` | `.labels` | Human-readable name for each index |
412+
| Combined context | `.combined_context` | `.combinedContext` | All contexts merged with `[Label]` headers |
413+
| Answer | `.answer` | `.answer` | Single answer over all sources (agentic only) |
414+
415+
**Example:**
416+
417+
```python
418+
multi = TreeDex.query_all(
419+
[index_a, index_b],
420+
"What are the safety guidelines?",
421+
labels=["Manual A", "Manual B"],
422+
agentic=True,
423+
)
424+
print(multi.combined_context) # [Manual A]\n...\n---\n[Manual B]\n...
425+
print(multi.answer) # unified answer across both documents
426+
print(multi.results[0].pages_str) # pages matched in Manual A
427+
```
428+
429+
```typescript
430+
const multi = await TreeDex.queryAll(
431+
[indexA, indexB],
432+
"What are the safety guidelines?",
433+
{ labels: ["Manual A", "Manual B"], agentic: true },
434+
);
435+
console.log(multi.combinedContext);
436+
console.log(multi.answer);
437+
```
438+
403439
### Hierarchy Utilities
404440

405441
| Function | Python | Node.js | Description |

docs/api.md

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: default
33
title: API Reference
4-
nav_order: 3
4+
nav_order: 4
55
---
66

77
# API Reference
@@ -123,6 +123,57 @@ const result = await index.query(question, {
123123
// Or shorthand: await index.query(question, llm)
124124
```
125125

126+
#### `query_all` / `queryAll` _(static)_
127+
128+
Query **multiple indexes simultaneously** and merge results into a single
129+
`MultiQueryResult`. All indexes are queried in parallel (Node.js) or
130+
sequentially (Python). Results are combined with clear `[Document N]`
131+
separators so downstream LLMs or users can distinguish sources.
132+
133+
```python
134+
multi = TreeDex.query_all(
135+
indexes: list[TreeDex],
136+
question: str,
137+
llm=None, # Shared LLM override (falls back to each index's LLM)
138+
agentic: bool = False, # Generate one answer over the combined context
139+
labels: list[str] = None # Human-readable names (default: "Document 1", "Document 2", …)
140+
) -> MultiQueryResult
141+
```
142+
143+
```typescript
144+
const multi = await TreeDex.queryAll(indexes, question, {
145+
llm?, // Shared LLM override
146+
agentic?, // Generate one answer over combined context
147+
labels?, // Human-readable names per index
148+
});
149+
```
150+
151+
**Example:**
152+
153+
```python
154+
multi = TreeDex.query_all(
155+
[index_a, index_b, index_c],
156+
"What are the safety guidelines?",
157+
llm=llm,
158+
labels=["Manual A", "Manual B", "Manual C"],
159+
agentic=True,
160+
)
161+
print(multi.combined_context) # merged text with [Manual A] / [Manual B] headers
162+
print(multi.answer) # single LLM-generated answer over all sources
163+
print(multi.results[0].pages_str) # pages matched in Manual A
164+
```
165+
166+
```typescript
167+
const multi = await TreeDex.queryAll(
168+
[indexA, indexB, indexC],
169+
"What are the safety guidelines?",
170+
{ llm, labels: ["Manual A", "Manual B", "Manual C"], agentic: true },
171+
);
172+
console.log(multi.combinedContext);
173+
console.log(multi.answer);
174+
console.log(multi.results[0].pagesStr);
175+
```
176+
126177
#### `save`
127178

128179
Export the index to a JSON file.
@@ -198,6 +249,33 @@ Returned by `index.query()`.
198249

199250
---
200251

252+
## MultiQueryResult
253+
254+
Returned by `TreeDex.query_all()` / `TreeDex.queryAll()`.
255+
256+
| Property | Python | Node.js | Type | Description |
257+
|----------|--------|---------|------|-------------|
258+
| Per-index results | `.results` | `.results` | `list[QueryResult]` | One `QueryResult` per index, in input order |
259+
| Labels | `.labels` | `.labels` | `list[str]` | Human-readable name for each index |
260+
| Combined context | `.combined_context` | `.combinedContext` | `str` | All contexts merged with `[Label]` headers and `---` separators |
261+
| Answer | `.answer` | `.answer` | `str` | Single LLM-generated answer over all sources (agentic mode only) |
262+
263+
**Combined context format:**
264+
265+
```
266+
[Manual A]
267+
[Section: Safety Guidelines]
268+
Content from Manual A...
269+
270+
---
271+
272+
[Manual B]
273+
[Section: Hazard Procedures]
274+
Content from Manual B...
275+
```
276+
277+
---
278+
201279
## PDF Parser Functions
202280

203281
### `extract_toc` / `extractToc`

docs/benchmark-report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: default
33
title: Benchmark Report
4-
nav_order: 8
4+
nav_order: 9
55
---
66

77
# Benchmark Report: TreeDex vs Vector RAG

docs/benchmarks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: default
33
title: Benchmarks
4-
nav_order: 5
4+
nav_order: 6
55
---
66

77
# Benchmarks

docs/case-studies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: default
33
title: Case Studies
4-
nav_order: 6
4+
nav_order: 7
55
---
66

77
# Case Studies

docs/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: default
33
title: Configuration
4-
nav_order: 7
4+
nav_order: 8
55
---
66

77
# Configuration & Tuning

0 commit comments

Comments
 (0)