bgts-context-engine 0.2.2__tar.gz → 0.2.3__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.
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/CHANGELOG.md +52 -1
- {bgts_context_engine-0.2.2/src/bgts_context_engine.egg-info → bgts_context_engine-0.2.3}/PKG-INFO +32 -3
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/README.md +31 -2
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/README.tr.md +31 -2
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/__init__.py +1 -1
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/ui/trace.py +4 -1
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/bench/pr_ablation.py +27 -5
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/cli.py +28 -4
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/config.py +39 -3
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/orchestrator/anchors.py +15 -5
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/orchestrator/orchestrator.py +21 -5
- bgts_context_engine-0.2.3/src/bce/core/orchestrator/profile.py +125 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/embedder/__init__.py +4 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/embedder/encoder.py +164 -3
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrator.py +64 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/tools/layer3/orchestration.py +11 -2
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3/src/bgts_context_engine.egg-info}/PKG-INFO +32 -3
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bgts_context_engine.egg-info/SOURCES.txt +1 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/LICENSE +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/MANIFEST.in +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/pyproject.toml +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/setup.cfg +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/mcp/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/mcp/server.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/mcp/tools.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/app.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/deps.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/routes.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/schemas.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/static/assets/index-Cc4pUpSE.js +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/static/assets/index-DrLRaTMU.css +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/static/favicon.svg +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/static/index.html +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/ui/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/ui/queries.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/ui/router.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/api/rest/ui/schemas.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/bench/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/bench/runner.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/assembler/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/assembler/assembler.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/auth/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/auth/scope.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/coverage/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/coverage/confidence.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/i18n/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/i18n/catalogs/en.json +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/i18n/catalogs/tr.json +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/i18n/locale.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/i18n/translator.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/logging.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/orchestrator/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/orchestrator/bulk.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/orchestrator/expand.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/orchestrator/text.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/scoring/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/scoring/engine.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/domain/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/domain/enums.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/domain/models.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/churn.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/embedder/embedder.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/extractor/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/extractor/bridges.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/extractor/designnote.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/extractor/extractor.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/extractor/routes.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/gitsync/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/gitsync/bitbucket.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/gitsync/local.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/gitsync/remote.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/indexer.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/linker/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/linker/linker.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/_treesitter.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/base.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/languages/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/languages/csharp_provider.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/languages/go_provider.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/languages/java_provider.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/languages/jsts_provider.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/languages/python_provider.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/registry.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/scip/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/scip/resolver.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/parser/symbol_id.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/upserter/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/indexing/upserter/upserter.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/jobs/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/jobs/store.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/jobs/worker.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/graph/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/graph/client.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/graph/repository.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/db.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0001_extensions_graph.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0002_relational.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0003_vector.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0004_rls.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0005_embeddings_dim.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0006_fts.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0007_jobs.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0008_graph_label_indexes.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0009_fts_identifier_parts.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/migrations/0010_file_churn.sql +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/relational/queries.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/vector/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/storage/vector/store.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/tools/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/tools/layer1/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/tools/layer1/primitives.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/tools/layer2/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/tools/layer2/search.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/tools/layer3/__init__.py +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bgts_context_engine.egg-info/dependency_links.txt +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bgts_context_engine.egg-info/entry_points.txt +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bgts_context_engine.egg-info/requires.txt +0 -0
- {bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bgts_context_engine.egg-info/top_level.txt +0 -0
|
@@ -7,6 +7,56 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.3] - 2026-09-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `BCE_EMBEDDING_PROVIDER=openai`: an encoder that talks to any server speaking the OpenAI
|
|
15
|
+
`/v1/embeddings` protocol (vLLM, Text Embeddings Inference, Ollama, or OpenAI itself), using
|
|
16
|
+
only the standard library. This is how an open model such as `jinaai/jina-code-embeddings-1.5b`
|
|
17
|
+
runs on-prem. Query/document asymmetry is a text prefix — the jina-code family's `nl2code`
|
|
18
|
+
prompts are the default when the model name contains `jina-code`; anything else is unset
|
|
19
|
+
unless `BCE_EMBEDDING_QUERY_PREFIX` / `BCE_EMBEDDING_DOCUMENT_PREFIX` say otherwise.
|
|
20
|
+
`BCE_EMBEDDING_BASE_URL`, `BCE_EMBEDDING_API_KEY`, `BCE_EMBEDDING_EXTRA_BODY` (default
|
|
21
|
+
`{"truncate_prompt_tokens": -1}` so vLLM truncates instead of rejecting a long batch; set
|
|
22
|
+
`{}` for OpenAI's own API, which rejects unknown fields) and `BCE_EMBEDDING_TIMEOUT` (600 s)
|
|
23
|
+
configure the client. Vectors wider than `BCE_EMBEDDING_DIM` are truncated and re-normalised
|
|
24
|
+
(Matryoshka); a narrower reply is a configuration error. Transient 5xx / connection failures
|
|
25
|
+
retry three times; 4xx does not.
|
|
26
|
+
- Retrieval profiles (`bce.core.orchestrator.profile`): the three engine constants that encode
|
|
27
|
+
*where in the model's ranked list the right answers sit* — semantic-anchor rank scale, the
|
|
28
|
+
share of the answer reserved for the model's ranks, and a guard on its top ranks in
|
|
29
|
+
narrowing — grouped per embedding family. `BCE_RETRIEVAL_PROFILE=auto` (the default) picks
|
|
30
|
+
`voyage` for `voyage-*` and unknown models (the historical constants, unchanged) and `jina`
|
|
31
|
+
for `jina-*`. Name one to force it; `BCE_RETRIEVAL_SEMANTIC_GUARD_RANKS` /
|
|
32
|
+
`_RESERVE_SHARE` / `_RANK_SCALE` override a single knob for fitting runs. A profile change
|
|
33
|
+
never needs a re-index. The active profile is written into every `bce bench-prs` report.
|
|
34
|
+
- `bce migrate --reset-embeddings`: after the SQL migrations, `align_embedding_dim` re-types
|
|
35
|
+
`embeddings.embedding` to `BCE_EMBEDDING_DIM`. Stored vectors of another width are refused
|
|
36
|
+
unless the flag is set (they belong to another model and would be discarded); then the HNSW
|
|
37
|
+
index is rebuilt the same way migration `0005` did.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Narrowing reads the active retrieval profile. The voyage path is unchanged (no guard,
|
|
42
|
+
share 0.5). The jina profile adds a guard of 10: the model's first ten ranks fill slots
|
|
43
|
+
1–10 (only the container cap can skip one). jina-code finds more correct symbols than
|
|
44
|
+
voyage-code-4 but spreads them over ranks 1–15, and under the voyage constants the engine's
|
|
45
|
+
agreement with the model's *deeper* ranks satisfied the reserve share and pushed those mid
|
|
46
|
+
hits to positions 11–19 or out of the answer (PR replay, 30 PRs: recall@20 35.7 % → 38.2 %,
|
|
47
|
+
holdout 27.8 % → 34.9 %, retention of the model's own hits 83 % → 92 %). The list stays
|
|
48
|
+
K-monotonic.
|
|
49
|
+
- `bce bench-prs` titles the markdown with the encoder that actually produced the snapshots
|
|
50
|
+
(`jina-code-embeddings-1.5b vs … + BCE`), because the JSON key `voyage` is historical, not
|
|
51
|
+
the model.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- `bce bench-prs --from-report` / `--rerun-bce` no longer treats a stored semantic pool
|
|
56
|
+
shorter than K as incomplete. A task whose neighbours are mostly tests legitimately has a
|
|
57
|
+
pool shorter than K — the live run had nothing more either — so the check is now "non-empty
|
|
58
|
+
and at least as long as the list scored from it".
|
|
59
|
+
|
|
10
60
|
## [0.2.2] - 2026-09-18
|
|
11
61
|
|
|
12
62
|
### Added
|
|
@@ -254,7 +304,8 @@ First public release.
|
|
|
254
304
|
- Docker Compose deployment with PostgreSQL, Apache AGE and pgvector preconfigured.
|
|
255
305
|
- `server.json` manifest describing the stdio server for the official MCP registry.
|
|
256
306
|
|
|
257
|
-
[Unreleased]: https://github.com/bgts-ai-org/bgts-context-engine/compare/v0.2.
|
|
307
|
+
[Unreleased]: https://github.com/bgts-ai-org/bgts-context-engine/compare/v0.2.3...HEAD
|
|
308
|
+
[0.2.3]: https://github.com/bgts-ai-org/bgts-context-engine/compare/v0.2.2...v0.2.3
|
|
258
309
|
[0.2.2]: https://github.com/bgts-ai-org/bgts-context-engine/compare/v0.2.1...v0.2.2
|
|
259
310
|
[0.2.1]: https://github.com/bgts-ai-org/bgts-context-engine/compare/v0.2.0...v0.2.1
|
|
260
311
|
[0.2.0]: https://github.com/bgts-ai-org/bgts-context-engine/compare/v0.1.0...v0.2.0
|
{bgts_context_engine-0.2.2/src/bgts_context_engine.egg-info → bgts_context_engine-0.2.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bgts-context-engine
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Deterministic code-graph context engine for AI coding agents - PostgreSQL, Apache AGE and pgvector, over MCP and REST
|
|
5
5
|
Author: Oğuz Öztürk, Enes İyidil
|
|
6
6
|
Maintainer: BGTS
|
|
@@ -69,7 +69,7 @@ that actually answer it — ranked, budgeted, and reproducible.
|
|
|
69
69
|
[](docs/mcp.md)
|
|
70
70
|
[](https://github.com/bgts-ai-org/bgts-context-engine/stargazers)
|
|
71
71
|
|
|
72
|
-
[Quick start](#quick-start) · [Use it from your agent](#use-it-from-your-agent) · [How it works](#how-it-works) · [Documentation](#documentation) · [Türkçe](README.tr.md)
|
|
72
|
+
[Quick start](#quick-start) · [Use it from your agent](#use-it-from-your-agent) · [How it works](#how-it-works) · [Supported models](#supported-models) · [Documentation](#documentation) · [Türkçe](README.tr.md)
|
|
73
73
|
|
|
74
74
|
</div>
|
|
75
75
|
|
|
@@ -294,7 +294,36 @@ The full formula, every weight, and the confidence thresholds are in
|
|
|
294
294
|
- **A UI that explains itself.** `/ui` ships in the wheel and replays a real retrieval call
|
|
295
295
|
stage by stage: anchors lighting up, expansion spreading, candidates scored and cut.
|
|
296
296
|
- **Runs offline.** The default embedding provider is deterministic arithmetic over token
|
|
297
|
-
digests. No API key, no network, repeatable benchmarks.
|
|
297
|
+
digests. No API key, no network, repeatable benchmarks. `openai` talks to any
|
|
298
|
+
OpenAI-compatible `/v1/embeddings` server (vLLM, TEI, Ollama), so a model such as
|
|
299
|
+
[jina-code-embeddings-1.5b](https://huggingface.co/jinaai/jina-code-embeddings-1.5b)
|
|
300
|
+
can run inside the perimeter.
|
|
301
|
+
|
|
302
|
+
## Supported models
|
|
303
|
+
|
|
304
|
+
Embeddings only find entry points when the task text names nothing the graph already
|
|
305
|
+
knows. They never rank the answer. Out of the box that seed is `hashing`: deterministic
|
|
306
|
+
arithmetic, no API key, no network. For a real code model, set `BCE_EMBEDDING_PROVIDER`
|
|
307
|
+
and `BCE_EMBEDDING_MODEL` to one of these:
|
|
308
|
+
|
|
309
|
+
| Model | Provider | Dimension |
|
|
310
|
+
| --- | --- | --- |
|
|
311
|
+
| [`voyage-code-3`](https://blog.voyageai.com/2024/12/04/voyage-code-3/) | Voyage AI (`voyage`) | 1024 |
|
|
312
|
+
| [`voyage-code-4`](https://blog.voyageai.com/2026/08/13/voyage-code-4/) | Voyage AI (`voyage`) | 1024 |
|
|
313
|
+
| [`jina-code-embeddings-1.5b`](https://huggingface.co/jinaai/jina-code-embeddings-1.5b) | OpenAI-compatible (`openai`) | 1536 |
|
|
314
|
+
|
|
315
|
+
Voyage is a hosted API — `pip install "bgts-context-engine[embed]"` and
|
|
316
|
+
`BCE_VOYAGE_API_KEY`. Jina is the on-prem path: any server that speaks `/v1/embeddings`
|
|
317
|
+
(vLLM, TEI, Ollama). Switching the model or the dimension is a re-index
|
|
318
|
+
(`bce migrate --reset-embeddings`). The knobs are in
|
|
319
|
+
[docs/deployment.md](docs/deployment.md).
|
|
320
|
+
|
|
321
|
+
**Coming next** — same `openai` socket, not yet a fitted retrieval profile:
|
|
322
|
+
|
|
323
|
+
- [`jina-code-embeddings-0.5b`](https://huggingface.co/jinaai/jina-code-embeddings-0.5b)
|
|
324
|
+
— the smaller sibling of 1.5b, for hosts that cannot hold 1.5B parameters.
|
|
325
|
+
- [`Nomic Embed Code`](https://huggingface.co/nomic-ai/nomic-embed-code) — an open 7B
|
|
326
|
+
code retriever.
|
|
298
327
|
|
|
299
328
|
## Where it fits
|
|
300
329
|
|
|
@@ -16,7 +16,7 @@ that actually answer it — ranked, budgeted, and reproducible.
|
|
|
16
16
|
[](docs/mcp.md)
|
|
17
17
|
[](https://github.com/bgts-ai-org/bgts-context-engine/stargazers)
|
|
18
18
|
|
|
19
|
-
[Quick start](#quick-start) · [Use it from your agent](#use-it-from-your-agent) · [How it works](#how-it-works) · [Documentation](#documentation) · [Türkçe](README.tr.md)
|
|
19
|
+
[Quick start](#quick-start) · [Use it from your agent](#use-it-from-your-agent) · [How it works](#how-it-works) · [Supported models](#supported-models) · [Documentation](#documentation) · [Türkçe](README.tr.md)
|
|
20
20
|
|
|
21
21
|
</div>
|
|
22
22
|
|
|
@@ -241,7 +241,36 @@ The full formula, every weight, and the confidence thresholds are in
|
|
|
241
241
|
- **A UI that explains itself.** `/ui` ships in the wheel and replays a real retrieval call
|
|
242
242
|
stage by stage: anchors lighting up, expansion spreading, candidates scored and cut.
|
|
243
243
|
- **Runs offline.** The default embedding provider is deterministic arithmetic over token
|
|
244
|
-
digests. No API key, no network, repeatable benchmarks.
|
|
244
|
+
digests. No API key, no network, repeatable benchmarks. `openai` talks to any
|
|
245
|
+
OpenAI-compatible `/v1/embeddings` server (vLLM, TEI, Ollama), so a model such as
|
|
246
|
+
[jina-code-embeddings-1.5b](https://huggingface.co/jinaai/jina-code-embeddings-1.5b)
|
|
247
|
+
can run inside the perimeter.
|
|
248
|
+
|
|
249
|
+
## Supported models
|
|
250
|
+
|
|
251
|
+
Embeddings only find entry points when the task text names nothing the graph already
|
|
252
|
+
knows. They never rank the answer. Out of the box that seed is `hashing`: deterministic
|
|
253
|
+
arithmetic, no API key, no network. For a real code model, set `BCE_EMBEDDING_PROVIDER`
|
|
254
|
+
and `BCE_EMBEDDING_MODEL` to one of these:
|
|
255
|
+
|
|
256
|
+
| Model | Provider | Dimension |
|
|
257
|
+
| --- | --- | --- |
|
|
258
|
+
| [`voyage-code-3`](https://blog.voyageai.com/2024/12/04/voyage-code-3/) | Voyage AI (`voyage`) | 1024 |
|
|
259
|
+
| [`voyage-code-4`](https://blog.voyageai.com/2026/08/13/voyage-code-4/) | Voyage AI (`voyage`) | 1024 |
|
|
260
|
+
| [`jina-code-embeddings-1.5b`](https://huggingface.co/jinaai/jina-code-embeddings-1.5b) | OpenAI-compatible (`openai`) | 1536 |
|
|
261
|
+
|
|
262
|
+
Voyage is a hosted API — `pip install "bgts-context-engine[embed]"` and
|
|
263
|
+
`BCE_VOYAGE_API_KEY`. Jina is the on-prem path: any server that speaks `/v1/embeddings`
|
|
264
|
+
(vLLM, TEI, Ollama). Switching the model or the dimension is a re-index
|
|
265
|
+
(`bce migrate --reset-embeddings`). The knobs are in
|
|
266
|
+
[docs/deployment.md](docs/deployment.md).
|
|
267
|
+
|
|
268
|
+
**Coming next** — same `openai` socket, not yet a fitted retrieval profile:
|
|
269
|
+
|
|
270
|
+
- [`jina-code-embeddings-0.5b`](https://huggingface.co/jinaai/jina-code-embeddings-0.5b)
|
|
271
|
+
— the smaller sibling of 1.5b, for hosts that cannot hold 1.5B parameters.
|
|
272
|
+
- [`Nomic Embed Code`](https://huggingface.co/nomic-ai/nomic-embed-code) — an open 7B
|
|
273
|
+
code retriever.
|
|
245
274
|
|
|
246
275
|
## Where it fits
|
|
247
276
|
|
|
@@ -14,7 +14,7 @@ veren sekiz sembolü sıralanmış, bütçelenmiş ve yeniden üretilebilir şek
|
|
|
14
14
|
[](docs/mcp.md)
|
|
15
15
|
[](https://github.com/bgts-ai-org/bgts-context-engine/stargazers)
|
|
16
16
|
|
|
17
|
-
[Hızlı başlangıç](#hızlı-başlangıç) · [Ajanınızdan kullanma](#ajanınızdan-kullanma) · [Nasıl çalışır](#nasıl-çalışır) · [Dokümantasyon](#dokümantasyon) · [English](README.md)
|
|
17
|
+
[Hızlı başlangıç](#hızlı-başlangıç) · [Ajanınızdan kullanma](#ajanınızdan-kullanma) · [Nasıl çalışır](#nasıl-çalışır) · [Desteklenen modeller](#desteklenen-modeller) · [Dokümantasyon](#dokümantasyon) · [English](README.md)
|
|
18
18
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
@@ -245,7 +245,36 @@ Formülün tamamı, her ağırlık ve güven eşikleri
|
|
|
245
245
|
aşama aşama oynatır: çapaların yanması, genişlemenin yayılması, adayların skorlanıp
|
|
246
246
|
kesilmesi.
|
|
247
247
|
- **Çevrimdışı çalışır.** Varsayılan embedding sağlayıcısı, token özetleri üzerinde
|
|
248
|
-
deterministik aritmetiktir. API anahtarı yok, ağ yok, tekrarlanabilir ölçümler.
|
|
248
|
+
deterministik aritmetiktir. API anahtarı yok, ağ yok, tekrarlanabilir ölçümler. `openai`
|
|
249
|
+
sağlayıcısı OpenAI uyumlu herhangi bir `/v1/embeddings` sunucusuna konuşur (vLLM, TEI,
|
|
250
|
+
Ollama); [jina-code-embeddings-1.5b](https://huggingface.co/jinaai/jina-code-embeddings-1.5b)
|
|
251
|
+
gibi bir model çevre içinde çalışır.
|
|
252
|
+
|
|
253
|
+
## Desteklenen modeller
|
|
254
|
+
|
|
255
|
+
Embedding yalnızca grafın tanımadığı bir görev metninde giriş noktası bulur. Cevabı asla
|
|
256
|
+
sıralamaz. Varsayılan `hashing`'dir: deterministik aritmetik, API anahtarı yok, ağ yok.
|
|
257
|
+
Gerçek bir kod modeli için `BCE_EMBEDDING_PROVIDER` ve `BCE_EMBEDDING_MODEL`'i şunlardan
|
|
258
|
+
birine ayarlayın:
|
|
259
|
+
|
|
260
|
+
| Model | Sağlayıcı | Boyut |
|
|
261
|
+
| --- | --- | --- |
|
|
262
|
+
| [`voyage-code-3`](https://blog.voyageai.com/2024/12/04/voyage-code-3/) | Voyage AI (`voyage`) | 1024 |
|
|
263
|
+
| [`voyage-code-4`](https://blog.voyageai.com/2026/08/13/voyage-code-4/) | Voyage AI (`voyage`) | 1024 |
|
|
264
|
+
| [`jina-code-embeddings-1.5b`](https://huggingface.co/jinaai/jina-code-embeddings-1.5b) | OpenAI uyumlu (`openai`) | 1536 |
|
|
265
|
+
|
|
266
|
+
Voyage barındırılan bir API'dir — `pip install "bgts-context-engine[embed]"` ve
|
|
267
|
+
`BCE_VOYAGE_API_KEY`. Jina çevre-içi yoldur: `/v1/embeddings` konuşan herhangi bir
|
|
268
|
+
sunucu (vLLM, TEI, Ollama). Model veya boyutu değiştirmek yeniden indekslemedir
|
|
269
|
+
(`bce migrate --reset-embeddings`). Ayarlar
|
|
270
|
+
[docs/deployment.md](docs/deployment.md) içindedir.
|
|
271
|
+
|
|
272
|
+
**Sırada** — aynı `openai` soketi, henüz ayrı bir getirme profili yok:
|
|
273
|
+
|
|
274
|
+
- [`jina-code-embeddings-0.5b`](https://huggingface.co/jinaai/jina-code-embeddings-0.5b)
|
|
275
|
+
— 1.5b'nin küçük kardeşi; 1.5B parametreyi taşıyamayan makineler için.
|
|
276
|
+
- [`Nomic Embed Code`](https://huggingface.co/nomic-ai/nomic-embed-code) — açık kaynaklı
|
|
277
|
+
7B kod getiricisi.
|
|
249
278
|
|
|
250
279
|
## Nereye oturur
|
|
251
280
|
|
|
@@ -21,6 +21,7 @@ from bce.core.coverage import compute_coverage
|
|
|
21
21
|
from bce.core.orchestrator.anchors import find_anchors
|
|
22
22
|
from bce.core.orchestrator.expand import expand_from_anchors, to_candidates
|
|
23
23
|
from bce.core.orchestrator.orchestrator import RetrievalResult, narrow
|
|
24
|
+
from bce.core.orchestrator.profile import active_profile
|
|
24
25
|
from bce.core.orchestrator.text import content_tokens
|
|
25
26
|
from bce.core.scoring.engine import ScoreWeights, score_candidates
|
|
26
27
|
from bce.storage.graph.repository import GraphRepository
|
|
@@ -69,6 +70,7 @@ def trace_context_for_task(
|
|
|
69
70
|
|
|
70
71
|
# Stage 1: multi-source anchors (spec section 6.2), with evidence strength per anchor.
|
|
71
72
|
t = time.perf_counter()
|
|
73
|
+
profile = active_profile()
|
|
72
74
|
anchors = find_anchors(
|
|
73
75
|
repository,
|
|
74
76
|
task_text=task_text,
|
|
@@ -78,6 +80,7 @@ def trace_context_for_task(
|
|
|
78
80
|
component_repo_ids=None,
|
|
79
81
|
repo_ids=repo_ids,
|
|
80
82
|
semantic_candidates=semantic_candidates,
|
|
83
|
+
profile=profile,
|
|
81
84
|
)
|
|
82
85
|
stages.append(
|
|
83
86
|
{
|
|
@@ -139,7 +142,7 @@ def trace_context_for_task(
|
|
|
139
142
|
|
|
140
143
|
# Stage 4: diversity-aware narrowing to top-N (the "1000 -> 8" step).
|
|
141
144
|
t = time.perf_counter()
|
|
142
|
-
narrowed = narrow(ranked, max_candidates)
|
|
145
|
+
narrowed = narrow(ranked, max_candidates, profile=profile)
|
|
143
146
|
stages.append(
|
|
144
147
|
{
|
|
145
148
|
"stage": "narrow",
|
|
@@ -666,6 +666,9 @@ class AblationReport:
|
|
|
666
666
|
cases: list[CaseOutcome]
|
|
667
667
|
#: Extra K columns derived from the K-long lists (see :func:`score_runs`).
|
|
668
668
|
ks: list[int] = field(default_factory=list)
|
|
669
|
+
#: The retrieval profile BCE ran with (name + the model-specific constants), so two runs of
|
|
670
|
+
#: the same snapshots can be told apart by their engine tuning.
|
|
671
|
+
profile: dict[str, Any] = field(default_factory=dict)
|
|
669
672
|
|
|
670
673
|
def _rows(
|
|
671
674
|
self, variant: str, *, scored_only: bool, split: str | None, level: str = "symbol"
|
|
@@ -848,6 +851,7 @@ class AblationReport:
|
|
|
848
851
|
"k": self.k,
|
|
849
852
|
"ks": list(self.ks),
|
|
850
853
|
"encoder_model": self.encoder_model,
|
|
854
|
+
"profile": dict(self.profile),
|
|
851
855
|
"variants": list(self.variants),
|
|
852
856
|
"summary": {v: self.aggregate(v) for v in self.variants},
|
|
853
857
|
"summary_by_split": {
|
|
@@ -1124,10 +1128,14 @@ def run_ablation(
|
|
|
1124
1128
|
for system in SYSTEMS
|
|
1125
1129
|
if replay is not None and (case.spec.number, variant, system) in replay
|
|
1126
1130
|
}
|
|
1131
|
+
# The stored pool is complete when it is at least as long as the list that was
|
|
1132
|
+
# scored from it: a task whose neighbours are mostly tests legitimately has a
|
|
1133
|
+
# pool shorter than K (the live run had nothing more either).
|
|
1127
1134
|
baselines_ok = (
|
|
1128
1135
|
"voyage" in replayed
|
|
1129
1136
|
and "voyage_raw" in replayed
|
|
1130
|
-
and
|
|
1137
|
+
and bool(replayed["voyage"].semantic_pool)
|
|
1138
|
+
and len(replayed["voyage"].semantic_pool) >= len(replayed["voyage"].returned)
|
|
1131
1139
|
)
|
|
1132
1140
|
if len(replayed) == len(SYSTEMS) and not rerun_bce:
|
|
1133
1141
|
runs = replayed
|
|
@@ -1157,8 +1165,15 @@ def run_ablation(
|
|
|
1157
1165
|
outcomes.append(outcome)
|
|
1158
1166
|
if progress is not None:
|
|
1159
1167
|
progress(outcome)
|
|
1168
|
+
from bce.core.orchestrator.profile import active_profile
|
|
1169
|
+
|
|
1160
1170
|
return AblationReport(
|
|
1161
|
-
k=k,
|
|
1171
|
+
k=k,
|
|
1172
|
+
encoder_model=encoder_model,
|
|
1173
|
+
variants=list(variants),
|
|
1174
|
+
cases=outcomes,
|
|
1175
|
+
ks=list(ks),
|
|
1176
|
+
profile=asdict(active_profile()),
|
|
1162
1177
|
)
|
|
1163
1178
|
|
|
1164
1179
|
|
|
@@ -1355,9 +1370,15 @@ def _render_text_buckets(lines: list[str], buckets: dict[str, Any]) -> None:
|
|
|
1355
1370
|
|
|
1356
1371
|
def render_markdown(report: AblationReport) -> str:
|
|
1357
1372
|
lines: list[str] = []
|
|
1358
|
-
|
|
1373
|
+
# The pure-embedding baseline is keyed ``voyage`` in the JSON for historical reasons; the
|
|
1374
|
+
# actual model is whatever encoded the snapshots, so the title says that instead.
|
|
1375
|
+
model_name = report.encoder_model.rpartition("-")[0] or report.encoder_model
|
|
1376
|
+
lines.append(f"# {model_name} vs {model_name} + BCE - PR replay benchmark")
|
|
1359
1377
|
lines.append("")
|
|
1360
1378
|
lines.append(f"- Encoder: `{report.encoder_model}` (same embedding rows for both systems)")
|
|
1379
|
+
if report.profile:
|
|
1380
|
+
knobs = ", ".join(f"{k}={v}" for k, v in report.profile.items() if k != "name")
|
|
1381
|
+
lines.append(f"- Retrieval profile: `{report.profile.get('name', '?')}` ({knobs})")
|
|
1361
1382
|
lines.append(f"- K (results per system): {report.k}")
|
|
1362
1383
|
scored = sorted({c.number for c in report.cases if c.scored})
|
|
1363
1384
|
unscored = sorted({c.number for c in report.cases if not c.scored})
|
|
@@ -1370,8 +1391,9 @@ def render_markdown(report: AblationReport) -> str:
|
|
|
1370
1391
|
lines.append("")
|
|
1371
1392
|
lines.append(
|
|
1372
1393
|
"Ground truth = symbols of the pre-PR snapshot that the PR's diff touched (tests and "
|
|
1373
|
-
"files created by the PR excluded). `voyage` =
|
|
1374
|
-
"
|
|
1394
|
+
f"files created by the PR excluded). `voyage` = pure `{model_name}` search, nearest "
|
|
1395
|
+
f"non-test symbol embeddings (the key is historical, not the model); `voyage_raw` = "
|
|
1396
|
+
"unfiltered nearest neighbours; `bce` = full engine with the same "
|
|
1375
1397
|
"neighbours as semantic anchors. Retention = share of `voyage`'s correct hits the system "
|
|
1376
1398
|
"still returns. Engine constants were fitted on `tune` only, so `holdout` is the honest "
|
|
1377
1399
|
"number."
|
|
@@ -69,15 +69,39 @@ def _root(
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
@app.command()
|
|
72
|
-
def migrate(
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
def migrate(
|
|
73
|
+
reset_embeddings: bool = typer.Option(
|
|
74
|
+
False,
|
|
75
|
+
"--reset-embeddings",
|
|
76
|
+
help=(
|
|
77
|
+
"Allow re-typing embeddings.embedding to BCE_EMBEDDING_DIM even when it holds vectors "
|
|
78
|
+
"of another width (they are discarded; reindex afterwards)"
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Apply pending database migrations and fit the vector column to BCE_EMBEDDING_DIM."""
|
|
83
|
+
from bce.config import get_settings
|
|
84
|
+
from bce.storage.relational.db import connection
|
|
85
|
+
from bce.storage.relational.migrator import (
|
|
86
|
+
EmbeddingDimMismatch,
|
|
87
|
+
align_embedding_dim,
|
|
88
|
+
run_migrations,
|
|
89
|
+
)
|
|
75
90
|
|
|
76
|
-
|
|
91
|
+
dim = get_settings().embedding_dim
|
|
92
|
+
with connection() as conn:
|
|
93
|
+
applied = run_migrations(conn)
|
|
94
|
+
try:
|
|
95
|
+
resized = align_embedding_dim(conn, dim, reset=reset_embeddings)
|
|
96
|
+
except EmbeddingDimMismatch as exc:
|
|
97
|
+
typer.echo(f"Configuration error: {exc}", err=True)
|
|
98
|
+
raise typer.Exit(code=1) from exc
|
|
77
99
|
if applied:
|
|
78
100
|
typer.echo("Applied migrations: " + ", ".join(applied))
|
|
79
101
|
else:
|
|
80
102
|
typer.echo("No pending migrations.")
|
|
103
|
+
if resized:
|
|
104
|
+
typer.echo(f"Re-typed embeddings.embedding to vector({dim}).")
|
|
81
105
|
|
|
82
106
|
|
|
83
107
|
@app.command()
|
|
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import os
|
|
15
15
|
from functools import lru_cache
|
|
16
|
+
from typing import Any
|
|
16
17
|
|
|
17
18
|
from pydantic import Field
|
|
18
19
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
@@ -61,19 +62,54 @@ class Settings(BaseSettings):
|
|
|
61
62
|
cors_origins: tuple[str, ...] = ("http://localhost:5173", "http://127.0.0.1:5173")
|
|
62
63
|
|
|
63
64
|
# --- Embedding (Phase 2; pinned for determinism, P2) ---
|
|
64
|
-
# Provider selects the encoder: "hashing" (default, dependency-free, deterministic fallback)
|
|
65
|
-
# "voyage" (Voyage AI code embeddings)
|
|
65
|
+
# Provider selects the encoder: "hashing" (default, dependency-free, deterministic fallback),
|
|
66
|
+
# "voyage" (Voyage AI code embeddings) or "openai" (any OpenAI-compatible ``/v1/embeddings``
|
|
67
|
+
# server: vLLM, TEI, Ollama, OpenAI itself - the way to run an open model such as
|
|
68
|
+
# jinaai/jina-code-embeddings-1.5b). Model + dim are pinned; a change is a reindex boundary.
|
|
66
69
|
embedding_provider: str = Field(
|
|
67
70
|
default="hashing",
|
|
68
|
-
description=
|
|
71
|
+
description=(
|
|
72
|
+
"Embedding backend: 'hashing' (local fallback), 'voyage' (Voyage AI) or 'openai' "
|
|
73
|
+
"(OpenAI-compatible HTTP endpoint)."
|
|
74
|
+
),
|
|
69
75
|
)
|
|
70
76
|
embedding_model: str = Field(
|
|
71
77
|
default="voyage-code-3",
|
|
72
78
|
description="Pinned embedding model identifier. Versioned for reproducibility (P2).",
|
|
73
79
|
)
|
|
80
|
+
# Width of the pgvector column. ``bce migrate`` re-types ``embeddings.embedding`` to this width;
|
|
81
|
+
# an encoder returning wider vectors is truncated to it and re-normalised (Matryoshka models).
|
|
74
82
|
embedding_dim: int = 1024
|
|
75
83
|
# Voyage AI API key (used only when embedding_provider == 'voyage').
|
|
76
84
|
voyage_api_key: str = ""
|
|
85
|
+
# --- provider "openai" only ---
|
|
86
|
+
# Base URL of the OpenAI-compatible server; ``/embeddings`` is appended.
|
|
87
|
+
embedding_base_url: str = "http://127.0.0.1:8001/v1"
|
|
88
|
+
# Bearer token; leave empty for a local server that does not check one.
|
|
89
|
+
embedding_api_key: str = ""
|
|
90
|
+
# Instruction prefixes prepended to queries / documents (instruction-tuned models). ``None``
|
|
91
|
+
# picks a built-in default from the model name (jina-code-* -> its nl2code prompts); set a
|
|
92
|
+
# string - even an empty one - to override.
|
|
93
|
+
embedding_query_prefix: str | None = None
|
|
94
|
+
embedding_document_prefix: str | None = None
|
|
95
|
+
# Extra JSON fields merged into every request body. The default asks vLLM to truncate inputs
|
|
96
|
+
# longer than its ``--max-model-len`` instead of rejecting the batch; other servers ignore the
|
|
97
|
+
# field, OpenAI's own API rejects unknown fields, so set it to ``{}`` there.
|
|
98
|
+
embedding_extra_body: dict[str, Any] = Field(
|
|
99
|
+
default_factory=lambda: {"truncate_prompt_tokens": -1}
|
|
100
|
+
)
|
|
101
|
+
# Seconds to wait for one embedding request (a local model on a laptop can need minutes).
|
|
102
|
+
embedding_timeout: float = 600.0
|
|
103
|
+
|
|
104
|
+
# --- Retrieval profile (engine constants that depend on the embedding model) ---
|
|
105
|
+
# "auto" (default) picks the profile fitted for ``embedding_model`` - "voyage" for voyage-*,
|
|
106
|
+
# "jina" for jina-*, voyage's constants for anything else; or name one explicitly. The three
|
|
107
|
+
# optional knobs override single values of the chosen profile (fitting runs; leave unset).
|
|
108
|
+
# See ``bce.core.orchestrator.profile``.
|
|
109
|
+
retrieval_profile: str = "auto"
|
|
110
|
+
retrieval_semantic_guard_ranks: int | None = None
|
|
111
|
+
retrieval_semantic_reserve_share: float | None = None
|
|
112
|
+
retrieval_semantic_rank_scale: float | None = None
|
|
77
113
|
|
|
78
114
|
# --- Logging (JSON to stdout; optional rotating file sink) ---
|
|
79
115
|
log_level: str = "INFO"
|
{bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/orchestrator/anchors.py
RENAMED
|
@@ -37,6 +37,7 @@ import re
|
|
|
37
37
|
from dataclasses import dataclass, field
|
|
38
38
|
|
|
39
39
|
from bce.core.orchestrator import bulk
|
|
40
|
+
from bce.core.orchestrator.profile import RetrievalProfile
|
|
40
41
|
from bce.core.orchestrator.text import (
|
|
41
42
|
explicit_references,
|
|
42
43
|
is_test_symbol,
|
|
@@ -86,6 +87,8 @@ _SATURATED_TERM_WEIGHT = 0.35
|
|
|
86
87
|
#: ``rank / len(list)`` form made rank 29 of a 30-list as strong as rank 10 of an 11-list).
|
|
87
88
|
#: Measured on the PR-replay tune split: 8 / 10 / 15 all lost semantic retention (80 %) and
|
|
88
89
|
#: recall@20 against 30, which keeps every model rank in the pool; steeper decay is not paid for.
|
|
90
|
+
#: This is the voyage-code-4 value; another embedding model may carry its own in its
|
|
91
|
+
#: :class:`~bce.core.orchestrator.profile.RetrievalProfile` (``semantic_rank_scale``).
|
|
89
92
|
SEMANTIC_RANK_SCALE = 30
|
|
90
93
|
|
|
91
94
|
|
|
@@ -144,11 +147,13 @@ def find_anchors(
|
|
|
144
147
|
lexical_terms: list[str] | None = None,
|
|
145
148
|
semantic_candidates: list[str] | None = None,
|
|
146
149
|
include_tests: bool = False,
|
|
150
|
+
profile: RetrievalProfile | None = None,
|
|
147
151
|
) -> AnchorResult:
|
|
148
152
|
"""Combine the four anchor sources deterministically. Callers supply pre-fetched metadata.
|
|
149
153
|
|
|
150
154
|
``semantic_candidates`` must be in the model's rank order (best first); rank decides the
|
|
151
155
|
semantic evidence strength. ``include_tests`` admits test symbols from lexical/semantic.
|
|
156
|
+
``profile`` supplies the model-specific rank scale (default: the voyage constants).
|
|
152
157
|
"""
|
|
153
158
|
result = AnchorResult()
|
|
154
159
|
for rid in sorted(set(component_repo_ids or [])):
|
|
@@ -181,7 +186,10 @@ def find_anchors(
|
|
|
181
186
|
result.add(sid, "lexical", strength)
|
|
182
187
|
|
|
183
188
|
# Source 4b: semantic candidates (lowest priority; caller supplies the model's ranked list).
|
|
184
|
-
|
|
189
|
+
scale = profile.semantic_rank_scale if profile is not None else SEMANTIC_RANK_SCALE
|
|
190
|
+
for sid, rank, strength in _semantic_anchors(
|
|
191
|
+
repository, semantic_candidates, include_tests, scale=scale
|
|
192
|
+
):
|
|
185
193
|
result.add(sid, "semantic", strength)
|
|
186
194
|
result.semantic_rank[sid] = rank
|
|
187
195
|
|
|
@@ -246,11 +254,13 @@ def _semantic_anchors(
|
|
|
246
254
|
repository: GraphRepository,
|
|
247
255
|
semantic_candidates: list[str] | None,
|
|
248
256
|
include_tests: bool,
|
|
257
|
+
*,
|
|
258
|
+
scale: float = SEMANTIC_RANK_SCALE,
|
|
249
259
|
) -> list[tuple[str, int, float]]:
|
|
250
260
|
"""``(symbol_id, rank, strength)`` for the model's ranked list, strongest first.
|
|
251
261
|
|
|
252
262
|
Strength decays hyperbolically with the absolute rank - ``cap / (1 + rank / scale)``
|
|
253
|
-
(:data:`SEMANTIC_RANK_SCALE`) - so the best hit is worth as much as a moderately ambiguous
|
|
263
|
+
(:data:`SEMANTIC_RANK_SCALE` unless the profile says otherwise) - so the best hit is worth as much as a moderately ambiguous
|
|
254
264
|
explicit name, mid ranks still expand, and the tail of a wide pool is kept as candidates only.
|
|
255
265
|
Only the rank enters the engine: a similarity number would let the model reorder candidates,
|
|
256
266
|
which is the one thing the deterministic core does not delegate.
|
|
@@ -265,13 +275,13 @@ def _semantic_anchors(
|
|
|
265
275
|
info = meta.get(sid) or {}
|
|
266
276
|
if is_test_symbol(sid, info.get("name"), info.get("file_id")):
|
|
267
277
|
continue
|
|
268
|
-
out.append((sid, rank, semantic_strength(rank)))
|
|
278
|
+
out.append((sid, rank, semantic_strength(rank, scale=scale)))
|
|
269
279
|
return out
|
|
270
280
|
|
|
271
281
|
|
|
272
|
-
def semantic_strength(rank: int) -> float:
|
|
282
|
+
def semantic_strength(rank: int, *, scale: float = SEMANTIC_RANK_SCALE) -> float:
|
|
273
283
|
"""Anchor strength for the model's 0-based ``rank`` (see :data:`SEMANTIC_RANK_SCALE`)."""
|
|
274
|
-
return round(SOURCE_STRENGTH["semantic"] / (1.0 + max(rank, 0) /
|
|
284
|
+
return round(SOURCE_STRENGTH["semantic"] / (1.0 + max(rank, 0) / float(scale)), 6)
|
|
275
285
|
|
|
276
286
|
|
|
277
287
|
def _lexical_anchors(
|
{bgts_context_engine-0.2.2 → bgts_context_engine-0.2.3}/src/bce/core/orchestrator/orchestrator.py
RENAMED
|
@@ -25,6 +25,7 @@ from dataclasses import dataclass, field
|
|
|
25
25
|
|
|
26
26
|
from bce.core.orchestrator.anchors import AnchorResult
|
|
27
27
|
from bce.core.orchestrator.expand import expand_from_anchors, to_candidates
|
|
28
|
+
from bce.core.orchestrator.profile import RetrievalProfile
|
|
28
29
|
from bce.core.orchestrator.text import query_terms
|
|
29
30
|
from bce.core.scoring.engine import (
|
|
30
31
|
CONTAINER_KINDS,
|
|
@@ -59,6 +60,8 @@ PER_CONTAINER_SHARE = 0.3
|
|
|
59
60
|
#: that split is where the model is weak (recall 10 %); on the holdout split, where the model is
|
|
60
61
|
#: stronger (18 %), 0.3 let three of the model's own hits slip from the top 5 / out of the list.
|
|
61
62
|
#: 0.5 is the a-priori choice: half the list is the model's, whatever the engine thinks.
|
|
63
|
+
#: This is the voyage-code-4 value; another embedding model may carry its own share and a
|
|
64
|
+
#: guard on its top ranks in its :class:`~bce.core.orchestrator.profile.RetrievalProfile`.
|
|
62
65
|
SEMANTIC_RESERVE_SHARE = 0.5
|
|
63
66
|
#: Diversity rule 3: at most ceil(n * share) container symbols among the first n (a class is a
|
|
64
67
|
#: location, not a change site; its members carry the change).
|
|
@@ -95,9 +98,12 @@ class RetrievalOrchestrator:
|
|
|
95
98
|
self,
|
|
96
99
|
repository: GraphRepository,
|
|
97
100
|
weights: ScoreWeights | None = None,
|
|
101
|
+
profile: RetrievalProfile | None = None,
|
|
98
102
|
) -> None:
|
|
99
103
|
self.repository = repository
|
|
100
104
|
self.weights = weights or ScoreWeights()
|
|
105
|
+
#: Model-specific narrowing constants; ``None`` = the voyage defaults.
|
|
106
|
+
self.profile = profile
|
|
101
107
|
|
|
102
108
|
def retrieve(
|
|
103
109
|
self,
|
|
@@ -141,7 +147,7 @@ class RetrievalOrchestrator:
|
|
|
141
147
|
timings["score_ms"] = _elapsed_ms(stage)
|
|
142
148
|
|
|
143
149
|
stage = time.perf_counter()
|
|
144
|
-
narrowed = narrow(ranked, max_candidates)
|
|
150
|
+
narrowed = narrow(ranked, max_candidates, profile=self.profile)
|
|
145
151
|
timings["narrow_ms"] = _elapsed_ms(stage)
|
|
146
152
|
|
|
147
153
|
signals = {c.symbol_id: c.task_signal for c in ranked if c.task_signal > 0.0}
|
|
@@ -168,7 +174,9 @@ def _container_of(cand: Candidate) -> str | None:
|
|
|
168
174
|
return "::".join(segments[:-1])
|
|
169
175
|
|
|
170
176
|
|
|
171
|
-
def narrow(
|
|
177
|
+
def narrow(
|
|
178
|
+
ranked: list[Candidate], max_candidates: int, *, profile: RetrievalProfile | None = None
|
|
179
|
+
) -> list[Candidate]:
|
|
172
180
|
"""Deterministic, diversity-aware top-N over an already ranked (score desc, id asc) list.
|
|
173
181
|
|
|
174
182
|
The list is built one slot at a time from two streams - the embedding model's ranks (rank
|
|
@@ -176,9 +184,15 @@ def narrow(ranked: list[Candidate], max_candidates: int) -> list[Candidate]:
|
|
|
176
184
|
``narrow(pool, n) == narrow(pool, n + 1)[:n]``: asking for a longer list never changes what
|
|
177
185
|
the shorter one was (K-monotonic), and a K=20 run can be cut to K=10 exactly.
|
|
178
186
|
|
|
179
|
-
|
|
187
|
+
``profile`` supplies the two model-specific constants (default: the voyage values,
|
|
188
|
+
:data:`SEMANTIC_RESERVE_SHARE` and no guard). Slot ``i`` (1-based) goes to:
|
|
180
189
|
|
|
181
|
-
|
|
190
|
+
0. the model's next rank while ``i <= profile.semantic_guard_ranks`` (the guard: the model's
|
|
191
|
+
first ``n`` ranks *are* slots 1..n, whatever the engine scored - only the container cap
|
|
192
|
+
can skip one). A model whose correct hits spread over ranks 1-15 (jina) needs this;
|
|
193
|
+
without it the engine's agreement with the model's *deeper* ranks satisfies the share
|
|
194
|
+
below and its own picks push the model's ranks 7-15 to positions 11-19 or out;
|
|
195
|
+
1. the model's next rank while fewer than ``i * semantic_reserve_share`` slots came from the
|
|
182
196
|
model stream (so position 1 is always the model's #1, and the model's top ranks can never
|
|
183
197
|
be ranked out of the answer);
|
|
184
198
|
2. else the engine's head (score order). An engine pick that *is* one of the model's ranks
|
|
@@ -206,6 +220,8 @@ def narrow(ranked: list[Candidate], max_candidates: int) -> list[Candidate]:
|
|
|
206
220
|
"""
|
|
207
221
|
if max_candidates <= 0 or not ranked:
|
|
208
222
|
return []
|
|
223
|
+
reserve_share = SEMANTIC_RESERVE_SHARE if profile is None else profile.semantic_reserve_share
|
|
224
|
+
guard_ranks = 0 if profile is None else max(0, int(profile.semantic_guard_ranks))
|
|
209
225
|
|
|
210
226
|
model_stream = sorted(
|
|
211
227
|
(c for c in ranked if c.semantic_rank is not None),
|
|
@@ -261,7 +277,7 @@ def narrow(ranked: list[Candidate], max_candidates: int) -> list[Candidate]:
|
|
|
261
277
|
if model_head is None and engine_head is None:
|
|
262
278
|
break
|
|
263
279
|
if model_head is not None and (
|
|
264
|
-
engine_head is None or model_taken < slot *
|
|
280
|
+
engine_head is None or slot <= guard_ranks or model_taken < slot * reserve_share
|
|
265
281
|
):
|
|
266
282
|
take(model_head, from_model=True)
|
|
267
283
|
elif engine_head is not None:
|