aecp
Embedding providers deprecate models constantly — ada-002 is gone, text-embedding-3 is next. When that happens, you either re-embed your entire corpus (expensive, slow, risky) or get stuck on a dead model. AECP lets you switch without re-embedding: fit a lightweight linear transform from ~2K calibration texts, apply it to stored vectors, and gate the migration on measured retrieval retention. 87-91% retention on BEIR benchmarks.
Install
pip install aecp
Python >= 3.10. Core deps: numpy, scikit-learn, typer, rich.
Optional extras:
pip install aecp[chroma]— ChromaDB adapterpip install aecp[langchain]— LangChain embeddings shimpip install aecp[llamaindex]— LlamaIndex query wrapperpip install aecp[sentence-transformers]— local model supportpip install aecp[qdrant]— Qdrant store adapterpip install aecp[openai]— OpenAI client shimpip install aecp[all]— everything above
5-minute trial: query-time wrapper
Zero writes to your vector store. Map new-model queries into legacy space on-the-fly. Fully reversible.
LlamaIndex
from aecp.wrappers.llamaindex import AECPEmbedding
from aecp.mapping.registry import load_mapping
mapping = load_mapping("mapping.aecp")
wrapper = AECPEmbedding(
new_model_embedder=your_llamaindex_embedder,
transform_artifact_path="mapping.aecp",
)
# Use wrapper anywhere LlamaIndex expects a BaseEmbedding
# Queries are mapped; document embeddings raise AECPWrapperUsageError
OpenAI client
import openai
from aecp.wrappers.openai_shim import AECPOpenAI
client = openai.OpenAI()
shim = AECPOpenAI(client, "mapping.aecp")
response = shim.embeddings.create(input=["query text"], model="text-embedding-3-small")
# response.data[0].embedding is now in legacy-model space
LangChain
from aecp.adapters.langchain import AECPEmbeddings
from langchain_openai import OpenAIEmbeddings
mapping = Mapping.load("mapping.aecp")
base = OpenAIEmbeddings(model="text-embedding-3-small")
ae = AECPEmbeddings(mapping, base)
from langchain_chroma import Chroma
db = Chroma.from_documents(docs, embedding=ae)
results = db.similarity_search("query", k=10)
Quality gate
Before migrating anything, verify the transform preserves retrieval quality:
aecp gate --mapping mapping.aecp \
--source-vectors X_sample.npy \
--target-vectors Y_sample.npy
Output: retention table (Recall@1/5/10, MRR), bootstrap confidence intervals, per-metric pass/fail, and a one-line verdict. Exit code 0 for PASS, 1 for WARN/FAIL — use it in CI.
Full migration
# 1. Plan cost
aecp plan --source-model ada-002 --target-model te3-large --corpus-size 1000000
# 2. Calibrate
aecp calibrate --source-vectors X.npy --target-vectors Y.npy -o mapping.aecp
# 3. Gate
aecp gate --mapping mapping.aecp --source-vectors X.npy --target-vectors Y.npy
# 4. Migrate
aecp transform --mapping mapping.aecp --source-dir ./old_store --target-dir ./new_store
Serve mode (zero corpus writes)
Map queries on-the-fly without touching stored data:
from aecp.serve import QueryAdapter
qa = QueryAdapter.load("mapping.aecp")
legacy_vec = qa.map_query(new_model_embed(query))
Adapter status
| Store | Serve mode | Offline migration | Status |
|---|---|---|---|
| ChromaDB | AECPChromaFunction |
migrate_collection() |
Supported |
| LangChain | AECPEmbeddings |
via store adapter | Supported |
| LlamaIndex | AECPEmbedding wrapper |
via store adapter | Query wrapper |
| OpenAI | AECPOpenAI shim |
N/A | Query shim |
| Qdrant | QdrantStore |
checkpointed in-place | Supported |
| Pinecone | — | shadow-namespace | Planned |
Claims policy
Every quantitative claim in this README or docs references a committed artifact in benchmarks/results/ and a row in aecp-python/CLAIMS.md. No exceptions. If a number isn't in CLAIMS.md, it isn't a claim.
Adapter comparison (SciFact, MiniLM→bge-large, K=4000, 3 seeds)
| Adapter | nDCG@10 retention | Notes |
|---|---|---|
| Ridge | 0.871 ± 0.006 | Default. Fast, stable. |
| LowRank | 0.857 ± 0.009 | Compressed matrix. ~1% worse. |
| MLP | 0.727 ± 0.007 | No tuning. Linear wins. |
K-sweep (all adapters averaged, SciFact, 3 seeds)
| K | nDCG@10 retention | Gate |
|---|---|---|
| 500 | 0.671 ± 0.041 | WARN |
| 1000 | 0.735 ± 0.058 | WARN |
| 2000 | 0.785 ± 0.052 | PASS |
| 4000 | 0.832 ± 0.061 | PASS |
Same-dim pair (bge-large→e5-large, 1024→1024)
| Metric | Value |
|---|---|
| Floor (raw cross-space) | 0.0 |
| AECP (mapped) | 0.667 |
| Ceiling (full re-embed) | 0.722 |
| Retention | 0.923 ± 0.010 |
Same dimension ≠ same space. e5 models require "query: "/"passage: " prefixes; without them ceiling drops to 0.36.
Confidence flags (predictive across both pairs)
| Pair | High-conf R@10 | Low-conf R@10 | Gap |
|---|---|---|---|
| bge→e5 | 0.955 | 0.637 | 0.318 |
| MiniLM→bge | 0.875 | 0.651 | 0.224 |
Score recalibration (MiniLM→bge, rectangular)
| Threshold | Raw recall | + Recalibration | Δ |
|---|---|---|---|
| τ = 0.60 | 78% | 100% | +22% |
| τ = 0.70 | 27% | 67% | +40% |
When NOT to use AECP
- Maximum retrieval quality matters more than cost → re-embed
- Calibration domain mismatches corpus (e.g., code index calibrated on prose)
- Quality gate returns FAIL → do not migrate; re-embed
- You need unsupervised migration (AECP requires paired calibration)
- K < 2000 (quality degrades significantly below this)
Anti-patterns
- Do not mix vectors from different models in one collection
- Do not assume same dimensionality means compatibility
- Do not skip the quality gate
- Do not use MLP adapter (0.727 vs 0.871 for Ridge, same cost)
How it works
- Embed K texts with source and target models → matrices X, Y
- Fit ridge map Y = [X | 1] W (handles unequal dims)
- Hold out 10% to estimate quality
- Transform corpus: V' = normalize(V @ W) (streaming batches)
- Write to new collection; keep old as rollback
Prior art
Engineering, not research. Built on:
- vec2vec (Jha et al., 2025)
- Drift-Adapter (EMNLP 2025)
- Platonic Representation Hypothesis (Huh et al., 2024)
Security
Embedding translation enables inversion-style attacks. Treat mapped vectors with same sensitivity as source text.
License
Apache-2.0
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aecp-0.2.1.tar.gz.
File metadata
- Download URL: aecp-0.2.1.tar.gz
- Upload date:
- Size: 68.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2706887743a70cd2cb21854c6468e61526944a8ced38d391de179299f639fc4
|
|
| MD5 |
0a337d411470f215fdf0177d07ee9325
|
|
| BLAKE2b-256 |
4ec1eddbd87d2376140947cac6e193301234d1370f85bb639f09c04a826619a1
|
Provenance
The following attestation bundles were made for aecp-0.2.1.tar.gz:
Publisher:
release.yml on krish1925/AECP
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aecp-0.2.1.tar.gz -
Subject digest:
a2706887743a70cd2cb21854c6468e61526944a8ced38d391de179299f639fc4 - Sigstore transparency entry: 2219483904
- Sigstore integration time:
-
Permalink:
krish1925/AECP@71f031e658872ed16b3e217c19ceba73aa953bb1 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/krish1925
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@71f031e658872ed16b3e217c19ceba73aa953bb1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file aecp-0.2.1-py3-none-any.whl.
File metadata
- Download URL: aecp-0.2.1-py3-none-any.whl
- Upload date:
- Size: 85.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2e01b965de6cd55b9a21eaecac7ef5c9b64fcab427b12988924641c430bac913
|
|
| MD5 |
a812a4c8410d50d3869f9e03db2c0e60
|
|
| BLAKE2b-256 |
54587ef98c543822ec7758e27a5cc861cb212ffbe276896d9a45590ad761ac3a
|
Provenance
The following attestation bundles were made for aecp-0.2.1-py3-none-any.whl:
Publisher:
release.yml on krish1925/AECP
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aecp-0.2.1-py3-none-any.whl -
Subject digest:
2e01b965de6cd55b9a21eaecac7ef5c9b64fcab427b12988924641c430bac913 - Sigstore transparency entry: 2219484401
- Sigstore integration time:
-
Permalink:
krish1925/AECP@71f031e658872ed16b3e217c19ceba73aa953bb1 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/krish1925
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@71f031e658872ed16b3e217c19ceba73aa953bb1 -
Trigger Event:
push
-
Statement type: