Catalog PyPI
Back to packageRELEASE DETAILS

0.2.1

PyPI

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.

ada-002beirchromadimension-mismatchembedding-alignmentembedding-migrationembedding-modelmodel-deprecationndcgopenai-embeddingspgvectorqdrantre-embeddingretrievalridge-regressionsentence-transformers
All keywords (20)
simsearchtext-embedding-3vector-databasevector-search
Published22 Jul 2026
Direct dependencies23
Transitive dependenciesNot calculated

README

Version 0.2.1 documentation

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 adapter
  • pip install aecp[langchain] — LangChain embeddings shim
  • pip install aecp[llamaindex] — LlamaIndex query wrapper
  • pip install aecp[sentence-transformers] — local model support
  • pip install aecp[qdrant] — Qdrant store adapter
  • pip install aecp[openai] — OpenAI client shim
  • pip 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

StoreServe modeOffline migrationStatus
ChromaDBAECPChromaFunctionmigrate_collection()Supported
LangChainAECPEmbeddingsvia store adapterSupported
LlamaIndexAECPEmbedding wrappervia store adapterQuery wrapper
OpenAIAECPOpenAI shimN/AQuery shim
QdrantQdrantStorecheckpointed in-placeSupported
Pinecone—shadow-namespacePlanned

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)

AdapternDCG@10 retentionNotes
Ridge0.871 ± 0.006Default. Fast, stable.
LowRank0.857 ± 0.009Compressed matrix. ~1% worse.
MLP0.727 ± 0.007No tuning. Linear wins.

K-sweep (all adapters averaged, SciFact, 3 seeds)

KnDCG@10 retentionGate
5000.671 ± 0.041WARN
10000.735 ± 0.058WARN
20000.785 ± 0.052PASS
40000.832 ± 0.061PASS

Same-dim pair (bge-large→e5-large, 1024→1024)

MetricValue
Floor (raw cross-space)0.0
AECP (mapped)0.667
Ceiling (full re-embed)0.722
Retention0.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)

PairHigh-conf R@10Low-conf R@10Gap
bge→e50.9550.6370.318
MiniLM→bge0.8750.6510.224

Score recalibration (MiniLM→bge, rectangular)

ThresholdRaw recall+ RecalibrationΔ
τ = 0.6078%100%+22%
τ = 0.7027%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

  1. Embed K texts with source and target models → matrices X, Y
  2. Fit ridge map Y = [X | 1] W (handles unequal dims)
  3. Hold out 10% to estimate quality
  4. Transform corpus: V' = normalize(V @ W) (streaming batches)
  5. 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

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 40

Native requirements
23 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
numpy
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
numpy>=1.24runtime
rich
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
rich>=13.0runtime
scikit-learn
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
scikit-learn>=1.3runtime
typer
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
typer>=0.12runtime
chromadb
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
chromadb>=0.4; extra == "all"runtime
cohere
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
cohere>=5.0; extra == "all"runtime
google-generativeai
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
google-generativeai>=0.5; extra == "all"runtime
langchain-core
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
langchain-core>=0.3; extra == "all"runtime
mypy
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
mypy>=1.8; extra == "all"runtime
openai
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
openai>=1.0; extra == "all"runtime
psycopg
Environment
{
  "extras": "binary",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
psycopg[binary]>=3.1; extra == "all"runtime
pyarrow
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pyarrow>=14.0; extra == "all"runtime
pytest-cov
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pytest-cov>=4.1; extra == "all"runtime
pytest
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pytest>=7.4; extra == "all"runtime
qdrant-client
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
qdrant-client>=1.7; extra == "all"runtime
ruff
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
ruff>=0.4; extra == "all"runtime
sentence-transformers
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
sentence-transformers>=2.2; extra == "all"runtime
torch
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
torch>=2.0; extra == "all"runtime
voyageai
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
voyageai>=0.2; extra == "all"runtime
datasets
Environment
{
  "extras": "",
  "marker": "extra == \"benchmarks\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
datasets>=2.14; extra == "benchmarks"runtime
ir-datasets
Environment
{
  "extras": "",
  "marker": "extra == \"benchmarks\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
ir-datasets>=0.5; extra == "benchmarks"runtime
sentence-transformers
Environment
{
  "extras": "",
  "marker": "extra == \"benchmarks\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
sentence-transformers>=2.2; extra == "benchmarks"runtime
chromadb
Environment
{
  "extras": "",
  "marker": "extra == \"chroma\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
chromadb>=0.4; extra == "chroma"runtime
cohere
Environment
{
  "extras": "",
  "marker": "extra == \"cohere\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
cohere>=5.0; extra == "cohere"runtime
mypy
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
mypy>=1.8; extra == "dev"runtime
pytest-cov
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pytest-cov>=4.1; extra == "dev"runtime
pytest
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pytest>=7.4; extra == "dev"runtime
ruff
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
ruff>=0.4; extra == "dev"runtime
sentence-transformers
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
sentence-transformers>=2.2; extra == "dev"runtime
google-generativeai
Environment
{
  "extras": "",
  "marker": "extra == \"gemini\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
google-generativeai>=0.5; extra == "gemini"runtime
langchain-core
Environment
{
  "extras": "",
  "marker": "extra == \"langchain\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
langchain-core>=0.3; extra == "langchain"runtime
llama-index-core
Environment
{
  "extras": "",
  "marker": "extra == \"llamaindex\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
llama-index-core>=0.10; extra == "llamaindex"runtime
torch
Environment
{
  "extras": "",
  "marker": "extra == \"mlp\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
torch>=2.0; extra == "mlp"runtime
openai
Environment
{
  "extras": "",
  "marker": "extra == \"openai\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
openai>=1.0; extra == "openai"runtime
pyarrow
Environment
{
  "extras": "",
  "marker": "extra == \"parquet\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pyarrow>=14.0; extra == "parquet"runtime
psycopg
Environment
{
  "extras": "binary",
  "marker": "extra == \"pgvector\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
psycopg[binary]>=3.1; extra == "pgvector"runtime
pinecone-client
Environment
{
  "extras": "",
  "marker": "extra == \"pinecone\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pinecone-client>=2.0; extra == "pinecone"runtime
qdrant-client
Environment
{
  "extras": "",
  "marker": "extra == \"qdrant\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
qdrant-client>=1.7; extra == "qdrant"runtime
sentence-transformers
Environment
{
  "extras": "",
  "marker": "extra == \"sentence-transformers\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
sentence-transformers>=2.2; extra == "sentence-transformers"runtime
voyageai
Environment
{
  "extras": "",
  "marker": "extra == \"voyage\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
voyageai>=0.2; extra == "voyage"runtime

Distribution files

2 collected
aecp-0.2.1-py3-none-any.whl

bdist_wheel · 83.01 KB · Python py3

Source file
aecp-0.2.1.tar.gz

sdist · 67.28 KB · Python source

Source file

Repository

PENDING
krish1925/aecp

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 20:09 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:95dea52d1cd027eb57cf478234696a0e0fd41c7ed71c8f238d89e1da69bd399e

Other collected versions

View all collected versions →