docugraph 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. docugraph-0.2.0/.github/workflows/bench.yml +79 -0
  2. {docugraph-0.1.0 → docugraph-0.2.0}/.github/workflows/ci.yml +1 -1
  3. {docugraph-0.1.0 → docugraph-0.2.0}/.github/workflows/docker-publish.yml +11 -3
  4. {docugraph-0.1.0 → docugraph-0.2.0}/.github/workflows/release.yml +9 -3
  5. {docugraph-0.1.0 → docugraph-0.2.0}/PKG-INFO +42 -1
  6. {docugraph-0.1.0 → docugraph-0.2.0}/README.md +37 -0
  7. docugraph-0.2.0/benchmarks/retrieval/MANIFEST.json +13 -0
  8. docugraph-0.2.0/benchmarks/retrieval/baselines/bge-small-en-v1.5.json +2669 -0
  9. docugraph-0.2.0/benchmarks/retrieval/corpus.jsonl +36 -0
  10. docugraph-0.2.0/benchmarks/retrieval/queries.jsonl +21 -0
  11. {docugraph-0.1.0 → docugraph-0.2.0}/docs/configuration.md +1 -1
  12. {docugraph-0.1.0 → docugraph-0.2.0}/pyproject.toml +7 -1
  13. docugraph-0.2.0/src/docugraph/evals/__init__.py +12 -0
  14. docugraph-0.2.0/src/docugraph/evals/arms.py +111 -0
  15. docugraph-0.2.0/src/docugraph/evals/compare.py +106 -0
  16. docugraph-0.2.0/src/docugraph/evals/dataset.py +210 -0
  17. docugraph-0.2.0/src/docugraph/evals/metrics.py +123 -0
  18. docugraph-0.2.0/src/docugraph/evals/runner.py +169 -0
  19. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/interfaces/cli.py +176 -0
  20. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/retrieval/hybrid_search.py +7 -3
  21. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/storage/vector_store.py +122 -21
  22. docugraph-0.2.0/tests/integration/test_bench_runner.py +126 -0
  23. docugraph-0.2.0/tests/unit/test_bench_dataset.py +133 -0
  24. docugraph-0.2.0/tests/unit/test_bench_metrics.py +95 -0
  25. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_hybrid_graph_status.py +7 -0
  26. {docugraph-0.1.0 → docugraph-0.2.0}/uv.lock +3 -3
  27. {docugraph-0.1.0 → docugraph-0.2.0}/.dockerignore +0 -0
  28. {docugraph-0.1.0 → docugraph-0.2.0}/.github/dependabot.yml +0 -0
  29. {docugraph-0.1.0 → docugraph-0.2.0}/.github/workflows/live-provider-smoke.yml +0 -0
  30. {docugraph-0.1.0 → docugraph-0.2.0}/.github/workflows/live-reranker.yml +0 -0
  31. {docugraph-0.1.0 → docugraph-0.2.0}/.gitignore +0 -0
  32. {docugraph-0.1.0 → docugraph-0.2.0}/.pre-commit-config.yaml +0 -0
  33. {docugraph-0.1.0 → docugraph-0.2.0}/.python-version +0 -0
  34. {docugraph-0.1.0 → docugraph-0.2.0}/LICENSE +0 -0
  35. {docugraph-0.1.0 → docugraph-0.2.0}/NOTICE +0 -0
  36. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/README.md +0 -0
  37. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/hooks/docugraph-auto-index.sh +0 -0
  38. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/hooks/docugraph-auto-search.sh +0 -0
  39. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/hooks/docugraph-post-compact.sh +0 -0
  40. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/hooks/docugraph-pre-compact.sh +0 -0
  41. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/hooks/docugraph-rule-check.sh +0 -0
  42. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/hooks/docugraph-session-end.sh +0 -0
  43. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/hooks/docugraph-session-start.sh +0 -0
  44. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/install.sh +0 -0
  45. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/settings.json +0 -0
  46. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/skills/architecture/SKILL.md +0 -0
  47. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/skills/context/SKILL.md +0 -0
  48. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/skills/docs/SKILL.md +0 -0
  49. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/skills/index/SKILL.md +0 -0
  50. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/skills/recall/SKILL.md +0 -0
  51. {docugraph-0.1.0 → docugraph-0.2.0}/claude-integration/skills/remember/SKILL.md +0 -0
  52. {docugraph-0.1.0 → docugraph-0.2.0}/docker/Dockerfile +0 -0
  53. {docugraph-0.1.0 → docugraph-0.2.0}/docker/docker-compose.yml +0 -0
  54. {docugraph-0.1.0 → docugraph-0.2.0}/docker-compose.yml +0 -0
  55. {docugraph-0.1.0 → docugraph-0.2.0}/docs/SECURITY.md +0 -0
  56. {docugraph-0.1.0 → docugraph-0.2.0}/docs/quickstart.md +0 -0
  57. {docugraph-0.1.0 → docugraph-0.2.0}/examples/crawl_and_search.py +0 -0
  58. {docugraph-0.1.0 → docugraph-0.2.0}/examples/index_local_repo.py +0 -0
  59. {docugraph-0.1.0 → docugraph-0.2.0}/examples/mcp_client_example.py +0 -0
  60. {docugraph-0.1.0 → docugraph-0.2.0}/scripts/live_provider_smoke.py +0 -0
  61. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/__init__.py +0 -0
  62. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/_version.py +0 -0
  63. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/agents/__init__.py +0 -0
  64. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/agents/coding_agent.py +0 -0
  65. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/agents/tools/__init__.py +0 -0
  66. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/agents/tools/graph_query.py +0 -0
  67. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/agents/tools/memory_ops.py +0 -0
  68. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/agents/tools/search_code.py +0 -0
  69. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/agents/tools/search_docs.py +0 -0
  70. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/core/__init__.py +0 -0
  71. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/core/config.py +0 -0
  72. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/core/embeddings.py +0 -0
  73. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/core/llm.py +0 -0
  74. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/core/models.py +0 -0
  75. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/ingestion/__init__.py +0 -0
  76. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/ingestion/api_docs.py +0 -0
  77. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/ingestion/chunker.py +0 -0
  78. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/ingestion/crawler.py +0 -0
  79. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/ingestion/git_indexer.py +0 -0
  80. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/ingestion/local_files.py +0 -0
  81. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/interfaces/__init__.py +0 -0
  82. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/interfaces/mcp_server.py +0 -0
  83. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/retrieval/__init__.py +0 -0
  84. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/retrieval/context_builder.py +0 -0
  85. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/retrieval/reranker.py +0 -0
  86. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/storage/__init__.py +0 -0
  87. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/storage/graph_store.py +0 -0
  88. {docugraph-0.1.0 → docugraph-0.2.0}/src/docugraph/storage/memory_store.py +0 -0
  89. {docugraph-0.1.0 → docugraph-0.2.0}/tests/e2e/__init__.py +0 -0
  90. {docugraph-0.1.0 → docugraph-0.2.0}/tests/e2e/test_cli.py +0 -0
  91. {docugraph-0.1.0 → docugraph-0.2.0}/tests/e2e/test_hooks.py +0 -0
  92. {docugraph-0.1.0 → docugraph-0.2.0}/tests/e2e/test_mcp_server.py +0 -0
  93. {docugraph-0.1.0 → docugraph-0.2.0}/tests/e2e/tools_snapshot.json +0 -0
  94. {docugraph-0.1.0 → docugraph-0.2.0}/tests/integration/__init__.py +0 -0
  95. {docugraph-0.1.0 → docugraph-0.2.0}/tests/integration/test_graph_store_init.py +0 -0
  96. {docugraph-0.1.0 → docugraph-0.2.0}/tests/integration/test_memory_store.py +0 -0
  97. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/__init__.py +0 -0
  98. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_chunker.py +0 -0
  99. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_cloud_provider_sdks.py +0 -0
  100. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_docs_consistency.py +0 -0
  101. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_embeddings.py +0 -0
  102. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_event_loop_acquisition.py +0 -0
  103. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_graph_embedder_adapter.py +0 -0
  104. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_graphiti_llm_client.py +0 -0
  105. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_llm_preflight.py +0 -0
  106. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_models.py +0 -0
  107. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_no_model_ids_in_source.py +0 -0
  108. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_reranker.py +0 -0
  109. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_vector_store.py +0 -0
  110. {docugraph-0.1.0 → docugraph-0.2.0}/tests/unit/test_version.py +0 -0
