mirror of
https://github.com/hwchase17/langchain.git
synced 2026-02-03 15:55:44 +00:00
Compare commits
19 Commits
isaac/site
...
eugene/uns
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd9103175f | ||
|
|
42b680d5fa | ||
|
|
1d1b6f22db | ||
|
|
0111ba6d27 | ||
|
|
9a5b641d42 | ||
|
|
e661e2574e | ||
|
|
505f1bf3d9 | ||
|
|
cf4d7fb64e | ||
|
|
b4f10e0080 | ||
|
|
a6158d8d20 | ||
|
|
07e3c3594e | ||
|
|
01400705f0 | ||
|
|
5762c4b63a | ||
|
|
4b519883eb | ||
|
|
7f3d999e1e | ||
|
|
4cc21c404e | ||
|
|
dc9f2e4174 | ||
|
|
610a2e07b8 | ||
|
|
54e40830f9 |
31
.github/workflows/_dependencies.yml
vendored
31
.github/workflows/_dependencies.yml
vendored
@@ -1,3 +1,12 @@
|
||||
# The dependencies action runs unit tests with different versions of the package
|
||||
# dependencies.
|
||||
# This is our current solution for setting up a test matrix primarily for
|
||||
# testing compatibility with different versions of pydantic.
|
||||
# This code has some duplication with the _test action due to the need to
|
||||
# do extra logic if pydantic version 2 is installed; i.e., maybe run
|
||||
# with LC_PYDANTIC_V2_EXPERIMENTAL=True
|
||||
# The extra logic may not be worth refactoring as it'll be deleted once
|
||||
# migration to pydantic v2 proper is complete.
|
||||
name: dependencies
|
||||
|
||||
on:
|
||||
@@ -103,7 +112,27 @@ jobs:
|
||||
if: ${{ !startsWith(inputs.working-directory, 'libs/partners/airbyte') }}
|
||||
shell: bash
|
||||
run: make test
|
||||
|
||||
- name: Maybe run unit tests with LC_PYDANTIC_V2_EXPERIMENTAL=True
|
||||
# This step will run unit tests if pydantic>=2.0.0 is installed
|
||||
# It will run unit tests with the environment variable LC_PYDANTIC_V2_EXPERIMENTAL=True
|
||||
# libraries that support the flag, will attempt to run unit tests
|
||||
# using pydantic proper rather than via the pydantic.v1 namespace.
|
||||
# TODO: Refactor how code is tested with LC_PYDANTIC_V2_EXPERIMENTAL=True
|
||||
# We should probably have a JSON file that lists which code paths
|
||||
# that support the flag
|
||||
if: ${{ !startsWith(inputs.working-directory, 'libs/core/') }}
|
||||
shell: bash
|
||||
run: |
|
||||
# Determine the major part of pydantic version
|
||||
REGULAR_VERSION=$(poetry run python -c "import pydantic; print(pydantic.__version__)" | cut -d. -f1)
|
||||
# If version 2 then we run tests with LC_PYDANTIC_V2_EXPERIMENTAL=True
|
||||
# Otherwise echo that there's nothing to do since the version is 1
|
||||
if [[ "$REGULAR_VERSION" == "2" ]]; then
|
||||
LC_PYDANTIC_V2_EXPERIMENTAL=True make test
|
||||
echo "Finished running unit tests with LC_PYDANTIC_V2_EXPERIMENTAL=True"
|
||||
else
|
||||
echo "No running tests with LC_PYDANTIC_V2_EXPERIMENTAL=True since pydantic version is 1"
|
||||
fi
|
||||
- name: Ensure the tests did not create any additional files
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
21
.github/workflows/_test.yml
vendored
21
.github/workflows/_test.yml
vendored
@@ -57,6 +57,27 @@ jobs:
|
||||
run: |
|
||||
make test
|
||||
|
||||
- name: Maybe run unit tests with LC_PYDANTIC_V2_EXPERIMENTAL=True
|
||||
# This step will run unit tests if pydantic>=2.0.0 is installed
|
||||
# It will run unit tests with the environment variable LC_PYDANTIC_V2_EXPERIMENTAL=True
|
||||
# libraries that support the flag, will attempt to run unit tests
|
||||
# using pydantic proper rather than via the pydantic.v1 namespace.
|
||||
# TODO: Refactor how code is tested with LC_PYDANTIC_V2_EXPERIMENTAL=True
|
||||
# We should probably have a JSON file that lists which code paths
|
||||
# that support the flag
|
||||
if: ${{ !startsWith(inputs.working-directory, 'libs/core/') }}
|
||||
shell: bash
|
||||
run: |
|
||||
# Determine the major part of pydantic version
|
||||
REGULAR_VERSION=$(poetry run python -c "import pydantic; print(pydantic.__version__)" | cut -d. -f1)
|
||||
# If version 2 then we run tests with LC_PYDANTIC_V2_EXPERIMENTAL=True
|
||||
# Otherwise echo that there's nothing to do since the version is 1
|
||||
if [[ "$REGULAR_VERSION" == "2" ]]; then
|
||||
LC_PYDANTIC_V2_EXPERIMENTAL=True make test
|
||||
echo "Finished running unit tests with LC_PYDANTIC_V2_EXPERIMENTAL=True"
|
||||
else
|
||||
echo "No running tests with LC_PYDANTIC_V2_EXPERIMENTAL=True since pydantic version is 1"
|
||||
fi
|
||||
- name: Ensure the tests did not create any additional files
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Optional, Sequence
|
||||
|
||||
from langchain_core.callbacks import Callbacks
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.pydantic_v1 import BaseModel
|
||||
from langchain_core.pydantic import BaseModel
|
||||
from langchain_core.runnables import run_in_executor
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ from langchain_core.messages import (
|
||||
get_buffer_string,
|
||||
)
|
||||
from langchain_core.prompt_values import PromptValue
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field, validator
|
||||
from langchain_core.pydantic import BaseModel, Field
|
||||
from langchain_core.pydantic_v1 import validator
|
||||
from langchain_core.runnables import Runnable, RunnableSerializable
|
||||
from langchain_core.utils import get_pydantic_field_names
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import (
|
||||
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
from langchain_core.pydantic_v1 import BaseModel, PrivateAttr
|
||||
from langchain_core.pydantic import BaseModel, PrivateAttr
|
||||
|
||||
|
||||
class BaseSerialized(TypedDict):
|
||||
@@ -163,7 +163,7 @@ class Serializable(BaseModel, ABC):
|
||||
for key in list(secrets):
|
||||
value = secrets[key]
|
||||
if key in this.__fields__:
|
||||
secrets[this.__fields__[key].alias] = value
|
||||
secrets[this.__fields__[key].alias] = value # type: ignore[index]
|
||||
lc_kwargs.update(this.lc_attributes)
|
||||
|
||||
# include all secrets, even if not specified in kwargs
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Literal
|
||||
from typing import Any, List, Literal
|
||||
|
||||
from langchain_core.messages import BaseMessage, BaseMessageChunk
|
||||
from langchain_core.outputs.generation import Generation
|
||||
from langchain_core.pydantic_v1 import root_validator
|
||||
from langchain_core.utils._merge import merge_dicts
|
||||
|
||||
|
||||
@@ -12,21 +11,19 @@ class ChatGeneration(Generation):
|
||||
"""A single chat generation output."""
|
||||
|
||||
text: str = ""
|
||||
"""*SHOULD NOT BE SET DIRECTLY* The text contents of the output message."""
|
||||
# """*SHOULD NOT BE SET DIRECTLY* The text contents of the output message."""
|
||||
message: BaseMessage
|
||||
"""The message output by the chat model."""
|
||||
# Override type to be ChatGeneration, ignore mypy error as this is intentional
|
||||
type: Literal["ChatGeneration"] = "ChatGeneration" # type: ignore[assignment]
|
||||
"""Type is used exclusively for serialization purposes."""
|
||||
|
||||
@root_validator
|
||||
def set_text(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Set the text attribute to be the contents of the message."""
|
||||
try:
|
||||
values["text"] = values["message"].content
|
||||
except (KeyError, AttributeError) as e:
|
||||
raise ValueError("Error while initializing ChatGeneration") from e
|
||||
return values
|
||||
def __init__(self, *, message: BaseMessage, **kwargs: Any) -> None:
|
||||
"""Initialize a ChatGeneration object."""
|
||||
# Backwards compatibility delete text if it provided.
|
||||
# This would arise primarily from de-serialization of old objects.
|
||||
kwargs["text"] = message.content
|
||||
super().__init__(message=message, **kwargs) # type: ignore[call-arg]
|
||||
|
||||
@classmethod
|
||||
def get_lc_namespace(cls) -> List[str]:
|
||||
|
||||
@@ -25,7 +25,8 @@ from langchain_core.prompt_values import (
|
||||
PromptValue,
|
||||
StringPromptValue,
|
||||
)
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field, root_validator
|
||||
from langchain_core.pydantic import BaseModel, Field
|
||||
from langchain_core.pydantic_v1 import root_validator
|
||||
from langchain_core.runnables import RunnableConfig, RunnableSerializable
|
||||
from langchain_core.runnables.config import ensure_config
|
||||
from langchain_core.runnables.utils import create_model
|
||||
|
||||
@@ -109,7 +109,7 @@ class MessagesPlaceholder(BaseMessagePromptTemplate):
|
||||
return ["langchain", "prompts", "chat"]
|
||||
|
||||
def __init__(self, variable_name: str, *, optional: bool = False, **kwargs: Any):
|
||||
super().__init__(variable_name=variable_name, optional=optional, **kwargs)
|
||||
super().__init__(variable_name=variable_name, optional=optional, **kwargs) # type: ignore[call-arg]
|
||||
|
||||
def format_messages(self, **kwargs: Any) -> List[BaseMessage]:
|
||||
"""Format messages from kwargs.
|
||||
|
||||
23
libs/core/langchain_core/pydantic/__init__.py
Normal file
23
libs/core/langchain_core/pydantic/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from .config import _PYDANTIC_MAJOR_VERSION, _PYDANTIC_VERSION, USE_PYDANTIC_V2
|
||||
|
||||
# This is a compatibility layer that pydantic 2 proper, pydantic.v1 in pydantic 2,
|
||||
# or pydantic 1
|
||||
|
||||
if USE_PYDANTIC_V2:
|
||||
from pydantic import BaseModel, Field, PrivateAttr
|
||||
else:
|
||||
from langchain_core.pydantic_v1 import ( # type: ignore[no-redef]
|
||||
BaseModel,
|
||||
Field,
|
||||
PrivateAttr,
|
||||
)
|
||||
|
||||
# Only expose things that are common across all pydantic versions
|
||||
__all__ = [ # noqa: F405
|
||||
"BaseModel",
|
||||
"Field",
|
||||
"PrivateAttr",
|
||||
"_PYDANTIC_MAJOR_VERSION",
|
||||
"_PYDANTIC_VERSION",
|
||||
"USE_PYDANTIC_V2",
|
||||
]
|
||||
30
libs/core/langchain_core/pydantic/config.py
Normal file
30
libs/core/langchain_core/pydantic/config.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import os
|
||||
from importlib import metadata
|
||||
|
||||
_PYDANTIC_VERSION = metadata.version("pydantic")
|
||||
|
||||
try:
|
||||
_PYDANTIC_MAJOR_VERSION: int = int(_PYDANTIC_VERSION.split(".")[0])
|
||||
except metadata.PackageNotFoundError:
|
||||
_PYDANTIC_MAJOR_VERSION = 0
|
||||
|
||||
|
||||
def _get_use_pydantic_v2() -> bool:
|
||||
"""Get the value of the LC_PYDANTIC_V2_EXPERIMENTAL environment variable."""
|
||||
value = os.environ.get("LC_PYDANTIC_V2_EXPERIMENTAL", "false").lower()
|
||||
if value == "true":
|
||||
if _PYDANTIC_MAJOR_VERSION != 2:
|
||||
raise ValueError(
|
||||
f"LC_PYDANTIC_V2_EXPERIMENTAL is set to true, "
|
||||
f"but pydantic version is {_PYDANTIC_VERSION}"
|
||||
)
|
||||
return True
|
||||
elif value == "false":
|
||||
return False
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid value for LANGCHAIN_PYDANTIC_V2_EXPERIMENTAL: {value}"
|
||||
)
|
||||
|
||||
|
||||
USE_PYDANTIC_V2 = _get_use_pydantic_v2()
|
||||
@@ -1,3 +1,4 @@
|
||||
# type: ignore
|
||||
from importlib import metadata
|
||||
|
||||
## Create namespaces for pydantic v1 and v2.
|
||||
@@ -12,12 +13,45 @@ from importlib import metadata
|
||||
# * This change is easier to roll out and roll back.
|
||||
|
||||
try:
|
||||
from pydantic.v1 import * # noqa: F403 # type: ignore
|
||||
from pydantic.v1 import ( # noqa: F403 # type: ignore
|
||||
BaseModel,
|
||||
Extra,
|
||||
Field,
|
||||
PrivateAttr,
|
||||
SecretStr,
|
||||
ValidationError,
|
||||
create_model,
|
||||
root_validator,
|
||||
validate_arguments,
|
||||
)
|
||||
except ImportError:
|
||||
from pydantic import * # noqa: F403 # type: ignore
|
||||
from pydantic import ( # noqa: F403 # type: ignore
|
||||
BaseModel,
|
||||
Extra,
|
||||
Field,
|
||||
PrivateAttr,
|
||||
SecretStr,
|
||||
ValidationError,
|
||||
create_model,
|
||||
root_validator,
|
||||
validate_arguments,
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
_PYDANTIC_MAJOR_VERSION: int = int(metadata.version("pydantic").split(".")[0])
|
||||
except metadata.PackageNotFoundError:
|
||||
_PYDANTIC_MAJOR_VERSION = 0
|
||||
|
||||
__all__ = [
|
||||
"BaseModel",
|
||||
"Field",
|
||||
"PrivateAttr",
|
||||
"SecretStr",
|
||||
"_PYDANTIC_MAJOR_VERSION",
|
||||
"Extra",
|
||||
"ValidationError",
|
||||
"root_validator",
|
||||
"validate_arguments",
|
||||
"create_model",
|
||||
]
|
||||
|
||||
@@ -237,7 +237,7 @@ class RunnableConfigurableFields(DynamicRunnable[Input, Output]):
|
||||
id=spec.id,
|
||||
name=spec.name,
|
||||
description=spec.description
|
||||
or self.default.__fields__[field_name].field_info.description,
|
||||
or self.default.__fields__[field_name].field_info.description, # type: ignore[attr-defined]
|
||||
annotation=spec.annotation
|
||||
or self.default.__fields__[field_name].annotation,
|
||||
default=getattr(self.default, field_name),
|
||||
@@ -245,7 +245,8 @@ class RunnableConfigurableFields(DynamicRunnable[Input, Output]):
|
||||
)
|
||||
if isinstance(spec, ConfigurableField)
|
||||
else make_options_spec(
|
||||
spec, self.default.__fields__[field_name].field_info.description
|
||||
spec,
|
||||
self.default.__fields__[field_name].field_info.description, # type: ignore[attr-defined]
|
||||
)
|
||||
for field_name, spec in self.fields.items()
|
||||
]
|
||||
|
||||
@@ -34,10 +34,9 @@ from langchain_core.callbacks import (
|
||||
Callbacks,
|
||||
)
|
||||
from langchain_core.load.serializable import Serializable
|
||||
from langchain_core.pydantic import BaseModel, Field
|
||||
from langchain_core.pydantic_v1 import (
|
||||
BaseModel,
|
||||
Extra,
|
||||
Field,
|
||||
ValidationError,
|
||||
create_model,
|
||||
root_validator,
|
||||
@@ -62,7 +61,7 @@ def _create_subset_model(
|
||||
"""Create a pydantic model with only a subset of model's fields."""
|
||||
fields = {}
|
||||
for field_name in field_names:
|
||||
field = model.__fields__[field_name]
|
||||
field = model.__fields__[field_name] # type: ignore[index]
|
||||
t = (
|
||||
# this isn't perfect but should work for most functions
|
||||
field.outer_type_
|
||||
@@ -272,7 +271,7 @@ class ChildTool(BaseTool):
|
||||
input_args = self.args_schema
|
||||
if isinstance(tool_input, str):
|
||||
if input_args is not None:
|
||||
key_ = next(iter(input_args.__fields__.keys()))
|
||||
key_ = next(iter(input_args.__fields__.keys())) # type: ignore[attr-defined]
|
||||
input_args.validate({key_: tool_input})
|
||||
return tool_input
|
||||
else:
|
||||
|
||||
@@ -330,7 +330,7 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
llm_run.outputs = response.dict()
|
||||
for i, generations in enumerate(response.generations):
|
||||
for j, generation in enumerate(generations):
|
||||
output_generation = llm_run.outputs["generations"][i][j]
|
||||
output_generation = llm_run.outputs["generations"][i][j] # type: ignore[index]
|
||||
if "message" in output_generation:
|
||||
output_generation["message"] = dumpd(
|
||||
cast(ChatGeneration, generation).message
|
||||
|
||||
@@ -81,6 +81,11 @@ select = [
|
||||
disallow_untyped_defs = "True"
|
||||
exclude = ["notebooks", "examples", "example_data", "langchain_core/pydantic"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "langchain_core.pydantic_v1"
|
||||
follow_imports = "skip"
|
||||
ignore_errors = true
|
||||
|
||||
[tool.coverage.run]
|
||||
omit = ["tests/*"]
|
||||
|
||||
|
||||
@@ -3,7 +3,16 @@ from importlib import util
|
||||
from typing import Dict, Sequence
|
||||
|
||||
import pytest
|
||||
from pytest import Config, Function, Parser
|
||||
from _pytest.config import Config
|
||||
from _pytest.terminal import TerminalReporter
|
||||
from pytest import Function, Parser
|
||||
|
||||
from langchain_core.pydantic import _PYDANTIC_VERSION
|
||||
from langchain_core.pydantic.config import USE_PYDANTIC_V2
|
||||
|
||||
# The maximum number of failed tests to allow when running with
|
||||
# This number should only be decreased over time until we're at 0!
|
||||
MAX_FAILED_LC_PYDANTIC_2_MIGRATION = 100
|
||||
|
||||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
@@ -18,6 +27,65 @@ def pytest_addoption(parser: Parser) -> None:
|
||||
action="store_true",
|
||||
help="Only run core tests. Never runs any extended tests.",
|
||||
)
|
||||
parser.addoption(
|
||||
"--max-fail",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Maximum number of failed tests to allow. "
|
||||
"Should only be set for LC_PYDANTIC_V2_EXPERIMENTAL=true.",
|
||||
)
|
||||
|
||||
|
||||
def pytest_sessionstart(session: pytest.Session) -> None:
|
||||
"""Initialize the count of passed and failed tests."""
|
||||
session.count_failed = 0 # type: ignore[attr-defined]
|
||||
|
||||
|
||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
||||
def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo) -> None: # type: ignore
|
||||
outcome = yield
|
||||
result = outcome.get_result()
|
||||
|
||||
if result.when == "call" and result.failed:
|
||||
item.session.count_failed += 1 # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
|
||||
"""Exit with a non-zero status if not enough tests pass."""
|
||||
max_fail = session.config.getoption(
|
||||
"--max-fail", default=MAX_FAILED_LC_PYDANTIC_2_MIGRATION
|
||||
)
|
||||
if max_fail > 0 and not USE_PYDANTIC_V2:
|
||||
raise ValueError(
|
||||
"The `--max-fail` option should only be set when "
|
||||
"running with `LC_PYDANTIC_V2_EXPERIMENTAL=true`."
|
||||
)
|
||||
# This will set up a ratchet approach so that the number of failures
|
||||
# has to go down over time.
|
||||
if session.count_failed > max_fail: # type: ignore[attr-defined]
|
||||
session.exitstatus = 1
|
||||
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
|
||||
reporter.section("Session errors", sep="-", red=True, bold=True) # type: ignore[union-attr]
|
||||
reporter.line( # type: ignore[union-attr]
|
||||
f"Regression in pydantic v2 migration. Expected at most {max_fail} failed "
|
||||
f"tests. Instead found {session.count_failed} failed tests." # type: ignore[attr-defined]
|
||||
)
|
||||
else:
|
||||
session.exitstatus = 0
|
||||
|
||||
|
||||
def pytest_terminal_summary(
|
||||
terminalreporter: TerminalReporter, exitstatus: int, config: Config
|
||||
) -> None:
|
||||
"""Add custom information to the terminal summary."""
|
||||
terminalreporter.write_sep("-", title="Pydantic Configuration")
|
||||
terminalreporter.write_line(f"Testing with pydantic version {_PYDANTIC_VERSION}.")
|
||||
# Let's print out the value of USE_PYDANTIC_V2
|
||||
terminalreporter.write_line(
|
||||
f"USE_PYDANTIC_V2: {USE_PYDANTIC_V2}. "
|
||||
f"Enable with `LC_PYDANTIC_V2_EXPERIMENTAL=true` env variable "
|
||||
f"and pydantic>=2 installed."
|
||||
)
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config: Config, items: Sequence[Function]) -> None:
|
||||
|
||||
Reference in New Issue
Block a user