rag-your-code 1.4.0__tar.gz → 1.4.1__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.
- {rag_your_code-1.4.0/src/rag_your_code.egg-info → rag_your_code-1.4.1}/PKG-INFO +31 -25
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/README.md +30 -24
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/pyproject.toml +1 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.1/src/rag_your_code.egg-info}/PKG-INFO +31 -25
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/__init__.py +1 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/annotate.py +9 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/document.py +1 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/parser.py +2 -2
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/search.py +36 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_ranking.py +54 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/LICENSE +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/setup.cfg +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/rag_your_code.egg-info/SOURCES.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/rag_your_code.egg-info/dependency_links.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/rag_your_code.egg-info/entry_points.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/rag_your_code.egg-info/requires.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/rag_your_code.egg-info/top_level.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/agentic.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/cli.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/config.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/descriptions.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/embeddings.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/graph.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/indexer.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/models.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/providers.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/py.typed +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/src/ragyourcode/workflow.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_absent_queries.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_agent_protocol.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_agentic.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_config.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_descriptions.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_doc_comments.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_document.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_e2e_cli.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_evidence.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_golden.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_graph_incremental.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_language_fixtures.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_large_repo.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_local_model.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_metadata.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_multilanguage.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_parser_edges.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_providers.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_ragyourcode.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_repo_queries.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_resilience.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_retrieval_correctness.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.1}/tests/test_workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rag-your-code
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: A local, explainable RAG index for codebases and coding agents
|
|
5
5
|
Author: rag-your-code contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -295,11 +295,11 @@ repository had grown by ninety units.
|
|
|
295
295
|
|---|---|---|---|---|---|
|
|
296
296
|
| **A** Flask 3.1.3, no descriptions | what a first-time user gets | 35 | 0.200 | 0.286 | 0.238 |
|
|
297
297
|
| **B** this repo, generated descriptions only | a cold index of familiar code | 70 | 0.314 | 0.471 | 0.383 |
|
|
298
|
-
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.
|
|
298
|
+
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.509 |
|
|
299
299
|
|
|
300
300
|
The corpora, without which none of the above is reproducible — **A** 1,572
|
|
301
|
-
units, `5fd51169eacc`; **B**
|
|
302
|
-
`
|
|
301
|
+
units, `5fd51169eacc`; **B** 584 units, `fb1f841fa43a`; **C** 584 units,
|
|
302
|
+
`c9df00350cbd`. Ruler A grades **a copy of Flask 3.1.3 carried in this
|
|
303
303
|
repository**, at [`benchmarks/corpus/flask`](benchmarks/corpus/flask), pinned
|
|
304
304
|
to commit `22d9247`. Through 1.3.0 it graded a checkout on one machine, and
|
|
305
305
|
that cost three things: two questions pointed at a declaration the subject had
|
|
@@ -330,10 +330,10 @@ Flask does not implement get through on exactly that.
|
|
|
330
330
|
|
|
331
331
|
| gate | A hit@1/3/MRR | B hit@1/3/MRR | C hit@1/3/MRR | silence own / foreign |
|
|
332
332
|
|---|---|---|---|---|
|
|
333
|
-
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.
|
|
334
|
-
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.
|
|
335
|
-
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.
|
|
336
|
-
| **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.
|
|
333
|
+
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.569 | 0.000 / 0.000 |
|
|
334
|
+
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.564 | 0.567 / 0.733 |
|
|
335
|
+
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.509 | 0.967 / 0.800 |
|
|
336
|
+
| **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.509 | **0.967 / 0.833** |
|
|
337
337
|
|
|
338
338
|
Ruler A is **unmoved by either bar**, and B by concentration. The whole cost is
|
|
339
339
|
three questions of seventy at hit@1 on the warmest ruler, and six at hit@3.
|
|
@@ -352,20 +352,20 @@ from 0.314 and C to 0.329 from 0.443. **0.28 was chosen before this corpus
|
|
|
352
352
|
existed and survived meeting it**, which is the only kind of evidence a default
|
|
353
353
|
can have.
|
|
354
354
|
|
|
355
|
-
**Latency** — warm corpus,
|
|
355
|
+
**Latency** — warm corpus, 584 units `c9df00350cbd`, five consecutive
|
|
356
356
|
invocations of `python -m benchmarks.query_latency --repeats 10` (420 samples
|
|
357
357
|
each):
|
|
358
358
|
|
|
359
359
|
| | | across the five |
|
|
360
360
|
|---|---|---|
|
|
361
|
-
| query, median | **
|
|
362
|
-
| query, p95 | 1.
|
|
363
|
-
| refusing an unanswerable query | **0.
|
|
364
|
-
| refusal cheaper than answering by | **~36×** |
|
|
361
|
+
| query, median | **0.61 ms** | 0.60 – 0.64 |
|
|
362
|
+
| query, p95 | 1.09 ms | 1.06 – 1.14 |
|
|
363
|
+
| refusing an unanswerable query | **0.017 ms** | 0.015 – 0.019 |
|
|
364
|
+
| refusal cheaper than answering by | **~36×** | 33 – 41 |
|
|
365
365
|
|
|
366
366
|
Two significant figures and a spread, because that is the precision the
|
|
367
|
-
measurement has. Across
|
|
368
|
-
machine the median has landed anywhere from 0.
|
|
367
|
+
measurement has. Across twenty invocations over three releases on the same idle
|
|
368
|
+
machine the median has landed anywhere from 0.51 to 1.44 ms and p95 from 0.85
|
|
369
369
|
to 7.34 ms — a band wider than any change the code has ever made to this
|
|
370
370
|
number. Releases before 1.3.0 published `0.83 ms / p95 1.68 ms` to three
|
|
371
371
|
figures from a script that was never committed; both values sit inside that
|
|
@@ -420,17 +420,17 @@ framework reversed it. The honest claim is narrower than either table alone:
|
|
|
420
420
|
| right file first | 22.9% | **37.1%** |
|
|
421
421
|
| right file in top 3 | 45.7% | **57.1%** |
|
|
422
422
|
| lines it hands back, all questions | 17,641 | — |
|
|
423
|
-
| characters returned, all questions | 1,415,656 | **
|
|
423
|
+
| characters returned, all questions | 1,415,656 | **258,236** |
|
|
424
424
|
| questions it answers | 30 | 30 |
|
|
425
425
|
|
|
426
426
|
**Once the vocabulary exists, it is not close.**
|
|
427
427
|
|
|
428
|
-
| this repository · 70 questions ·
|
|
428
|
+
| this repository · 70 questions · 584 units `c9df00350cbd` · 304 described | Grep loop | rag-your-code |
|
|
429
429
|
|---|---|---|
|
|
430
430
|
| right file first | 22.9% | **58.6%** |
|
|
431
|
-
| right file in top 3 | 54.3% | **
|
|
432
|
-
| lines it hands back, all questions | 11,
|
|
433
|
-
| characters returned, all questions | 1,
|
|
431
|
+
| right file in top 3 | 54.3% | **77.1%** |
|
|
432
|
+
| lines it hands back, all questions | 11,959 | — |
|
|
433
|
+
| characters returned, all questions | 1,135,411 | **615,673** |
|
|
434
434
|
| questions it answers | **61** | 60 |
|
|
435
435
|
|
|
436
436
|
Those two tables are the whole argument of section 3.3, measured against a real
|
|
@@ -467,11 +467,17 @@ Four qualifications, because the table would otherwise flatter both sides:
|
|
|
467
467
|
`the` gets every file back in no order. It is also why Grep declines nine of
|
|
468
468
|
the seventy questions here — those had no word left that this corpus does not
|
|
469
469
|
use everywhere.
|
|
470
|
-
- **Payload is counted in characters on both sides
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
470
|
+
- **Payload is counted in characters on both sides**, and 1.4.1 changed what
|
|
471
|
+
fits in it. A generated description ends with the author's docstring so the
|
|
472
|
+
docstring is searchable, and the source printed below it said the same thing
|
|
473
|
+
again — 2,381 of 3,382 characters of prose header on Flask were a verbatim
|
|
474
|
+
repeat of the code beneath. The block no longer prints what the code shows,
|
|
475
|
+
and the same 12,000-character budget now carries **119 declarations instead
|
|
476
|
+
of 92** there, 323 instead of 305 here. Grep
|
|
477
|
+
hands back 18,600 characters per question it answers, unranked and without
|
|
478
|
+
spans, against 10,300 here, ranked and capped by `search.max_chars` — a
|
|
479
|
+
factor of 1.8. On Flask it is a factor of 5.5 — 47,200 characters against
|
|
480
|
+
8,600 — because a framework repeats its own vocabulary across many files and
|
|
475
481
|
Grep has no way to rank what it finds. Both sides decline the same five of
|
|
476
482
|
those 35 — and they are the five Chinese ones, all of them. A Chinese word is
|
|
477
483
|
not a substring of English source and it is not a token in an index built
|
|
@@ -266,11 +266,11 @@ repository had grown by ninety units.
|
|
|
266
266
|
|---|---|---|---|---|---|
|
|
267
267
|
| **A** Flask 3.1.3, no descriptions | what a first-time user gets | 35 | 0.200 | 0.286 | 0.238 |
|
|
268
268
|
| **B** this repo, generated descriptions only | a cold index of familiar code | 70 | 0.314 | 0.471 | 0.383 |
|
|
269
|
-
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.
|
|
269
|
+
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.509 |
|
|
270
270
|
|
|
271
271
|
The corpora, without which none of the above is reproducible — **A** 1,572
|
|
272
|
-
units, `5fd51169eacc`; **B**
|
|
273
|
-
`
|
|
272
|
+
units, `5fd51169eacc`; **B** 584 units, `fb1f841fa43a`; **C** 584 units,
|
|
273
|
+
`c9df00350cbd`. Ruler A grades **a copy of Flask 3.1.3 carried in this
|
|
274
274
|
repository**, at [`benchmarks/corpus/flask`](benchmarks/corpus/flask), pinned
|
|
275
275
|
to commit `22d9247`. Through 1.3.0 it graded a checkout on one machine, and
|
|
276
276
|
that cost three things: two questions pointed at a declaration the subject had
|
|
@@ -301,10 +301,10 @@ Flask does not implement get through on exactly that.
|
|
|
301
301
|
|
|
302
302
|
| gate | A hit@1/3/MRR | B hit@1/3/MRR | C hit@1/3/MRR | silence own / foreign |
|
|
303
303
|
|---|---|---|---|---|
|
|
304
|
-
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.
|
|
305
|
-
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.
|
|
306
|
-
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.
|
|
307
|
-
| **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.
|
|
304
|
+
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.569 | 0.000 / 0.000 |
|
|
305
|
+
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.564 | 0.567 / 0.733 |
|
|
306
|
+
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.509 | 0.967 / 0.800 |
|
|
307
|
+
| **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.509 | **0.967 / 0.833** |
|
|
308
308
|
|
|
309
309
|
Ruler A is **unmoved by either bar**, and B by concentration. The whole cost is
|
|
310
310
|
three questions of seventy at hit@1 on the warmest ruler, and six at hit@3.
|
|
@@ -323,20 +323,20 @@ from 0.314 and C to 0.329 from 0.443. **0.28 was chosen before this corpus
|
|
|
323
323
|
existed and survived meeting it**, which is the only kind of evidence a default
|
|
324
324
|
can have.
|
|
325
325
|
|
|
326
|
-
**Latency** — warm corpus,
|
|
326
|
+
**Latency** — warm corpus, 584 units `c9df00350cbd`, five consecutive
|
|
327
327
|
invocations of `python -m benchmarks.query_latency --repeats 10` (420 samples
|
|
328
328
|
each):
|
|
329
329
|
|
|
330
330
|
| | | across the five |
|
|
331
331
|
|---|---|---|
|
|
332
|
-
| query, median | **
|
|
333
|
-
| query, p95 | 1.
|
|
334
|
-
| refusing an unanswerable query | **0.
|
|
335
|
-
| refusal cheaper than answering by | **~36×** |
|
|
332
|
+
| query, median | **0.61 ms** | 0.60 – 0.64 |
|
|
333
|
+
| query, p95 | 1.09 ms | 1.06 – 1.14 |
|
|
334
|
+
| refusing an unanswerable query | **0.017 ms** | 0.015 – 0.019 |
|
|
335
|
+
| refusal cheaper than answering by | **~36×** | 33 – 41 |
|
|
336
336
|
|
|
337
337
|
Two significant figures and a spread, because that is the precision the
|
|
338
|
-
measurement has. Across
|
|
339
|
-
machine the median has landed anywhere from 0.
|
|
338
|
+
measurement has. Across twenty invocations over three releases on the same idle
|
|
339
|
+
machine the median has landed anywhere from 0.51 to 1.44 ms and p95 from 0.85
|
|
340
340
|
to 7.34 ms — a band wider than any change the code has ever made to this
|
|
341
341
|
number. Releases before 1.3.0 published `0.83 ms / p95 1.68 ms` to three
|
|
342
342
|
figures from a script that was never committed; both values sit inside that
|
|
@@ -391,17 +391,17 @@ framework reversed it. The honest claim is narrower than either table alone:
|
|
|
391
391
|
| right file first | 22.9% | **37.1%** |
|
|
392
392
|
| right file in top 3 | 45.7% | **57.1%** |
|
|
393
393
|
| lines it hands back, all questions | 17,641 | — |
|
|
394
|
-
| characters returned, all questions | 1,415,656 | **
|
|
394
|
+
| characters returned, all questions | 1,415,656 | **258,236** |
|
|
395
395
|
| questions it answers | 30 | 30 |
|
|
396
396
|
|
|
397
397
|
**Once the vocabulary exists, it is not close.**
|
|
398
398
|
|
|
399
|
-
| this repository · 70 questions ·
|
|
399
|
+
| this repository · 70 questions · 584 units `c9df00350cbd` · 304 described | Grep loop | rag-your-code |
|
|
400
400
|
|---|---|---|
|
|
401
401
|
| right file first | 22.9% | **58.6%** |
|
|
402
|
-
| right file in top 3 | 54.3% | **
|
|
403
|
-
| lines it hands back, all questions | 11,
|
|
404
|
-
| characters returned, all questions | 1,
|
|
402
|
+
| right file in top 3 | 54.3% | **77.1%** |
|
|
403
|
+
| lines it hands back, all questions | 11,959 | — |
|
|
404
|
+
| characters returned, all questions | 1,135,411 | **615,673** |
|
|
405
405
|
| questions it answers | **61** | 60 |
|
|
406
406
|
|
|
407
407
|
Those two tables are the whole argument of section 3.3, measured against a real
|
|
@@ -438,11 +438,17 @@ Four qualifications, because the table would otherwise flatter both sides:
|
|
|
438
438
|
`the` gets every file back in no order. It is also why Grep declines nine of
|
|
439
439
|
the seventy questions here — those had no word left that this corpus does not
|
|
440
440
|
use everywhere.
|
|
441
|
-
- **Payload is counted in characters on both sides
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
441
|
+
- **Payload is counted in characters on both sides**, and 1.4.1 changed what
|
|
442
|
+
fits in it. A generated description ends with the author's docstring so the
|
|
443
|
+
docstring is searchable, and the source printed below it said the same thing
|
|
444
|
+
again — 2,381 of 3,382 characters of prose header on Flask were a verbatim
|
|
445
|
+
repeat of the code beneath. The block no longer prints what the code shows,
|
|
446
|
+
and the same 12,000-character budget now carries **119 declarations instead
|
|
447
|
+
of 92** there, 323 instead of 305 here. Grep
|
|
448
|
+
hands back 18,600 characters per question it answers, unranked and without
|
|
449
|
+
spans, against 10,300 here, ranked and capped by `search.max_chars` — a
|
|
450
|
+
factor of 1.8. On Flask it is a factor of 5.5 — 47,200 characters against
|
|
451
|
+
8,600 — because a framework repeats its own vocabulary across many files and
|
|
446
452
|
Grep has no way to rank what it finds. Both sides decline the same five of
|
|
447
453
|
those 35 — and they are the five Chinese ones, all of them. A Chinese word is
|
|
448
454
|
not a substring of English source and it is not a token in an index built
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rag-your-code
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: A local, explainable RAG index for codebases and coding agents
|
|
5
5
|
Author: rag-your-code contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -295,11 +295,11 @@ repository had grown by ninety units.
|
|
|
295
295
|
|---|---|---|---|---|---|
|
|
296
296
|
| **A** Flask 3.1.3, no descriptions | what a first-time user gets | 35 | 0.200 | 0.286 | 0.238 |
|
|
297
297
|
| **B** this repo, generated descriptions only | a cold index of familiar code | 70 | 0.314 | 0.471 | 0.383 |
|
|
298
|
-
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.
|
|
298
|
+
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.509 |
|
|
299
299
|
|
|
300
300
|
The corpora, without which none of the above is reproducible — **A** 1,572
|
|
301
|
-
units, `5fd51169eacc`; **B**
|
|
302
|
-
`
|
|
301
|
+
units, `5fd51169eacc`; **B** 584 units, `fb1f841fa43a`; **C** 584 units,
|
|
302
|
+
`c9df00350cbd`. Ruler A grades **a copy of Flask 3.1.3 carried in this
|
|
303
303
|
repository**, at [`benchmarks/corpus/flask`](benchmarks/corpus/flask), pinned
|
|
304
304
|
to commit `22d9247`. Through 1.3.0 it graded a checkout on one machine, and
|
|
305
305
|
that cost three things: two questions pointed at a declaration the subject had
|
|
@@ -330,10 +330,10 @@ Flask does not implement get through on exactly that.
|
|
|
330
330
|
|
|
331
331
|
| gate | A hit@1/3/MRR | B hit@1/3/MRR | C hit@1/3/MRR | silence own / foreign |
|
|
332
332
|
|---|---|---|---|---|
|
|
333
|
-
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.
|
|
334
|
-
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.
|
|
335
|
-
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.
|
|
336
|
-
| **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.
|
|
333
|
+
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.569 | 0.000 / 0.000 |
|
|
334
|
+
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.564 | 0.567 / 0.733 |
|
|
335
|
+
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.509 | 0.967 / 0.800 |
|
|
336
|
+
| **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.509 | **0.967 / 0.833** |
|
|
337
337
|
|
|
338
338
|
Ruler A is **unmoved by either bar**, and B by concentration. The whole cost is
|
|
339
339
|
three questions of seventy at hit@1 on the warmest ruler, and six at hit@3.
|
|
@@ -352,20 +352,20 @@ from 0.314 and C to 0.329 from 0.443. **0.28 was chosen before this corpus
|
|
|
352
352
|
existed and survived meeting it**, which is the only kind of evidence a default
|
|
353
353
|
can have.
|
|
354
354
|
|
|
355
|
-
**Latency** — warm corpus,
|
|
355
|
+
**Latency** — warm corpus, 584 units `c9df00350cbd`, five consecutive
|
|
356
356
|
invocations of `python -m benchmarks.query_latency --repeats 10` (420 samples
|
|
357
357
|
each):
|
|
358
358
|
|
|
359
359
|
| | | across the five |
|
|
360
360
|
|---|---|---|
|
|
361
|
-
| query, median | **
|
|
362
|
-
| query, p95 | 1.
|
|
363
|
-
| refusing an unanswerable query | **0.
|
|
364
|
-
| refusal cheaper than answering by | **~36×** |
|
|
361
|
+
| query, median | **0.61 ms** | 0.60 – 0.64 |
|
|
362
|
+
| query, p95 | 1.09 ms | 1.06 – 1.14 |
|
|
363
|
+
| refusing an unanswerable query | **0.017 ms** | 0.015 – 0.019 |
|
|
364
|
+
| refusal cheaper than answering by | **~36×** | 33 – 41 |
|
|
365
365
|
|
|
366
366
|
Two significant figures and a spread, because that is the precision the
|
|
367
|
-
measurement has. Across
|
|
368
|
-
machine the median has landed anywhere from 0.
|
|
367
|
+
measurement has. Across twenty invocations over three releases on the same idle
|
|
368
|
+
machine the median has landed anywhere from 0.51 to 1.44 ms and p95 from 0.85
|
|
369
369
|
to 7.34 ms — a band wider than any change the code has ever made to this
|
|
370
370
|
number. Releases before 1.3.0 published `0.83 ms / p95 1.68 ms` to three
|
|
371
371
|
figures from a script that was never committed; both values sit inside that
|
|
@@ -420,17 +420,17 @@ framework reversed it. The honest claim is narrower than either table alone:
|
|
|
420
420
|
| right file first | 22.9% | **37.1%** |
|
|
421
421
|
| right file in top 3 | 45.7% | **57.1%** |
|
|
422
422
|
| lines it hands back, all questions | 17,641 | — |
|
|
423
|
-
| characters returned, all questions | 1,415,656 | **
|
|
423
|
+
| characters returned, all questions | 1,415,656 | **258,236** |
|
|
424
424
|
| questions it answers | 30 | 30 |
|
|
425
425
|
|
|
426
426
|
**Once the vocabulary exists, it is not close.**
|
|
427
427
|
|
|
428
|
-
| this repository · 70 questions ·
|
|
428
|
+
| this repository · 70 questions · 584 units `c9df00350cbd` · 304 described | Grep loop | rag-your-code |
|
|
429
429
|
|---|---|---|
|
|
430
430
|
| right file first | 22.9% | **58.6%** |
|
|
431
|
-
| right file in top 3 | 54.3% | **
|
|
432
|
-
| lines it hands back, all questions | 11,
|
|
433
|
-
| characters returned, all questions | 1,
|
|
431
|
+
| right file in top 3 | 54.3% | **77.1%** |
|
|
432
|
+
| lines it hands back, all questions | 11,959 | — |
|
|
433
|
+
| characters returned, all questions | 1,135,411 | **615,673** |
|
|
434
434
|
| questions it answers | **61** | 60 |
|
|
435
435
|
|
|
436
436
|
Those two tables are the whole argument of section 3.3, measured against a real
|
|
@@ -467,11 +467,17 @@ Four qualifications, because the table would otherwise flatter both sides:
|
|
|
467
467
|
`the` gets every file back in no order. It is also why Grep declines nine of
|
|
468
468
|
the seventy questions here — those had no word left that this corpus does not
|
|
469
469
|
use everywhere.
|
|
470
|
-
- **Payload is counted in characters on both sides
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
470
|
+
- **Payload is counted in characters on both sides**, and 1.4.1 changed what
|
|
471
|
+
fits in it. A generated description ends with the author's docstring so the
|
|
472
|
+
docstring is searchable, and the source printed below it said the same thing
|
|
473
|
+
again — 2,381 of 3,382 characters of prose header on Flask were a verbatim
|
|
474
|
+
repeat of the code beneath. The block no longer prints what the code shows,
|
|
475
|
+
and the same 12,000-character budget now carries **119 declarations instead
|
|
476
|
+
of 92** there, 323 instead of 305 here. Grep
|
|
477
|
+
hands back 18,600 characters per question it answers, unranked and without
|
|
478
|
+
spans, against 10,300 here, ranked and capped by `search.max_chars` — a
|
|
479
|
+
factor of 1.8. On Flask it is a factor of 5.5 — 47,200 characters against
|
|
480
|
+
8,600 — because a framework repeats its own vocabulary across many files and
|
|
475
481
|
Grep has no way to rank what it finds. Both sides decline the same five of
|
|
476
482
|
those 35 — and they are the five Chinese ones, all of them. A Chinese word is
|
|
477
483
|
not a substring of English source and it is not a token in an index built
|
|
@@ -5,6 +5,14 @@ from __future__ import annotations
|
|
|
5
5
|
import ast
|
|
6
6
|
import re
|
|
7
7
|
|
|
8
|
+
# Where a generated description stops describing the signature and starts
|
|
9
|
+
# quoting what the author wrote. Three modules needed to recognise it and all
|
|
10
|
+
# three spelled it out: this one writes it, the parser writes it again for the
|
|
11
|
+
# other fourteen languages, and `document` looked for it. A string literal
|
|
12
|
+
# duplicated across modules that must agree is a rename away from a silent
|
|
13
|
+
# disagreement.
|
|
14
|
+
DOCUMENTED_MARKER = "Documented intent:"
|
|
15
|
+
|
|
8
16
|
|
|
9
17
|
def _humanize(name: str) -> str:
|
|
10
18
|
"""Turns a programmer identifier into ordinary words: splits camelCase
|
|
@@ -37,7 +45,7 @@ def describe_python(node: ast.AST, source: str, calls: list[str], imports: list[
|
|
|
37
45
|
pieces.append("using " + ", ".join(imports[:8]))
|
|
38
46
|
doc = ast.get_docstring(node)
|
|
39
47
|
if doc:
|
|
40
|
-
pieces.append("
|
|
48
|
+
pieces.append(f"{DOCUMENTED_MARKER} " + " ".join(doc.split()))
|
|
41
49
|
return ". ".join(pieces) + "."
|
|
42
50
|
|
|
43
51
|
|
|
@@ -26,6 +26,7 @@ import re
|
|
|
26
26
|
from dataclasses import dataclass
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
|
+
from .annotate import DOCUMENTED_MARKER
|
|
29
30
|
from .descriptions import DescriptionStore
|
|
30
31
|
from .models import CodeUnit
|
|
31
32
|
from .parser import EXTENSIONS, parse_file
|
|
@@ -59,7 +60,6 @@ WRAP_WIDTH = 76
|
|
|
59
60
|
# rewritten read as undocumented. That mistake proposed a hundred and seven
|
|
60
61
|
# insertions against a repository with seventeen genuinely undocumented
|
|
61
62
|
# declarations.
|
|
62
|
-
DOCUMENTED_MARKER = "Documented intent:"
|
|
63
63
|
_CJK = re.compile(r"[一-鿿]")
|
|
64
64
|
|
|
65
65
|
|
|
@@ -26,7 +26,7 @@ import re
|
|
|
26
26
|
from dataclasses import dataclass
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
|
-
from .annotate import describe_python
|
|
29
|
+
from .annotate import DOCUMENTED_MARKER, describe_python
|
|
30
30
|
from .models import CodeUnit
|
|
31
31
|
|
|
32
32
|
PARSER_VERSION = "3"
|
|
@@ -542,7 +542,7 @@ def _generic_units(path: Path, source: str, relative: str, language: str) -> lis
|
|
|
542
542
|
if documented:
|
|
543
543
|
# Phrased exactly as the Python path phrases a docstring, so the
|
|
544
544
|
# two routes produce the same shape of text for the same thing.
|
|
545
|
-
description += f"
|
|
545
|
+
description += f" {DOCUMENTED_MARKER} {documented}"
|
|
546
546
|
parent = dotted.rsplit(".", 1)[0] if "." in dotted else None
|
|
547
547
|
units.append(
|
|
548
548
|
CodeUnit(
|
|
@@ -8,6 +8,7 @@ from bisect import bisect_left
|
|
|
8
8
|
from collections import Counter, defaultdict
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
|
|
11
|
+
from .annotate import DOCUMENTED_MARKER
|
|
11
12
|
from .config import BY_PATH
|
|
12
13
|
from .embeddings import DEFAULT_DIMENSIONS, LocalEmbedder, tokenize
|
|
13
14
|
from .models import CodeUnit, SearchResult
|
|
@@ -549,13 +550,47 @@ def search(
|
|
|
549
550
|
]
|
|
550
551
|
|
|
551
552
|
|
|
553
|
+
def _visible_description(unit: CodeUnit) -> str:
|
|
554
|
+
"""The description minus whatever the code printed under it already says.
|
|
555
|
+
|
|
556
|
+
A generated description ends with the author's own docstring, because that
|
|
557
|
+
is how a docstring becomes searchable -- and the source below the block
|
|
558
|
+
then prints it a second time. Measured on a repository whose author wrote
|
|
559
|
+
them, 2,381 of 3,382 characters of prose header were a verbatim repeat of
|
|
560
|
+
the code beneath it: a fifth of everything a query returned, paid for
|
|
561
|
+
twice. Retrieval is the half of RAG that has to fit in a context window,
|
|
562
|
+
so this is not a cosmetic saving.
|
|
563
|
+
|
|
564
|
+
Only the rendering drops it. ``searchable_text`` keeps the whole thing, so
|
|
565
|
+
no ruler moves and the docstring stays exactly as findable as it was.
|
|
566
|
+
|
|
567
|
+
The quoted docstring is re-flowed onto one line where the source has it
|
|
568
|
+
indented across many, so the test is against whitespace-collapsed source
|
|
569
|
+
rather than against the source itself. An authored description the source
|
|
570
|
+
does not carry is kept: it is the one part a reader cannot get from the
|
|
571
|
+
code.
|
|
572
|
+
"""
|
|
573
|
+
head, marker, documented = unit.description.partition(DOCUMENTED_MARKER)
|
|
574
|
+
if not marker:
|
|
575
|
+
return unit.description
|
|
576
|
+
quoted = " ".join(documented.split())
|
|
577
|
+
# `describe_python` joins its pieces with ". " and closes with ".", so a
|
|
578
|
+
# docstring that already ended in one arrives here with two. Matching on
|
|
579
|
+
# the raw text found nothing at all on the first repository tried.
|
|
580
|
+
trimmed = quoted[:-1] if quoted.endswith(".") else quoted
|
|
581
|
+
body = " ".join(unit.source.split())
|
|
582
|
+
if quoted in body or trimmed in body:
|
|
583
|
+
return head.rstrip()
|
|
584
|
+
return unit.description
|
|
585
|
+
|
|
586
|
+
|
|
552
587
|
def _block(result: SearchResult) -> str:
|
|
553
588
|
"""One result as an agent reads it: identifier, score, why it matched,
|
|
554
589
|
what it is, and the code itself.
|
|
555
590
|
"""
|
|
556
591
|
unit = result.unit
|
|
557
592
|
evidence = "\nEvidence: " + " | ".join(result.evidence) if result.evidence else ""
|
|
558
|
-
return f"[{unit.id}] score={result.score:.3f}{evidence}\n{unit
|
|
593
|
+
return f"[{unit.id}] score={result.score:.3f}{evidence}\n{_visible_description(unit)}\n```{unit.language}\n{unit.source}\n```"
|
|
559
594
|
|
|
560
595
|
|
|
561
596
|
def within_budget(results: list[SearchResult], max_chars: int) -> list[SearchResult]:
|
|
@@ -18,6 +18,7 @@ present anywhere in the unit -- ranks the *wrong* answer first.
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
import dataclasses
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
|
|
23
24
|
from ragyourcode.indexer import build_units
|
|
@@ -200,3 +201,56 @@ def test_a_field_every_unit_leaves_empty_does_not_divide_by_zero():
|
|
|
200
201
|
)
|
|
201
202
|
index = build_search_index([unit])
|
|
202
203
|
assert search([unit], "nothing", limit=1, search_index=index)[0].unit.id == unit.id
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_a_block_does_not_reprint_the_docstring_the_code_below_it_shows(tmp_path: Path):
|
|
207
|
+
"""The author's own words reached the block twice: once quoted into the
|
|
208
|
+
generated description so they are searchable, once in the source.
|
|
209
|
+
|
|
210
|
+
Measured on a repository whose author wrote them, 2,381 of 3,382
|
|
211
|
+
characters of prose header were a verbatim repeat of the code beneath it.
|
|
212
|
+
At a fixed budget that is answers crowded out by their own duplicate: the
|
|
213
|
+
same twelve thousand characters carried 92 declarations before this and
|
|
214
|
+
119 after.
|
|
215
|
+
|
|
216
|
+
Both halves are asserted. Dropping the quote must not drop what only the
|
|
217
|
+
header says, and it must not touch what the index can find -- the docstring
|
|
218
|
+
stays in `searchable_text`, which is why no ruler moves.
|
|
219
|
+
"""
|
|
220
|
+
(tmp_path / "billing.py").write_text(
|
|
221
|
+
'def charge(amount):\n'
|
|
222
|
+
' """Charge the amount against the stored card and return a receipt."""\n'
|
|
223
|
+
' return amount\n',
|
|
224
|
+
encoding="utf-8",
|
|
225
|
+
)
|
|
226
|
+
unit = build_units(tmp_path)[0]
|
|
227
|
+
index = build_search_index([unit])
|
|
228
|
+
rendered = _block(search([unit], "charge the amount", search_index=index, limit=1)[0])
|
|
229
|
+
|
|
230
|
+
quoted = "Charge the amount against the stored card and return a receipt."
|
|
231
|
+
assert rendered.count(quoted) == 1, "the docstring is printed once, by the code"
|
|
232
|
+
assert rendered.index(quoted) > rendered.index("```"), "the surviving copy is the one in the source"
|
|
233
|
+
assert "This method charge" in rendered, "the generated half is not in the source and must stay"
|
|
234
|
+
assert quoted in unit.searchable_text, "dropping it from the block must not drop it from the index"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_a_written_description_the_source_does_not_carry_survives(tmp_path: Path):
|
|
238
|
+
"""The rule is "the code already shows it", not "it came after the marker".
|
|
239
|
+
|
|
240
|
+
An authored description is the one part of a block a reader cannot recover
|
|
241
|
+
by reading the code, so a rule that keyed on the marker alone would delete
|
|
242
|
+
exactly the text this project spends tokens to produce.
|
|
243
|
+
"""
|
|
244
|
+
(tmp_path / "billing.py").write_text(
|
|
245
|
+
'def charge(amount):\n'
|
|
246
|
+
' """Charge the card."""\n'
|
|
247
|
+
' return amount\n',
|
|
248
|
+
encoding="utf-8",
|
|
249
|
+
)
|
|
250
|
+
unit = build_units(tmp_path)[0]
|
|
251
|
+
unit = dataclasses.replace(
|
|
252
|
+
unit, description="This method charge. Documented intent: retries a declined authorisation once."
|
|
253
|
+
)
|
|
254
|
+
index = build_search_index([unit])
|
|
255
|
+
rendered = _block(search([unit], "charge the amount", search_index=index, limit=1)[0])
|
|
256
|
+
assert "retries a declined authorisation once." in rendered
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|