@@ -0,0 +1,79 @@
1
+ # The retrieval benchmark, run against the real embedder, weekly and on demand.
2
+ #
3
+ # Fast CI proves the harness with a deterministic fake embedder
4
+ # (tests/integration/test_bench_runner.py) and never downloads weights. What
5
+ # that leaves unexercised is the thing the benchmark exists for: the numbers
6
+ # on the shipped model. This job runs `docugraph bench run` on the real
7
+ # bge-small-en-v1.5 (~130 MB, cached between runs) and holds the result
8
+ # against the committed baseline with `docugraph bench compare`, which exits 1
9
+ # when nDCG@10 -- overall or in any single failure-mode category -- drops by
10
+ # more than the tolerance.
11
+ #
12
+ # The baseline under benchmarks/retrieval/baselines/ is committed, per
13
+ # embedding model, and regenerated deliberately: when the dataset or the
14
+ # retrieval stack changes on purpose, a human reruns the benchmark, reads the
15
+ # delta table, and commits the new baseline with the change that moved it.
16
+ # Until a baseline exists for the model, this job fails -- a comparison
17
+ # against nothing proving nothing would be green that means "checked nothing"
18
+ # (the live-reranker header has the longer form of this argument).
19
+ #
20
+ # Reranking arms are not measured here: the cross-encoder is ~2.3 GB and has
21
+ # its own opt-in workflow; run `docugraph bench run --rerank` locally when a
22
+ # reranker change needs numbers.
23
+ name: Retrieval benchmark
24
+
25
+ on:
26
+ # Manual: Actions -> Retrieval benchmark -> Run workflow.
27
+ workflow_dispatch:
28
+ schedule:
29
+ # Weekly, Monday 06:17 UTC. An off-hour minute, deliberately: scheduled
30
+ # workflows on :00 queue behind everyone else's.
31
+ - cron: "17 6 * * 1"
32
+
33
+ env:
34
+ FASTEMBED_CACHE_PATH: /home/runner/.cache/fastembed
35
+
36
+ jobs:
37
+ bench:
38
+ name: bench run · compare to baseline
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - name: Checkout repository
42
+ uses: actions/checkout@v4
43
+
44
+ - name: Set up Python
45
+ uses: actions/setup-python@v5
46
+ with:
47
+ python-version: '3.12'
48
+
49
+ - name: Install uv
50
+ uses: astral-sh/setup-uv@v4
51
+
52
+ - name: Sync dependencies
53
+ run: uv sync --all-extras
54
+
55
+ - name: Cache embedding model
56
+ uses: actions/cache@v4
57
+ with:
58
+ path: /home/runner/.cache/fastembed
59
+ key: fastembed-bge-small-en-v1.5
60
+
61
+ - name: Run benchmark
62
+ run: uv run docugraph bench run --json bench-report.json
63
+
64
+ - name: Compare against committed baseline
65
+ run: |
66
+ baseline="benchmarks/retrieval/baselines/bge-small-en-v1.5.json"
67
+ if [ ! -f "$baseline" ]; then
68
+ echo "::error::No committed baseline at $baseline. Run 'docugraph bench run --json' locally and commit the report as the baseline; a compare against nothing is not a gate."
69
+ exit 1
70
+ fi
71
+ uv run docugraph bench compare "$baseline" bench-report.json
72
+
73
+ - name: Upload report
74
+ if: always()
75
+ uses: actions/upload-artifact@v4
76
+ with:
77
+ name: bench-report
78
+ path: bench-report.json
79
+ if-no-files-found: ignore
@@ -165,7 +165,7 @@ jobs:
165
165
  python3 - <<'PY'
