mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-30 14:15:49 +00:00
fix(core): avoid dict shadowing in language models (#38480)
Fixes #37835 --- When Pydantic collects fields for a `BaseLanguageModel` subclass that defines a `dict()` method, inherited annotations can resolve `dict` against the subclass namespace instead of the builtin. With Pydantic 2.14.0a1 this caused `BaseLanguageModel.metadata: dict[str, Any] | None` to fail during rebuild/import with `'function' object is not subscriptable`. This qualifies the inherited `metadata` field annotation as `builtins.dict[...]`, matching the existing pattern in chat models, and documents why the runtime import cannot move behind `TYPE_CHECKING`. It also adds a regression test that rebuilds a `BaseLanguageModel` subclass with a `dict()` method so core catches this failure before partner packages hit it at import time. Related to #37924, which hardens `_create_subset_model_v2`; this PR fixes the `BaseLanguageModel` class-construction failure directly.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import builtins # noqa: TC003 # runtime-evaluated; subclass `dict()` shadows the builtin
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
@@ -191,7 +192,7 @@ class BaseLanguageModel(
|
||||
tags: list[str] | None = Field(default=None, exclude=True)
|
||||
"""Tags to add to the run trace."""
|
||||
|
||||
metadata: dict[str, Any] | None = Field(default=None, exclude=True)
|
||||
metadata: builtins.dict[str, Any] | None = Field(default=None, exclude=True)
|
||||
"""Metadata to add to the run trace."""
|
||||
|
||||
custom_get_token_ids: Callable[[str], list[int]] | None = Field(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import builtins # noqa: TC003
|
||||
import builtins # noqa: TC003 # runtime-evaluated; subclass `dict()` shadows the builtin
|
||||
import contextlib
|
||||
import inspect
|
||||
import json
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
from langchain_core.language_models import __all__
|
||||
from typing import Any
|
||||
|
||||
from langchain_core.callbacks import Callbacks
|
||||
from langchain_core.language_models import BaseLanguageModel, __all__
|
||||
from langchain_core.outputs import LLMResult
|
||||
from langchain_core.prompt_values import PromptValue
|
||||
|
||||
EXPECTED_ALL = [
|
||||
"BaseLanguageModel",
|
||||
@@ -26,3 +31,41 @@ EXPECTED_ALL = [
|
||||
|
||||
def test_all_imports() -> None:
|
||||
assert set(__all__) == set(EXPECTED_ALL)
|
||||
|
||||
|
||||
def test_pydantic_rebuild_handles_subclass_dict_method_shadowing_builtin() -> None:
|
||||
"""Regression for Pydantic field collection with subclasses that define `dict()`.
|
||||
|
||||
Pydantic 2.14.0a1 evaluates inherited field annotations during subclass
|
||||
rebuilds. If `BaseLanguageModel.metadata` uses a plain `dict[...]`
|
||||
annotation, the subclass `dict()` method can shadow the builtin and make
|
||||
annotation evaluation fail with `'function' object is not subscriptable`.
|
||||
"""
|
||||
|
||||
class DictMethodLanguageModel(BaseLanguageModel[str]):
|
||||
name: str = "test"
|
||||
|
||||
def generate_prompt(
|
||||
self,
|
||||
prompts: list[PromptValue],
|
||||
stop: list[str] | None = None,
|
||||
callbacks: Callbacks = None,
|
||||
**kwargs: Any,
|
||||
) -> LLMResult:
|
||||
raise NotImplementedError
|
||||
|
||||
async def agenerate_prompt(
|
||||
self,
|
||||
prompts: list[PromptValue],
|
||||
stop: list[str] | None = None,
|
||||
callbacks: Callbacks = None,
|
||||
**kwargs: Any,
|
||||
) -> LLMResult:
|
||||
raise NotImplementedError
|
||||
|
||||
def dict(self, **_kwargs: Any) -> dict[str, Any]:
|
||||
return {}
|
||||
|
||||
DictMethodLanguageModel.model_rebuild(force=True)
|
||||
|
||||
assert "metadata" in DictMethodLanguageModel.model_fields
|
||||
|
||||
Reference in New Issue
Block a user