Skip to content

Commit d3d1c61

Browse files
committed
docs(examples): add index metrics example with detailed pipeline analysis
Add a new example demonstrating how to use IndexMetrics to inspect detailed indexing pipeline metrics including timing breakdowns, LLM usage statistics, and reasoning index performance. The example includes: - README with setup instructions and environment variables - Main script comparing documents with/without summaries enabled - Detailed metrics reporting for parse, build, and enhance stages - LLM call statistics and token usage analysis - Node processing and indexing success metrics This helps users understand how different IndexOptions affect pipeline performance and resource utilization.
1 parent a0da791 commit d3d1c61

2 files changed

Lines changed: 278 additions & 0 deletions

File tree

examples/index_metrics/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# IndexMetrics Example
2+
3+
Demonstrates how to inspect detailed indexing pipeline metrics via `IndexMetrics`.
4+
5+
`IndexMetrics` is attached to each `IndexItem` and provides:
6+
7+
| Field | Description |
8+
|------------------------|----------------------------------------------|
9+
| `total_time_ms` | Total indexing time |
10+
| `parse_time_ms` | Document parsing stage duration |
11+
| `build_time_ms` | Tree building stage duration |
12+
| `enhance_time_ms` | Summary/enhancement stage duration |
13+
| `nodes_processed` | Number of tree nodes processed |
14+
| `summaries_generated` | Successfully generated summaries |
15+
| `summaries_failed` | Failed summary generations |
16+
| `llm_calls` | Total LLM API calls made |
17+
| `total_tokens_generated` | Total tokens produced by the LLM |
18+
| `topics_indexed` | Topics added to the reasoning index |
19+
| `keywords_indexed` | Keywords added to the reasoning index |
20+
21+
This example compares documents indexed with and without summaries enabled
22+
to show how `IndexOptions` affect pipeline stages and LLM usage.
23+
24+
## Setup
25+
26+
```bash
27+
pip install vectorless
28+
```
29+
30+
## Run
31+
32+
```bash
33+
python main.py
34+
```
35+
36+
## Environment Variables
37+
38+
| Variable | Description | Default |
39+
|------------------------|----------------------|-----------|
40+
| `VECTORLESS_API_KEY` | LLM API key | `sk-...` |
41+
| `VECTORLESS_MODEL` | LLM model name | `gpt-4o` |
42+
| `VECTORLESS_ENDPOINT` | Custom API endpoint | `None` |