166
166
  import json, pathlib, sys
167
167
 
168
- EXPECTED = "0.1.0" # the version this repo publishes; pinned deliberately
168
+ EXPECTED = "0.1.1" # the version this repo publishes; pinned deliberately
169
169
  raw = pathlib.Path("/tmp/handshake.jsonl").read_text()
170
170
 
171
171
  # stdout carries the JSON-RPC stream and nothing else. A stray line
@@ -12,7 +12,6 @@ on:
12
12
 
13
13
  env:
14
14
  REGISTRY: ghcr.io
15
- IMAGE_NAME: ${{ github.repository }}
16
15
 
17
16
  jobs:
18
17
  build-and-push:
@@ -25,6 +24,15 @@ jobs:
25
24
  - name: Checkout repository
26
25
  uses: actions/checkout@v4
27
26
 
27
+ # ghcr.io requires a lowercase repository path, and `github.repository`
28
+ # keeps the owner's case. Expressions have no lowercase function, so it
29
+ # is done in the shell. `docker/metadata-action` lowercases the `images:`
30
+ # input itself, but the tags built by hand below are not, so they all read
31
+ # the same resolved value.
32
+ - name: Resolve image name
33
+ id: image
34
+ run: echo "name=${REGISTRY}/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
35
+
28
36
  # The arm64 half of the multi-platform build runs under emulation on the
