langchain-ai/langchain · error · NotImplementedError

Function {func} contains a mix of Pydantic v1 and v2 annotat

Error message

Function {func} contains a mix of Pydantic v1 and v2 annotations. Only one version of Pydantic annotations per function is supported.

What it means

When inferring a tool schema from a function, langchain checks each parameter annotation for Pydantic v1 vs v2 models and refuses functions that mix both (e.g. one arg annotated with a `pydantic.v1.BaseModel` subclass and another with a `pydantic.BaseModel` subclass), raising NotImplementedError because a single schema cannot be generated across both major versions.

Source

Thrown at libs/core/langchain_core/tools/base.py:250

        True if all Pydantic annotations are from v1, `False` otherwise.

    Raises:
        NotImplementedError: If the function contains mixed v1 and v2 annotations.
    """
    any_v1_annotations = any(
        _is_pydantic_annotation(parameter.annotation, pydantic_version="v1")
        for parameter in signature.parameters.values()
    )
    any_v2_annotations = any(
        _is_pydantic_annotation(parameter.annotation, pydantic_version="v2")
        for parameter in signature.parameters.values()
    )
    if any_v1_annotations and any_v2_annotations:
        msg = (
            f"Function {func} contains a mix of Pydantic v1 and v2 annotations. "
            "Only one version of Pydantic annotations per function is supported."
        )
        raise NotImplementedError(msg)
    return any_v1_annotations and not any_v2_annotations


class _SchemaConfig:
    """Configuration for Pydantic models generated from function signatures."""

    extra: str = "forbid"
    """Whether to allow extra fields in the model."""

    arbitrary_types_allowed: bool = True
    """Whether to allow arbitrary types in the model."""


def create_schema_from_function(
    model_name: str,
    func: Callable[..., Any],
    *,
    filter_args: Sequence[str] | None = None,

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Migrate all parameter models to one Pydantic major version — preferably v2 (`pydantic.BaseModel`).
  2. Check each annotation's module (`type(x).__module__`) to find the v1 stragglers, often imported from a dependency pinned to `pydantic.v1`.
  3. If a dependency only offers v1 models, wrap its inputs in a plain v2 model or primitive types at the tool boundary.
  4. If mixed versions are unavoidable, supply an explicit `args_schema` (single-version) instead of relying on inference.

Example fix

# before
from pydantic.v1 import BaseModel as V1Base
from pydantic import BaseModel

class A(V1Base): x: int
class B(BaseModel): y: int

@tool
def f(a: A, b: B) -> str: ...  # NotImplementedError
# after
from pydantic import BaseModel

class A(BaseModel): x: int
class B(BaseModel): y: int

@tool
def f(a: A, b: B) -> str: ...
Defensive patterns

Strategy: type-guard

Validate before calling

import inspect, pydantic

def annotations_single_pydantic_version(fn) -> bool:
    versions = set()
    for p in inspect.signature(fn).parameters.values():
        ann = p.annotation
        mod = getattr(ann, '__module__', '')
        if 'pydantic' in mod:
            versions.add('v1' if mod.startswith('pydantic.v1') else 'v2')
    return len(versions) <= 1

assert annotations_single_pydantic_version(fn)

Type guard

import pydantic, pydantic.v1

def is_pydantic_v2_model(cls) -> bool:
    return inspect.isclass(cls) and issubclass(cls, pydantic.BaseModel)

def is_pydantic_v1_model(cls) -> bool:
    return inspect.isclass(cls) and issubclass(cls, pydantic.v1.BaseModel)

Try / catch

try:
    t = tool(fn)
except NotImplementedError as e:
    if 'Pydantic v1 and v2' in str(e):
        fn = migrate_annotations_to_v2(fn)  # retype params, then retry
        t = tool(fn)
    else:
        raise

Prevention

When it happens

Trigger: `@tool def f(a: MyV1Model, b: MyV2Model)` where `MyV1Model` inherits `pydantic.v1.BaseModel`; a function annotated with a model imported from a package still on Pydantic v1 alongside a locally-defined v2 model.

Common situations: Migrating a codebase (or a dependency) to Pydantic v2 while some imports still resolve to `pydantic.v1`; partner/integration packages that expose v1 models; notebooks where an old model class lingers after upgrading pydantic.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/0f505e5e0777c679. Report an issue: GitHub.