examples/index_metrics/main.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
"""
2+
IndexMetrics example -- demonstrates inspecting detailed indexing pipeline metrics.
3+
4+
IndexMetrics exposes timing, node processing, LLM usage, and reasoning index
5+
statistics for each indexed document. This example compares two documents with
6+
different IndexOptions to show how options affect the pipeline.
7+
8+
Usage:
9+
pip install vectorless
10+
python main.py
11+
"""
12+
13+
import asyncio
14+
import os
15+
16+
from vectorless import (
17+
Engine,
18+
IndexContext,
19+
IndexItem,
20+
IndexMetrics,
21+
IndexOptions,
22+
VectorlessError,
23+
)
24+
25+
# --- Configuration ---
26+
API_KEY = os.environ.get("VECTORLESS_API_KEY", "sk-...")
27+
MODEL = os.environ.get("VECTORLESS_MODEL", "gpt-4o")
28+
ENDPOINT = os.environ.get("VECTORLESS_ENDPOINT", None)
29+
WORKSPACE = "./workspace"
30+
31+
# --- Sample documents with varying complexity ---
32+
SIMPLE_DOC = """\
33+
# Quick Note
34+
35+
This is a short note about caching strategies.
36+
Redis is commonly used as an in-memory cache.
37+
"""
38+
39+
COMPLEX_DOC = """\
40+
# Distributed Systems Design Guide
41+
42+
## Consensus
43+
44+
Raft is a consensus algorithm designed to be easy to understand.
45+
It elects a leader via randomized timeouts and replicates log entries
46+
to a majority of followers before committing them.
47+
48+
## Replication
49+
50+
State machine replication ensures that all replicas execute the same
51+
commands in the same order. Primary-backup replication is simpler but
52+
provides lower availability during leader failover.
53+
54+
## Partitioning
55+
56+
Consistent hashing distributes keys across nodes with minimal
57+
remapping when the cluster size changes. Virtual nodes improve balance
58+
when the key space is small.
59+
60+
## Failure Detection
61+
62+
Phi accrual failure detection treats failure as a continuous suspicion
63+
level rather than a binary alive/dead state. This reduces false
64+
positives during transient network issues.
65+
"""
66+
67+
68+
def print_pipeline_breakdown(m: IndexMetrics) -> None:
69+
"""Print a breakdown of pipeline stages and their percentages."""
70+
total = m.total_time_ms
71+
if total == 0:
72+
print(" (no timing data)")
73+
return
74+
75+
parse_pct = m.parse_time_ms / total * 100
76+
build_pct = m.build_time_ms / total * 100
77+
enhance_pct = m.enhance_time_ms / total * 100
78+
other_pct = max(0, 100 - parse_pct - build_pct - enhance_pct)
79+
80+
print(f" Parse: {m.parse_time_ms:>5} ms ({parse_pct:5.1f}%)")
81+
print(f" Build: {m.build_time_ms:>5} ms ({build_pct:5.1f}%)")
82+
print(f" Enhance: {m.enhance_time_ms:>5} ms ({enhance_pct:5.1f}%)")
83+
print(f" Other: {total - m.parse_time_ms - m.build_time_ms - m.enhance_time_ms:>5} ms ({other_pct:5.1f}%)")
84+
85+
86+
def print_llm_stats(m: IndexMetrics) -> None:
87+
"""Print LLM utilization statistics."""
88+
print(f" LLM calls: {m.llm_calls}")
89+
print(f" Tokens generated: {m.total_tokens_generated}")
90+
if m.llm_calls > 0:
91+
avg_tokens = m.total_tokens_generated / m.llm_calls
92+
print(f" Avg tokens/call: {avg_tokens:.0f}")
93+
94+
95+
def print_summary_stats(m: IndexMetrics) -> None:
96+
"""Print summary generation success/failure."""
97+
total = m.summaries_generated + m.summaries_failed
98+
print(f" Summaries ok: {m.summaries_generated}")
99+
print(f" Summaries failed: {m.summaries_failed}")
100+
if total > 0:
101+
success_rate = m.summaries_generated / total * 100
102+
print(f" Success rate: {success_rate:.1f}%")
103+
104+
105+
def print_reasoning_index(m: IndexMetrics) -> None:
106+
"""Print reasoning index statistics."""
107+
print(f" Nodes processed: {m.nodes_processed}")
108+
print(f" Topics indexed: {m.topics_indexed}")
109+
print(f" Keywords indexed: {m.keywords_indexed}")
110+
111+
112+
def print_full_report(item: IndexItem) -> None:
113+
"""Print a full metrics report for an indexed item."""
114+
m = item.metrics
115+
print(f" Document: {item.name} ({item.format})")
116+
if m is None:
117+
print(" (no metrics)")
118+
return
119+
120+
print(f" Total time: {m.total_time_ms} ms")
121+
print(f" repr: {repr(m)}")
122+
123+
print()
124+
print(" Pipeline stages:")
125+
print_pipeline_breakdown(m)
126+
127+
print()
128+
print(" LLM usage:")
129+
print_llm_stats(m)
130+
131+
print()
132+
print(" Summary generation:")
133+
print_summary_stats(m)
134+
135+
print()
136+
print(" Reasoning index:")
137+
print_reasoning_index(m)
138+
139+
140+
async def main() -> None:
141+
engine = Engine(
142+
workspace=WORKSPACE,
143+
api_key=API_KEY,
144+
model=MODEL,
145+
endpoint=ENDPOINT,
146+
)
147+
148+
# ================================================================
149+
# 1. Index a simple document WITHOUT summaries
150+
# ================================================================
151+
print("=" * 55)
152+
print(" Run 1: Simple doc, summaries OFF")
153+
print("=" * 55)
154+
155+
opts_no_summary = IndexOptions(
156+
generate_summaries=False,
157+
generate_description=False,
158+
)
159+
result = await engine.index(
160+
IndexContext.from_content(SIMPLE_DOC, "markdown")
161+
.with_name("simple_no_summary")
162+
.with_options(opts_no_summary)
163+
)
164+
item = result.items[0]
165+
print_full_report(item)
166+
doc_id_1 = item.doc_id
167+
print()
168+
169+
# ================================================================
170+
# 2. Index the same simple document WITH summaries
171+
# ================================================================
172+
print("=" * 55)
173+
print(" Run 2: Simple doc, summaries ON")
174+
print("=" * 55)
175+
176+
opts_with_summary = IndexOptions(
177+
generate_summaries=True,
178+
generate_description=True,
179+
)
180+
result = await engine.index(
181+
IndexContext.from_content(SIMPLE_DOC, "markdown")
182+
.with_name("simple_with_summary")
183+
.with_options(opts_with_summary)
184+
)
185+
item = result.items[0]
186+
print_full_report(item)
187+
doc_id_2 = item.doc_id
188+
print()
189+
190+
# ================================================================
191+
# 3. Compare: summaries OFF vs ON for the simple doc
192+
# ================================================================
193+
m_off = (await engine.list())[0] # first indexed
194+
# Find the second document's metrics via a fresh index
195+
# (We already have both items above; let's compare directly)
196+
197+
# ================================================================
198+
# 4. Index a complex document WITH summaries
199+
# ================================================================
200+
print("=" * 55)
201+
print(" Run 3: Complex doc, summaries ON")
202+
print("=" * 55)
203+
204+
result = await engine.index(
205+
IndexContext.from_content(COMPLEX_DOC, "markdown")
206+
.with_name("complex_with_summary")
207+
.with_options(opts_with_summary)
208+
)
209+
item = result.items[0]
210+
print_full_report(item)
211+
doc_id_3 = item.doc_id
212+
print()
213+
214+
# ================================================================
215+
# 5. Summary table
216+
# ================================================================
217+
print("=" * 55)
218+
print(" Comparison table")
219+
print("=" * 55)
220+
221+
docs = await engine.list()
222+
for doc in docs:
223+
print(f" {doc.name:<30} id={doc.id[:8]}...")
224+
if doc.description:
225+
print(f" description: {doc.description[:80]}")
226+
227+
# ================================================================
228+
# Cleanup
229+
# ================================================================
230+
print()
231+
cleared = await engine.clear()
232+
print(f"Cleaned up {cleared} document(s).")
233+
234+
235+
if __name__ == "__main__":
236+
asyncio.run(main())

0 commit comments

Comments
 (0)