29
37
  # amd64 runners, which requires QEMU registered with binfmt. Without this
30
38
  # step the arm64 build fails.
@@ -45,7 +53,7 @@ jobs:
45
53
  id: meta
46
54
  uses: docker/metadata-action@v5
47
55
  with:
48
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
56
+ images: ${{ steps.image.outputs.name }}
49
57
  tags: |
50
58
  # The tag as pushed (v1.2.3)
51
59
  type=ref,event=tag
@@ -78,7 +86,7 @@ jobs:
78
86
  file: ./docker/Dockerfile
79
87
  target: development
80
88
  push: true
81
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
89
+ tags: ${{ steps.image.outputs.name }}:dev
82
90
  labels: ${{ steps.meta.outputs.labels }}
83
91
  cache-from: type=gha
84
92
  cache-to: type=gha,mode=max
@@ -80,12 +80,18 @@ jobs:
80
80
 
81
81
  env:
82
82
  REGISTRY: ghcr.io
83
- IMAGE_NAME: ${{ github.repository }}
84
83
 
85
84
  steps:
86
85
  - name: Checkout repository
87
86
  uses: actions/checkout@v4
88
87
 
88
+ # ghcr.io requires a lowercase repository path, and `github.repository`
89
+ # keeps the owner's case. Expressions have no lowercase function, so it
90
+ # is done in the shell.
91
+ - name: Resolve image name
92
+ id: image
93
+ run: echo "name=${REGISTRY}/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
94
+
89
95
  # linux/amd64 only. Building arm64 means running it under emulation, which
90
96
  # needs a QEMU step registered with binfmt -- the step that used to sit
91
97
  # here. It is a post-release item: add both back once an arm64 image has
@@ -117,8 +123,8 @@ jobs:
117
123
  target: production
118
124
  push: true
119
125
  tags: |
120
- ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
121
- ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
126
+ ${{ steps.image.outputs.name }}:${{ steps.version.outputs.version }}
127
+ ${{ steps.image.outputs.name }}:latest
122
128
  cache-from: type=gha
123
129
  cache-to: type=gha,mode=max
124
130
  platforms: linux/amd64
