embedflow 0.6.0__tar.gz → 0.7.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.
- {embedflow-0.6.0 → embedflow-0.7.0}/CHANGELOG.md +12 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/CITATION.cff +1 -1
- {embedflow-0.6.0 → embedflow-0.7.0}/PKG-INFO +22 -2
- {embedflow-0.6.0 → embedflow-0.7.0}/README.md +20 -1
- {embedflow-0.6.0 → embedflow-0.7.0}/README_PYPI.md +21 -1
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/api.md +8 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/cli.md +7 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/configuration.md +47 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/limitations.md +1 -1
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/releasing.md +8 -8
- embedflow-0.7.0/docs/shadow-mode.md +104 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/__init__.py +3 -3
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/cache/persistent_cache.py +23 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/cli.py +140 -10
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/config.py +169 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/facade.py +50 -4
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/materializer.py +65 -2
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/runtime.py +60 -2
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/serving/api.py +26 -3
- embedflow-0.7.0/embedflow/serving/engine.py +630 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/serving/schemas.py +1 -0
- embedflow-0.7.0/embedflow/shadow/__init__.py +9 -0
- embedflow-0.7.0/embedflow/shadow/models.py +60 -0
- embedflow-0.7.0/embedflow/shadow/report.py +48 -0
- embedflow-0.7.0/embedflow/shadow/runner.py +328 -0
- embedflow-0.7.0/embedflow/shadow/telemetry.py +773 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/PKG-INFO +22 -2
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/SOURCES.txt +10 -0
- embedflow-0.7.0/examples/shadow/README.md +13 -0
- embedflow-0.7.0/examples/shadow/embedflow.yaml.example +25 -0
- embedflow-0.7.0/examples/shadow/run_demo.py +95 -0
- embedflow-0.7.0/examples/shadow/run_demo.sh +10 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/pyproject.toml +1 -1
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/release_gate.py +16 -4
- embedflow-0.6.0/embedflow/serving/engine.py +0 -228
- {embedflow-0.6.0 → embedflow-0.7.0}/CONTRIBUTING.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/LICENSE +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/MANIFEST.in +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/SECURITY.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/assets/README.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/assets/candidate-gap-example.svg +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/assets/dashboard-screenshot.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/assets/terminal-demo.txt +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/concepts.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/contributing-benchmarks.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/economics.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/installation.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/faiss.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/milvus.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/pgvector.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/pinecone.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/qdrant.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/weaviate.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/methodology.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/planner.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/quickstart.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/docs/registry.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/__main__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/analysis.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/api.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/cache/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/cache/base.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/candidate_gap.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/containment.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/evaluate.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/metrics.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/migration_depth.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/probe.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/report.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/t2.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/benchmark_profiles.jsonl +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/checksums.sha256 +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/migrations.jsonl +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/registry_manifest.json +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/research_summaries.json +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/schema_version.json +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/frozen/T2_V1_FROZEN_SPEC.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/frozen/T2_V1_FROZEN_SPEC.sha256 +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/base.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/faiss_backend.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/milvus_backend.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/pgvector_backend.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/pinecone_backend.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/qdrant_backend.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/weaviate_backend.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/metrics/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/metrics/latency.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/compatibility.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/planner.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/state.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/models/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/models/base.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/models/huggingface.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/economics.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/models.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/planner.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/rendering.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/registry/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/registry/loader.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/registry/matcher.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/registry/schema.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/serving/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/serving/factory.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/dependency_links.txt +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/entry_points.txt +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/requires.txt +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/top_level.txt +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/faiss/README.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/faiss/documents.jsonl +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/faiss/embedflow.yaml +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/faiss/queries.jsonl +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/milvus/README.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/milvus/compose.yaml +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/milvus/embedflow.yaml.example +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/milvus/run_demo.sh +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/README.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/build_index.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/compose.yaml +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/embedflow.yaml +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/init.sql +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/queries.jsonl +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/run_demo.sh +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pinecone/README.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pinecone/embedflow.yaml.example +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/pinecone/run_smoke.sh +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/planner/README.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/planner/run_demo.sh +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/README.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/build_index.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/documents.jsonl +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/embedflow.yaml +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/queries.jsonl +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/README.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/documents.jsonl +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/embedflow.yaml +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/qrels.json +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/queries.jsonl +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/weaviate/README.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/weaviate/compose.yaml +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/weaviate/embedflow.yaml.example +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/examples/weaviate/run_demo.sh +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/frozen/T2_V1_FROZEN_SPEC.md +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/frozen/T2_V1_FROZEN_SPEC.sha256 +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/requirements-dev.txt +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/requirements.txt +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/milvus_fixture.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/pinecone_smoke.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/real_qdrant_smoke.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/run_demo.sh +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/run_tests.sh +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/validate_milvus.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/validate_pgvector_10k.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/validate_weaviate.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/weaviate_fixture.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/scripts/weaviate_smoke.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/setup.cfg +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/src/__init__.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/src/embed.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/src/probe_features.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/src/storage.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/src/t2_v1.py +0 -0
- {embedflow-0.6.0 → embedflow-0.7.0}/src/utils.py +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.7.0 — Source-authoritative Shadow Mode
|
|
4
|
+
|
|
5
|
+
- Added bounded, deterministic, source-authoritative Shadow Mode for observing
|
|
6
|
+
target reranking on sampled traffic without delaying or changing source
|
|
7
|
+
responses.
|
|
8
|
+
- Added failure/timeout isolation, queue backpressure, optional asynchronous
|
|
9
|
+
target-cache materialization, target-coverage and ranking diagnostics, and
|
|
10
|
+
privacy-conscious SQLite telemetry.
|
|
11
|
+
- Added `embedflow shadow report`, API/status integration, documentation, and a
|
|
12
|
+
deterministic offline demonstration. Shadow reports remain operational
|
|
13
|
+
diagnostics and do not claim qrel-based retrieval quality.
|
|
14
|
+
|
|
3
15
|
## v0.6.0 — Migration planner
|
|
4
16
|
|
|
5
17
|
- Added an advisory `embedflow plan` command and Python API that combine
|
|
@@ -2,7 +2,7 @@ cff-version: 1.2.0
|
|
|
2
2
|
title: "EmbedFlow: Upgrading Legacy Embeddings Without Full Upfront Re-Embedding"
|
|
3
3
|
message: "If EmbedFlow contributes to your work, please cite this software release."
|
|
4
4
|
type: software
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.7.0
|
|
6
6
|
date-released: 2026-09-13
|
|
7
7
|
repository-code: "https://github.com/arnsri33/embedflow"
|
|
8
8
|
url: "https://github.com/arnsri33/embedflow"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: embedflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Progressive embedding-model migration over existing vector indexes.
|
|
5
5
|
Author: Arnav Srivastav
|
|
6
6
|
License-Expression: AGPL-3.0-only
|
|
@@ -204,6 +204,26 @@ behavior, candidate K, cache/economics projections, and staged rollout
|
|
|
204
204
|
guidance. It never routes traffic or mutates the source index; `SAFE` is not a
|
|
205
205
|
qrels-based retrieval-quality guarantee. See the [planner guide](https://github.com/arnsri33/embedflow/blob/main/docs/planner.md).
|
|
206
206
|
|
|
207
|
+
### Source-authoritative Shadow Mode
|
|
208
|
+
|
|
209
|
+
Run the target migration path beside sampled traffic while always returning the
|
|
210
|
+
source result:
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
runtime: {mode: shadow}
|
|
214
|
+
shadow: {enabled: true, sample_rate: 0.10, candidate_k: 100, materialize: true}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
embedflow serve --config ./embedflow.yaml
|
|
219
|
+
embedflow shadow report --config ./embedflow.yaml --since 24h --format json
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Shadow work is asynchronous, bounded, privacy-conscious, and failure isolated.
|
|
223
|
+
Reports contain operational and ranking-disagreement diagnostics—not qrel-based
|
|
224
|
+
quality guarantees—and recommendations never route canary traffic. See the
|
|
225
|
+
[Shadow Mode guide](https://github.com/arnsri33/embedflow/blob/main/docs/shadow-mode.md).
|
|
226
|
+
|
|
207
227
|
## Research
|
|
208
228
|
|
|
209
229
|
For candidate depth `K`, EmbedFlow measures:
|
|
@@ -258,7 +278,7 @@ cover the remaining commands and endpoints.
|
|
|
258
278
|
|
|
259
279
|
## Status
|
|
260
280
|
|
|
261
|
-
EmbedFlow v0.
|
|
281
|
+
EmbedFlow v0.7.0 is a pre-1.0 release for research and early real-world
|
|
262
282
|
testing. T2-v1 is an empirical finite-tail diagnostic, partial rankings can
|
|
263
283
|
differ from fully warm target reranking, and ANN fidelity needs a reference
|
|
264
284
|
comparison to audit.
|
|
@@ -156,6 +156,24 @@ embedflow plan --config ./embedflow.yaml --queries ./probe_queries.jsonl --forma
|
|
|
156
156
|
`SAFE` is an empirical finite-tail signal, not a retrieval-quality guarantee.
|
|
157
157
|
See [`docs/planner.md`](https://github.com/arnsri33/embedflow/blob/main/docs/planner.md).
|
|
158
158
|
|
|
159
|
+
Observe the reviewed migration path on real traffic without changing the
|
|
160
|
+
source result:
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
runtime: {mode: shadow}
|
|
164
|
+
shadow: {enabled: true, sample_rate: 0.10, candidate_k: 100, materialize: true}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
embedflow serve --config ./embedflow.yaml
|
|
169
|
+
embedflow shadow report --config ./embedflow.yaml --since 24h
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Shadow Mode is source-authoritative, bounded, and advisory. It records cache,
|
|
173
|
+
coverage, latency, and ranking-disagreement diagnostics; it does not claim
|
|
174
|
+
retrieval-quality preservation without qrels and never routes canary traffic.
|
|
175
|
+
See [`docs/shadow-mode.md`](https://github.com/arnsri33/embedflow/blob/main/docs/shadow-mode.md).
|
|
176
|
+
|
|
159
177
|
Search responses expose `COLD`, `PARTIAL`, or `WARM`, cache hits and misses,
|
|
160
178
|
synchronous work, queued work, and stage timings. Once the candidate vectors
|
|
161
179
|
are warm, target scoring over that candidate set is deterministic.
|
|
@@ -263,13 +281,14 @@ OpenAPI documentation; see
|
|
|
263
281
|
- [CLI reference](https://github.com/arnsri33/embedflow/blob/main/docs/cli.md)
|
|
264
282
|
- [API](https://github.com/arnsri33/embedflow/blob/main/docs/api.md)
|
|
265
283
|
- [Economics](https://github.com/arnsri33/embedflow/blob/main/docs/economics.md)
|
|
284
|
+
- [Shadow Mode](https://github.com/arnsri33/embedflow/blob/main/docs/shadow-mode.md)
|
|
266
285
|
- [Limitations](https://github.com/arnsri33/embedflow/blob/main/docs/limitations.md)
|
|
267
286
|
- [Contributing](https://github.com/arnsri33/embedflow/blob/main/CONTRIBUTING.md)
|
|
268
287
|
- [Security](https://github.com/arnsri33/embedflow/blob/main/SECURITY.md)
|
|
269
288
|
|
|
270
289
|
## Status
|
|
271
290
|
|
|
272
|
-
EmbedFlow v0.
|
|
291
|
+
EmbedFlow v0.7.0 is a pre-1.0 release for research and early real-world
|
|
273
292
|
testing.
|
|
274
293
|
|
|
275
294
|
- T2-v1 reports an empirical finite-tail diagnostic.
|
|
@@ -138,6 +138,26 @@ behavior, candidate K, cache/economics projections, and staged rollout
|
|
|
138
138
|
guidance. It never routes traffic or mutates the source index; `SAFE` is not a
|
|
139
139
|
qrels-based retrieval-quality guarantee. See the [planner guide](https://github.com/arnsri33/embedflow/blob/main/docs/planner.md).
|
|
140
140
|
|
|
141
|
+
### Source-authoritative Shadow Mode
|
|
142
|
+
|
|
143
|
+
Run the target migration path beside sampled traffic while always returning the
|
|
144
|
+
source result:
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
runtime: {mode: shadow}
|
|
148
|
+
shadow: {enabled: true, sample_rate: 0.10, candidate_k: 100, materialize: true}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
embedflow serve --config ./embedflow.yaml
|
|
153
|
+
embedflow shadow report --config ./embedflow.yaml --since 24h --format json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Shadow work is asynchronous, bounded, privacy-conscious, and failure isolated.
|
|
157
|
+
Reports contain operational and ranking-disagreement diagnostics—not qrel-based
|
|
158
|
+
quality guarantees—and recommendations never route canary traffic. See the
|
|
159
|
+
[Shadow Mode guide](https://github.com/arnsri33/embedflow/blob/main/docs/shadow-mode.md).
|
|
160
|
+
|
|
141
161
|
## Research
|
|
142
162
|
|
|
143
163
|
For candidate depth `K`, EmbedFlow measures:
|
|
@@ -192,7 +212,7 @@ cover the remaining commands and endpoints.
|
|
|
192
212
|
|
|
193
213
|
## Status
|
|
194
214
|
|
|
195
|
-
EmbedFlow v0.
|
|
215
|
+
EmbedFlow v0.7.0 is a pre-1.0 release for research and early real-world
|
|
196
216
|
testing. T2-v1 is an empirical finite-tail diagnostic, partial rankings can
|
|
197
217
|
differ from fully warm target reranking, and ANN fidelity needs a reference
|
|
198
218
|
comparison to audit.
|
|
@@ -21,6 +21,7 @@ Interactive OpenAPI documentation is available at
|
|
|
21
21
|
| GET | `/metrics` | Aggregated latency and queue metrics |
|
|
22
22
|
| GET | `/plan` | Current migration plan |
|
|
23
23
|
| GET | `/economics` | Configured economics estimate |
|
|
24
|
+
| GET | `/shadow/report` | Bounded Shadow Mode diagnostics |
|
|
24
25
|
|
|
25
26
|
## Search
|
|
26
27
|
|
|
@@ -48,6 +49,13 @@ The response includes the result list and migration fields such as:
|
|
|
48
49
|
request. A partial response scores the available target vectors; it can differ
|
|
49
50
|
from the fully warm ranking.
|
|
50
51
|
|
|
52
|
+
When `runtime.mode=shadow`, `/search` returns the source-only result and marks
|
|
53
|
+
`migration.source_authoritative=true`; target work is scheduled in the
|
|
54
|
+
background. `/shadow/report`, `/status`, and `/metrics` expose aggregate shadow
|
|
55
|
+
observations without raw query/document text or vectors. Shadow failures and
|
|
56
|
+
timeouts are isolated from the response. Ranking overlap is diagnostic and is
|
|
57
|
+
not a qrels-based quality claim.
|
|
58
|
+
|
|
51
59
|
The advisory migration planner is exposed through the Python API and the
|
|
52
60
|
`embedflow plan` CLI. It is intentionally not a synchronous FastAPI endpoint:
|
|
53
61
|
probe analysis may load models and perform bounded candidate work, so operators
|
|
@@ -10,6 +10,7 @@ embedflow init --config ./embedflow.yaml
|
|
|
10
10
|
embedflow analyze --config ./embedflow.yaml --output-dir ./analysis
|
|
11
11
|
embedflow evaluate --config ./experiment.yaml --output-dir ./results
|
|
12
12
|
embedflow plan --config ./embedflow.yaml --queries ./probe_queries.jsonl
|
|
13
|
+
embedflow shadow report --config ./embedflow.yaml --since 24h
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
`analyze` is the no-target-index workflow. It uses probe queries and frozen
|
|
@@ -42,6 +43,12 @@ materialization throughput. `audit-index` checks the source index against an
|
|
|
42
43
|
exact/reference configuration where supported. `prewarm` schedules target
|
|
43
44
|
document work; it does not change source-index results.
|
|
44
45
|
|
|
46
|
+
Use `embedflow serve --mode shadow` to opt into source-authoritative Shadow
|
|
47
|
+
Mode for one process. `embedflow shadow report` reads the bounded telemetry
|
|
48
|
+
store and supports `--format text|json|yaml`, `--since`, `--output`, and
|
|
49
|
+
`--quiet`. A valid `DEFER`/`EXPAND_K` report is still an analytical success;
|
|
50
|
+
only invalid configuration or initialization returns a non-zero exit code.
|
|
51
|
+
|
|
45
52
|
## Registry and profiles
|
|
46
53
|
|
|
47
54
|
```bash
|
|
@@ -77,9 +77,38 @@ planner:
|
|
|
77
77
|
corpus_name: null
|
|
78
78
|
corpus_fingerprint: null
|
|
79
79
|
|
|
80
|
+
# Source-authoritative observation mode. ``runtime.mode: migration`` (the
|
|
81
|
+
# default) leaves Shadow Mode inactive. ``mode: shadow`` is an explicit opt-in.
|
|
82
|
+
runtime:
|
|
83
|
+
mode: migration # migration, normal, source, or shadow
|
|
84
|
+
|
|
85
|
+
shadow:
|
|
86
|
+
enabled: true
|
|
87
|
+
sample_rate: 0.10
|
|
88
|
+
sample_seed: 42
|
|
89
|
+
candidate_k: 100
|
|
90
|
+
materialize: true
|
|
91
|
+
max_inflight: 32
|
|
92
|
+
queue_capacity: 1000
|
|
93
|
+
timeout_ms: 10000
|
|
94
|
+
shutdown_grace_ms: 1000
|
|
95
|
+
telemetry:
|
|
96
|
+
enabled: true
|
|
97
|
+
path: ./.embedflow/shadow.sqlite3
|
|
98
|
+
retain_query_records: false
|
|
99
|
+
retain_query_text: false
|
|
100
|
+
max_records: 10000
|
|
101
|
+
retention_days: null
|
|
102
|
+
report_k: 10
|
|
103
|
+
min_target_coverage_for_ranking: 1.0
|
|
104
|
+
|
|
80
105
|
state_path: ./embedflow_state.json
|
|
81
106
|
```
|
|
82
107
|
|
|
108
|
+
Shadow Mode always returns the source-authoritative result before target work
|
|
109
|
+
finishes. See [`shadow-mode.md`](shadow-mode.md) for queue, timeout, privacy,
|
|
110
|
+
materialization, and report semantics.
|
|
111
|
+
|
|
83
112
|
## Model contracts
|
|
84
113
|
|
|
85
114
|
Model configuration can include `revision`, `dimension`, `max_length`,
|
|
@@ -164,6 +193,24 @@ EMBEDFLOW_PLANNER_LATENCY_BUDGET_MS
|
|
|
164
193
|
EMBEDFLOW_PLANNER_ACCESS_TRACE
|
|
165
194
|
EMBEDFLOW_PLANNER_CORPUS_NAME
|
|
166
195
|
EMBEDFLOW_PLANNER_CORPUS_FINGERPRINT
|
|
196
|
+
EMBEDFLOW_RUNTIME_MODE
|
|
197
|
+
EMBEDFLOW_SHADOW_ENABLED
|
|
198
|
+
EMBEDFLOW_SHADOW_SAMPLE_RATE
|
|
199
|
+
EMBEDFLOW_SHADOW_SAMPLE_SEED
|
|
200
|
+
EMBEDFLOW_SHADOW_CANDIDATE_K
|
|
201
|
+
EMBEDFLOW_SHADOW_MATERIALIZE
|
|
202
|
+
EMBEDFLOW_SHADOW_MAX_INFLIGHT
|
|
203
|
+
EMBEDFLOW_SHADOW_QUEUE_CAPACITY
|
|
204
|
+
EMBEDFLOW_SHADOW_TIMEOUT_MS
|
|
205
|
+
EMBEDFLOW_SHADOW_SHUTDOWN_GRACE_MS
|
|
206
|
+
EMBEDFLOW_SHADOW_TELEMETRY_ENABLED
|
|
207
|
+
EMBEDFLOW_SHADOW_TELEMETRY_PATH
|
|
208
|
+
EMBEDFLOW_SHADOW_RETAIN_QUERY_RECORDS
|
|
209
|
+
EMBEDFLOW_SHADOW_RETAIN_QUERY_TEXT
|
|
210
|
+
EMBEDFLOW_SHADOW_MAX_RECORDS
|
|
211
|
+
EMBEDFLOW_SHADOW_REPORT_K
|
|
212
|
+
EMBEDFLOW_SHADOW_RETENTION_DAYS
|
|
213
|
+
EMBEDFLOW_SHADOW_MIN_TARGET_COVERAGE
|
|
167
214
|
```
|
|
168
215
|
|
|
169
216
|
## Input files
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Limitations and release scope
|
|
2
2
|
|
|
3
|
-
EmbedFlow v0.
|
|
3
|
+
EmbedFlow v0.7.0 is a pre-1.0 release for research and early real-world
|
|
4
4
|
testing. The serving path is designed to make migration experiments concrete;
|
|
5
5
|
production rollout still requires application-specific validation.
|
|
6
6
|
|
|
@@ -18,8 +18,8 @@ python -m twine check dist/*
|
|
|
18
18
|
Inspect both archives before uploading:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
unzip -l dist/embedflow-0.
|
|
22
|
-
tar -tzf dist/embedflow-0.
|
|
21
|
+
unzip -l dist/embedflow-0.7.0-py3-none-any.whl
|
|
22
|
+
tar -tzf dist/embedflow-0.7.0.tar.gz
|
|
23
23
|
sha256sum dist/*
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -32,7 +32,7 @@ Test the wheel outside the source tree:
|
|
|
32
32
|
```bash
|
|
33
33
|
python -m venv /tmp/embedflow-wheel-test
|
|
34
34
|
/tmp/embedflow-wheel-test/bin/python -m pip install --upgrade pip
|
|
35
|
-
/tmp/embedflow-wheel-test/bin/python -m pip install dist/embedflow-0.
|
|
35
|
+
/tmp/embedflow-wheel-test/bin/python -m pip install dist/embedflow-0.7.0-py3-none-any.whl
|
|
36
36
|
cd /tmp
|
|
37
37
|
/tmp/embedflow-wheel-test/bin/python -c "import embedflow; print(embedflow.__version__)"
|
|
38
38
|
/tmp/embedflow-wheel-test/bin/embedflow --help
|
|
@@ -65,7 +65,7 @@ python -m venv /tmp/embedflow-testpypi
|
|
|
65
65
|
/tmp/embedflow-testpypi/bin/python -m pip install \
|
|
66
66
|
--index-url https://test.pypi.org/simple/ \
|
|
67
67
|
--extra-index-url https://pypi.org/simple/ \
|
|
68
|
-
embedflow==0.
|
|
68
|
+
embedflow==0.7.0
|
|
69
69
|
cd /tmp
|
|
70
70
|
/tmp/embedflow-testpypi/bin/python -c "import embedflow; print(embedflow.__version__)"
|
|
71
71
|
/tmp/embedflow-testpypi/bin/embedflow --help
|
|
@@ -79,11 +79,11 @@ Test optional integrations in a second clean environment:
|
|
|
79
79
|
/tmp/embedflow-testpypi/bin/python -m pip install \
|
|
80
80
|
--index-url https://test.pypi.org/simple/ \
|
|
81
81
|
--extra-index-url https://pypi.org/simple/ \
|
|
82
|
-
"embedflow[faiss,dashboard,pinecone,milvus,weaviate]==0.
|
|
82
|
+
"embedflow[faiss,dashboard,pinecone,milvus,weaviate]==0.7.0"
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
If the same filename already exists on TestPyPI, use a pre-release
|
|
86
|
-
|
|
85
|
+
If the same filename already exists on TestPyPI, use a pre-release for the
|
|
86
|
+
TestPyPI-only trial. Never overwrite the production version.
|
|
87
87
|
|
|
88
88
|
## Trusted Publishing configuration
|
|
89
89
|
|
|
@@ -115,7 +115,7 @@ above keeps the test step explicit.
|
|
|
115
115
|
3. Run the final release gate and review the generated report.
|
|
116
116
|
4. Configure the PyPI pending publisher and protected `pypi` environment.
|
|
117
117
|
5. Create a Git tag and GitHub Release for the exact package version, for
|
|
118
|
-
example `v0.
|
|
118
|
+
example `v0.7.0`.
|
|
119
119
|
6. Approve the `pypi` environment when the release workflow is ready.
|
|
120
120
|
7. Verify the files and metadata on PyPI.
|
|
121
121
|
8. Install from production PyPI in a directory outside this checkout.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Shadow Mode
|
|
2
|
+
|
|
3
|
+
Shadow Mode runs the configured target migration path beside real traffic while
|
|
4
|
+
the existing source result remains authoritative. It is an observation and
|
|
5
|
+
cache-warming tool, not a traffic router:
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
runtime:
|
|
9
|
+
mode: shadow
|
|
10
|
+
|
|
11
|
+
shadow:
|
|
12
|
+
enabled: true
|
|
13
|
+
sample_rate: 0.10
|
|
14
|
+
sample_seed: 42
|
|
15
|
+
candidate_k: 100
|
|
16
|
+
materialize: true
|
|
17
|
+
max_inflight: 32
|
|
18
|
+
queue_capacity: 1000
|
|
19
|
+
timeout_ms: 10000
|
|
20
|
+
telemetry:
|
|
21
|
+
enabled: true
|
|
22
|
+
path: ./.embedflow/shadow.sqlite3
|
|
23
|
+
retain_query_records: false
|
|
24
|
+
retain_query_text: false
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Install the optional backend/model extras required by the selected
|
|
28
|
+
configuration, then start the ordinary service:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install "embedflow[dashboard]"
|
|
32
|
+
embedflow serve --config embedflow.yaml
|
|
33
|
+
# or override the file for one process:
|
|
34
|
+
embedflow serve --config embedflow.yaml --mode shadow
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The request path performs source encoding, source ANN retrieval, and document
|
|
38
|
+
resolution exactly as source-only serving. It returns that result without
|
|
39
|
+
waiting for target encoding, cache misses, reranking, telemetry, or the
|
|
40
|
+
materialization worker. Shadow jobs use a bounded queue and worker pool. A full
|
|
41
|
+
queue drops only shadow work; a target/model/cache/telemetry failure or timeout
|
|
42
|
+
is recorded and cannot change the primary response.
|
|
43
|
+
|
|
44
|
+
If the target model cannot be loaded during startup, source/shadow serving
|
|
45
|
+
still opens with target work marked unavailable; sampled requests record the
|
|
46
|
+
isolated target-encoding failure. Normal migration mode remains fail-fast for
|
|
47
|
+
the same startup error.
|
|
48
|
+
|
|
49
|
+
`candidate_k` is explicit. The planner may suggest a value, but Shadow Mode
|
|
50
|
+
does not silently select one. `materialize: false` reads already cached target
|
|
51
|
+
vectors without changing cache or queue state. With `materialize: true`, cold
|
|
52
|
+
candidate IDs are deduplicated in the existing persistent materialization queue
|
|
53
|
+
and warmed asynchronously; synchronous target misses remain zero in Shadow
|
|
54
|
+
Mode. A comparison with incomplete target vectors is reported as `partial`,
|
|
55
|
+
with target coverage shown separately.
|
|
56
|
+
|
|
57
|
+
## Reports
|
|
58
|
+
|
|
59
|
+
Reports use the privacy-preserving SQLite telemetry store (by default beside
|
|
60
|
+
the configured cache):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
embedflow shadow report --config embedflow.yaml --since 24h
|
|
64
|
+
embedflow shadow report --config embedflow.yaml --since 24h --format json --output shadow-report.json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Use `/shadow/report` or the `shadow` section of `/status` and `/metrics` in the
|
|
68
|
+
FastAPI service. JSON/YAML stdout contains only the requested artifact; progress
|
|
69
|
+
and errors belong on stderr. `--since` accepts seconds or `s`, `m`, `h`, `d`,
|
|
70
|
+
and `w` suffixes.
|
|
71
|
+
|
|
72
|
+
Reports contain sampled/completed/partial/failed/timed-out/dropped counts,
|
|
73
|
+
cache and materialization accounting, source and shadow latency distributions,
|
|
74
|
+
target coverage, top-1 agreement, and top-k overlap. Shadow latency is not
|
|
75
|
+
user-facing latency because it is off the primary critical path. Ranking
|
|
76
|
+
disagreement and overlap are diagnostics, not recall, nDCG, or quality loss;
|
|
77
|
+
without qrels/native-target evaluation no quality guarantee is made.
|
|
78
|
+
|
|
79
|
+
T2-v1 remains the frozen window-level diagnostic. EmbedFlow does not label an
|
|
80
|
+
individual production query `T2 SAFE`. A report recommendation is operational
|
|
81
|
+
guidance only:
|
|
82
|
+
|
|
83
|
+
- `CONTINUE_SHADOW` means evidence or coverage is still limited.
|
|
84
|
+
- `EXPAND_K` means the accumulated T2 window requests a larger candidate pool.
|
|
85
|
+
- `INVESTIGATE` means failures, timeouts, or uncertain T2 behavior need review.
|
|
86
|
+
- `READY_FOR_CANARY_EVALUATION` means an operator may consider a separately
|
|
87
|
+
designed canary; it never routes traffic automatically.
|
|
88
|
+
|
|
89
|
+
Raw query text, candidate text, source vectors, target vectors, and credentials
|
|
90
|
+
are not persisted in telemetry. Query IDs may be retained only when explicitly
|
|
91
|
+
enabled, and retention is bounded by `max_records`. The telemetry database is
|
|
92
|
+
segmented by source/target contract and candidate-K fingerprint so unrelated
|
|
93
|
+
migrations are not mixed.
|
|
94
|
+
|
|
95
|
+
On shutdown EmbedFlow stops accepting new shadow work, drops queued jobs when
|
|
96
|
+
necessary, and waits only the configured bounded grace period. A corrupted or
|
|
97
|
+
unwritable telemetry file degrades observability; it does not take source
|
|
98
|
+
serving down. Source indexes are read-only during Shadow Mode. The planner and
|
|
99
|
+
Shadow Mode are separate: generate a plan first, then copy its recommended K
|
|
100
|
+
explicitly into a reviewed Shadow configuration.
|
|
101
|
+
|
|
102
|
+
Shadow Mode is advisory and experimental operational instrumentation. It does
|
|
103
|
+
not provide autonomous rollout, rollback, qrel evaluation, or ANN-fidelity
|
|
104
|
+
proof.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""EmbedFlow: progressive embedding-model migration for existing indexes."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.
|
|
3
|
+
__version__ = "0.7.0"
|
|
4
4
|
|
|
5
|
-
from .config import EmbedFlowConfig, PlannerConfig, load_config
|
|
5
|
+
from .config import EmbedFlowConfig, PlannerConfig, RuntimeConfig, ShadowConfig, ShadowTelemetryConfig, load_config
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def plan(*args, **kwargs):
|
|
@@ -28,4 +28,4 @@ def analyze_migration(*args, **kwargs):
|
|
|
28
28
|
return _analyze_migration(*args, **kwargs)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
__all__ = ["EmbedFlowConfig", "PlannerConfig", "load_config", "migrate", "plan", "analyze_migration", "__version__"]
|
|
31
|
+
__all__ = ["EmbedFlowConfig", "PlannerConfig", "RuntimeConfig", "ShadowConfig", "ShadowTelemetryConfig", "load_config", "migrate", "plan", "analyze_migration", "__version__"]
|
|
@@ -141,6 +141,29 @@ class SQLiteVectorCache(TargetVectorCache):
|
|
|
141
141
|
self._db.commit()
|
|
142
142
|
return values
|
|
143
143
|
|
|
144
|
+
def peek(self, document_ids: list[str]) -> dict[str, np.ndarray]:
|
|
145
|
+
"""Read cached vectors without changing hit/miss telemetry.
|
|
146
|
+
|
|
147
|
+
Shadow analysis with ``materialize=false`` must not make ordinary
|
|
148
|
+
serving cache statistics look like migration traffic. This method is
|
|
149
|
+
intentionally a read-only fast path; callers that need accounting
|
|
150
|
+
should continue to use :meth:`get`.
|
|
151
|
+
"""
|
|
152
|
+
ids = [str(x) for x in document_ids]
|
|
153
|
+
if not ids:
|
|
154
|
+
return {}
|
|
155
|
+
with self._lock:
|
|
156
|
+
self._ensure_open()
|
|
157
|
+
rows: list[tuple[Any, ...]] = []
|
|
158
|
+
for start in range(0, len(ids), self._LOOKUP_BATCH_SIZE):
|
|
159
|
+
chunk = ids[start:start + self._LOOKUP_BATCH_SIZE]
|
|
160
|
+
placeholders = ",".join("?" for _ in chunk)
|
|
161
|
+
rows.extend(self._db.execute(
|
|
162
|
+
f"SELECT document_id,model_fingerprint,dimension,dtype,vector,checksum,created_at,accessed_at "
|
|
163
|
+
f"FROM target_vectors WHERE model_fingerprint=? AND document_id IN ({placeholders})",
|
|
164
|
+
[self.model_fingerprint, *chunk]).fetchall())
|
|
165
|
+
return {str(row[0]): self._decode(row) for row in rows}
|
|
166
|
+
|
|
144
167
|
def put(self, document_ids: list[str], vectors: np.ndarray) -> None:
|
|
145
168
|
ids = [str(x) for x in document_ids]
|
|
146
169
|
values = np.asarray(vectors, dtype=self.dtype)
|