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 / LLMConfig emits one DeprecationWarning per MemorySettings construction and otherwise behaves identically.

  • The deprecation warning escalates to FutureWarning in v0.4.

  • Legacy EmbeddingConfig / LLMConfig are 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.

Before (v0.2.x — still works in v0.3, one warning)
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",
    ),
)
After (v0.3 — no warning)
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:

  1. Drop and recreate the indexes (loses existing embeddings).

  2. 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:

  1. Coerces dict input to legacy configs (so MemorySettings(embedding={…​}) still works).

  2. Resolves provider strings via from_provider.

  3. Emits DeprecationWarning when the user explicitly passed legacy EmbeddingConfig / LLMConfig instances.

  4. Validates Provider instances against the runtime-checkable EmbeddingProvider / LLMProvider Protocols.

  5. Leaves llm=None alone (lenient fallback continues to materialize an LLMConfig when 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.