Skip to content

Commit b0111ac

Browse files
authored
feat: add index persistence (#140)
* feat: add index persistence * fix: comments * fix * docs * fix agents files * fixes
1 parent 5f32479 commit b0111ac

31 files changed

Lines changed: 3684 additions & 3303 deletions

README.md

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ semble search "save_pretrained" ./my-project
6363
semble search "save model to disk" ./my-project --top-k 10
6464
​```
6565

66+
If you anticipate doing more than one search, use `semble index` to create an index.
67+
68+
​```bash
69+
semble index ./my-project -o my_index
70+
​```
71+
72+
You can then reuse this index later on:
73+
74+
​```bash
75+
semble search "save_pretrained" --index my_index
76+
​```
77+
78+
An index is not automatically updated, so if the code changes significantly, reindex. If you notice stale results while resolving searches to files, reindex.
79+
6680
Use `--content docs` to search documentation and prose, `--content config` for config files (yaml, toml, etc.), or `--content all` to search code, docs, and config:
6781

6882
​```bash
@@ -77,17 +91,20 @@ Use `semble find-related` to discover code similar to a known location (pass `fi
7791
semble find-related src/auth.py 42 ./my-project
7892
​```
7993

94+
Like search, `find-related` also accepts an `--index` argument.
95+
8096
`path` defaults to the current directory when omitted; git URLs are accepted.
8197

8298
If `semble` is not on `$PATH`, use `uvx --from "semble[mcp]" semble` in its place.
8399

84100
### Workflow
85101

86-
1. Start with `semble search` to find relevant chunks.
87-
2. Use `--content docs` for documentation, `--content config` for config files, or `--content all` for everything.
88-
3. Inspect full files only when the returned chunk is not enough context.
89-
4. Optionally use `semble find-related` with a promising result's `file_path` and `line` to discover related implementations.
90-
5. Use grep only when you need exhaustive literal matches or quick confirmation of an exact string.
102+
1. Index the repo using `semble index -o cached_index`.
103+
2. Start with `semble search` to find relevant chunks. Pass the index to achieve results faster.
104+
3. Use `--content docs` for documentation, `--content config` for config files, or `--content all` for everything.
105+
4. Inspect full files only when the returned chunk does not give enough context.
106+
5. Optionally use `semble find-related` with a promising result's `file_path` and `line` to discover related implementations.
107+
6. Use grep only when you need exhaustive literal matches or quick confirmation of an exact string.
91108
```
92109

93110
</details>
@@ -318,6 +335,20 @@ semble search "save_pretrained" ./my-project
318335
semble search "save model to disk" ./my-project --top-k 10
319336
​```
320337

338+
If you anticipate doing more than one search, use `semble index` to create an index.
339+
340+
​```bash
341+
semble index ./my-project -o my_index
342+
​```
343+
344+
You can then reuse this index later on:
345+
346+
​```bash
347+
semble search "save_pretrained" --index my_index
348+
​```
349+
350+
An index is not automatically updated, so if the code changes significantly, reindex. If you notice stale results while resolving searches to files, reindex.
351+
321352
Use `--content docs` to search documentation and prose, `--content config` for config files (yaml, toml, etc.), or `--content all` to search code, docs, and config:
322353

323354
​```bash
@@ -332,17 +363,20 @@ Use `semble find-related` to discover code similar to a known location (pass `fi
332363
semble find-related src/auth.py 42 ./my-project
333364
​```
334365

366+
Like search, `find-related` also accepts an `--index` argument.
367+
335368
`path` defaults to the current directory when omitted; git URLs are accepted.
336369

337370
If `semble` is not on `$PATH`, use `uvx --from "semble[mcp]" semble` in its place.
338371

339-
## Workflow
372+
### Workflow
340373

341-
1. Start with `semble search` to find relevant chunks.
342-
2. Use `--content docs` for documentation, `--content config` for config files, or `--content all` for everything.
343-
3. Inspect full files only when the returned chunk is not enough context.
344-
4. Optionally use `semble find-related` with a promising result's `file_path` and `line` to discover related implementations.
345-
5. Use grep only when you need exhaustive literal matches or quick confirmation of an exact string.
374+
1. Index the repo using `semble index -o cached_index`.
375+
2. Start with `semble search` to find relevant chunks. Pass the index to achieve results faster.
376+
3. Use `--content docs` for documentation, `--content config` for config files, or `--content all` for everything.
377+
4. Inspect full files only when the returned chunk does not give enough context.
378+
5. Optionally use `semble find-related` with a promising result's `file_path` and `line` to discover related implementations.
379+
6. Use grep only when you need exhaustive literal matches or quick confirmation of an exact string.
346380
```
347381

348382
### Sub-agent setup
@@ -365,8 +399,14 @@ If semble is not on `$PATH`, prefix the command with `uvx --from "semble[mcp]"`.
365399
Semble also ships as a standalone CLI. This is useful in scripts or anywhere you want search results without an MCP session.
366400

367401
```bash
402+
# Index a local repository
403+
semble index ./my-project -o my-index
404+
368405
# Search a local repo
369406
semble search "authentication flow" ./my-project
407+
# Or with index (significantly faster)
408+
# the index flag applies to all commands below.
409+
semble search "authentication flow" --index my-index
370410

371411
# Search for a symbol or identifier
372412
semble search "save_pretrained" ./my-project

benchmarks/baselines/ablations.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from dataclasses import asdict
66

77
import numpy as np
8-
from model2vec import StaticModel
98

109
from benchmarks.data import (
1110
RepoSpec,
@@ -38,8 +37,6 @@
3837
def _bench(
3938
repo_tasks: dict[str, list[Task]],
4039
specs: dict[str, RepoSpec],
41-
model: StaticModel,
42-
modes: list[str],
4340
*,
4441
verbose: bool = False,
4542
) -> list[RepoResult]:
@@ -62,7 +59,7 @@ def _bench(
6259
print(f"\n--- {repo} ---", file=sys.stderr)
6360

6461
started = time.perf_counter()
65-
index = SembleIndex.from_path(spec.benchmark_dir, model=model)
62+
index = SembleIndex.from_path(spec.benchmark_dir)
6663
index_ms = (time.perf_counter() - started) * 1000
6764

6865
for mode, (alpha, rerank) in sorted(_MODE_PARAMS.items()):
@@ -98,30 +95,26 @@ def _bench(
9895
def _parse_args() -> argparse.Namespace:
9996
parser = argparse.ArgumentParser(description="semble ablation benchmarks.")
10097
add_filter_args(parser, verbose=True)
101-
parser.add_argument(
102-
"--mode", action="append", default=[], choices=sorted(_MODE_PARAMS), help="Mode(s) to evaluate (default: all)."
103-
)
10498
return parser.parse_args()
10599

106100

107101
def main() -> None:
108102
"""Run the semble ablation benchmarks."""
109103
args = _parse_args()
110-
modes = args.mode or sorted(_MODE_PARAMS)
111104

112105
repo_specs, tasks = load_filtered_tasks(args.repo or None, args.language or None)
113106

114107
print("Loading model...", file=sys.stderr)
115108
started = time.perf_counter()
116-
model = StaticModel.from_pretrained(_DEFAULT_MODEL_NAME)
117109
print(f"Loaded in {(time.perf_counter() - started) * 1000:.0f}ms", file=sys.stderr)
118110
print(file=sys.stderr)
119111

120-
results = _bench(grouped_tasks(tasks), repo_specs, model, modes, verbose=args.verbose)
112+
results = _bench(grouped_tasks(tasks), repo_specs, verbose=args.verbose)
121113

122114
if not results:
123115
return
124116

117+
modes = sorted(_MODE_PARAMS)
125118
print(file=sys.stderr)
126119
for mode in modes:
127120
mode_results = [r for r in results if r.mode == mode]

benchmarks/baselines/coderankembed.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ class RepoResult:
6363
def _evaluate(
6464
index: SembleIndex,
6565
tasks: list[Task],
66-
mode: str,
6766
*,
6867
verbose: bool = False,
6968
) -> tuple[float, float, list[float], dict[str, float]]:
@@ -78,7 +77,7 @@ def _evaluate(
7877
results: list[SearchResult] = []
7978
for _ in range(_LATENCY_RUNS):
8079
started = time.perf_counter()
81-
results = index.search(task.query, top_k=_TOP_K, mode=mode)
80+
results = index.search(task.query, top_k=_TOP_K)
8281
query_latencies.append((time.perf_counter() - started) * 1000)
8382
latencies.append(float(np.median(query_latencies)))
8483

@@ -176,12 +175,12 @@ def _bench(
176175
print(f"\n--- {repo} ---", file=sys.stderr)
177176

178177
started = time.perf_counter()
179-
index = SembleIndex.from_path(spec.benchmark_dir, model=model)
178+
index = SembleIndex.from_path(spec.benchmark_dir)
180179
index_ms = (time.perf_counter() - started) * 1000
181180

182181
repo_results: list[RepoResult] = []
183182
for mode in modes:
184-
ndcg5, ndcg10, latencies, by_category = _evaluate(index, tasks, mode, verbose=verbose)
183+
ndcg5, ndcg10, latencies, by_category = _evaluate(index, tasks, verbose=verbose)
185184
p50, p90 = np.percentile(latencies, [50, 90]).tolist()
186185
result = RepoResult(
187186
repo=repo,

0 commit comments

Comments
 (0)