Observed since 20 Oct, 20:09 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
aecp
Migrate a vector database to a new embedding model without re-embedding the corpus. Fits a linear map from ~2K calibration texts; 87-91% retrieval retention measured on BEIR.
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.
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 aboveZero writes to your vector store. Map new-model queries into legacy space on-the-fly. Fully reversible.
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
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
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)
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.
# 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
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))
| 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 |
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 | 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 | 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 |
| 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.
| 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 |
| Threshold | Raw recall | + Recalibration | Δ |
|---|---|---|---|
| τ = 0.60 | 78% | 100% | +22% |
| τ = 0.70 | 27% | 67% | +40% |
Engineering, not research. Built on:
Embedding translation enables inversion-style attacks. Treat mapped vectors with same sensitivity as source text.
Apache-2.0
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
numpyEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | numpy>=1.24 | runtime |
richEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | rich>=13.0 | runtime |
scikit-learnEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | scikit-learn>=1.3 | runtime |
typerEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | typer>=0.12 | runtime |
chromadbEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | chromadb>=0.4; extra == "all" | runtime |
cohereEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | cohere>=5.0; extra == "all" | runtime |
google-generativeaiEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | google-generativeai>=0.5; extra == "all" | runtime |
langchain-coreEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | langchain-core>=0.3; extra == "all" | runtime |
mypyEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | mypy>=1.8; extra == "all" | runtime |
openaiEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | openai>=1.0; extra == "all" | runtime |
psycopgEnvironment{
"extras": "binary",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | psycopg[binary]>=3.1; extra == "all" | runtime |
pyarrowEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pyarrow>=14.0; extra == "all" | runtime |
pytest-covEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pytest-cov>=4.1; extra == "all" | runtime |
pytestEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pytest>=7.4; extra == "all" | runtime |
qdrant-clientEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | qdrant-client>=1.7; extra == "all" | runtime |
ruffEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | ruff>=0.4; extra == "all" | runtime |
sentence-transformersEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | sentence-transformers>=2.2; extra == "all" | runtime |
torchEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | torch>=2.0; extra == "all" | runtime |
voyageaiEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | voyageai>=0.2; extra == "all" | runtime |
datasetsEnvironment{
"extras": "",
"marker": "extra == \"benchmarks\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | datasets>=2.14; extra == "benchmarks" | runtime |
ir-datasetsEnvironment{
"extras": "",
"marker": "extra == \"benchmarks\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | ir-datasets>=0.5; extra == "benchmarks" | runtime |
sentence-transformersEnvironment{
"extras": "",
"marker": "extra == \"benchmarks\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | sentence-transformers>=2.2; extra == "benchmarks" | runtime |
chromadbEnvironment{
"extras": "",
"marker": "extra == \"chroma\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | chromadb>=0.4; extra == "chroma" | runtime |
cohereEnvironment{
"extras": "",
"marker": "extra == \"cohere\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | cohere>=5.0; extra == "cohere" | runtime |
mypyEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | mypy>=1.8; extra == "dev" | runtime |
pytest-covEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pytest-cov>=4.1; extra == "dev" | runtime |
pytestEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pytest>=7.4; extra == "dev" | runtime |
ruffEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | ruff>=0.4; extra == "dev" | runtime |
sentence-transformersEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | sentence-transformers>=2.2; extra == "dev" | runtime |
google-generativeaiEnvironment{
"extras": "",
"marker": "extra == \"gemini\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | google-generativeai>=0.5; extra == "gemini" | runtime |
langchain-coreEnvironment{
"extras": "",
"marker": "extra == \"langchain\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | langchain-core>=0.3; extra == "langchain" | runtime |
llama-index-coreEnvironment{
"extras": "",
"marker": "extra == \"llamaindex\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | llama-index-core>=0.10; extra == "llamaindex" | runtime |
torchEnvironment{
"extras": "",
"marker": "extra == \"mlp\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | torch>=2.0; extra == "mlp" | runtime |
openaiEnvironment{
"extras": "",
"marker": "extra == \"openai\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | openai>=1.0; extra == "openai" | runtime |
pyarrowEnvironment{
"extras": "",
"marker": "extra == \"parquet\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pyarrow>=14.0; extra == "parquet" | runtime |
psycopgEnvironment{
"extras": "binary",
"marker": "extra == \"pgvector\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | psycopg[binary]>=3.1; extra == "pgvector" | runtime |
pinecone-clientEnvironment{
"extras": "",
"marker": "extra == \"pinecone\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pinecone-client>=2.0; extra == "pinecone" | runtime |
qdrant-clientEnvironment{
"extras": "",
"marker": "extra == \"qdrant\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | qdrant-client>=1.7; extra == "qdrant" | runtime |
sentence-transformersEnvironment{
"extras": "",
"marker": "extra == \"sentence-transformers\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | sentence-transformers>=2.2; extra == "sentence-transformers" | runtime |
voyageaiEnvironment{
"extras": "",
"marker": "extra == \"voyage\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | voyageai>=0.2; extra == "voyage" | runtime |
bdist_wheel · 83.01 KB · Python py3
sdist · 67.28 KB · Python source
Observed since 20 Oct, 20:09 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:95dea52d1cd027eb57cf478234696a0e0fd41c7ed71c8f238d89e1da69bd399e