Migrate to v0.3 (Pluggable Providers)
How to move v0.2.x configuration over to v0.3’s pluggable-provider API.
v0.3 makes the LLM and embedding model pluggable. MemorySettings.embedding and MemorySettings.llm now accept three shapes: legacy EmbeddingConfig / LLMConfig (deprecated), a provider-string shorthand, or a fully-constructed Provider instance. This guide walks each migration pattern with side-by-side code.
Compatibility summary
-
v0.2.x code keeps working — passing
EmbeddingConfig/LLMConfigemits oneDeprecationWarningperMemorySettingsconstruction and otherwise behaves identically. -
The deprecation warning escalates to
FutureWarningin v0.4. -
Legacy
EmbeddingConfig/LLMConfigare removed in v0.5.0. -
All v0.2.x example code in this repo has been verified to still run unmodified.
Pattern 1: Drop in a provider-string shorthand
The simplest migration. Replace the legacy config with a single string.
from neo4j_agent_memory import MemoryClient, MemorySettings
from neo4j_agent_memory.config.settings import (
EmbeddingConfig, EmbeddingProvider, LLMConfig, LLMProvider,
)
settings = MemorySettings(
neo4j={"uri": "bolt://localhost:7687", "password": "p"},
embedding=EmbeddingConfig(
provider=EmbeddingProvider.OPENAI,
model="text-embedding-3-small",
),
llm=LLMConfig(
provider=LLMProvider.OPENAI,
model="gpt-4o-mini",
),
)
from neo4j_agent_memory import MemoryClient, MemorySettings
settings = MemorySettings(
neo4j={"uri": "bolt://localhost:7687", "password": "p"},
embedding="openai/text-embedding-3-small",
llm="openai/gpt-4o-mini",
)
Strings are resolved via neo4j_agent_memory.llm.from_provider. The factory does native-first dispatch: with both [openai] and [litellm] installed, an "openai/…" model uses the native adapter; unsupported providers fall through to LiteLLM.
Pattern 2: Switch provider entirely (OpenAI → Anthropic)
from neo4j_agent_memory import MemoryClient, MemorySettings
settings = MemorySettings(
neo4j={"uri": "bolt://localhost:7687", "password": "p"},
embedding="openai/text-embedding-3-small",
llm="anthropic/claude-3-5-sonnet-latest",
)
Install the corresponding extra: pip install neo4j-agent-memory[anthropic]. Set ANTHROPIC_API_KEY in your environment (or pass --llm-api-key to the MCP CLI).
Pattern 3: Local embeddings + Anthropic LLM (no OpenAI dependency)
from neo4j_agent_memory import MemoryClient, MemorySettings
settings = MemorySettings(
neo4j={"uri": "bolt://localhost:7687", "password": "p"},
embedding="BAAI/bge-small-en-v1.5", # local sentence-transformers
llm="anthropic/claude-3-5-sonnet-latest",
)
Install: pip install neo4j-agent-memory[anthropic,sentence-transformers].
This pattern is appealing for cost, privacy, and offline workflows: embeddings stay on your machine; only the LLM call leaves your network. See the tutorial for a full walk-through.
Pattern 4: Construct a Provider instance directly (full control)
When you need to pass an api_base (vLLM, Ollama, an internal endpoint) or provider-specific kwargs, construct the adapter explicitly:
from neo4j_agent_memory import MemoryClient, MemorySettings
from neo4j_agent_memory.llm.adapters.litellm import LiteLLMProvider
from neo4j_agent_memory.llm.adapters.openai import OpenAIEmbeddingProvider
settings = MemorySettings(
neo4j={"uri": "bolt://localhost:7687", "password": "p"},
embedding=OpenAIEmbeddingProvider("openai/text-embedding-3-small"),
llm=LiteLLMProvider(
"ollama/llama3.2",
api_base="http://localhost:11434",
),
)
This is identical to the string shorthand but lets you set adapter-specific options. Provider instances also bypass `from_provider’s native-first dispatch — you get exactly what you asked for.
Pattern 5: Pass through a framework-native model
Already configured a LangChain / Pydantic AI / LlamaIndex / CrewAI / Strands / Microsoft Agent model? Hand it directly to the matching pass-through helper:
from langchain_anthropic import ChatAnthropic
from neo4j_agent_memory import MemoryClient, MemorySettings
from neo4j_agent_memory.integrations.langchain import (
llm_provider_from_langchain,
)
chat = ChatAnthropic(model_name="claude-3-5-sonnet-latest")
settings = MemorySettings(
neo4j={"uri": "bolt://localhost:7687", "password": "p"},
llm=llm_provider_from_langchain(chat),
)
Every integration package exposes a llm_provider_from_<framework> helper:
-
from neo4j_agent_memory.integrations.langchain import llm_provider_from_langchain -
from neo4j_agent_memory.integrations.pydantic_ai import llm_provider_from_pydantic_ai -
from neo4j_agent_memory.integrations.llamaindex import llm_provider_from_llamaindex -
from neo4j_agent_memory.integrations.crewai import llm_provider_from_crewai -
from neo4j_agent_memory.integrations.openai_agents import llm_provider_from_openai_agents -
from neo4j_agent_memory.integrations.microsoft_agent import llm_provider_from_microsoft_agent -
from neo4j_agent_memory.integrations.google_adk import llm_provider_from_google_adk -
from neo4j_agent_memory.integrations.strands import llm_provider_from_strands
Pattern 6: No LLM at all (unchanged)
MemorySettings.llm=None continues to work exactly as in v0.2 — see Run Without an LLM.
Embedding dimensions and existing vector indexes
If you change embedding model — for example, OpenAI’s 1536-dim model to a 384-dim sentence-transformers model — MemoryClient.connect() will detect the mismatch against existing Neo4j vector indexes and raise EmbeddingDimensionMismatchError. Two options:
-
Drop and recreate the indexes (loses existing embeddings).
-
Revert to the previous embedding model.
See Migrate Embedding Model for the index-rebuild runbook.
Validating your migration
After updating, this should hold:
import warnings
from neo4j_agent_memory import MemorySettings
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always", DeprecationWarning)
settings = MemorySettings(
neo4j={"password": "p"},
embedding="openai/text-embedding-3-small",
llm="anthropic/claude-3-5-sonnet-latest",
)
assert not [w for w in caught if issubclass(w.category, DeprecationWarning)]
If any DeprecationWarning fires, search the message text — it identifies the field (embedding or llm) and points back to this guide.
Suppressing the warning during transition
If you must keep the legacy types for a release cycle while migrating other code, the standard warnings.filterwarnings call works:
import warnings
warnings.filterwarnings(
"ignore",
message=".*EmbeddingConfig.*deprecated.*",
category=DeprecationWarning,
)
This is intentionally not the default — silent deprecations make migrations slip.
Behind the scenes
The migration is implemented by a @model_validator(mode="after") on MemorySettings (named _resolve_providers). It:
-
Coerces dict input to legacy configs (so
MemorySettings(embedding={…})still works). -
Resolves provider strings via
from_provider. -
Emits
DeprecationWarningwhen the user explicitly passed legacyEmbeddingConfig/LLMConfiginstances. -
Validates Provider instances against the runtime-checkable
EmbeddingProvider/LLMProviderProtocols. -
Leaves
llm=Nonealone (lenient fallback continues to materialize anLLMConfigwhen extraction needs one).
After resolution, MemoryClient._create_embedder and _create_extractor consume either shape transparently. A small adapter (_ProviderToEmbedderAdapter) bridges new Provider instances back to the legacy Embedder API so downstream memory layers keep working unchanged.
Related
-
Bring Your Own Model — the headline how-to for choosing a provider.
-
Configuration — the canonical
MemorySettingsreference. -
Why the Provider Protocol? — design rationale.