diff --git a/libs/langchain/langchain/agents/agent.py b/libs/langchain/langchain/agents/agent.py index acae0280bf9..f695a6d616b 100644 --- a/libs/langchain/langchain/agents/agent.py +++ b/libs/langchain/langchain/agents/agent.py @@ -1375,7 +1375,7 @@ class AgentExecutor(Chain): elif callable(self.handle_parsing_errors): observation = self.handle_parsing_errors(e) else: - msg = "Got unexpected type of `handle_parsing_errors`" + msg = "Got unexpected type of `handle_parsing_errors`" # type: ignore[unreachable] raise ValueError(msg) from e # noqa: TRY004 output = AgentAction("_Exception", observation, text) if run_manager: @@ -1514,7 +1514,7 @@ class AgentExecutor(Chain): elif callable(self.handle_parsing_errors): observation = self.handle_parsing_errors(e) else: - msg = "Got unexpected type of `handle_parsing_errors`" + msg = "Got unexpected type of `handle_parsing_errors`" # type: ignore[unreachable] raise ValueError(msg) from e # noqa: TRY004 output = AgentAction("_Exception", observation, text) tool_run_kwargs = self._action_agent.tool_run_logging_kwargs() diff --git a/libs/langchain/langchain/agents/format_scratchpad/openai_functions.py b/libs/langchain/langchain/agents/format_scratchpad/openai_functions.py index 188fd0a07ec..7487ef29af6 100644 --- a/libs/langchain/langchain/agents/format_scratchpad/openai_functions.py +++ b/libs/langchain/langchain/agents/format_scratchpad/openai_functions.py @@ -1,5 +1,6 @@ import json from collections.abc import Sequence +from typing import Any from langchain_core.agents import AgentAction, AgentActionMessageLog from langchain_core.messages import AIMessage, BaseMessage, FunctionMessage @@ -30,7 +31,7 @@ def _convert_agent_action_to_messages( def _create_function_message( agent_action: AgentAction, - observation: str, + observation: Any, ) -> FunctionMessage: """Convert agent action and observation into a function message. Args: diff --git a/libs/langchain/langchain/agents/format_scratchpad/tools.py b/libs/langchain/langchain/agents/format_scratchpad/tools.py index 6414b059f2a..2f12e1f3e9e 100644 --- a/libs/langchain/langchain/agents/format_scratchpad/tools.py +++ b/libs/langchain/langchain/agents/format_scratchpad/tools.py @@ -1,5 +1,6 @@ import json from collections.abc import Sequence +from typing import Any from langchain_core.agents import AgentAction from langchain_core.messages import ( @@ -13,7 +14,7 @@ from langchain.agents.output_parsers.tools import ToolAgentAction def _create_tool_message( agent_action: ToolAgentAction, - observation: str, + observation: Any, ) -> ToolMessage: """Convert agent action and observation into a tool message. diff --git a/libs/langchain/langchain/agents/mrkl/base.py b/libs/langchain/langchain/agents/mrkl/base.py index e26c149154c..f5af05a4234 100644 --- a/libs/langchain/langchain/agents/mrkl/base.py +++ b/libs/langchain/langchain/agents/mrkl/base.py @@ -170,7 +170,7 @@ class ZeroShotAgent(Agent): raise ValueError(msg) for tool in tools: if tool.description is None: - msg = ( + msg = ( # type: ignore[unreachable] f"Got a tool {tool.name} without a description. For this agent, " f"a description must always be provided." ) diff --git a/libs/langchain/langchain/agents/output_parsers/json.py b/libs/langchain/langchain/agents/output_parsers/json.py index 4e8cdd35fc6..31d5acb9d45 100644 --- a/libs/langchain/langchain/agents/output_parsers/json.py +++ b/libs/langchain/langchain/agents/output_parsers/json.py @@ -45,9 +45,9 @@ class JSONAgentOutputParser(AgentOutputParser): def parse(self, text: str) -> Union[AgentAction, AgentFinish]: try: response = parse_json_markdown(text) - if isinstance(response, list): + if isinstance(response, list): # type: ignore[unreachable] # gpt turbo frequently ignores the directive to emit a single action - logger.warning("Got multiple action responses: %s", response) + logger.warning("Got multiple action responses: %s", response) # type: ignore[unreachable] response = response[0] if response["action"] == "Final Answer": return AgentFinish({"output": response["action_input"]}, text) diff --git a/libs/langchain/langchain/chains/base.py b/libs/langchain/langchain/chains/base.py index 50bc65bcbab..06de4a737dc 100644 --- a/libs/langchain/langchain/chains/base.py +++ b/libs/langchain/langchain/chains/base.py @@ -286,7 +286,7 @@ class Chain(RunnableSerializable[dict[str, Any], dict[str, Any]], ABC): def output_keys(self) -> list[str]: """Keys expected to be in the chain output.""" - def _validate_inputs(self, inputs: dict[str, Any]) -> None: + def _validate_inputs(self, inputs: Any) -> None: """Check that all inputs are present.""" if not isinstance(inputs, dict): _input_keys = set(self.input_keys) diff --git a/libs/langchain/langchain/chains/conversational_retrieval/base.py b/libs/langchain/langchain/chains/conversational_retrieval/base.py index 71d572fd956..b43f3769374 100644 --- a/libs/langchain/langchain/chains/conversational_retrieval/base.py +++ b/libs/langchain/langchain/chains/conversational_retrieval/base.py @@ -54,7 +54,7 @@ def _get_chat_history(chat_history: list[CHAT_TURN_TYPE]) -> str: ai = "Assistant: " + dialogue_turn[1] buffer += f"\n{human}\n{ai}" else: - msg = ( + msg = ( # type: ignore[unreachable] f"Unsupported chat history format: {type(dialogue_turn)}." f" Full chat history: {chat_history} " ) diff --git a/libs/langchain/langchain/chains/query_constructor/parser.py b/libs/langchain/langchain/chains/query_constructor/parser.py index 97bb312d680..495cd428e68 100644 --- a/libs/langchain/langchain/chains/query_constructor/parser.py +++ b/libs/langchain/langchain/chains/query_constructor/parser.py @@ -9,6 +9,8 @@ from typing_extensions import TypedDict try: check_package_version("lark", gte_version="1.1.5") from lark import Lark, Transformer, v_args + + _HAS_LARK = True except ImportError: def v_args(*_: Any, **__: Any) -> Any: # type: ignore[misc] @@ -17,6 +19,7 @@ except ImportError: Transformer = object # type: ignore[assignment,misc] Lark = object # type: ignore[assignment,misc] + _HAS_LARK = False from langchain_core.structured_query import ( Comparator, @@ -260,8 +263,7 @@ def get_parser( Returns: Lark parser for the query language. """ - # QueryTransformer is None when Lark cannot be imported. - if QueryTransformer is None: + if not _HAS_LARK: msg = "Cannot import lark, please install it with 'pip install lark'." raise ImportError(msg) transformer = QueryTransformer( diff --git a/libs/langchain/langchain/chains/structured_output/base.py b/libs/langchain/langchain/chains/structured_output/base.py index aa22ef2948c..58e3d041cc2 100644 --- a/libs/langchain/langchain/chains/structured_output/base.py +++ b/libs/langchain/langchain/chains/structured_output/base.py @@ -439,7 +439,7 @@ def create_structured_output_runnable( output_parser=output_parser, **kwargs, ) - msg = ( + msg = ( # type: ignore[unreachable] f"Invalid mode {mode}. Expected one of 'openai-tools', 'openai-functions', " f"'openai-json'." ) diff --git a/libs/langchain/langchain/embeddings/cache.py b/libs/langchain/langchain/embeddings/cache.py index 6be687f6196..d56ed2f5af8 100644 --- a/libs/langchain/langchain/embeddings/cache.py +++ b/libs/langchain/langchain/embeddings/cache.py @@ -336,7 +336,7 @@ class CacheBackedEmbeddings(Embeddings): ) raise ValueError(msg) else: - msg = ( + msg = ( # type: ignore[unreachable] "key_encoder must be either 'blake2b', 'sha1', 'sha256', 'sha512' " "or a callable that encodes keys." ) diff --git a/libs/langchain/langchain/memory/vectorstore_token_buffer_memory.py b/libs/langchain/langchain/memory/vectorstore_token_buffer_memory.py index 480e037dd13..de0403a4e62 100644 --- a/libs/langchain/langchain/memory/vectorstore_token_buffer_memory.py +++ b/libs/langchain/langchain/memory/vectorstore_token_buffer_memory.py @@ -9,7 +9,7 @@ sessions. import warnings from datetime import datetime -from typing import Any +from typing import Any, Optional from langchain_core.messages import BaseMessage from langchain_core.prompts.chat import SystemMessagePromptTemplate @@ -109,7 +109,7 @@ class ConversationVectorStoreTokenBufferMemory(ConversationTokenBufferMemory): previous_history_template: str = DEFAULT_HISTORY_TEMPLATE split_chunk_size: int = 1000 - _memory_retriever: VectorStoreRetrieverMemory = PrivateAttr(default=None) # type: ignore[assignment] + _memory_retriever: Optional[VectorStoreRetrieverMemory] = PrivateAttr(default=None) _timestamps: list[datetime] = PrivateAttr(default_factory=list) @property diff --git a/libs/langchain/langchain/model_laboratory.py b/libs/langchain/langchain/model_laboratory.py index a07e57cbfab..3e87f212f3d 100644 --- a/libs/langchain/langchain/model_laboratory.py +++ b/libs/langchain/langchain/model_laboratory.py @@ -34,7 +34,7 @@ class ModelLaboratory: """ for chain in chains: if not isinstance(chain, Chain): - msg = ( + msg = ( # type: ignore[unreachable] "ModelLaboratory should now be initialized with Chains. " "If you want to initialize with LLMs, use the `from_llms` method " "instead (`ModelLaboratory.from_llms(...)`)" diff --git a/libs/langchain/langchain/retrievers/document_compressors/base.py b/libs/langchain/langchain/retrievers/document_compressors/base.py index 42f126fa56a..5f4daef678f 100644 --- a/libs/langchain/langchain/retrievers/document_compressors/base.py +++ b/libs/langchain/langchain/retrievers/document_compressors/base.py @@ -47,7 +47,7 @@ class DocumentCompressorPipeline(BaseDocumentCompressor): elif isinstance(_transformer, BaseDocumentTransformer): documents = _transformer.transform_documents(documents) else: - msg = f"Got unexpected transformer type: {_transformer}" + msg = f"Got unexpected transformer type: {_transformer}" # type: ignore[unreachable] raise ValueError(msg) # noqa: TRY004 return documents @@ -77,6 +77,6 @@ class DocumentCompressorPipeline(BaseDocumentCompressor): elif isinstance(_transformer, BaseDocumentTransformer): documents = await _transformer.atransform_documents(documents) else: - msg = f"Got unexpected transformer type: {_transformer}" + msg = f"Got unexpected transformer type: {_transformer}" # type: ignore[unreachable] raise ValueError(msg) # noqa: TRY004 return documents diff --git a/libs/langchain/langchain/retrievers/ensemble.py b/libs/langchain/langchain/retrievers/ensemble.py index 6679d1cd4cc..7c385339bba 100644 --- a/libs/langchain/langchain/retrievers/ensemble.py +++ b/libs/langchain/langchain/retrievers/ensemble.py @@ -236,7 +236,7 @@ class EnsembleRetriever(BaseRetriever): # Enforce that retrieved docs are Documents for each list in retriever_docs for i in range(len(retriever_docs)): retriever_docs[i] = [ - Document(page_content=cast("str", doc)) if isinstance(doc, str) else doc + Document(page_content=cast("str", doc)) if isinstance(doc, str) else doc # type: ignore[unreachable] for doc in retriever_docs[i] ] diff --git a/libs/langchain/langchain/smith/evaluation/runner_utils.py b/libs/langchain/langchain/smith/evaluation/runner_utils.py index 987805b5355..b0f5517cf6e 100644 --- a/libs/langchain/langchain/smith/evaluation/runner_utils.py +++ b/libs/langchain/langchain/smith/evaluation/runner_utils.py @@ -238,7 +238,7 @@ def _wrap_in_chain_factory( return lambda: RunnableLambda(constructor) # Typical correct case return constructor - return llm_or_chain_factory + return llm_or_chain_factory # type: ignore[unreachable] def _get_prompt(inputs: dict[str, Any]) -> str: @@ -679,7 +679,7 @@ def _load_run_evaluators( elif callable(custom_evaluator): run_evaluators.append(run_evaluator_dec(custom_evaluator)) else: - msg = ( + msg = ( # type: ignore[unreachable] f"Unsupported custom evaluator: {custom_evaluator}." f" Expected RunEvaluator or StringEvaluator." ) diff --git a/libs/langchain/langchain/smith/evaluation/string_run_evaluator.py b/libs/langchain/langchain/smith/evaluation/string_run_evaluator.py index 0ec44e2d2f4..307e1109bfb 100644 --- a/libs/langchain/langchain/smith/evaluation/string_run_evaluator.py +++ b/libs/langchain/langchain/smith/evaluation/string_run_evaluator.py @@ -4,7 +4,7 @@ from __future__ import annotations import uuid from abc import abstractmethod -from typing import Any, Optional +from typing import Any, Optional, Union, cast from langchain_core.callbacks.manager import ( AsyncCallbackManagerForChainRun, @@ -55,16 +55,20 @@ class StringRunMapper(Serializable): class LLMStringRunMapper(StringRunMapper): """Extract items to evaluate from the run object.""" - def serialize_chat_messages(self, messages: list[dict]) -> str: + def serialize_chat_messages( + self, messages: Union[list[dict], list[list[dict]]] + ) -> str: """Extract the input messages from the run.""" if isinstance(messages, list) and messages: if isinstance(messages[0], dict): - chat_messages = _get_messages_from_run_dict(messages) + chat_messages = _get_messages_from_run_dict( + cast("list[dict]", messages) + ) elif isinstance(messages[0], list): # Runs from Tracer have messages as a list of lists of dicts chat_messages = _get_messages_from_run_dict(messages[0]) else: - msg = f"Could not extract messages to evaluate {messages}" + msg = f"Could not extract messages to evaluate {messages}" # type: ignore[unreachable] raise ValueError(msg) return get_buffer_string(chat_messages) msg = f"Could not extract messages to evaluate {messages}" @@ -107,11 +111,11 @@ class LLMStringRunMapper(StringRunMapper): if not outputs.get("generations"): msg = "Cannot evaluate LLM Run without generations." raise ValueError(msg) - generations: list[dict] = outputs["generations"] + generations: Union[list[dict], list[list[dict]]] = outputs["generations"] if not generations: msg = "Cannot evaluate LLM run with empty generations." raise ValueError(msg) - first_generation: dict = generations[0] + first_generation: Union[dict, list[dict]] = generations[0] if isinstance(first_generation, list): # Runs from Tracer have generations as a list of lists of dicts # Whereas Runs from the API have a list of dicts @@ -450,7 +454,7 @@ class StringRunEvaluatorChain(Chain, RunEvaluator): ): example_mapper = StringExampleMapper(reference_key=reference_key) elif evaluator.requires_reference: - msg = ( + msg = ( # type: ignore[unreachable] f"Evaluator {evaluator.evaluation_name} requires a reference" " example from the dataset. Please specify the reference key from" " amongst the dataset outputs keys." diff --git a/libs/langchain/langchain/storage/_lc_store.py b/libs/langchain/langchain/storage/_lc_store.py index 30403b09c80..24603b93599 100644 --- a/libs/langchain/langchain/storage/_lc_store.py +++ b/libs/langchain/langchain/storage/_lc_store.py @@ -1,6 +1,6 @@ """Create a key-value store for any langchain serializable object.""" -from typing import Callable, Optional +from typing import Any, Callable, Optional from langchain_core.documents import Document from langchain_core.load import Serializable, dumps, loads @@ -14,7 +14,7 @@ def _dump_as_bytes(obj: Serializable) -> bytes: return dumps(obj).encode("utf-8") -def _dump_document_as_bytes(obj: Document) -> bytes: +def _dump_document_as_bytes(obj: Any) -> bytes: """Return a bytes representation of a document.""" if not isinstance(obj, Document): msg = "Expected a Document instance" diff --git a/libs/langchain/pyproject.toml b/libs/langchain/pyproject.toml index cdf0add9555..f209169ee12 100644 --- a/libs/langchain/pyproject.toml +++ b/libs/langchain/pyproject.toml @@ -129,6 +129,7 @@ strict_bytes = "True" ignore_missing_imports = "True" enable_error_code = "deprecated" report_deprecated_as_note = "True" +warn_unreachable = "True" # TODO: activate for 'strict' checking disallow_untyped_calls = "False" diff --git a/libs/langchain/tests/unit_tests/agents/test_agent.py b/libs/langchain/tests/unit_tests/agents/test_agent.py index d1bc7e6c2a0..36045d82a0d 100644 --- a/libs/langchain/tests/unit_tests/agents/test_agent.py +++ b/libs/langchain/tests/unit_tests/agents/test_agent.py @@ -514,20 +514,20 @@ async def test_runnable_agent() -> None: ] # stream log - results: list[RunLogPatch] = [ # type: ignore[no-redef] + log_results: list[RunLogPatch] = [ r async for r in executor.astream_log({"question": "hello"}) ] # # Let's stream just the llm tokens. messages = [] - for log_record in results: - for op in log_record.ops: # type: ignore[attr-defined] + for log_record in log_results: + for op in log_record.ops: if op["op"] == "add" and isinstance(op["value"], AIMessageChunk): messages.append(op["value"]) # noqa: PERF401 assert messages != [] # Aggregate state - run_log = reduce(operator.add, results) + run_log = reduce(operator.add, log_results) assert isinstance(run_log, RunLog) diff --git a/libs/langchain/tests/unit_tests/agents/test_structured_chat.py b/libs/langchain/tests/unit_tests/agents/test_structured_chat.py index 18c833fd4e1..9ca04087d65 100644 --- a/libs/langchain/tests/unit_tests/agents/test_structured_chat.py +++ b/libs/langchain/tests/unit_tests/agents/test_structured_chat.py @@ -23,7 +23,7 @@ def get_action_and_input(text: str) -> tuple[str, str]: return output.tool, str(output.tool_input) if isinstance(output, AgentFinish): return output.return_values["output"], output.log - msg = "Unexpected output type" + msg = "Unexpected output type" # type: ignore[unreachable] raise ValueError(msg) diff --git a/libs/langchain/tests/unit_tests/chains/query_constructor/test_parser.py b/libs/langchain/tests/unit_tests/chains/query_constructor/test_parser.py index 836c759d413..4f4d02b77f6 100644 --- a/libs/langchain/tests/unit_tests/chains/query_constructor/test_parser.py +++ b/libs/langchain/tests/unit_tests/chains/query_constructor/test_parser.py @@ -1,6 +1,6 @@ """Test LLM-generated structured query parsing.""" -from typing import Any, cast +from typing import Any, Optional, cast import lark import pytest @@ -149,7 +149,7 @@ def test_parse_date_value(x: str) -> None: ), ], ) -def test_parse_datetime_value(x: str, expected: dict) -> None: +def test_parse_datetime_value(x: str, expected: Optional[dict[str, str]]) -> None: """Test parsing of datetime values with ISO 8601 format.""" try: parsed = cast("Comparison", DEFAULT_PARSER.parse(f'eq("publishedAt", {x})')) diff --git a/libs/langchain/tests/unit_tests/llms/fake_chat_model.py b/libs/langchain/tests/unit_tests/llms/fake_chat_model.py index 63455c3a482..c4f2670fb86 100644 --- a/libs/langchain/tests/unit_tests/llms/fake_chat_model.py +++ b/libs/langchain/tests/unit_tests/llms/fake_chat_model.py @@ -107,7 +107,7 @@ class GenericFakeChatModel(BaseChatModel): **kwargs, ) if not isinstance(chat_result, ChatResult): - msg = ( + msg = ( # type: ignore[unreachable] f"Expected generate to return a ChatResult, " f"but got {type(chat_result)} instead." ) diff --git a/libs/langchain/uv.lock b/libs/langchain/uv.lock index c569cf265ab..faab41eff1d 100644 --- a/libs/langchain/uv.lock +++ b/libs/langchain/uv.lock @@ -2346,7 +2346,7 @@ wheels = [ [[package]] name = "langchain-openai" -version = "0.3.29" +version = "0.3.30" source = { editable = "../partners/openai" } dependencies = [ { name = "langchain-core" }, @@ -2357,14 +2357,14 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "langchain-core", editable = "../core" }, - { name = "openai", specifier = ">=1.86.0,<2.0.0" }, + { name = "openai", specifier = ">=1.99.9,<2.0.0" }, { name = "tiktoken", specifier = ">=0.7,<1" }, ] [package.metadata.requires-dev] codespell = [{ name = "codespell", specifier = ">=2.2.0,<3.0.0" }] dev = [{ name = "langchain-core", editable = "../core" }] -lint = [{ name = "ruff", specifier = ">=0.12.2,<0.13" }] +lint = [{ name = "ruff", specifier = ">=0.12.8,<0.13" }] test = [ { name = "freezegun", specifier = ">=1.2.2,<2.0.0" }, { name = "langchain-core", editable = "../core" }, @@ -2390,7 +2390,7 @@ test-integration = [ ] typing = [ { name = "langchain-core", editable = "../core" }, - { name = "mypy", specifier = ">=1.10,<2.0" }, + { name = "mypy", specifier = ">=1.17.1,<2.0" }, { name = "types-tqdm", specifier = ">=4.66.0.5,<5.0.0.0" }, ] @@ -2497,7 +2497,7 @@ test-integration = [ ] typing = [ { name = "lxml-stubs", specifier = ">=0.5.1,<1.0.0" }, - { name = "mypy", specifier = ">=1.15,<2.0" }, + { name = "mypy", specifier = ">=1.17.1,<1.18" }, { name = "tiktoken", specifier = ">=0.8.0,<1.0.0" }, { name = "types-requests", specifier = ">=2.31.0.20240218,<3.0.0.0" }, ] @@ -3066,7 +3066,7 @@ wheels = [ [[package]] name = "openai" -version = "1.87.0" +version = "1.99.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -3078,9 +3078,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/ed/2b3f6c7e950784e9442115ab8ebeff514d543fb33da10607b39364645a75/openai-1.87.0.tar.gz", hash = "sha256:5c69764171e0db9ef993e7a4d8a01fd8ff1026b66f8bdd005b9461782b6e7dfc", size = 470880, upload-time = "2025-06-16T19:04:26.316Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/d2/ef89c6f3f36b13b06e271d3cc984ddd2f62508a0972c1cbcc8485a6644ff/openai-1.99.9.tar.gz", hash = "sha256:f2082d155b1ad22e83247c3de3958eb4255b20ccf4a1de2e6681b6957b554e92", size = 506992, upload-time = "2025-08-12T02:31:10.054Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/ac/313ded47ce1d5bc2ec02ed5dd5506bf5718678a4655ac20f337231d9aae3/openai-1.87.0-py3-none-any.whl", hash = "sha256:f9bcae02ac4fff6522276eee85d33047335cfb692b863bd8261353ce4ada5692", size = 734368, upload-time = "2025-06-16T19:04:23.181Z" }, + { url = "https://files.pythonhosted.org/packages/e8/fb/df274ca10698ee77b07bff952f302ea627cc12dac6b85289485dd77db6de/openai-1.99.9-py3-none-any.whl", hash = "sha256:9dbcdb425553bae1ac5d947147bebbd630d91bbfc7788394d4c4f3a35682ab3a", size = 786816, upload-time = "2025-08-12T02:31:08.34Z" }, ] [[package]]