@@ -1,7 +1,11 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docugraph
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Documentation RAG for coding agents: vector search, knowledge graph, agent memory, and an MCP server — torch-free local embeddings via fastembed
5
+ Project-URL: Homepage, https://github.com/CJ-coding-apps/docugraph
6
+ Project-URL: Repository, https://github.com/CJ-coding-apps/docugraph
7
+ Project-URL: Issues, https://github.com/CJ-coding-apps/docugraph/issues
8
+ Project-URL: Changelog, https://github.com/CJ-coding-apps/docugraph/releases
5
9
  Author-email: Jim Cuttler <jimcuttler@gmail.com>
6
10
  License: Apache-2.0
7
11
  License-File: LICENSE
@@ -74,6 +78,7 @@ Code and other MCP clients. The CLI is for out-of-band index bootstrapping
74
78
  - **Agent memory**: persistent, repository/branch-scoped key-value store
75
79
  - **Optional reranking**: local multilingual cross-encoder (BAAI/bge-reranker-v2-m3), opt-in
76
80
  - **Torch-free**: default embeddings and reranking run on ONNX — no PyTorch in the dependency tree
81
+ - **Measured retrieval**: a committed adversarial benchmark scores every search mode per failure category, with a regression gate (`docugraph bench`)
77
82
 
78
83
  ## Quick start
79
84
 
@@ -304,6 +309,42 @@ required**. However, Graphiti performs entity extraction with an **LLM**, so
304
309
  OpenAI/Anthropic key). Without one, the graph tools degrade gracefully with a
305
310
  clear message; vector and keyword search are unaffected.
306
311
 
312
+ ## Retrieval benchmark
313
+
314
+ Retrieval quality is measured, not asserted. `benchmarks/retrieval/` commits a
315
+ hand-authored documentation corpus for a fictional SDK plus graded relevance
316
+ judgments, built adversarially around seven failure modes: exact-token lookups
317
+ (error codes, parameter names), paraphrase queries with no lexical overlap,
318
+ keyword-stuffed distractor pages, near-sibling semantic distractors, version-
319
+ pinned questions, acronym/synonym gaps, and "X without Y" constraint queries.
320
+ Each query records the trap it sets in a `design_note`, and a manifest pins the
321
+ dataset by hash — editing it is a deliberate act that invalidates baselines.
322
+
323
+ ```bash
324
+ # Score every arm (vector-only, BM25-only, LanceDB-native hybrid, RRF, weighted)
325
+ # against a temporary store built from the committed corpus:
326
+ docugraph bench run --json report.json
327
+
328
+ # Hold a report against a committed baseline; exits 1 when nDCG@10 drops by
329
+ # more than the tolerance, overall or in any single category:
330
+ docugraph bench compare benchmarks/retrieval/baselines/bge-small-en-v1.5.json report.json
331
+ ```
332
+
333
+ Reported metrics are recall@1/5/10, MRR@10, and nDCG@10 (graded, linear gain),
334
+ macro-averaged overall and per category. The per-category table is the point:
335
+ a fusion change that wins on average while losing exact-code lookup shows up
336
+ as the regression it is. Baselines are per embedding model and live in
337
+ `benchmarks/retrieval/baselines/`; the `Retrieval benchmark` workflow re-runs
338
+ the suite weekly against the committed baseline.
339
+
340
+ Honest scope: the corpus is small and purpose-built, so scores are comparable
341
+ across docugraph changes — not with BEIR or any external leaderboard. The
342
+ first thing this benchmark caught predates it: before v0.2.0 no FTS index was
343
+ ever created, so "keyword" search silently returned vector results and
344
+ RRF fused the vector ranking with itself. The benchmark's `fts` arm and the
345
+ hybrid arm's refuse-to-score-on-fallback check exist so that class of silent
346
+ degradation cannot come back unnoticed.
347
+
307
348
  ### KuzuDB version
308
349
 
309
350
  `kuzu` is pinned to `==0.11.3` in `pyproject.toml` (reached through
@@ -16,6 +16,7 @@ Code and other MCP clients. The CLI is for out-of-band index bootstrapping
16
16
  - **Agent memory**: persistent, repository/branch-scoped key-value store
17
17
  - **Optional reranking**: local multilingual cross-encoder (BAAI/bge-reranker-v2-m3), opt-in
18
18
  - **Torch-free**: default embeddings and reranking run on ONNX — no PyTorch in the dependency tree
19
+ - **Measured retrieval**: a committed adversarial benchmark scores every search mode per failure category, with a regression gate (`docugraph bench`)
19
20
 
20
21
  ## Quick start
21
22
 
@@ -246,6 +247,42 @@ required**. However, Graphiti performs entity extraction with an **LLM**, so
246
247
  OpenAI/Anthropic key). Without one, the graph tools degrade gracefully with a
247
248
  clear message; vector and keyword search are unaffected.
248
249
 
250
+ ## Retrieval benchmark
251
+
252
+ Retrieval quality is measured, not asserted. `benchmarks/retrieval/` commits a
253
+ hand-authored documentation corpus for a fictional SDK plus graded relevance
254
+ judgments, built adversarially around seven failure modes: exact-token lookups
255
+ (error codes, parameter names), paraphrase queries with no lexical overlap,
256
+ keyword-stuffed distractor pages, near-sibling semantic distractors, version-
257
+ pinned questions, acronym/synonym gaps, and "X without Y" constraint queries.
258
+ Each query records the trap it sets in a `design_note`, and a manifest pins the
259
+ dataset by hash — editing it is a deliberate act that invalidates baselines.
260
+
261
+ ```bash
262
+ # Score every arm (vector-only, BM25-only, LanceDB-native hybrid, RRF, weighted)
263
+ # against a temporary store built from the committed corpus:
264
+ docugraph bench run --json report.json
265
+
266
+ # Hold a report against a committed baseline; exits 1 when nDCG@10 drops by
267
+ # more than the tolerance, overall or in any single category:
268
+ docugraph bench compare benchmarks/retrieval/baselines/bge-small-en-v1.5.json report.json
269
+ ```
270
+
271
+ Reported metrics are recall@1/5/10, MRR@10, and nDCG@10 (graded, linear gain),
272
+ macro-averaged overall and per category. The per-category table is the point:
273
+ a fusion change that wins on average while losing exact-code lookup shows up
274
+ as the regression it is. Baselines are per embedding model and live in
275
+ `benchmarks/retrieval/baselines/`; the `Retrieval benchmark` workflow re-runs
276
+ the suite weekly against the committed baseline.
277
+
278
+ Honest scope: the corpus is small and purpose-built, so scores are comparable
279
+ across docugraph changes — not with BEIR or any external leaderboard. The
280
+ first thing this benchmark caught predates it: before v0.2.0 no FTS index was
281
+ ever created, so "keyword" search silently returned vector results and
282
+ RRF fused the vector ranking with itself. The benchmark's `fts` arm and the
283
+ hybrid arm's refuse-to-score-on-fallback check exist so that class of silent
284
+ degradation cannot come back unnoticed.
285
+
249
286
  ### KuzuDB version
250
287
 
251
288
  `kuzu` is pinned to `==0.11.3` in `pyproject.toml` (reached through
@@ -0,0 +1,13 @@
1
+ {
2
+ "dataset": "docugraph-retrieval-bench",
3
+ "version": "0.1.0-pilot",
4
+ "description": "Hand-authored adversarial documentation corpus (fictional 'Quorva' SDK) with graded relevance judgments across seven retrieval failure modes. Pilot scale; see the benchmark README section for authoring rules.",
5
+ "counts": {
6
+ "corpus": 36,
7
+ "queries": 21
8
+ },
9
+ "sha256": {
10
+ "corpus.jsonl": "6318b464c188eccd62a4f6eea8c457e7c63780d1d3b7d3e6a0728795955ea5ca",
11
+ "queries.jsonl": "afcc08a10c73d350244fd13c2006a047aa82d18a4be4fbfc2323cb3741979344"
12
+ }
13
+ }