From bbd4c4be010a0adff25be2417557fa9c72d1018d Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Wed, 8 Oct 2025 23:49:51 -0400 Subject: [PATCH] chore(langchain): begin deleting old chains, memory --- .../langchain_classic/chains/api/base.py | 400 ---------------- .../langchain_classic/chains/base.py | 274 +---------- .../langchain/langchain_classic/chains/llm.py | 445 ------------------ .../langchain_classic/chains/loading.py | 44 -- .../langchain_classic/chains/mapreduce.py | 123 ----- .../chains/openai_tools/__init__.py | 5 - .../chains/openai_tools/extraction.py | 77 --- .../chains/qa_generation/__init__.py | 0 .../chains/qa_generation/base.py | 128 ----- .../chains/qa_generation/prompt.py | 53 --- .../chains/qa_with_sources/__init__.py | 5 - .../chains/qa_with_sources/base.py | 268 ----------- .../chains/qa_with_sources/loading.py | 214 --------- .../qa_with_sources/map_reduce_prompt.py | 54 --- .../chains/qa_with_sources/refine_prompts.py | 37 -- .../chains/qa_with_sources/retrieval.py | 75 --- .../chains/qa_with_sources/stuff_prompt.py | 43 -- .../chains/qa_with_sources/vector_db.py | 89 ---- .../chains/query_constructor/base.py | 59 --- .../chains/question_answering/chain.py | 57 --- .../chains/retrieval_qa/__init__.py | 1 - .../chains/retrieval_qa/base.py | 377 --------------- .../chains/retrieval_qa/prompt.py | 11 - .../chains/router/llm_router.py | 199 -------- .../chains/router/multi_prompt.py | 190 -------- .../chains/structured_output/base.py | 421 +---------------- .../langchain_classic/memory/summary.py | 171 ------- 27 files changed, 2 insertions(+), 3818 deletions(-) delete mode 100644 libs/langchain/langchain_classic/chains/api/base.py delete mode 100644 libs/langchain/langchain_classic/chains/llm.py delete mode 100644 libs/langchain/langchain_classic/chains/mapreduce.py delete mode 100644 libs/langchain/langchain_classic/chains/openai_tools/__init__.py delete mode 100644 libs/langchain/langchain_classic/chains/openai_tools/extraction.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_generation/__init__.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_generation/base.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_generation/prompt.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_with_sources/__init__.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_with_sources/base.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_with_sources/loading.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_with_sources/map_reduce_prompt.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_with_sources/refine_prompts.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_with_sources/retrieval.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_with_sources/stuff_prompt.py delete mode 100644 libs/langchain/langchain_classic/chains/qa_with_sources/vector_db.py delete mode 100644 libs/langchain/langchain_classic/chains/retrieval_qa/__init__.py delete mode 100644 libs/langchain/langchain_classic/chains/retrieval_qa/base.py delete mode 100644 libs/langchain/langchain_classic/chains/retrieval_qa/prompt.py delete mode 100644 libs/langchain/langchain_classic/chains/router/llm_router.py delete mode 100644 libs/langchain/langchain_classic/chains/router/multi_prompt.py delete mode 100644 libs/langchain/langchain_classic/memory/summary.py diff --git a/libs/langchain/langchain_classic/chains/api/base.py b/libs/langchain/langchain_classic/chains/api/base.py deleted file mode 100644 index 33867cabdf9b7..0000000000000 --- a/libs/langchain/langchain_classic/chains/api/base.py +++ /dev/null @@ -1,400 +0,0 @@ -"""Chain that makes API calls and summarizes the responses to answer a question.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Any -from urllib.parse import urlparse - -from langchain_core._api import deprecated -from langchain_core.callbacks import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) -from langchain_core.language_models import BaseLanguageModel -from langchain_core.prompts import BasePromptTemplate -from pydantic import Field, model_validator -from typing_extensions import Self - -from langchain_classic.chains.api.prompt import API_RESPONSE_PROMPT, API_URL_PROMPT -from langchain_classic.chains.base import Chain -from langchain_classic.chains.llm import LLMChain - - -def _extract_scheme_and_domain(url: str) -> tuple[str, str]: - """Extract the scheme + domain from a given URL. - - Args: - url (str): The input URL. - - Returns: - return a 2-tuple of scheme and domain - """ - parsed_uri = urlparse(url) - return parsed_uri.scheme, parsed_uri.netloc - - -def _check_in_allowed_domain(url: str, limit_to_domains: Sequence[str]) -> bool: - """Check if a URL is in the allowed domains. - - Args: - url (str): The input URL. - limit_to_domains (Sequence[str]): The allowed domains. - - Returns: - bool: True if the URL is in the allowed domains, False otherwise. - """ - scheme, domain = _extract_scheme_and_domain(url) - - for allowed_domain in limit_to_domains: - allowed_scheme, allowed_domain_ = _extract_scheme_and_domain(allowed_domain) - if scheme == allowed_scheme and domain == allowed_domain_: - return True - return False - - -try: - from langchain_community.utilities.requests import TextRequestsWrapper - - @deprecated( - since="0.2.13", - message=( - "This class is deprecated and will be removed in langchain 1.0. " - "See API reference for replacement: " - "https://api.python.langchain.com/en/latest/chains/langchain.chains.api.base.APIChain.html" - ), - removal="1.0", - ) - class APIChain(Chain): - """Chain that makes API calls and summarizes the responses to answer a question. - - *Security Note*: This API chain uses the requests toolkit - to make GET, POST, PATCH, PUT, and DELETE requests to an API. - - Exercise care in who is allowed to use this chain. If exposing - to end users, consider that users will be able to make arbitrary - requests on behalf of the server hosting the code. For example, - users could ask the server to make a request to a private API - that is only accessible from the server. - - Control access to who can submit issue requests using this toolkit and - what network access it has. - - See https://python.langchain.com/docs/security for more information. - - !!! note - This class is deprecated. See below for a replacement implementation using - LangGraph. The benefits of this implementation are: - - - Uses LLM tool calling features to encourage properly-formatted API requests; - - Support for both token-by-token and step-by-step streaming; - - Support for checkpointing and memory of chat history; - - Easier to modify or extend - (e.g., with additional tools, structured responses, etc.) - - Install LangGraph with: - - .. code-block:: bash - - pip install -U langgraph - - .. code-block:: python - - from typing import Annotated, Sequence - from typing_extensions import TypedDict - - from langchain_classic.chains.api.prompt import API_URL_PROMPT - from langchain_community.agent_toolkits.openapi.toolkit import RequestsToolkit - from langchain_community.utilities.requests import TextRequestsWrapper - from langchain_core.messages import BaseMessage - from langchain_core.prompts import ChatPromptTemplate - from langchain_openai import ChatOpenAI - from langchain_core.runnables import RunnableConfig - from langgraph.graph import END, StateGraph - from langgraph.graph.message import add_messages - from langgraph.prebuilt.tool_node import ToolNode - - # NOTE: There are inherent risks in giving models discretion - # to execute real-world actions. We must "opt-in" to these - # risks by setting allow_dangerous_request=True to use these tools. - # This can be dangerous for calling unwanted requests. Please make - # sure your custom OpenAPI spec (yaml) is safe and that permissions - # associated with the tools are narrowly-scoped. - ALLOW_DANGEROUS_REQUESTS = True - - # Subset of spec for https://jsonplaceholder.typicode.com - api_spec = \"\"\" - openapi: 3.0.0 - info: - title: JSONPlaceholder API - version: 1.0.0 - servers: - - url: https://jsonplaceholder.typicode.com - paths: - /posts: - get: - summary: Get posts - parameters: &id001 - - name: _limit - in: query - required: false - schema: - type: integer - example: 2 - description: Limit the number of results - \"\"\" - - llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) - toolkit = RequestsToolkit( - requests_wrapper=TextRequestsWrapper(headers={}), # no auth required - allow_dangerous_requests=ALLOW_DANGEROUS_REQUESTS, - ) - tools = toolkit.get_tools() - - api_request_chain = ( - API_URL_PROMPT.partial(api_docs=api_spec) - | llm.bind_tools(tools, tool_choice="any") - ) - - class ChainState(TypedDict): - \"\"\"LangGraph state.\"\"\" - - messages: Annotated[Sequence[BaseMessage], add_messages] - - - async def acall_request_chain(state: ChainState, config: RunnableConfig): - last_message = state["messages"][-1] - response = await api_request_chain.ainvoke( - {"question": last_message.content}, config - ) - return {"messages": [response]} - - async def acall_model(state: ChainState, config: RunnableConfig): - response = await llm.ainvoke(state["messages"], config) - return {"messages": [response]} - - graph_builder = StateGraph(ChainState) - graph_builder.add_node("call_tool", acall_request_chain) - graph_builder.add_node("execute_tool", ToolNode(tools)) - graph_builder.add_node("call_model", acall_model) - graph_builder.set_entry_point("call_tool") - graph_builder.add_edge("call_tool", "execute_tool") - graph_builder.add_edge("execute_tool", "call_model") - graph_builder.add_edge("call_model", END) - chain = graph_builder.compile() - - .. code-block:: python - - example_query = "Fetch the top two posts. What are their titles?" - - events = chain.astream( - {"messages": [("user", example_query)]}, - stream_mode="values", - ) - async for event in events: - event["messages"][-1].pretty_print() - - """ # noqa: E501 - - api_request_chain: LLMChain - api_answer_chain: LLMChain - requests_wrapper: TextRequestsWrapper = Field(exclude=True) - api_docs: str - question_key: str = "question" #: :meta private: - output_key: str = "output" #: :meta private: - limit_to_domains: Sequence[str] | None = Field(default_factory=list) # type: ignore[arg-type] - """Use to limit the domains that can be accessed by the API chain. - - * For example, to limit to just the domain `https://www.example.com`, set - `limit_to_domains=["https://www.example.com"]`. - - * The default value is an empty tuple, which means that no domains are - allowed by default. By design this will raise an error on instantiation. - * Use a None if you want to allow all domains by default -- this is not - recommended for security reasons, as it would allow malicious users to - make requests to arbitrary URLS including internal APIs accessible from - the server. - """ - - @property - def input_keys(self) -> list[str]: - """Expect input key. - - :meta private: - """ - return [self.question_key] - - @property - def output_keys(self) -> list[str]: - """Expect output key. - - :meta private: - """ - return [self.output_key] - - @model_validator(mode="after") - def validate_api_request_prompt(self) -> Self: - """Check that api request prompt expects the right variables.""" - input_vars = self.api_request_chain.prompt.input_variables - expected_vars = {"question", "api_docs"} - if set(input_vars) != expected_vars: - msg = f"Input variables should be {expected_vars}, got {input_vars}" - raise ValueError(msg) - return self - - @model_validator(mode="before") - @classmethod - def validate_limit_to_domains(cls, values: dict) -> Any: - """Check that allowed domains are valid.""" - # This check must be a pre=True check, so that a default of None - # won't be set to limit_to_domains if it's not provided. - if "limit_to_domains" not in values: - msg = ( - "You must specify a list of domains to limit access using " - "`limit_to_domains`" - ) - raise ValueError(msg) - if ( - not values["limit_to_domains"] - and values["limit_to_domains"] is not None - ): - msg = ( - "Please provide a list of domains to limit access using " - "`limit_to_domains`." - ) - raise ValueError(msg) - return values - - @model_validator(mode="after") - def validate_api_answer_prompt(self) -> Self: - """Check that api answer prompt expects the right variables.""" - input_vars = self.api_answer_chain.prompt.input_variables - expected_vars = {"question", "api_docs", "api_url", "api_response"} - if set(input_vars) != expected_vars: - msg = f"Input variables should be {expected_vars}, got {input_vars}" - raise ValueError(msg) - return self - - def _call( - self, - inputs: dict[str, Any], - run_manager: CallbackManagerForChainRun | None = None, - ) -> dict[str, str]: - _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager() - question = inputs[self.question_key] - api_url = self.api_request_chain.predict( - question=question, - api_docs=self.api_docs, - callbacks=_run_manager.get_child(), - ) - _run_manager.on_text(api_url, color="green", end="\n", verbose=self.verbose) - api_url = api_url.strip() - if self.limit_to_domains and not _check_in_allowed_domain( - api_url, - self.limit_to_domains, - ): - msg = ( - f"{api_url} is not in the allowed domains: {self.limit_to_domains}" - ) - raise ValueError(msg) - api_response = self.requests_wrapper.get(api_url) - _run_manager.on_text( - str(api_response), - color="yellow", - end="\n", - verbose=self.verbose, - ) - answer = self.api_answer_chain.predict( - question=question, - api_docs=self.api_docs, - api_url=api_url, - api_response=api_response, - callbacks=_run_manager.get_child(), - ) - return {self.output_key: answer} - - async def _acall( - self, - inputs: dict[str, Any], - run_manager: AsyncCallbackManagerForChainRun | None = None, - ) -> dict[str, str]: - _run_manager = ( - run_manager or AsyncCallbackManagerForChainRun.get_noop_manager() - ) - question = inputs[self.question_key] - api_url = await self.api_request_chain.apredict( - question=question, - api_docs=self.api_docs, - callbacks=_run_manager.get_child(), - ) - await _run_manager.on_text( - api_url, - color="green", - end="\n", - verbose=self.verbose, - ) - api_url = api_url.strip() - if self.limit_to_domains and not _check_in_allowed_domain( - api_url, - self.limit_to_domains, - ): - msg = ( - f"{api_url} is not in the allowed domains: {self.limit_to_domains}" - ) - raise ValueError(msg) - api_response = await self.requests_wrapper.aget(api_url) - await _run_manager.on_text( - str(api_response), - color="yellow", - end="\n", - verbose=self.verbose, - ) - answer = await self.api_answer_chain.apredict( - question=question, - api_docs=self.api_docs, - api_url=api_url, - api_response=api_response, - callbacks=_run_manager.get_child(), - ) - return {self.output_key: answer} - - @classmethod - def from_llm_and_api_docs( - cls, - llm: BaseLanguageModel, - api_docs: str, - headers: dict | None = None, - api_url_prompt: BasePromptTemplate = API_URL_PROMPT, - api_response_prompt: BasePromptTemplate = API_RESPONSE_PROMPT, - limit_to_domains: Sequence[str] | None = (), - **kwargs: Any, - ) -> APIChain: - """Load chain from just an LLM and the api docs.""" - get_request_chain = LLMChain(llm=llm, prompt=api_url_prompt) - requests_wrapper = TextRequestsWrapper(headers=headers) - get_answer_chain = LLMChain(llm=llm, prompt=api_response_prompt) - return cls( - api_request_chain=get_request_chain, - api_answer_chain=get_answer_chain, - requests_wrapper=requests_wrapper, - api_docs=api_docs, - limit_to_domains=limit_to_domains, - **kwargs, - ) - - @property - def _chain_type(self) -> str: - return "api_chain" - -except ImportError: - - class APIChain: # type: ignore[no-redef] - """Raise an ImportError if APIChain is used without langchain_community.""" - - def __init__(self, *_: Any, **__: Any) -> None: - """Raise an ImportError if APIChain is used without langchain_community.""" - msg = ( - "To use the APIChain, you must install the langchain_community package." - "pip install langchain_community" - ) - raise ImportError(msg) diff --git a/libs/langchain/langchain_classic/chains/base.py b/libs/langchain/langchain_classic/chains/base.py index a40e482bebcbd..9a5812675698b 100644 --- a/libs/langchain/langchain_classic/chains/base.py +++ b/libs/langchain/langchain_classic/chains/base.py @@ -1,6 +1,5 @@ """Base interface that all chains should implement.""" -import builtins import contextlib import inspect import json @@ -8,10 +7,9 @@ import warnings from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, cast +from typing import Any import yaml -from langchain_core._api import deprecated from langchain_core.callbacks import ( AsyncCallbackManager, AsyncCallbackManagerForChainRun, @@ -365,109 +363,6 @@ async def _acall( run_manager.get_sync() if run_manager else None, ) - @deprecated("0.1.0", alternative="invoke", removal="1.0") - def __call__( - self, - inputs: dict[str, Any] | Any, - return_only_outputs: bool = False, # noqa: FBT001,FBT002 - callbacks: Callbacks = None, - *, - tags: list[str] | None = None, - metadata: dict[str, Any] | None = None, - run_name: str | None = None, - include_run_info: bool = False, - ) -> dict[str, Any]: - """Execute the chain. - - Args: - inputs: Dictionary of inputs, or single input if chain expects - only one param. Should contain all inputs specified in - `Chain.input_keys` except for inputs that will be set by the chain's - memory. - return_only_outputs: Whether to return only outputs in the - response. If `True`, only new keys generated by this chain will be - returned. If `False`, both input keys and new keys generated by this - chain will be returned. Defaults to `False`. - callbacks: Callbacks to use for this chain run. These will be called in - addition to callbacks passed to the chain during construction, but only - these runtime callbacks will propagate to calls to other objects. - tags: List of string tags to pass to all callbacks. These will be passed in - addition to tags passed to the chain during construction, but only - these runtime tags will propagate to calls to other objects. - metadata: Optional metadata associated with the chain. Defaults to `None`. - run_name: Optional name for this run of the chain. - include_run_info: Whether to include run info in the response. Defaults - to False. - - Returns: - A dict of named outputs. Should contain all outputs specified in - `Chain.output_keys`. - """ - config = { - "callbacks": callbacks, - "tags": tags, - "metadata": metadata, - "run_name": run_name, - } - - return self.invoke( - inputs, - cast("RunnableConfig", {k: v for k, v in config.items() if v is not None}), - return_only_outputs=return_only_outputs, - include_run_info=include_run_info, - ) - - @deprecated("0.1.0", alternative="ainvoke", removal="1.0") - async def acall( - self, - inputs: dict[str, Any] | Any, - return_only_outputs: bool = False, # noqa: FBT001,FBT002 - callbacks: Callbacks = None, - *, - tags: list[str] | None = None, - metadata: dict[str, Any] | None = None, - run_name: str | None = None, - include_run_info: bool = False, - ) -> dict[str, Any]: - """Asynchronously execute the chain. - - Args: - inputs: Dictionary of inputs, or single input if chain expects - only one param. Should contain all inputs specified in - `Chain.input_keys` except for inputs that will be set by the chain's - memory. - return_only_outputs: Whether to return only outputs in the - response. If `True`, only new keys generated by this chain will be - returned. If `False`, both input keys and new keys generated by this - chain will be returned. Defaults to `False`. - callbacks: Callbacks to use for this chain run. These will be called in - addition to callbacks passed to the chain during construction, but only - these runtime callbacks will propagate to calls to other objects. - tags: List of string tags to pass to all callbacks. These will be passed in - addition to tags passed to the chain during construction, but only - these runtime tags will propagate to calls to other objects. - metadata: Optional metadata associated with the chain. Defaults to `None`. - run_name: Optional name for this run of the chain. - include_run_info: Whether to include run info in the response. Defaults - to False. - - Returns: - A dict of named outputs. Should contain all outputs specified in - `Chain.output_keys`. - """ - config = { - "callbacks": callbacks, - "tags": tags, - "metadata": metadata, - "run_name": run_name, - } - return await self.ainvoke( - inputs, - cast("RunnableConfig", {k: v for k, v in config.items() if k is not None}), - return_only_outputs=return_only_outputs, - include_run_info=include_run_info, - ) - def prep_outputs( self, inputs: dict[str, str], @@ -576,164 +471,6 @@ def _run_output_key(self) -> str: raise ValueError(msg) return self.output_keys[0] - @deprecated("0.1.0", alternative="invoke", removal="1.0") - def run( - self, - *args: Any, - callbacks: Callbacks = None, - tags: list[str] | None = None, - metadata: dict[str, Any] | None = None, - **kwargs: Any, - ) -> Any: - """Convenience method for executing chain. - - The main difference between this method and `Chain.__call__` is that this - method expects inputs to be passed directly in as positional arguments or - keyword arguments, whereas `Chain.__call__` expects a single input dictionary - with all the inputs - - Args: - *args: If the chain expects a single input, it can be passed in as the - sole positional argument. - callbacks: Callbacks to use for this chain run. These will be called in - addition to callbacks passed to the chain during construction, but only - these runtime callbacks will propagate to calls to other objects. - tags: List of string tags to pass to all callbacks. These will be passed in - addition to tags passed to the chain during construction, but only - these runtime tags will propagate to calls to other objects. - metadata: Optional metadata associated with the chain. - **kwargs: If the chain expects multiple inputs, they can be passed in - directly as keyword arguments. - - Returns: - The chain output. - - Example: - .. code-block:: python - - # Suppose we have a single-input chain that takes a 'question' string: - chain.run("What's the temperature in Boise, Idaho?") - # -> "The temperature in Boise is..." - - # Suppose we have a multi-input chain that takes a 'question' string - # and 'context' string: - question = "What's the temperature in Boise, Idaho?" - context = "Weather report for Boise, Idaho on 07/03/23..." - chain.run(question=question, context=context) - # -> "The temperature in Boise is..." - - """ - # Run at start to make sure this is possible/defined - _output_key = self._run_output_key - - if args and not kwargs: - if len(args) != 1: - msg = "`run` supports only one positional argument." - raise ValueError(msg) - return self(args[0], callbacks=callbacks, tags=tags, metadata=metadata)[ - _output_key - ] - - if kwargs and not args: - return self(kwargs, callbacks=callbacks, tags=tags, metadata=metadata)[ - _output_key - ] - - if not kwargs and not args: - msg = ( - "`run` supported with either positional arguments or keyword arguments," - " but none were provided." - ) - raise ValueError(msg) - msg = ( - f"`run` supported with either positional arguments or keyword arguments" - f" but not both. Got args: {args} and kwargs: {kwargs}." - ) - raise ValueError(msg) - - @deprecated("0.1.0", alternative="ainvoke", removal="1.0") - async def arun( - self, - *args: Any, - callbacks: Callbacks = None, - tags: list[str] | None = None, - metadata: dict[str, Any] | None = None, - **kwargs: Any, - ) -> Any: - """Convenience method for executing chain. - - The main difference between this method and `Chain.__call__` is that this - method expects inputs to be passed directly in as positional arguments or - keyword arguments, whereas `Chain.__call__` expects a single input dictionary - with all the inputs - - - Args: - *args: If the chain expects a single input, it can be passed in as the - sole positional argument. - callbacks: Callbacks to use for this chain run. These will be called in - addition to callbacks passed to the chain during construction, but only - these runtime callbacks will propagate to calls to other objects. - tags: List of string tags to pass to all callbacks. These will be passed in - addition to tags passed to the chain during construction, but only - these runtime tags will propagate to calls to other objects. - metadata: Optional metadata associated with the chain. - **kwargs: If the chain expects multiple inputs, they can be passed in - directly as keyword arguments. - - Returns: - The chain output. - - Example: - .. code-block:: python - - # Suppose we have a single-input chain that takes a 'question' string: - await chain.arun("What's the temperature in Boise, Idaho?") - # -> "The temperature in Boise is..." - - # Suppose we have a multi-input chain that takes a 'question' string - # and 'context' string: - question = "What's the temperature in Boise, Idaho?" - context = "Weather report for Boise, Idaho on 07/03/23..." - await chain.arun(question=question, context=context) - # -> "The temperature in Boise is..." - - """ - if len(self.output_keys) != 1: - msg = ( - f"`run` not supported when there is not exactly " - f"one output key. Got {self.output_keys}." - ) - raise ValueError(msg) - if args and not kwargs: - if len(args) != 1: - msg = "`run` supports only one positional argument." - raise ValueError(msg) - return ( - await self.acall( - args[0], - callbacks=callbacks, - tags=tags, - metadata=metadata, - ) - )[self.output_keys[0]] - - if kwargs and not args: - return ( - await self.acall( - kwargs, - callbacks=callbacks, - tags=tags, - metadata=metadata, - ) - )[self.output_keys[0]] - - msg = ( - f"`run` supported with either positional arguments or keyword arguments" - f" but not both. Got args: {args} and kwargs: {kwargs}." - ) - raise ValueError(msg) - def dict(self, **kwargs: Any) -> dict: """Dictionary representation of chain. @@ -799,12 +536,3 @@ def save(self, file_path: Path | str) -> None: else: msg = f"{save_path} must be json or yaml" raise ValueError(msg) - - @deprecated("0.1.0", alternative="batch", removal="1.0") - def apply( - self, - input_list: list[builtins.dict[str, Any]], - callbacks: Callbacks = None, - ) -> list[builtins.dict[str, str]]: - """Call the chain on all inputs in the list.""" - return [self(inputs, callbacks=callbacks) for inputs in input_list] diff --git a/libs/langchain/langchain_classic/chains/llm.py b/libs/langchain/langchain_classic/chains/llm.py deleted file mode 100644 index 0076c29828798..0000000000000 --- a/libs/langchain/langchain_classic/chains/llm.py +++ /dev/null @@ -1,445 +0,0 @@ -"""Chain that just formats a prompt and calls an LLM.""" - -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import Any, cast - -from langchain_core._api import deprecated -from langchain_core.callbacks import ( - AsyncCallbackManager, - AsyncCallbackManagerForChainRun, - CallbackManager, - CallbackManagerForChainRun, - Callbacks, -) -from langchain_core.language_models import ( - BaseLanguageModel, - LanguageModelInput, -) -from langchain_core.messages import BaseMessage -from langchain_core.output_parsers import BaseLLMOutputParser, StrOutputParser -from langchain_core.outputs import ChatGeneration, Generation, LLMResult -from langchain_core.prompt_values import PromptValue -from langchain_core.prompts import BasePromptTemplate, PromptTemplate -from langchain_core.runnables import ( - Runnable, - RunnableBinding, - RunnableBranch, - RunnableWithFallbacks, -) -from langchain_core.runnables.configurable import DynamicRunnable -from langchain_core.utils.input import get_colored_text -from pydantic import ConfigDict, Field -from typing_extensions import override - -from langchain_classic.chains.base import Chain - - -@deprecated( - since="0.1.17", - alternative="RunnableSequence, e.g., `prompt | llm`", - removal="1.0", -) -class LLMChain(Chain): - """Chain to run queries against LLMs. - - This class is deprecated. See below for an example implementation using - LangChain runnables: - - .. code-block:: python - - from langchain_core.output_parsers import StrOutputParser - from langchain_core.prompts import PromptTemplate - from langchain_openai import OpenAI - - prompt_template = "Tell me a {adjective} joke" - prompt = PromptTemplate( - input_variables=["adjective"], template=prompt_template - ) - llm = OpenAI() - chain = prompt | llm | StrOutputParser() - - chain.invoke("your adjective here") - - Example: - .. code-block:: python - - from langchain_classic.chains import LLMChain - from langchain_community.llms import OpenAI - from langchain_core.prompts import PromptTemplate - - prompt_template = "Tell me a {adjective} joke" - prompt = PromptTemplate( - input_variables=["adjective"], template=prompt_template - ) - llm = LLMChain(llm=OpenAI(), prompt=prompt) - - """ - - @classmethod - @override - def is_lc_serializable(cls) -> bool: - return True - - prompt: BasePromptTemplate - """Prompt object to use.""" - llm: Runnable[LanguageModelInput, str] | Runnable[LanguageModelInput, BaseMessage] - """Language model to call.""" - output_key: str = "text" #: :meta private: - output_parser: BaseLLMOutputParser = Field(default_factory=StrOutputParser) - """Output parser to use. - Defaults to one that takes the most likely string but does not change it - otherwise.""" - return_final_only: bool = True - """Whether to return only the final parsed result. Defaults to `True`. - If false, will return a bunch of extra information about the generation.""" - llm_kwargs: dict = Field(default_factory=dict) - - model_config = ConfigDict( - arbitrary_types_allowed=True, - extra="forbid", - ) - - @property - def input_keys(self) -> list[str]: - """Will be whatever keys the prompt expects. - - :meta private: - """ - return self.prompt.input_variables - - @property - def output_keys(self) -> list[str]: - """Will always return text key. - - :meta private: - """ - if self.return_final_only: - return [self.output_key] - return [self.output_key, "full_generation"] - - def _call( - self, - inputs: dict[str, Any], - run_manager: CallbackManagerForChainRun | None = None, - ) -> dict[str, str]: - response = self.generate([inputs], run_manager=run_manager) - return self.create_outputs(response)[0] - - def generate( - self, - input_list: list[dict[str, Any]], - run_manager: CallbackManagerForChainRun | None = None, - ) -> LLMResult: - """Generate LLM result from inputs.""" - prompts, stop = self.prep_prompts(input_list, run_manager=run_manager) - callbacks = run_manager.get_child() if run_manager else None - if isinstance(self.llm, BaseLanguageModel): - return self.llm.generate_prompt( - prompts, - stop, - callbacks=callbacks, - **self.llm_kwargs, - ) - results = self.llm.bind(stop=stop, **self.llm_kwargs).batch( - cast("list", prompts), - {"callbacks": callbacks}, - ) - generations: list[list[Generation]] = [] - for res in results: - if isinstance(res, BaseMessage): - generations.append([ChatGeneration(message=res)]) - else: - generations.append([Generation(text=res)]) - return LLMResult(generations=generations) - - async def agenerate( - self, - input_list: list[dict[str, Any]], - run_manager: AsyncCallbackManagerForChainRun | None = None, - ) -> LLMResult: - """Generate LLM result from inputs.""" - prompts, stop = await self.aprep_prompts(input_list, run_manager=run_manager) - callbacks = run_manager.get_child() if run_manager else None - if isinstance(self.llm, BaseLanguageModel): - return await self.llm.agenerate_prompt( - prompts, - stop, - callbacks=callbacks, - **self.llm_kwargs, - ) - results = await self.llm.bind(stop=stop, **self.llm_kwargs).abatch( - cast("list", prompts), - {"callbacks": callbacks}, - ) - generations: list[list[Generation]] = [] - for res in results: - if isinstance(res, BaseMessage): - generations.append([ChatGeneration(message=res)]) - else: - generations.append([Generation(text=res)]) - return LLMResult(generations=generations) - - def prep_prompts( - self, - input_list: list[dict[str, Any]], - run_manager: CallbackManagerForChainRun | None = None, - ) -> tuple[list[PromptValue], list[str] | None]: - """Prepare prompts from inputs.""" - stop = None - if len(input_list) == 0: - return [], stop - if "stop" in input_list[0]: - stop = input_list[0]["stop"] - prompts = [] - for inputs in input_list: - selected_inputs = {k: inputs[k] for k in self.prompt.input_variables} - prompt = self.prompt.format_prompt(**selected_inputs) - _colored_text = get_colored_text(prompt.to_string(), "green") - _text = "Prompt after formatting:\n" + _colored_text - if run_manager: - run_manager.on_text(_text, end="\n", verbose=self.verbose) - if "stop" in inputs and inputs["stop"] != stop: - msg = "If `stop` is present in any inputs, should be present in all." - raise ValueError(msg) - prompts.append(prompt) - return prompts, stop - - async def aprep_prompts( - self, - input_list: list[dict[str, Any]], - run_manager: AsyncCallbackManagerForChainRun | None = None, - ) -> tuple[list[PromptValue], list[str] | None]: - """Prepare prompts from inputs.""" - stop = None - if len(input_list) == 0: - return [], stop - if "stop" in input_list[0]: - stop = input_list[0]["stop"] - prompts = [] - for inputs in input_list: - selected_inputs = {k: inputs[k] for k in self.prompt.input_variables} - prompt = self.prompt.format_prompt(**selected_inputs) - _colored_text = get_colored_text(prompt.to_string(), "green") - _text = "Prompt after formatting:\n" + _colored_text - if run_manager: - await run_manager.on_text(_text, end="\n", verbose=self.verbose) - if "stop" in inputs and inputs["stop"] != stop: - msg = "If `stop` is present in any inputs, should be present in all." - raise ValueError(msg) - prompts.append(prompt) - return prompts, stop - - def apply( - self, - input_list: list[dict[str, Any]], - callbacks: Callbacks = None, - ) -> list[dict[str, str]]: - """Utilize the LLM generate method for speed gains.""" - callback_manager = CallbackManager.configure( - callbacks, - self.callbacks, - self.verbose, - ) - run_manager = callback_manager.on_chain_start( - None, - {"input_list": input_list}, - name=self.get_name(), - ) - try: - response = self.generate(input_list, run_manager=run_manager) - except BaseException as e: - run_manager.on_chain_error(e) - raise - outputs = self.create_outputs(response) - run_manager.on_chain_end({"outputs": outputs}) - return outputs - - async def aapply( - self, - input_list: list[dict[str, Any]], - callbacks: Callbacks = None, - ) -> list[dict[str, str]]: - """Utilize the LLM generate method for speed gains.""" - callback_manager = AsyncCallbackManager.configure( - callbacks, - self.callbacks, - self.verbose, - ) - run_manager = await callback_manager.on_chain_start( - None, - {"input_list": input_list}, - name=self.get_name(), - ) - try: - response = await self.agenerate(input_list, run_manager=run_manager) - except BaseException as e: - await run_manager.on_chain_error(e) - raise - outputs = self.create_outputs(response) - await run_manager.on_chain_end({"outputs": outputs}) - return outputs - - @property - def _run_output_key(self) -> str: - return self.output_key - - def create_outputs(self, llm_result: LLMResult) -> list[dict[str, Any]]: - """Create outputs from response.""" - result = [ - # Get the text of the top generated string. - { - self.output_key: self.output_parser.parse_result(generation), - "full_generation": generation, - } - for generation in llm_result.generations - ] - if self.return_final_only: - result = [{self.output_key: r[self.output_key]} for r in result] - return result - - async def _acall( - self, - inputs: dict[str, Any], - run_manager: AsyncCallbackManagerForChainRun | None = None, - ) -> dict[str, str]: - response = await self.agenerate([inputs], run_manager=run_manager) - return self.create_outputs(response)[0] - - def predict(self, callbacks: Callbacks = None, **kwargs: Any) -> str: - """Format prompt with kwargs and pass to LLM. - - Args: - callbacks: Callbacks to pass to LLMChain - **kwargs: Keys to pass to prompt template. - - Returns: - Completion from LLM. - - Example: - .. code-block:: python - - completion = llm.predict(adjective="funny") - - """ - return self(kwargs, callbacks=callbacks)[self.output_key] - - async def apredict(self, callbacks: Callbacks = None, **kwargs: Any) -> str: - """Format prompt with kwargs and pass to LLM. - - Args: - callbacks: Callbacks to pass to LLMChain - **kwargs: Keys to pass to prompt template. - - Returns: - Completion from LLM. - - Example: - .. code-block:: python - - completion = llm.predict(adjective="funny") - - """ - return (await self.acall(kwargs, callbacks=callbacks))[self.output_key] - - def predict_and_parse( - self, - callbacks: Callbacks = None, - **kwargs: Any, - ) -> str | list[str] | dict[str, Any]: - """Call predict and then parse the results.""" - warnings.warn( - "The predict_and_parse method is deprecated, " - "instead pass an output parser directly to LLMChain.", - stacklevel=2, - ) - result = self.predict(callbacks=callbacks, **kwargs) - if self.prompt.output_parser is not None: - return self.prompt.output_parser.parse(result) - return result - - async def apredict_and_parse( - self, - callbacks: Callbacks = None, - **kwargs: Any, - ) -> str | list[str] | dict[str, str]: - """Call apredict and then parse the results.""" - warnings.warn( - "The apredict_and_parse method is deprecated, " - "instead pass an output parser directly to LLMChain.", - stacklevel=2, - ) - result = await self.apredict(callbacks=callbacks, **kwargs) - if self.prompt.output_parser is not None: - return self.prompt.output_parser.parse(result) - return result - - def apply_and_parse( - self, - input_list: list[dict[str, Any]], - callbacks: Callbacks = None, - ) -> Sequence[str | list[str] | dict[str, str]]: - """Call apply and then parse the results.""" - warnings.warn( - "The apply_and_parse method is deprecated, " - "instead pass an output parser directly to LLMChain.", - stacklevel=2, - ) - result = self.apply(input_list, callbacks=callbacks) - return self._parse_generation(result) - - def _parse_generation( - self, - generation: list[dict[str, str]], - ) -> Sequence[str | list[str] | dict[str, str]]: - if self.prompt.output_parser is not None: - return [ - self.prompt.output_parser.parse(res[self.output_key]) - for res in generation - ] - return generation - - async def aapply_and_parse( - self, - input_list: list[dict[str, Any]], - callbacks: Callbacks = None, - ) -> Sequence[str | list[str] | dict[str, str]]: - """Call apply and then parse the results.""" - warnings.warn( - "The aapply_and_parse method is deprecated, " - "instead pass an output parser directly to LLMChain.", - stacklevel=2, - ) - result = await self.aapply(input_list, callbacks=callbacks) - return self._parse_generation(result) - - @property - def _chain_type(self) -> str: - return "llm_chain" - - @classmethod - def from_string(cls, llm: BaseLanguageModel, template: str) -> LLMChain: - """Create LLMChain from LLM and template.""" - prompt_template = PromptTemplate.from_template(template) - return cls(llm=llm, prompt=prompt_template) - - def _get_num_tokens(self, text: str) -> int: - return _get_language_model(self.llm).get_num_tokens(text) - - -def _get_language_model(llm_like: Runnable) -> BaseLanguageModel: - if isinstance(llm_like, BaseLanguageModel): - return llm_like - if isinstance(llm_like, RunnableBinding): - return _get_language_model(llm_like.bound) - if isinstance(llm_like, RunnableWithFallbacks): - return _get_language_model(llm_like.runnable) - if isinstance(llm_like, (RunnableBranch, DynamicRunnable)): - return _get_language_model(llm_like.default) - msg = ( - f"Unable to extract BaseLanguageModel from llm_like object of type " - f"{type(llm_like)}" - ) - raise ValueError(msg) diff --git a/libs/langchain/langchain_classic/chains/loading.py b/libs/langchain/langchain_classic/chains/loading.py index bb926081cf6ba..e56ff550d4fe6 100644 --- a/libs/langchain/langchain_classic/chains/loading.py +++ b/libs/langchain/langchain_classic/chains/loading.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any import yaml -from langchain_core._api import deprecated from langchain_core.prompts.loading import ( _load_output_parser, load_prompt, @@ -674,49 +673,6 @@ def _load_llm_requests_chain(config: dict, **kwargs: Any) -> LLMRequestsChain: } -@deprecated( - since="0.2.13", - message=( - "This function is deprecated and will be removed in langchain 1.0. " - "At that point chains must be imported from their respective modules." - ), - removal="1.0", -) -def load_chain_from_config(config: dict, **kwargs: Any) -> Chain: - """Load chain from Config Dict.""" - if "_type" not in config: - msg = "Must specify a chain Type in config" - raise ValueError(msg) - config_type = config.pop("_type") - - if config_type not in type_to_loader_dict: - msg = f"Loading {config_type} chain not supported" - raise ValueError(msg) - - chain_loader = type_to_loader_dict[config_type] - return chain_loader(config, **kwargs) - - -@deprecated( - since="0.2.13", - message=( - "This function is deprecated and will be removed in langchain 1.0. " - "At that point chains must be imported from their respective modules." - ), - removal="1.0", -) -def load_chain(path: str | Path, **kwargs: Any) -> Chain: - """Unified method for loading a chain from LangChainHub or local fs.""" - if isinstance(path, str) and path.startswith("lc://"): - msg = ( - "Loading from the deprecated github-based Hub is no longer supported. " - "Please use the new LangChain Hub at https://smith.langchain.com/hub " - "instead." - ) - raise RuntimeError(msg) - return _load_chain_from_file(path, **kwargs) - - def _load_chain_from_file(file: str | Path, **kwargs: Any) -> Chain: """Load chain from file.""" # Convert file to Path object. diff --git a/libs/langchain/langchain_classic/chains/mapreduce.py b/libs/langchain/langchain_classic/chains/mapreduce.py deleted file mode 100644 index 81dc973190656..0000000000000 --- a/libs/langchain/langchain_classic/chains/mapreduce.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Map-reduce chain. - -Splits up a document, sends the smaller parts to the LLM with one prompt, -then combines the results with another one. -""" - -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any - -from langchain_core._api import deprecated -from langchain_core.callbacks import CallbackManagerForChainRun, Callbacks -from langchain_core.documents import Document -from langchain_core.language_models import BaseLanguageModel -from langchain_core.prompts import BasePromptTemplate -from langchain_text_splitters import TextSplitter -from pydantic import ConfigDict - -from langchain_classic.chains import ReduceDocumentsChain -from langchain_classic.chains.base import Chain -from langchain_classic.chains.combine_documents.base import BaseCombineDocumentsChain -from langchain_classic.chains.combine_documents.map_reduce import ( - MapReduceDocumentsChain, -) -from langchain_classic.chains.combine_documents.stuff import StuffDocumentsChain -from langchain_classic.chains.llm import LLMChain - - -@deprecated( - since="0.2.13", - removal="1.0", - message=( - "Refer to migration guide here for a recommended implementation using " - "LangGraph: https://python.langchain.com/docs/versions/migrating_chains/map_reduce_chain/" - ". See also LangGraph guides for map-reduce: " - "https://langchain-ai.github.io/langgraph/how-tos/map-reduce/." - ), -) -class MapReduceChain(Chain): - """Map-reduce chain.""" - - combine_documents_chain: BaseCombineDocumentsChain - """Chain to use to combine documents.""" - text_splitter: TextSplitter - """Text splitter to use.""" - input_key: str = "input_text" #: :meta private: - output_key: str = "output_text" #: :meta private: - - @classmethod - def from_params( - cls, - llm: BaseLanguageModel, - prompt: BasePromptTemplate, - text_splitter: TextSplitter, - callbacks: Callbacks = None, - combine_chain_kwargs: Mapping[str, Any] | None = None, - reduce_chain_kwargs: Mapping[str, Any] | None = None, - **kwargs: Any, - ) -> MapReduceChain: - """Construct a map-reduce chain that uses the chain for map and reduce.""" - llm_chain = LLMChain(llm=llm, prompt=prompt, callbacks=callbacks) - stuff_chain = StuffDocumentsChain( - llm_chain=llm_chain, - callbacks=callbacks, - **(reduce_chain_kwargs if reduce_chain_kwargs else {}), - ) - reduce_documents_chain = ReduceDocumentsChain( - combine_documents_chain=stuff_chain, - ) - combine_documents_chain = MapReduceDocumentsChain( - llm_chain=llm_chain, - reduce_documents_chain=reduce_documents_chain, - callbacks=callbacks, - **(combine_chain_kwargs if combine_chain_kwargs else {}), - ) - return cls( - combine_documents_chain=combine_documents_chain, - text_splitter=text_splitter, - callbacks=callbacks, - **kwargs, - ) - - model_config = ConfigDict( - arbitrary_types_allowed=True, - extra="forbid", - ) - - @property - def input_keys(self) -> list[str]: - """Expect input key. - - :meta private: - """ - return [self.input_key] - - @property - def output_keys(self) -> list[str]: - """Return output key. - - :meta private: - """ - return [self.output_key] - - def _call( - self, - inputs: dict[str, str], - run_manager: CallbackManagerForChainRun | None = None, - ) -> dict[str, str]: - _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager() - # Split the larger text into smaller chunks. - doc_text = inputs.pop(self.input_key) - texts = self.text_splitter.split_text(doc_text) - docs = [Document(page_content=text) for text in texts] - _inputs: dict[str, Any] = { - **inputs, - self.combine_documents_chain.input_key: docs, - } - outputs = self.combine_documents_chain.run( - _inputs, - callbacks=_run_manager.get_child(), - ) - return {self.output_key: outputs} diff --git a/libs/langchain/langchain_classic/chains/openai_tools/__init__.py b/libs/langchain/langchain_classic/chains/openai_tools/__init__.py deleted file mode 100644 index a83779677127e..0000000000000 --- a/libs/langchain/langchain_classic/chains/openai_tools/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from langchain_classic.chains.openai_tools.extraction import ( - create_extraction_chain_pydantic, -) - -__all__ = ["create_extraction_chain_pydantic"] diff --git a/libs/langchain/langchain_classic/chains/openai_tools/extraction.py b/libs/langchain/langchain_classic/chains/openai_tools/extraction.py deleted file mode 100644 index 36c5729cdbcbd..0000000000000 --- a/libs/langchain/langchain_classic/chains/openai_tools/extraction.py +++ /dev/null @@ -1,77 +0,0 @@ -from langchain_core._api import deprecated -from langchain_core.language_models import BaseLanguageModel -from langchain_core.output_parsers.openai_tools import PydanticToolsParser -from langchain_core.prompts import ChatPromptTemplate -from langchain_core.runnables import Runnable -from langchain_core.utils.function_calling import convert_pydantic_to_openai_function -from pydantic import BaseModel - -_EXTRACTION_TEMPLATE = """Extract and save the relevant entities mentioned \ -in the following passage together with their properties. - -If a property is not present and is not required in the function parameters, do not include it in the output.""" # noqa: E501 - - -@deprecated( - since="0.1.14", - message=( - "LangChain has introduced a method called `with_structured_output` that" - "is available on ChatModels capable of tool calling." - "You can read more about the method here: " - ". " - "Please follow our extraction use case documentation for more guidelines" - "on how to do information extraction with LLMs." - ". " - "with_structured_output does not currently support a list of pydantic schemas. " - "If this is a blocker or if you notice other issues, please provide " - "feedback here:" - "" - ), - removal="1.0", - alternative=( - """ - from pydantic import BaseModel, Field - from langchain_anthropic import ChatAnthropic - - class Joke(BaseModel): - setup: str = Field(description="The setup of the joke") - punchline: str = Field(description="The punchline to the joke") - - # Or any other chat model that supports tools. - # Please reference to to the documentation of structured_output - # to see an up to date list of which models support - # with_structured_output. - model = ChatAnthropic(model="claude-3-opus-20240229", temperature=0) - structured_llm = model.with_structured_output(Joke) - structured_llm.invoke("Tell me a joke about cats. - Make sure to call the Joke function.") - """ - ), -) -def create_extraction_chain_pydantic( - pydantic_schemas: list[type[BaseModel]] | type[BaseModel], - llm: BaseLanguageModel, - system_message: str = _EXTRACTION_TEMPLATE, -) -> Runnable: - """Creates a chain that extracts information from a passage. - - Args: - pydantic_schemas: The schema of the entities to extract. - llm: The language model to use. - system_message: The system message to use for extraction. - - Returns: - A runnable that extracts information from a passage. - """ - if not isinstance(pydantic_schemas, list): - pydantic_schemas = [pydantic_schemas] - prompt = ChatPromptTemplate.from_messages( - [ - ("system", system_message), - ("user", "{input}"), - ], - ) - functions = [convert_pydantic_to_openai_function(p) for p in pydantic_schemas] - tools = [{"type": "function", "function": d} for d in functions] - model = llm.bind(tools=tools) - return prompt | model | PydanticToolsParser(tools=pydantic_schemas) diff --git a/libs/langchain/langchain_classic/chains/qa_generation/__init__.py b/libs/langchain/langchain_classic/chains/qa_generation/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/libs/langchain/langchain_classic/chains/qa_generation/base.py b/libs/langchain/langchain_classic/chains/qa_generation/base.py deleted file mode 100644 index 365ab9645267f..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_generation/base.py +++ /dev/null @@ -1,128 +0,0 @@ -from __future__ import annotations - -import json -from typing import Any - -from langchain_core._api import deprecated -from langchain_core.callbacks import CallbackManagerForChainRun -from langchain_core.language_models import BaseLanguageModel -from langchain_core.prompts import BasePromptTemplate -from langchain_text_splitters import RecursiveCharacterTextSplitter, TextSplitter -from pydantic import Field -from typing_extensions import override - -from langchain_classic.chains.base import Chain -from langchain_classic.chains.llm import LLMChain -from langchain_classic.chains.qa_generation.prompt import PROMPT_SELECTOR - - -@deprecated( - since="0.2.7", - alternative=( - "example in API reference with more detail: " - "https://api.python.langchain.com/en/latest/chains/langchain.chains.qa_generation.base.QAGenerationChain.html" - ), - removal="1.0", -) -class QAGenerationChain(Chain): - """Base class for question-answer generation chains. - - This class is deprecated. See below for an alternative implementation. - - Advantages of this implementation include: - - - Supports async and streaming; - - Surfaces prompt and text splitter for easier customization; - - Use of JsonOutputParser supports JSONPatch operations in streaming mode, - as well as robustness to markdown. - - .. code-block:: python - - from langchain_classic.chains.qa_generation.prompt import ( - CHAT_PROMPT as prompt, - ) - - # Note: import PROMPT if using a legacy non-chat model. - from langchain_core.output_parsers import JsonOutputParser - from langchain_core.runnables import ( - RunnableLambda, - RunnableParallel, - RunnablePassthrough, - ) - from langchain_core.runnables.base import RunnableEach - from langchain_openai import ChatOpenAI - from langchain_text_splitters import RecursiveCharacterTextSplitter - - llm = ChatOpenAI() - text_splitter = RecursiveCharacterTextSplitter(chunk_overlap=500) - split_text = RunnableLambda(lambda x: text_splitter.create_documents([x])) - - chain = RunnableParallel( - text=RunnablePassthrough(), - questions=( - split_text | RunnableEach(bound=prompt | llm | JsonOutputParser()) - ), - ) - - """ - - llm_chain: LLMChain - """LLM Chain that generates responses from user input and context.""" - text_splitter: TextSplitter = Field( - default=RecursiveCharacterTextSplitter(chunk_overlap=500), - ) - """Text splitter that splits the input into chunks.""" - input_key: str = "text" - """Key of the input to the chain.""" - output_key: str = "questions" - """Key of the output of the chain.""" - k: int | None = None - """Number of questions to generate.""" - - @classmethod - def from_llm( - cls, - llm: BaseLanguageModel, - prompt: BasePromptTemplate | None = None, - **kwargs: Any, - ) -> QAGenerationChain: - """Create a QAGenerationChain from a language model. - - Args: - llm: a language model - prompt: a prompt template - **kwargs: additional arguments - - Returns: - a QAGenerationChain class - """ - _prompt = prompt or PROMPT_SELECTOR.get_prompt(llm) - chain = LLMChain(llm=llm, prompt=_prompt) - return cls(llm_chain=chain, **kwargs) - - @property - def _chain_type(self) -> str: - raise NotImplementedError - - @property - @override - def input_keys(self) -> list[str]: - return [self.input_key] - - @property - @override - def output_keys(self) -> list[str]: - return [self.output_key] - - def _call( - self, - inputs: dict[str, Any], - run_manager: CallbackManagerForChainRun | None = None, - ) -> dict[str, list]: - docs = self.text_splitter.create_documents([inputs[self.input_key]]) - results = self.llm_chain.generate( - [{"text": d.page_content} for d in docs], - run_manager=run_manager, - ) - qa = [json.loads(res[0].text) for res in results.generations] - return {self.output_key: qa} diff --git a/libs/langchain/langchain_classic/chains/qa_generation/prompt.py b/libs/langchain/langchain_classic/chains/qa_generation/prompt.py deleted file mode 100644 index 85093c0c15317..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_generation/prompt.py +++ /dev/null @@ -1,53 +0,0 @@ -from langchain_core.prompts.chat import ( - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -) -from langchain_core.prompts.prompt import PromptTemplate - -from langchain_classic.chains.prompt_selector import ( - ConditionalPromptSelector, - is_chat_model, -) - -templ1 = """You are a smart assistant designed to help high school teachers come up with reading comprehension questions. -Given a piece of text, you must come up with a question and answer pair that can be used to test a student's reading comprehension abilities. -When coming up with this question/answer pair, you must respond in the following format: -``` -{{ - "question": "$YOUR_QUESTION_HERE", - "answer": "$THE_ANSWER_HERE" -}} -``` - -Everything between the ``` must be valid json. -""" # noqa: E501 -templ2 = """Please come up with a question/answer pair, in the specified JSON format, for the following text: ----------------- -{text}""" # noqa: E501 -CHAT_PROMPT = ChatPromptTemplate.from_messages( - [ - SystemMessagePromptTemplate.from_template(templ1), - HumanMessagePromptTemplate.from_template(templ2), - ] -) -templ = """You are a smart assistant designed to help high school teachers come up with reading comprehension questions. -Given a piece of text, you must come up with a question and answer pair that can be used to test a student's reading comprehension abilities. -When coming up with this question/answer pair, you must respond in the following format: -``` -{{ - "question": "$YOUR_QUESTION_HERE", - "answer": "$THE_ANSWER_HERE" -}} -``` - -Everything between the ``` must be valid json. - -Please come up with a question/answer pair, in the specified JSON format, for the following text: ----------------- -{text}""" # noqa: E501 -PROMPT = PromptTemplate.from_template(templ) - -PROMPT_SELECTOR = ConditionalPromptSelector( - default_prompt=PROMPT, conditionals=[(is_chat_model, CHAT_PROMPT)] -) diff --git a/libs/langchain/langchain_classic/chains/qa_with_sources/__init__.py b/libs/langchain/langchain_classic/chains/qa_with_sources/__init__.py deleted file mode 100644 index 244220a22de22..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_with_sources/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Load question answering with sources chains.""" - -from langchain_classic.chains.qa_with_sources.loading import load_qa_with_sources_chain - -__all__ = ["load_qa_with_sources_chain"] diff --git a/libs/langchain/langchain_classic/chains/qa_with_sources/base.py b/libs/langchain/langchain_classic/chains/qa_with_sources/base.py deleted file mode 100644 index 30966c177c21a..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_with_sources/base.py +++ /dev/null @@ -1,268 +0,0 @@ -"""Question answering with sources over documents.""" - -from __future__ import annotations - -import inspect -import re -from abc import ABC, abstractmethod -from typing import Any - -from langchain_core._api import deprecated -from langchain_core.callbacks import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) -from langchain_core.documents import Document -from langchain_core.language_models import BaseLanguageModel -from langchain_core.prompts import BasePromptTemplate -from pydantic import ConfigDict, model_validator -from typing_extensions import override - -from langchain_classic.chains import ReduceDocumentsChain -from langchain_classic.chains.base import Chain -from langchain_classic.chains.combine_documents.base import BaseCombineDocumentsChain -from langchain_classic.chains.combine_documents.map_reduce import ( - MapReduceDocumentsChain, -) -from langchain_classic.chains.combine_documents.stuff import StuffDocumentsChain -from langchain_classic.chains.llm import LLMChain -from langchain_classic.chains.qa_with_sources.loading import load_qa_with_sources_chain -from langchain_classic.chains.qa_with_sources.map_reduce_prompt import ( - COMBINE_PROMPT, - EXAMPLE_PROMPT, - QUESTION_PROMPT, -) - - -@deprecated( - since="0.2.13", - removal="1.0", - message=( - "This class is deprecated. Refer to this guide on retrieval and question " - "answering with sources: " - "https://python.langchain.com/docs/how_to/qa_sources/" - ), -) -class BaseQAWithSourcesChain(Chain, ABC): - """Question answering chain with sources over documents.""" - - combine_documents_chain: BaseCombineDocumentsChain - """Chain to use to combine documents.""" - question_key: str = "question" #: :meta private: - input_docs_key: str = "docs" #: :meta private: - answer_key: str = "answer" #: :meta private: - sources_answer_key: str = "sources" #: :meta private: - return_source_documents: bool = False - """Return the source documents.""" - - @classmethod - def from_llm( - cls, - llm: BaseLanguageModel, - document_prompt: BasePromptTemplate = EXAMPLE_PROMPT, - question_prompt: BasePromptTemplate = QUESTION_PROMPT, - combine_prompt: BasePromptTemplate = COMBINE_PROMPT, - **kwargs: Any, - ) -> BaseQAWithSourcesChain: - """Construct the chain from an LLM.""" - llm_question_chain = LLMChain(llm=llm, prompt=question_prompt) - llm_combine_chain = LLMChain(llm=llm, prompt=combine_prompt) - combine_results_chain = StuffDocumentsChain( - llm_chain=llm_combine_chain, - document_prompt=document_prompt, - document_variable_name="summaries", - ) - reduce_documents_chain = ReduceDocumentsChain( - combine_documents_chain=combine_results_chain, - ) - combine_documents_chain = MapReduceDocumentsChain( - llm_chain=llm_question_chain, - reduce_documents_chain=reduce_documents_chain, - document_variable_name="context", - ) - return cls( - combine_documents_chain=combine_documents_chain, - **kwargs, - ) - - @classmethod - def from_chain_type( - cls, - llm: BaseLanguageModel, - chain_type: str = "stuff", - chain_type_kwargs: dict | None = None, - **kwargs: Any, - ) -> BaseQAWithSourcesChain: - """Load chain from chain type.""" - _chain_kwargs = chain_type_kwargs or {} - combine_documents_chain = load_qa_with_sources_chain( - llm, - chain_type=chain_type, - **_chain_kwargs, - ) - return cls(combine_documents_chain=combine_documents_chain, **kwargs) - - model_config = ConfigDict( - arbitrary_types_allowed=True, - extra="forbid", - ) - - @property - def input_keys(self) -> list[str]: - """Expect input key. - - :meta private: - """ - return [self.question_key] - - @property - def output_keys(self) -> list[str]: - """Return output key. - - :meta private: - """ - _output_keys = [self.answer_key, self.sources_answer_key] - if self.return_source_documents: - _output_keys = [*_output_keys, "source_documents"] - return _output_keys - - @model_validator(mode="before") - @classmethod - def validate_naming(cls, values: dict) -> Any: - """Fix backwards compatibility in naming.""" - if "combine_document_chain" in values: - values["combine_documents_chain"] = values.pop("combine_document_chain") - return values - - def _split_sources(self, answer: str) -> tuple[str, str]: - """Split sources from answer.""" - if re.search(r"SOURCES?:", answer, re.IGNORECASE): - answer, sources = re.split( - r"SOURCES?:|QUESTION:\s", - answer, - flags=re.IGNORECASE, - )[:2] - sources = re.split(r"\n", sources)[0].strip() - else: - sources = "" - return answer, sources - - @abstractmethod - def _get_docs( - self, - inputs: dict[str, Any], - *, - run_manager: CallbackManagerForChainRun, - ) -> list[Document]: - """Get docs to run questioning over.""" - - def _call( - self, - inputs: dict[str, Any], - run_manager: CallbackManagerForChainRun | None = None, - ) -> dict[str, str]: - _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager() - accepts_run_manager = ( - "run_manager" in inspect.signature(self._get_docs).parameters - ) - if accepts_run_manager: - docs = self._get_docs(inputs, run_manager=_run_manager) - else: - docs = self._get_docs(inputs) # type: ignore[call-arg] - - answer = self.combine_documents_chain.run( - input_documents=docs, - callbacks=_run_manager.get_child(), - **inputs, - ) - answer, sources = self._split_sources(answer) - result: dict[str, Any] = { - self.answer_key: answer, - self.sources_answer_key: sources, - } - if self.return_source_documents: - result["source_documents"] = docs - return result - - @abstractmethod - async def _aget_docs( - self, - inputs: dict[str, Any], - *, - run_manager: AsyncCallbackManagerForChainRun, - ) -> list[Document]: - """Get docs to run questioning over.""" - - async def _acall( - self, - inputs: dict[str, Any], - run_manager: AsyncCallbackManagerForChainRun | None = None, - ) -> dict[str, Any]: - _run_manager = run_manager or AsyncCallbackManagerForChainRun.get_noop_manager() - accepts_run_manager = ( - "run_manager" in inspect.signature(self._aget_docs).parameters - ) - if accepts_run_manager: - docs = await self._aget_docs(inputs, run_manager=_run_manager) - else: - docs = await self._aget_docs(inputs) # type: ignore[call-arg] - answer = await self.combine_documents_chain.arun( - input_documents=docs, - callbacks=_run_manager.get_child(), - **inputs, - ) - answer, sources = self._split_sources(answer) - result: dict[str, Any] = { - self.answer_key: answer, - self.sources_answer_key: sources, - } - if self.return_source_documents: - result["source_documents"] = docs - return result - - -@deprecated( - since="0.2.13", - removal="1.0", - message=( - "This class is deprecated. Refer to this guide on retrieval and question " - "answering with sources: " - "https://python.langchain.com/docs/how_to/qa_sources/" - ), -) -class QAWithSourcesChain(BaseQAWithSourcesChain): - """Question answering with sources over documents.""" - - input_docs_key: str = "docs" #: :meta private: - - @property - def input_keys(self) -> list[str]: - """Expect input key. - - :meta private: - """ - return [self.input_docs_key, self.question_key] - - @override - def _get_docs( - self, - inputs: dict[str, Any], - *, - run_manager: CallbackManagerForChainRun, - ) -> list[Document]: - """Get docs to run questioning over.""" - return inputs.pop(self.input_docs_key) - - @override - async def _aget_docs( - self, - inputs: dict[str, Any], - *, - run_manager: AsyncCallbackManagerForChainRun, - ) -> list[Document]: - """Get docs to run questioning over.""" - return inputs.pop(self.input_docs_key) - - @property - def _chain_type(self) -> str: - return "qa_with_sources_chain" diff --git a/libs/langchain/langchain_classic/chains/qa_with_sources/loading.py b/libs/langchain/langchain_classic/chains/qa_with_sources/loading.py deleted file mode 100644 index 2589309170f7a..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_with_sources/loading.py +++ /dev/null @@ -1,214 +0,0 @@ -"""Load question answering with sources chains.""" - -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, Protocol - -from langchain_core._api import deprecated -from langchain_core.language_models import BaseLanguageModel -from langchain_core.prompts import BasePromptTemplate - -from langchain_classic.chains.combine_documents.base import BaseCombineDocumentsChain -from langchain_classic.chains.combine_documents.map_reduce import ( - MapReduceDocumentsChain, -) -from langchain_classic.chains.combine_documents.map_rerank import ( - MapRerankDocumentsChain, -) -from langchain_classic.chains.combine_documents.reduce import ReduceDocumentsChain -from langchain_classic.chains.combine_documents.refine import RefineDocumentsChain -from langchain_classic.chains.combine_documents.stuff import StuffDocumentsChain -from langchain_classic.chains.llm import LLMChain -from langchain_classic.chains.qa_with_sources import ( - map_reduce_prompt, - refine_prompts, - stuff_prompt, -) -from langchain_classic.chains.question_answering.map_rerank_prompt import ( - PROMPT as MAP_RERANK_PROMPT, -) - - -class LoadingCallable(Protocol): - """Interface for loading the combine documents chain.""" - - def __call__( - self, - llm: BaseLanguageModel, - **kwargs: Any, - ) -> BaseCombineDocumentsChain: - """Callable to load the combine documents chain.""" - - -def _load_map_rerank_chain( - llm: BaseLanguageModel, - *, - prompt: BasePromptTemplate = MAP_RERANK_PROMPT, - verbose: bool = False, - document_variable_name: str = "context", - rank_key: str = "score", - answer_key: str = "answer", - **kwargs: Any, -) -> MapRerankDocumentsChain: - llm_chain = LLMChain(llm=llm, prompt=prompt, verbose=verbose) - return MapRerankDocumentsChain( - llm_chain=llm_chain, - rank_key=rank_key, - answer_key=answer_key, - document_variable_name=document_variable_name, - **kwargs, - ) - - -def _load_stuff_chain( - llm: BaseLanguageModel, - *, - prompt: BasePromptTemplate = stuff_prompt.PROMPT, - document_prompt: BasePromptTemplate = stuff_prompt.EXAMPLE_PROMPT, - document_variable_name: str = "summaries", - verbose: bool | None = None, - **kwargs: Any, -) -> StuffDocumentsChain: - llm_chain = LLMChain(llm=llm, prompt=prompt, verbose=verbose) - return StuffDocumentsChain( - llm_chain=llm_chain, - document_variable_name=document_variable_name, - document_prompt=document_prompt, - verbose=verbose, - **kwargs, - ) - - -def _load_map_reduce_chain( - llm: BaseLanguageModel, - *, - question_prompt: BasePromptTemplate = map_reduce_prompt.QUESTION_PROMPT, - combine_prompt: BasePromptTemplate = map_reduce_prompt.COMBINE_PROMPT, - document_prompt: BasePromptTemplate = map_reduce_prompt.EXAMPLE_PROMPT, - combine_document_variable_name: str = "summaries", - map_reduce_document_variable_name: str = "context", - collapse_prompt: BasePromptTemplate | None = None, - reduce_llm: BaseLanguageModel | None = None, - collapse_llm: BaseLanguageModel | None = None, - verbose: bool | None = None, - token_max: int = 3000, - **kwargs: Any, -) -> MapReduceDocumentsChain: - map_chain = LLMChain(llm=llm, prompt=question_prompt, verbose=verbose) - _reduce_llm = reduce_llm or llm - reduce_chain = LLMChain(llm=_reduce_llm, prompt=combine_prompt, verbose=verbose) - combine_documents_chain = StuffDocumentsChain( - llm_chain=reduce_chain, - document_variable_name=combine_document_variable_name, - document_prompt=document_prompt, - verbose=verbose, - ) - if collapse_prompt is None: - collapse_chain = None - if collapse_llm is not None: - msg = ( - "collapse_llm provided, but collapse_prompt was not: please " - "provide one or stop providing collapse_llm." - ) - raise ValueError(msg) - else: - _collapse_llm = collapse_llm or llm - collapse_chain = StuffDocumentsChain( - llm_chain=LLMChain( - llm=_collapse_llm, - prompt=collapse_prompt, - verbose=verbose, - ), - document_variable_name=combine_document_variable_name, - document_prompt=document_prompt, - ) - reduce_documents_chain = ReduceDocumentsChain( - combine_documents_chain=combine_documents_chain, - collapse_documents_chain=collapse_chain, - token_max=token_max, - verbose=verbose, - ) - return MapReduceDocumentsChain( - llm_chain=map_chain, - reduce_documents_chain=reduce_documents_chain, - document_variable_name=map_reduce_document_variable_name, - verbose=verbose, - **kwargs, - ) - - -def _load_refine_chain( - llm: BaseLanguageModel, - *, - question_prompt: BasePromptTemplate = refine_prompts.DEFAULT_TEXT_QA_PROMPT, - refine_prompt: BasePromptTemplate = refine_prompts.DEFAULT_REFINE_PROMPT, - document_prompt: BasePromptTemplate = refine_prompts.EXAMPLE_PROMPT, - document_variable_name: str = "context_str", - initial_response_name: str = "existing_answer", - refine_llm: BaseLanguageModel | None = None, - verbose: bool | None = None, - **kwargs: Any, -) -> RefineDocumentsChain: - initial_chain = LLMChain(llm=llm, prompt=question_prompt, verbose=verbose) - _refine_llm = refine_llm or llm - refine_chain = LLMChain(llm=_refine_llm, prompt=refine_prompt, verbose=verbose) - return RefineDocumentsChain( - initial_llm_chain=initial_chain, - refine_llm_chain=refine_chain, - document_variable_name=document_variable_name, - initial_response_name=initial_response_name, - document_prompt=document_prompt, - verbose=verbose, - **kwargs, - ) - - -@deprecated( - since="0.2.13", - removal="1.0", - message=( - "This function is deprecated. Refer to this guide on retrieval and question " - "answering with sources: " - "https://python.langchain.com/docs/how_to/qa_sources/" - "\nSee also the following migration guides for replacements " - "based on `chain_type`:\n" - "stuff: https://python.langchain.com/docs/versions/migrating_chains/stuff_docs_chain\n" - "map_reduce: https://python.langchain.com/docs/versions/migrating_chains/map_reduce_chain\n" - "refine: https://python.langchain.com/docs/versions/migrating_chains/refine_chain\n" - "map_rerank: https://python.langchain.com/docs/versions/migrating_chains/map_rerank_docs_chain\n" - ), -) -def load_qa_with_sources_chain( - llm: BaseLanguageModel, - chain_type: str = "stuff", - verbose: bool | None = None, # noqa: FBT001 - **kwargs: Any, -) -> BaseCombineDocumentsChain: - """Load a question answering with sources chain. - - Args: - llm: Language Model to use in the chain. - chain_type: Type of document combining chain to use. Should be one of "stuff", - "map_reduce", "refine" and "map_rerank". - verbose: Whether chains should be run in verbose mode or not. Note that this - applies to all chains that make up the final chain. - **kwargs: Additional keyword arguments. - - Returns: - A chain to use for question answering with sources. - """ - loader_mapping: Mapping[str, LoadingCallable] = { - "stuff": _load_stuff_chain, - "map_reduce": _load_map_reduce_chain, - "refine": _load_refine_chain, - "map_rerank": _load_map_rerank_chain, - } - if chain_type not in loader_mapping: - msg = ( - f"Got unsupported chain type: {chain_type}. " - f"Should be one of {loader_mapping.keys()}" - ) - raise ValueError(msg) - _func: LoadingCallable = loader_mapping[chain_type] - return _func(llm, verbose=verbose, **kwargs) diff --git a/libs/langchain/langchain_classic/chains/qa_with_sources/map_reduce_prompt.py b/libs/langchain/langchain_classic/chains/qa_with_sources/map_reduce_prompt.py deleted file mode 100644 index 0b4304721fec5..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_with_sources/map_reduce_prompt.py +++ /dev/null @@ -1,54 +0,0 @@ -from langchain_core.prompts import PromptTemplate - -question_prompt_template = """Use the following portion of a long document to see if any of the text is relevant to answer the question. -Return any relevant text verbatim. -{context} -Question: {question} -Relevant text, if any:""" # noqa: E501 -QUESTION_PROMPT = PromptTemplate( - template=question_prompt_template, input_variables=["context", "question"] -) - -combine_prompt_template = """Given the following extracted parts of a long document and a question, create a final answer with references ("SOURCES"). -If you don't know the answer, just say that you don't know. Don't try to make up an answer. -ALWAYS return a "SOURCES" part in your answer. - -QUESTION: Which state/country's law governs the interpretation of the contract? -========= -Content: This Agreement is governed by English law and the parties submit to the exclusive jurisdiction of the English courts in relation to any dispute (contractual or non-contractual) concerning this Agreement save that either party may apply to any court for an injunction or other relief to protect its Intellectual Property Rights. -Source: 28-pl -Content: No Waiver. Failure or delay in exercising any right or remedy under this Agreement shall not constitute a waiver of such (or any other) right or remedy.\n\n11.7 Severability. The invalidity, illegality or unenforceability of any term (or part of a term) of this Agreement shall not affect the continuation in force of the remainder of the term (if any) and this Agreement.\n\n11.8 No Agency. Except as expressly stated otherwise, nothing in this Agreement shall create an agency, partnership or joint venture of any kind between the parties.\n\n11.9 No Third-Party Beneficiaries. -Source: 30-pl -Content: (b) if Google believes, in good faith, that the Distributor has violated or caused Google to violate any Anti-Bribery Laws (as defined in Clause 8.5) or that such a violation is reasonably likely to occur, -Source: 4-pl -========= -FINAL ANSWER: This Agreement is governed by English law. -SOURCES: 28-pl - -QUESTION: What did the president say about Michael Jackson? -========= -Content: Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \n\nLast year COVID-19 kept us apart. This year we are finally together again. \n\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \n\nWith a duty to one another to the American people to the Constitution. \n\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \n\nSix days ago, Russia's Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \n\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \n\nHe met the Ukrainian people. \n\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. \n\nGroups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland. -Source: 0-pl -Content: And we won't stop. \n\nWe have lost so much to COVID-19. Time with one another. And worst of all, so much loss of life. \n\nLet's use this moment to reset. Let's stop looking at COVID-19 as a partisan dividing line and see it for what it is: A God-awful disease. \n\nLet's stop seeing each other as enemies, and start seeing each other for who we really are: Fellow Americans. \n\nWe can't change how divided we've been. But we can change how we move forward—on COVID-19 and other issues we must face together. \n\nI recently visited the New York City Police Department days after the funerals of Officer Wilbert Mora and his partner, Officer Jason Rivera. \n\nThey were responding to a 9-1-1 call when a man shot and killed them with a stolen gun. \n\nOfficer Mora was 27 years old. \n\nOfficer Rivera was 22. \n\nBoth Dominican Americans who'd grown up on the same streets they later chose to patrol as police officers. \n\nI spoke with their families and told them that we are forever in debt for their sacrifice, and we will carry on their mission to restore the trust and safety every community deserves. -Source: 24-pl -Content: And a proud Ukrainian people, who have known 30 years of independence, have repeatedly shown that they will not tolerate anyone who tries to take their country backwards. \n\nTo all Americans, I will be honest with you, as I've always promised. A Russian dictator, invading a foreign country, has costs around the world. \n\nAnd I'm taking robust action to make sure the pain of our sanctions is targeted at Russia's economy. And I will use every tool at our disposal to protect American businesses and consumers. \n\nTonight, I can announce that the United States has worked with 30 other countries to release 60 Million barrels of oil from reserves around the world. \n\nAmerica will lead that effort, releasing 30 Million barrels from our own Strategic Petroleum Reserve. And we stand ready to do more if necessary, unified with our allies. \n\nThese steps will help blunt gas prices here at home. And I know the news about what's happening can seem alarming. \n\nBut I want you to know that we are going to be okay. -Source: 5-pl -Content: More support for patients and families. \n\nTo get there, I call on Congress to fund ARPA-H, the Advanced Research Projects Agency for Health. \n\nIt's based on DARPA—the Defense Department project that led to the Internet, GPS, and so much more. \n\nARPA-H will have a singular purpose—to drive breakthroughs in cancer, Alzheimer's, diabetes, and more. \n\nA unity agenda for the nation. \n\nWe can do this. \n\nMy fellow Americans—tonight , we have gathered in a sacred space—the citadel of our democracy. \n\nIn this Capitol, generation after generation, Americans have debated great questions amid great strife, and have done great things. \n\nWe have fought for freedom, expanded liberty, defeated totalitarianism and terror. \n\nAnd built the strongest, freest, and most prosperous nation the world has ever known. \n\nNow is the hour. \n\nOur moment of responsibility. \n\nOur test of resolve and conscience, of history itself. \n\nIt is in this moment that our character is formed. Our purpose is found. Our future is forged. \n\nWell I know this nation. -Source: 34-pl -========= -FINAL ANSWER: The president did not mention Michael Jackson. -SOURCES: - -QUESTION: {question} -========= -{summaries} -========= -FINAL ANSWER:""" # noqa: E501 -COMBINE_PROMPT = PromptTemplate( - template=combine_prompt_template, input_variables=["summaries", "question"] -) - -EXAMPLE_PROMPT = PromptTemplate( - template="Content: {page_content}\nSource: {source}", - input_variables=["page_content", "source"], -) diff --git a/libs/langchain/langchain_classic/chains/qa_with_sources/refine_prompts.py b/libs/langchain/langchain_classic/chains/qa_with_sources/refine_prompts.py deleted file mode 100644 index 537f9e5ca607b..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_with_sources/refine_prompts.py +++ /dev/null @@ -1,37 +0,0 @@ -from langchain_core.prompts import PromptTemplate - -DEFAULT_REFINE_PROMPT_TMPL = ( - "The original question is as follows: {question}\n" - "We have provided an existing answer, including sources: {existing_answer}\n" - "We have the opportunity to refine the existing answer" - "(only if needed) with some more context below.\n" - "------------\n" - "{context_str}\n" - "------------\n" - "Given the new context, refine the original answer to better " - "answer the question. " - "If you do update it, please update the sources as well. " - "If the context isn't useful, return the original answer." -) -DEFAULT_REFINE_PROMPT = PromptTemplate( - input_variables=["question", "existing_answer", "context_str"], - template=DEFAULT_REFINE_PROMPT_TMPL, -) - - -DEFAULT_TEXT_QA_PROMPT_TMPL = ( - "Context information is below. \n" - "---------------------\n" - "{context_str}" - "\n---------------------\n" - "Given the context information and not prior knowledge, " - "answer the question: {question}\n" -) -DEFAULT_TEXT_QA_PROMPT = PromptTemplate( - input_variables=["context_str", "question"], template=DEFAULT_TEXT_QA_PROMPT_TMPL -) - -EXAMPLE_PROMPT = PromptTemplate( - template="Content: {page_content}\nSource: {source}", - input_variables=["page_content", "source"], -) diff --git a/libs/langchain/langchain_classic/chains/qa_with_sources/retrieval.py b/libs/langchain/langchain_classic/chains/qa_with_sources/retrieval.py deleted file mode 100644 index 72ff05ba968f0..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_with_sources/retrieval.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Question-answering with sources over an index.""" - -from typing import Any - -from langchain_core.callbacks import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) -from langchain_core.documents import Document -from langchain_core.retrievers import BaseRetriever -from pydantic import Field - -from langchain_classic.chains.combine_documents.stuff import StuffDocumentsChain -from langchain_classic.chains.qa_with_sources.base import BaseQAWithSourcesChain - - -class RetrievalQAWithSourcesChain(BaseQAWithSourcesChain): - """Question-answering with sources over an index.""" - - retriever: BaseRetriever = Field(exclude=True) - """Index to connect to.""" - reduce_k_below_max_tokens: bool = False - """Reduce the number of results to return from store based on tokens limit""" - max_tokens_limit: int = 3375 - """Restrict the docs to return from store based on tokens, - enforced only for StuffDocumentChain and if reduce_k_below_max_tokens is to true""" - - def _reduce_tokens_below_limit(self, docs: list[Document]) -> list[Document]: - num_docs = len(docs) - - if self.reduce_k_below_max_tokens and isinstance( - self.combine_documents_chain, - StuffDocumentsChain, - ): - tokens = [ - self.combine_documents_chain.llm_chain._get_num_tokens(doc.page_content) # noqa: SLF001 - for doc in docs - ] - token_count = sum(tokens[:num_docs]) - while token_count > self.max_tokens_limit: - num_docs -= 1 - token_count -= tokens[num_docs] - - return docs[:num_docs] - - def _get_docs( - self, - inputs: dict[str, Any], - *, - run_manager: CallbackManagerForChainRun, - ) -> list[Document]: - question = inputs[self.question_key] - docs = self.retriever.invoke( - question, - config={"callbacks": run_manager.get_child()}, - ) - return self._reduce_tokens_below_limit(docs) - - async def _aget_docs( - self, - inputs: dict[str, Any], - *, - run_manager: AsyncCallbackManagerForChainRun, - ) -> list[Document]: - question = inputs[self.question_key] - docs = await self.retriever.ainvoke( - question, - config={"callbacks": run_manager.get_child()}, - ) - return self._reduce_tokens_below_limit(docs) - - @property - def _chain_type(self) -> str: - """Return the chain type.""" - return "retrieval_qa_with_sources_chain" diff --git a/libs/langchain/langchain_classic/chains/qa_with_sources/stuff_prompt.py b/libs/langchain/langchain_classic/chains/qa_with_sources/stuff_prompt.py deleted file mode 100644 index 11e844812ae2b..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_with_sources/stuff_prompt.py +++ /dev/null @@ -1,43 +0,0 @@ -from langchain_core.prompts import PromptTemplate - -template = """Given the following extracted parts of a long document and a question, create a final answer with references ("SOURCES"). -If you don't know the answer, just say that you don't know. Don't try to make up an answer. -ALWAYS return a "SOURCES" part in your answer. - -QUESTION: Which state/country's law governs the interpretation of the contract? -========= -Content: This Agreement is governed by English law and the parties submit to the exclusive jurisdiction of the English courts in relation to any dispute (contractual or non-contractual) concerning this Agreement save that either party may apply to any court for an injunction or other relief to protect its Intellectual Property Rights. -Source: 28-pl -Content: No Waiver. Failure or delay in exercising any right or remedy under this Agreement shall not constitute a waiver of such (or any other) right or remedy.\n\n11.7 Severability. The invalidity, illegality or unenforceability of any term (or part of a term) of this Agreement shall not affect the continuation in force of the remainder of the term (if any) and this Agreement.\n\n11.8 No Agency. Except as expressly stated otherwise, nothing in this Agreement shall create an agency, partnership or joint venture of any kind between the parties.\n\n11.9 No Third-Party Beneficiaries. -Source: 30-pl -Content: (b) if Google believes, in good faith, that the Distributor has violated or caused Google to violate any Anti-Bribery Laws (as defined in Clause 8.5) or that such a violation is reasonably likely to occur, -Source: 4-pl -========= -FINAL ANSWER: This Agreement is governed by English law. -SOURCES: 28-pl - -QUESTION: What did the president say about Michael Jackson? -========= -Content: Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \n\nLast year COVID-19 kept us apart. This year we are finally together again. \n\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \n\nWith a duty to one another to the American people to the Constitution. \n\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \n\nSix days ago, Russia's Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \n\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \n\nHe met the Ukrainian people. \n\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. \n\nGroups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland. -Source: 0-pl -Content: And we won't stop. \n\nWe have lost so much to COVID-19. Time with one another. And worst of all, so much loss of life. \n\nLet's use this moment to reset. Let's stop looking at COVID-19 as a partisan dividing line and see it for what it is: A God-awful disease. \n\nLet's stop seeing each other as enemies, and start seeing each other for who we really are: Fellow Americans. \n\nWe can't change how divided we've been. But we can change how we move forward—on COVID-19 and other issues we must face together. \n\nI recently visited the New York City Police Department days after the funerals of Officer Wilbert Mora and his partner, Officer Jason Rivera. \n\nThey were responding to a 9-1-1 call when a man shot and killed them with a stolen gun. \n\nOfficer Mora was 27 years old. \n\nOfficer Rivera was 22. \n\nBoth Dominican Americans who'd grown up on the same streets they later chose to patrol as police officers. \n\nI spoke with their families and told them that we are forever in debt for their sacrifice, and we will carry on their mission to restore the trust and safety every community deserves. -Source: 24-pl -Content: And a proud Ukrainian people, who have known 30 years of independence, have repeatedly shown that they will not tolerate anyone who tries to take their country backwards. \n\nTo all Americans, I will be honest with you, as I've always promised. A Russian dictator, invading a foreign country, has costs around the world. \n\nAnd I'm taking robust action to make sure the pain of our sanctions is targeted at Russia's economy. And I will use every tool at our disposal to protect American businesses and consumers. \n\nTonight, I can announce that the United States has worked with 30 other countries to release 60 Million barrels of oil from reserves around the world. \n\nAmerica will lead that effort, releasing 30 Million barrels from our own Strategic Petroleum Reserve. And we stand ready to do more if necessary, unified with our allies. \n\nThese steps will help blunt gas prices here at home. And I know the news about what's happening can seem alarming. \n\nBut I want you to know that we are going to be okay. -Source: 5-pl -Content: More support for patients and families. \n\nTo get there, I call on Congress to fund ARPA-H, the Advanced Research Projects Agency for Health. \n\nIt's based on DARPA—the Defense Department project that led to the Internet, GPS, and so much more. \n\nARPA-H will have a singular purpose—to drive breakthroughs in cancer, Alzheimer's, diabetes, and more. \n\nA unity agenda for the nation. \n\nWe can do this. \n\nMy fellow Americans—tonight , we have gathered in a sacred space—the citadel of our democracy. \n\nIn this Capitol, generation after generation, Americans have debated great questions amid great strife, and have done great things. \n\nWe have fought for freedom, expanded liberty, defeated totalitarianism and terror. \n\nAnd built the strongest, freest, and most prosperous nation the world has ever known. \n\nNow is the hour. \n\nOur moment of responsibility. \n\nOur test of resolve and conscience, of history itself. \n\nIt is in this moment that our character is formed. Our purpose is found. Our future is forged. \n\nWell I know this nation. -Source: 34-pl -========= -FINAL ANSWER: The president did not mention Michael Jackson. -SOURCES: - -QUESTION: {question} -========= -{summaries} -========= -FINAL ANSWER:""" # noqa: E501 -PROMPT = PromptTemplate(template=template, input_variables=["summaries", "question"]) - -EXAMPLE_PROMPT = PromptTemplate( - template="Content: {page_content}\nSource: {source}", - input_variables=["page_content", "source"], -) diff --git a/libs/langchain/langchain_classic/chains/qa_with_sources/vector_db.py b/libs/langchain/langchain_classic/chains/qa_with_sources/vector_db.py deleted file mode 100644 index 6d98a891b0a23..0000000000000 --- a/libs/langchain/langchain_classic/chains/qa_with_sources/vector_db.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Question-answering with sources over a vector database.""" - -import warnings -from typing import Any - -from langchain_core.callbacks import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) -from langchain_core.documents import Document -from langchain_core.vectorstores import VectorStore -from pydantic import Field, model_validator -from typing_extensions import override - -from langchain_classic.chains.combine_documents.stuff import StuffDocumentsChain -from langchain_classic.chains.qa_with_sources.base import BaseQAWithSourcesChain - - -class VectorDBQAWithSourcesChain(BaseQAWithSourcesChain): - """Question-answering with sources over a vector database.""" - - vectorstore: VectorStore = Field(exclude=True) - """Vector Database to connect to.""" - k: int = 4 - """Number of results to return from store""" - reduce_k_below_max_tokens: bool = False - """Reduce the number of results to return from store based on tokens limit""" - max_tokens_limit: int = 3375 - """Restrict the docs to return from store based on tokens, - enforced only for StuffDocumentChain and if reduce_k_below_max_tokens is to true""" - search_kwargs: dict[str, Any] = Field(default_factory=dict) - """Extra search args.""" - - def _reduce_tokens_below_limit(self, docs: list[Document]) -> list[Document]: - num_docs = len(docs) - - if self.reduce_k_below_max_tokens and isinstance( - self.combine_documents_chain, - StuffDocumentsChain, - ): - tokens = [ - self.combine_documents_chain.llm_chain._get_num_tokens(doc.page_content) # noqa: SLF001 - for doc in docs - ] - token_count = sum(tokens[:num_docs]) - while token_count > self.max_tokens_limit: - num_docs -= 1 - token_count -= tokens[num_docs] - - return docs[:num_docs] - - @override - def _get_docs( - self, - inputs: dict[str, Any], - *, - run_manager: CallbackManagerForChainRun, - ) -> list[Document]: - question = inputs[self.question_key] - docs = self.vectorstore.similarity_search( - question, - k=self.k, - **self.search_kwargs, - ) - return self._reduce_tokens_below_limit(docs) - - async def _aget_docs( - self, - inputs: dict[str, Any], - *, - run_manager: AsyncCallbackManagerForChainRun, - ) -> list[Document]: - msg = "VectorDBQAWithSourcesChain does not support async" - raise NotImplementedError(msg) - - @model_validator(mode="before") - @classmethod - def _raise_deprecation(cls, values: dict) -> Any: - warnings.warn( - "`VectorDBQAWithSourcesChain` is deprecated - " - "please use `from langchain_classic.chains import " - "RetrievalQAWithSourcesChain`", - stacklevel=5, - ) - return values - - @property - def _chain_type(self) -> str: - return "vector_db_qa_with_sources_chain" diff --git a/libs/langchain/langchain_classic/chains/query_constructor/base.py b/libs/langchain/langchain_classic/chains/query_constructor/base.py index 902ea26b8b306..de0a304df387f 100644 --- a/libs/langchain/langchain_classic/chains/query_constructor/base.py +++ b/libs/langchain/langchain_classic/chains/query_constructor/base.py @@ -6,7 +6,6 @@ from collections.abc import Callable, Sequence from typing import Any, cast -from langchain_core._api import deprecated from langchain_core.exceptions import OutputParserException from langchain_core.language_models import BaseLanguageModel from langchain_core.output_parsers import BaseOutputParser @@ -24,7 +23,6 @@ ) from typing_extensions import override -from langchain_classic.chains.llm import LLMChain from langchain_classic.chains.query_constructor.parser import get_parser from langchain_classic.chains.query_constructor.prompt import ( DEFAULT_EXAMPLES, @@ -265,63 +263,6 @@ def get_query_constructor_prompt( ) -@deprecated( - since="0.2.13", - alternative="load_query_constructor_runnable", - removal="1.0", -) -def load_query_constructor_chain( - llm: BaseLanguageModel, - document_contents: str, - attribute_info: Sequence[AttributeInfo | dict], - examples: list | None = None, - allowed_comparators: Sequence[Comparator] = tuple(Comparator), - allowed_operators: Sequence[Operator] = tuple(Operator), - enable_limit: bool = False, # noqa: FBT001,FBT002 - schema_prompt: BasePromptTemplate | None = None, - **kwargs: Any, -) -> LLMChain: - """Load a query constructor chain. - - Args: - llm: BaseLanguageModel to use for the chain. - document_contents: The contents of the document to be queried. - attribute_info: Sequence of attributes in the document. - examples: Optional list of examples to use for the chain. - allowed_comparators: Sequence of allowed comparators. Defaults to all - Comparators. - allowed_operators: Sequence of allowed operators. Defaults to all Operators. - enable_limit: Whether to enable the limit operator. Defaults to `False`. - schema_prompt: Prompt for describing query schema. Should have string input - variables allowed_comparators and allowed_operators. - **kwargs: Arbitrary named params to pass to LLMChain. - - Returns: - A LLMChain that can be used to construct queries. - """ - prompt = get_query_constructor_prompt( - document_contents, - attribute_info, - examples=examples, - allowed_comparators=allowed_comparators, - allowed_operators=allowed_operators, - enable_limit=enable_limit, - schema_prompt=schema_prompt, - ) - allowed_attributes = [ - ainfo.name if isinstance(ainfo, AttributeInfo) else ainfo["name"] - for ainfo in attribute_info - ] - output_parser = StructuredQueryOutputParser.from_components( - allowed_comparators=allowed_comparators, - allowed_operators=allowed_operators, - allowed_attributes=allowed_attributes, - ) - # For backwards compatibility. - prompt.output_parser = output_parser - return LLMChain(llm=llm, prompt=prompt, output_parser=output_parser, **kwargs) - - def load_query_constructor_runnable( llm: BaseLanguageModel, document_contents: str, diff --git a/libs/langchain/langchain_classic/chains/question_answering/chain.py b/libs/langchain/langchain_classic/chains/question_answering/chain.py index f772517f95069..59629fc7f29aa 100644 --- a/libs/langchain/langchain_classic/chains/question_answering/chain.py +++ b/libs/langchain/langchain_classic/chains/question_answering/chain.py @@ -1,9 +1,7 @@ """Load question answering chains.""" -from collections.abc import Mapping from typing import Any, Protocol -from langchain_core._api import deprecated from langchain_core.callbacks import BaseCallbackManager, Callbacks from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import BasePromptTemplate @@ -227,58 +225,3 @@ def _load_refine_chain( callbacks=callbacks, **kwargs, ) - - -@deprecated( - since="0.2.13", - removal="1.0", - message=( - "This class is deprecated. See the following migration guides for replacements " - "based on `chain_type`:\n" - "stuff: https://python.langchain.com/docs/versions/migrating_chains/stuff_docs_chain\n" - "map_reduce: https://python.langchain.com/docs/versions/migrating_chains/map_reduce_chain\n" - "refine: https://python.langchain.com/docs/versions/migrating_chains/refine_chain\n" - "map_rerank: https://python.langchain.com/docs/versions/migrating_chains/map_rerank_docs_chain\n" - "\nSee also guides on retrieval and question-answering here: " - "https://python.langchain.com/docs/how_to/#qa-with-rag" - ), -) -def load_qa_chain( - llm: BaseLanguageModel, - chain_type: str = "stuff", - verbose: bool | None = None, # noqa: FBT001 - callback_manager: BaseCallbackManager | None = None, - **kwargs: Any, -) -> BaseCombineDocumentsChain: - """Load question answering chain. - - Args: - llm: Language Model to use in the chain. - chain_type: Type of document combining chain to use. Should be one of "stuff", - "map_reduce", "map_rerank", and "refine". - verbose: Whether chains should be run in verbose mode or not. Note that this - applies to all chains that make up the final chain. - callback_manager: Callback manager to use for the chain. - **kwargs: Additional keyword arguments. - - Returns: - A chain to use for question answering. - """ - loader_mapping: Mapping[str, LoadingCallable] = { - "stuff": _load_stuff_chain, - "map_reduce": _load_map_reduce_chain, - "refine": _load_refine_chain, - "map_rerank": _load_map_rerank_chain, - } - if chain_type not in loader_mapping: - msg = ( - f"Got unsupported chain type: {chain_type}. " - f"Should be one of {loader_mapping.keys()}" - ) - raise ValueError(msg) - return loader_mapping[chain_type]( - llm, - verbose=verbose, - callback_manager=callback_manager, - **kwargs, - ) diff --git a/libs/langchain/langchain_classic/chains/retrieval_qa/__init__.py b/libs/langchain/langchain_classic/chains/retrieval_qa/__init__.py deleted file mode 100644 index b8e4d9aa0b201..0000000000000 --- a/libs/langchain/langchain_classic/chains/retrieval_qa/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Chain for question-answering against a vector database.""" diff --git a/libs/langchain/langchain_classic/chains/retrieval_qa/base.py b/libs/langchain/langchain_classic/chains/retrieval_qa/base.py deleted file mode 100644 index 4277e89f731a1..0000000000000 --- a/libs/langchain/langchain_classic/chains/retrieval_qa/base.py +++ /dev/null @@ -1,377 +0,0 @@ -"""Chain for question-answering against a vector database.""" - -from __future__ import annotations - -import inspect -from abc import abstractmethod -from typing import Any - -from langchain_core._api import deprecated -from langchain_core.callbacks import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, - Callbacks, -) -from langchain_core.documents import Document -from langchain_core.language_models import BaseLanguageModel -from langchain_core.prompts import PromptTemplate -from langchain_core.retrievers import BaseRetriever -from langchain_core.vectorstores import VectorStore -from pydantic import ConfigDict, Field, model_validator -from typing_extensions import override - -from langchain_classic.chains.base import Chain -from langchain_classic.chains.combine_documents.base import BaseCombineDocumentsChain -from langchain_classic.chains.combine_documents.stuff import StuffDocumentsChain -from langchain_classic.chains.llm import LLMChain -from langchain_classic.chains.question_answering import load_qa_chain -from langchain_classic.chains.question_answering.stuff_prompt import PROMPT_SELECTOR - - -@deprecated( - since="0.2.13", - removal="1.0", - message=( - "This class is deprecated. Use the `create_retrieval_chain` constructor " - "instead. See migration guide here: " - "https://python.langchain.com/docs/versions/migrating_chains/retrieval_qa/" - ), -) -class BaseRetrievalQA(Chain): - """Base class for question-answering chains.""" - - combine_documents_chain: BaseCombineDocumentsChain - """Chain to use to combine the documents.""" - input_key: str = "query" #: :meta private: - output_key: str = "result" #: :meta private: - return_source_documents: bool = False - """Return the source documents or not.""" - - model_config = ConfigDict( - populate_by_name=True, - arbitrary_types_allowed=True, - extra="forbid", - ) - - @property - def input_keys(self) -> list[str]: - """Input keys. - - :meta private: - """ - return [self.input_key] - - @property - def output_keys(self) -> list[str]: - """Output keys. - - :meta private: - """ - _output_keys = [self.output_key] - if self.return_source_documents: - _output_keys = [*_output_keys, "source_documents"] - return _output_keys - - @classmethod - def from_llm( - cls, - llm: BaseLanguageModel, - prompt: PromptTemplate | None = None, - callbacks: Callbacks = None, - llm_chain_kwargs: dict | None = None, - **kwargs: Any, - ) -> BaseRetrievalQA: - """Initialize from LLM.""" - _prompt = prompt or PROMPT_SELECTOR.get_prompt(llm) - llm_chain = LLMChain( - llm=llm, - prompt=_prompt, - callbacks=callbacks, - **(llm_chain_kwargs or {}), - ) - document_prompt = PromptTemplate( - input_variables=["page_content"], - template="Context:\n{page_content}", - ) - combine_documents_chain = StuffDocumentsChain( - llm_chain=llm_chain, - document_variable_name="context", - document_prompt=document_prompt, - callbacks=callbacks, - ) - - return cls( - combine_documents_chain=combine_documents_chain, - callbacks=callbacks, - **kwargs, - ) - - @classmethod - def from_chain_type( - cls, - llm: BaseLanguageModel, - chain_type: str = "stuff", - chain_type_kwargs: dict | None = None, - **kwargs: Any, - ) -> BaseRetrievalQA: - """Load chain from chain type.""" - _chain_type_kwargs = chain_type_kwargs or {} - combine_documents_chain = load_qa_chain( - llm, - chain_type=chain_type, - **_chain_type_kwargs, - ) - return cls(combine_documents_chain=combine_documents_chain, **kwargs) - - @abstractmethod - def _get_docs( - self, - question: str, - *, - run_manager: CallbackManagerForChainRun, - ) -> list[Document]: - """Get documents to do question answering over.""" - - def _call( - self, - inputs: dict[str, Any], - run_manager: CallbackManagerForChainRun | None = None, - ) -> dict[str, Any]: - """Run get_relevant_text and llm on input query. - - If chain has 'return_source_documents' as 'True', returns - the retrieved documents as well under the key 'source_documents'. - - Example: - .. code-block:: python - - res = indexqa({'query': 'This is my query'}) - answer, docs = res['result'], res['source_documents'] - - """ - _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager() - question = inputs[self.input_key] - accepts_run_manager = ( - "run_manager" in inspect.signature(self._get_docs).parameters - ) - if accepts_run_manager: - docs = self._get_docs(question, run_manager=_run_manager) - else: - docs = self._get_docs(question) # type: ignore[call-arg] - answer = self.combine_documents_chain.run( - input_documents=docs, - question=question, - callbacks=_run_manager.get_child(), - ) - - if self.return_source_documents: - return {self.output_key: answer, "source_documents": docs} - return {self.output_key: answer} - - @abstractmethod - async def _aget_docs( - self, - question: str, - *, - run_manager: AsyncCallbackManagerForChainRun, - ) -> list[Document]: - """Get documents to do question answering over.""" - - async def _acall( - self, - inputs: dict[str, Any], - run_manager: AsyncCallbackManagerForChainRun | None = None, - ) -> dict[str, Any]: - """Run get_relevant_text and llm on input query. - - If chain has 'return_source_documents' as 'True', returns - the retrieved documents as well under the key 'source_documents'. - - Example: - .. code-block:: python - - res = indexqa({'query': 'This is my query'}) - answer, docs = res['result'], res['source_documents'] - - """ - _run_manager = run_manager or AsyncCallbackManagerForChainRun.get_noop_manager() - question = inputs[self.input_key] - accepts_run_manager = ( - "run_manager" in inspect.signature(self._aget_docs).parameters - ) - if accepts_run_manager: - docs = await self._aget_docs(question, run_manager=_run_manager) - else: - docs = await self._aget_docs(question) # type: ignore[call-arg] - answer = await self.combine_documents_chain.arun( - input_documents=docs, - question=question, - callbacks=_run_manager.get_child(), - ) - - if self.return_source_documents: - return {self.output_key: answer, "source_documents": docs} - return {self.output_key: answer} - - -@deprecated( - since="0.1.17", - removal="1.0", - message=( - "This class is deprecated. Use the `create_retrieval_chain` constructor " - "instead. See migration guide here: " - "https://python.langchain.com/docs/versions/migrating_chains/retrieval_qa/" - ), -) -class RetrievalQA(BaseRetrievalQA): - """Chain for question-answering against an index. - - This class is deprecated. See below for an example implementation using - `create_retrieval_chain`: - - .. code-block:: python - - from langchain_classic.chains import create_retrieval_chain - from langchain_classic.chains.combine_documents import ( - create_stuff_documents_chain, - ) - from langchain_core.prompts import ChatPromptTemplate - from langchain_openai import ChatOpenAI - - - retriever = ... # Your retriever - llm = ChatOpenAI() - - system_prompt = ( - "Use the given context to answer the question. " - "If you don't know the answer, say you don't know. " - "Use three sentence maximum and keep the answer concise. " - "Context: {context}" - ) - prompt = ChatPromptTemplate.from_messages( - [ - ("system", system_prompt), - ("human", "{input}"), - ] - ) - question_answer_chain = create_stuff_documents_chain(llm, prompt) - chain = create_retrieval_chain(retriever, question_answer_chain) - - chain.invoke({"input": query}) - - Example: - .. code-block:: python - - from langchain_community.llms import OpenAI - from langchain_classic.chains import RetrievalQA - from langchain_community.vectorstores import FAISS - from langchain_core.vectorstores import VectorStoreRetriever - - retriever = VectorStoreRetriever(vectorstore=FAISS(...)) - retrievalQA = RetrievalQA.from_llm(llm=OpenAI(), retriever=retriever) - - """ - - retriever: BaseRetriever = Field(exclude=True) - - def _get_docs( - self, - question: str, - *, - run_manager: CallbackManagerForChainRun, - ) -> list[Document]: - """Get docs.""" - return self.retriever.invoke( - question, - config={"callbacks": run_manager.get_child()}, - ) - - async def _aget_docs( - self, - question: str, - *, - run_manager: AsyncCallbackManagerForChainRun, - ) -> list[Document]: - """Get docs.""" - return await self.retriever.ainvoke( - question, - config={"callbacks": run_manager.get_child()}, - ) - - @property - def _chain_type(self) -> str: - """Return the chain type.""" - return "retrieval_qa" - - -@deprecated( - since="0.2.13", - removal="1.0", - message=( - "This class is deprecated. Use the `create_retrieval_chain` constructor " - "instead. See migration guide here: " - "https://python.langchain.com/docs/versions/migrating_chains/retrieval_qa/" - ), -) -class VectorDBQA(BaseRetrievalQA): - """Chain for question-answering against a vector database.""" - - vectorstore: VectorStore = Field(exclude=True, alias="vectorstore") - """Vector Database to connect to.""" - k: int = 4 - """Number of documents to query for.""" - search_type: str = "similarity" - """Search type to use over vectorstore. `similarity` or `mmr`.""" - search_kwargs: dict[str, Any] = Field(default_factory=dict) - """Extra search args.""" - - @model_validator(mode="before") - @classmethod - def validate_search_type(cls, values: dict) -> Any: - """Validate search type.""" - if "search_type" in values: - search_type = values["search_type"] - if search_type not in ("similarity", "mmr"): - msg = f"search_type of {search_type} not allowed." - raise ValueError(msg) - return values - - @override - def _get_docs( - self, - question: str, - *, - run_manager: CallbackManagerForChainRun, - ) -> list[Document]: - """Get docs.""" - if self.search_type == "similarity": - docs = self.vectorstore.similarity_search( - question, - k=self.k, - **self.search_kwargs, - ) - elif self.search_type == "mmr": - docs = self.vectorstore.max_marginal_relevance_search( - question, - k=self.k, - **self.search_kwargs, - ) - else: - msg = f"search_type of {self.search_type} not allowed." - raise ValueError(msg) - return docs - - async def _aget_docs( - self, - question: str, - *, - run_manager: AsyncCallbackManagerForChainRun, - ) -> list[Document]: - """Get docs.""" - msg = "VectorDBQA does not support async" - raise NotImplementedError(msg) - - @property - def _chain_type(self) -> str: - """Return the chain type.""" - return "vector_db_qa" diff --git a/libs/langchain/langchain_classic/chains/retrieval_qa/prompt.py b/libs/langchain/langchain_classic/chains/retrieval_qa/prompt.py deleted file mode 100644 index 1b588038a6278..0000000000000 --- a/libs/langchain/langchain_classic/chains/retrieval_qa/prompt.py +++ /dev/null @@ -1,11 +0,0 @@ -from langchain_core.prompts import PromptTemplate - -prompt_template = """Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. - -{context} - -Question: {question} -Helpful Answer:""" # noqa: E501 -PROMPT = PromptTemplate( - template=prompt_template, input_variables=["context", "question"] -) diff --git a/libs/langchain/langchain_classic/chains/router/llm_router.py b/libs/langchain/langchain_classic/chains/router/llm_router.py deleted file mode 100644 index 64e4823b8eb26..0000000000000 --- a/libs/langchain/langchain_classic/chains/router/llm_router.py +++ /dev/null @@ -1,199 +0,0 @@ -"""Base classes for LLM-powered router chains.""" - -from __future__ import annotations - -from typing import Any, cast - -from langchain_core._api import deprecated -from langchain_core.callbacks import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) -from langchain_core.exceptions import OutputParserException -from langchain_core.language_models import BaseLanguageModel -from langchain_core.output_parsers import BaseOutputParser -from langchain_core.prompts import BasePromptTemplate -from langchain_core.utils.json import parse_and_check_json_markdown -from pydantic import model_validator -from typing_extensions import Self, override - -from langchain_classic.chains import LLMChain -from langchain_classic.chains.router.base import RouterChain - - -@deprecated( - since="0.2.12", - removal="1.0", - message=( - "Use RunnableLambda to select from multiple prompt templates. See example " - "in API reference: " - "https://api.python.langchain.com/en/latest/chains/langchain.chains.router.llm_router.LLMRouterChain.html" - ), -) -class LLMRouterChain(RouterChain): - """A router chain that uses an LLM chain to perform routing. - - This class is deprecated. See below for a replacement, which offers several - benefits, including streaming and batch support. - - Below is an example implementation: - - .. code-block:: python - - from operator import itemgetter - from typing import Literal - from typing_extensions import TypedDict - - from langchain_core.output_parsers import StrOutputParser - from langchain_core.prompts import ChatPromptTemplate - from langchain_core.runnables import RunnableLambda, RunnablePassthrough - from langchain_openai import ChatOpenAI - - llm = ChatOpenAI(model="gpt-4o-mini") - - prompt_1 = ChatPromptTemplate.from_messages( - [ - ("system", "You are an expert on animals."), - ("human", "{query}"), - ] - ) - prompt_2 = ChatPromptTemplate.from_messages( - [ - ("system", "You are an expert on vegetables."), - ("human", "{query}"), - ] - ) - - chain_1 = prompt_1 | llm | StrOutputParser() - chain_2 = prompt_2 | llm | StrOutputParser() - - route_system = "Route the user's query to either the animal " - "or vegetable expert." - route_prompt = ChatPromptTemplate.from_messages( - [ - ("system", route_system), - ("human", "{query}"), - ] - ) - - - class RouteQuery(TypedDict): - \"\"\"Route query to destination.\"\"\" - destination: Literal["animal", "vegetable"] - - - route_chain = ( - route_prompt - | llm.with_structured_output(RouteQuery) - | itemgetter("destination") - ) - - chain = { - "destination": route_chain, # "animal" or "vegetable" - "query": lambda x: x["query"], # pass through input query - } | RunnableLambda( - # if animal, chain_1. otherwise, chain_2. - lambda x: chain_1 if x["destination"] == "animal" else chain_2, - ) - - chain.invoke({"query": "what color are carrots"}) - - """ - - llm_chain: LLMChain - """LLM chain used to perform routing""" - - @model_validator(mode="after") - def _validate_prompt(self) -> Self: - prompt = self.llm_chain.prompt - if prompt.output_parser is None: - msg = ( - "LLMRouterChain requires base llm_chain prompt to have an output" - " parser that converts LLM text output to a dictionary with keys" - " 'destination' and 'next_inputs'. Received a prompt with no output" - " parser." - ) - raise ValueError(msg) - return self - - @property - def input_keys(self) -> list[str]: - """Will be whatever keys the LLM chain prompt expects. - - :meta private: - """ - return self.llm_chain.input_keys - - def _validate_outputs(self, outputs: dict[str, Any]) -> None: - super()._validate_outputs(outputs) - if not isinstance(outputs["next_inputs"], dict): - raise ValueError # noqa: TRY004 - - def _call( - self, - inputs: dict[str, Any], - run_manager: CallbackManagerForChainRun | None = None, - ) -> dict[str, Any]: - _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager() - callbacks = _run_manager.get_child() - - prediction = self.llm_chain.predict(callbacks=callbacks, **inputs) - return cast( - "dict[str, Any]", - self.llm_chain.prompt.output_parser.parse(prediction), - ) - - async def _acall( - self, - inputs: dict[str, Any], - run_manager: AsyncCallbackManagerForChainRun | None = None, - ) -> dict[str, Any]: - _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager() - callbacks = _run_manager.get_child() - return cast( - "dict[str, Any]", - await self.llm_chain.apredict_and_parse(callbacks=callbacks, **inputs), - ) - - @classmethod - def from_llm( - cls, - llm: BaseLanguageModel, - prompt: BasePromptTemplate, - **kwargs: Any, - ) -> LLMRouterChain: - """Convenience constructor.""" - llm_chain = LLMChain(llm=llm, prompt=prompt) - return cls(llm_chain=llm_chain, **kwargs) - - -class RouterOutputParser(BaseOutputParser[dict[str, str]]): - """Parser for output of router chain in the multi-prompt chain.""" - - default_destination: str = "DEFAULT" - next_inputs_type: type = str - next_inputs_inner_key: str = "input" - - @override - def parse(self, text: str) -> dict[str, Any]: - try: - expected_keys = ["destination", "next_inputs"] - parsed = parse_and_check_json_markdown(text, expected_keys) - if not isinstance(parsed["destination"], str): - msg = "Expected 'destination' to be a string." - raise TypeError(msg) - if not isinstance(parsed["next_inputs"], self.next_inputs_type): - msg = f"Expected 'next_inputs' to be {self.next_inputs_type}." - raise TypeError(msg) - parsed["next_inputs"] = {self.next_inputs_inner_key: parsed["next_inputs"]} - if ( - parsed["destination"].strip().lower() - == self.default_destination.lower() - ): - parsed["destination"] = None - else: - parsed["destination"] = parsed["destination"].strip() - except Exception as e: - msg = f"Parsing text\n{text}\n raised following error:\n{e}" - raise OutputParserException(msg) from e - return parsed diff --git a/libs/langchain/langchain_classic/chains/router/multi_prompt.py b/libs/langchain/langchain_classic/chains/router/multi_prompt.py deleted file mode 100644 index 413f1cd58a6b2..0000000000000 --- a/libs/langchain/langchain_classic/chains/router/multi_prompt.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Use a single chain to route an input to one of multiple llm chains.""" - -from __future__ import annotations - -from typing import Any - -from langchain_core._api import deprecated -from langchain_core.language_models import BaseLanguageModel -from langchain_core.prompts import PromptTemplate -from typing_extensions import override - -from langchain_classic.chains import ConversationChain -from langchain_classic.chains.base import Chain -from langchain_classic.chains.llm import LLMChain -from langchain_classic.chains.router.base import MultiRouteChain -from langchain_classic.chains.router.llm_router import ( - LLMRouterChain, - RouterOutputParser, -) -from langchain_classic.chains.router.multi_prompt_prompt import ( - MULTI_PROMPT_ROUTER_TEMPLATE, -) - - -@deprecated( - since="0.2.12", - removal="1.0", - message=( - "Please see migration guide here for recommended implementation: " - "https://python.langchain.com/docs/versions/migrating_chains/multi_prompt_chain/" - ), -) -class MultiPromptChain(MultiRouteChain): - """A multi-route chain that uses an LLM router chain to choose amongst prompts. - - This class is deprecated. See below for a replacement, which offers several - benefits, including streaming and batch support. - - Below is an example implementation: - - .. code-block:: python - - from operator import itemgetter - from typing import Literal - - from langchain_core.output_parsers import StrOutputParser - from langchain_core.prompts import ChatPromptTemplate - from langchain_core.runnables import RunnableConfig - from langchain_openai import ChatOpenAI - from langgraph.graph import END, START, StateGraph - from typing_extensions import TypedDict - - llm = ChatOpenAI(model="gpt-4o-mini") - - # Define the prompts we will route to - prompt_1 = ChatPromptTemplate.from_messages( - [ - ("system", "You are an expert on animals."), - ("human", "{input}"), - ] - ) - prompt_2 = ChatPromptTemplate.from_messages( - [ - ("system", "You are an expert on vegetables."), - ("human", "{input}"), - ] - ) - - # Construct the chains we will route to. These format the input query - # into the respective prompt, run it through a chat model, and cast - # the result to a string. - chain_1 = prompt_1 | llm | StrOutputParser() - chain_2 = prompt_2 | llm | StrOutputParser() - - - # Next: define the chain that selects which branch to route to. - # Here we will take advantage of tool-calling features to force - # the output to select one of two desired branches. - route_system = "Route the user's query to either the animal " - "or vegetable expert." - route_prompt = ChatPromptTemplate.from_messages( - [ - ("system", route_system), - ("human", "{input}"), - ] - ) - - - # Define schema for output: - class RouteQuery(TypedDict): - \"\"\"Route query to destination expert.\"\"\" - - destination: Literal["animal", "vegetable"] - - - route_chain = route_prompt | llm.with_structured_output(RouteQuery) - - - # For LangGraph, we will define the state of the graph to hold the query, - # destination, and final answer. - class State(TypedDict): - query: str - destination: RouteQuery - answer: str - - - # We define functions for each node, including routing the query: - async def route_query(state: State, config: RunnableConfig): - destination = await route_chain.ainvoke(state["query"], config) - return {"destination": destination} - - - # And one node for each prompt - async def prompt_1(state: State, config: RunnableConfig): - return {"answer": await chain_1.ainvoke(state["query"], config)} - - - async def prompt_2(state: State, config: RunnableConfig): - return {"answer": await chain_2.ainvoke(state["query"], config)} - - - # We then define logic that selects the prompt based on the classification - def select_node(state: State) -> Literal["prompt_1", "prompt_2"]: - if state["destination"] == "animal": - return "prompt_1" - else: - return "prompt_2" - - - # Finally, assemble the multi-prompt chain. This is a sequence of two steps: - # 1) Select "animal" or "vegetable" via the route_chain, and collect the - # answer alongside the input query. - # 2) Route the input query to chain_1 or chain_2, based on the - # selection. - graph = StateGraph(State) - graph.add_node("route_query", route_query) - graph.add_node("prompt_1", prompt_1) - graph.add_node("prompt_2", prompt_2) - - graph.add_edge(START, "route_query") - graph.add_conditional_edges("route_query", select_node) - graph.add_edge("prompt_1", END) - graph.add_edge("prompt_2", END) - app = graph.compile() - - result = await app.ainvoke({"query": "what color are carrots"}) - print(result["destination"]) - print(result["answer"]) - - """ - - @property - @override - def output_keys(self) -> list[str]: - return ["text"] - - @classmethod - def from_prompts( - cls, - llm: BaseLanguageModel, - prompt_infos: list[dict[str, str]], - default_chain: Chain | None = None, - **kwargs: Any, - ) -> MultiPromptChain: - """Convenience constructor for instantiating from destination prompts.""" - destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos] - destinations_str = "\n".join(destinations) - router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format( - destinations=destinations_str, - ) - router_prompt = PromptTemplate( - template=router_template, - input_variables=["input"], - output_parser=RouterOutputParser(), - ) - router_chain = LLMRouterChain.from_llm(llm, router_prompt) - destination_chains = {} - for p_info in prompt_infos: - name = p_info["name"] - prompt_template = p_info["prompt_template"] - prompt = PromptTemplate(template=prompt_template, input_variables=["input"]) - chain = LLMChain(llm=llm, prompt=prompt) - destination_chains[name] = chain - _default_chain = default_chain or ConversationChain(llm=llm, output_key="text") - return cls( - router_chain=router_chain, - destination_chains=destination_chains, - default_chain=_default_chain, - **kwargs, - ) diff --git a/libs/langchain/langchain_classic/chains/structured_output/base.py b/libs/langchain/langchain_classic/chains/structured_output/base.py index 34ab4232084a8..ee48413e4a223 100644 --- a/libs/langchain/langchain_classic/chains/structured_output/base.py +++ b/libs/langchain/langchain_classic/chains/structured_output/base.py @@ -1,8 +1,7 @@ import json from collections.abc import Callable, Sequence -from typing import Any, Literal +from typing import Any -from langchain_core._api import deprecated from langchain_core.output_parsers import ( BaseGenerationOutputParser, BaseOutputParser, @@ -28,424 +27,6 @@ from pydantic import BaseModel -@deprecated( - since="0.1.14", - message=( - "LangChain has introduced a method called `with_structured_output` that " - "is available on ChatModels capable of tool calling. " - "You can read more about the method here: " - ". " - "Please follow our extraction use case documentation for more guidelines " - "on how to do information extraction with LLMs. " - ". " - "If you notice other issues, please provide " - "feedback here: " - "" - ), - removal="1.0", - alternative=( - """ - from pydantic import BaseModel, Field - from langchain_anthropic import ChatAnthropic - - class Joke(BaseModel): - setup: str = Field(description="The setup of the joke") - punchline: str = Field(description="The punchline to the joke") - - # Or any other chat model that supports tools. - # Please reference to to the documentation of structured_output - # to see an up to date list of which models support - # with_structured_output. - model = ChatAnthropic(model="claude-3-opus-20240229", temperature=0) - structured_llm = model.with_structured_output(Joke) - structured_llm.invoke("Tell me a joke about cats. - Make sure to call the Joke function.") - """ - ), -) -def create_openai_fn_runnable( - functions: Sequence[dict[str, Any] | type[BaseModel] | Callable], - llm: Runnable, - prompt: BasePromptTemplate | None = None, - *, - enforce_single_function_usage: bool = True, - output_parser: BaseOutputParser | BaseGenerationOutputParser | None = None, - **llm_kwargs: Any, -) -> Runnable: - """Create a runnable sequence that uses OpenAI functions. - - Args: - functions: A sequence of either dictionaries, pydantic.BaseModels classes, or - Python functions. If dictionaries are passed in, they are assumed to - already be a valid OpenAI functions. If only a single - function is passed in, then it will be enforced that the model use that - function. pydantic.BaseModels and Python functions should have docstrings - describing what the function does. For best results, pydantic.BaseModels - should have descriptions of the parameters and Python functions should have - Google Python style args descriptions in the docstring. Additionally, - Python functions should only use primitive types (str, int, float, bool) or - pydantic.BaseModels for arguments. - llm: Language model to use, assumed to support the OpenAI function-calling API. - prompt: BasePromptTemplate to pass to the model. - enforce_single_function_usage: only used if a single function is passed in. If - True, then the model will be forced to use the given function. If `False`, - then the model will be given the option to use the given function or not. - output_parser: BaseLLMOutputParser to use for parsing model outputs. By default - will be inferred from the function types. If pydantic.BaseModels are passed - in, then the OutputParser will try to parse outputs using those. Otherwise - model outputs will simply be parsed as JSON. If multiple functions are - passed in and they are not pydantic.BaseModels, the chain output will - include both the name of the function that was returned and the arguments - to pass to the function. - **llm_kwargs: Additional named arguments to pass to the language model. - - Returns: - A runnable sequence that will pass in the given functions to the model when run. - - Example: - .. code-block:: python - - from typing import Optional - - from langchain_classic.chains.structured_output import create_openai_fn_runnable - from langchain_openai import ChatOpenAI - from pydantic import BaseModel, Field - - - class RecordPerson(BaseModel): - '''Record some identifying information about a person.''' - - name: str = Field(..., description="The person's name") - age: int = Field(..., description="The person's age") - fav_food: str | None = Field(None, description="The person's favorite food") - - - class RecordDog(BaseModel): - '''Record some identifying information about a dog.''' - - name: str = Field(..., description="The dog's name") - color: str = Field(..., description="The dog's color") - fav_food: str | None = Field(None, description="The dog's favorite food") - - - llm = ChatOpenAI(model="gpt-4", temperature=0) - structured_llm = create_openai_fn_runnable([RecordPerson, RecordDog], llm) - structured_llm.invoke("Harry was a chubby brown beagle who loved chicken) - # -> RecordDog(name="Harry", color="brown", fav_food="chicken") - - """ # noqa: E501 - if not functions: - msg = "Need to pass in at least one function. Received zero." - raise ValueError(msg) - openai_functions = [convert_to_openai_function(f) for f in functions] - llm_kwargs_: dict[str, Any] = {"functions": openai_functions, **llm_kwargs} - if len(openai_functions) == 1 and enforce_single_function_usage: - llm_kwargs_["function_call"] = {"name": openai_functions[0]["name"]} - output_parser = output_parser or get_openai_output_parser(functions) - if prompt: - return prompt | llm.bind(**llm_kwargs_) | output_parser - return llm.bind(**llm_kwargs_) | output_parser - - -@deprecated( - since="0.1.17", - message=( - "LangChain has introduced a method called `with_structured_output` that " - "is available on ChatModels capable of tool calling. " - "You can read more about the method here: " - "." - "Please follow our extraction use case documentation for more guidelines " - "on how to do information extraction with LLMs. " - ". " - "If you notice other issues, please provide " - "feedback here: " - "" - ), - removal="1.0", - alternative=( - """ - from pydantic import BaseModel, Field - from langchain_anthropic import ChatAnthropic - - class Joke(BaseModel): - setup: str = Field(description="The setup of the joke") - punchline: str = Field(description="The punchline to the joke") - - # Or any other chat model that supports tools. - # Please reference to to the documentation of structured_output - # to see an up to date list of which models support - # with_structured_output. - model = ChatAnthropic(model="claude-3-opus-20240229", temperature=0) - structured_llm = model.with_structured_output(Joke) - structured_llm.invoke("Tell me a joke about cats. - Make sure to call the Joke function.") - """ - ), -) -def create_structured_output_runnable( - output_schema: dict[str, Any] | type[BaseModel], - llm: Runnable, - prompt: BasePromptTemplate | None = None, - *, - output_parser: BaseOutputParser | BaseGenerationOutputParser | None = None, - enforce_function_usage: bool = True, - return_single: bool = True, - mode: Literal[ - "openai-functions", - "openai-tools", - "openai-json", - ] = "openai-functions", - **kwargs: Any, -) -> Runnable: - """Create a runnable for extracting structured outputs. - - Args: - output_schema: Either a dictionary or pydantic.BaseModel class. If a dictionary - is passed in, it's assumed to already be a valid JsonSchema. - For best results, pydantic.BaseModels should have docstrings describing what - the schema represents and descriptions for the parameters. - llm: Language model to use. Assumed to support the OpenAI function-calling API - if mode is 'openai-function'. Assumed to support OpenAI response_format - parameter if mode is 'openai-json'. - prompt: BasePromptTemplate to pass to the model. If mode is 'openai-json' and - prompt has input variable 'output_schema' then the given output_schema - will be converted to a JsonSchema and inserted in the prompt. - output_parser: Output parser to use for parsing model outputs. By default - will be inferred from the function types. If pydantic.BaseModel is passed - in, then the OutputParser will try to parse outputs using the pydantic - class. Otherwise model outputs will be parsed as JSON. - mode: How structured outputs are extracted from the model. If 'openai-functions' - then OpenAI function calling is used with the deprecated 'functions', - 'function_call' schema. If 'openai-tools' then OpenAI function - calling with the latest 'tools', 'tool_choice' schema is used. This is - recommended over 'openai-functions'. If 'openai-json' then OpenAI model - with response_format set to JSON is used. - enforce_function_usage: Only applies when mode is 'openai-tools' or - 'openai-functions'. If `True`, then the model will be forced to use the given - output schema. If `False`, then the model can elect whether to use the output - schema. - return_single: Only applies when mode is 'openai-tools'. Whether to a list of - structured outputs or a single one. If `True` and model does not return any - structured outputs then chain output is None. If `False` and model does not - return any structured outputs then chain output is an empty list. - kwargs: Additional named arguments. - - Returns: - A runnable sequence that will return a structured output(s) matching the given - output_schema. - - OpenAI tools example with Pydantic schema (mode='openai-tools'): - .. code-block:: python - - from typing import Optional - - from langchain_classic.chains import create_structured_output_runnable - from langchain_openai import ChatOpenAI - from pydantic import BaseModel, Field - - - class RecordDog(BaseModel): - '''Record some identifying information about a dog.''' - - name: str = Field(..., description="The dog's name") - color: str = Field(..., description="The dog's color") - fav_food: str | None = Field(None, description="The dog's favorite food") - - llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0) - prompt = ChatPromptTemplate.from_messages( - [ - ("system", "You are an extraction algorithm. Please extract every possible instance"), - ('human', '{input}') - ] - ) - structured_llm = create_structured_output_runnable( - RecordDog, - llm, - mode="openai-tools", - enforce_function_usage=True, - return_single=True - ) - structured_llm.invoke({"input": "Harry was a chubby brown beagle who loved chicken"}) - # -> RecordDog(name="Harry", color="brown", fav_food="chicken") - - OpenAI tools example with dict schema (mode="openai-tools"): - .. code-block:: python - - from typing import Optional - - from langchain_classic.chains import create_structured_output_runnable - from langchain_openai import ChatOpenAI - - - dog_schema = { - "type": "function", - "function": { - "name": "record_dog", - "description": "Record some identifying information about a dog.", - "parameters": { - "type": "object", - "properties": { - "name": { - "description": "The dog's name", - "type": "string" - }, - "color": { - "description": "The dog's color", - "type": "string" - }, - "fav_food": { - "description": "The dog's favorite food", - "type": "string" - } - }, - "required": ["name", "color"] - } - } - } - - - llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0) - structured_llm = create_structured_output_runnable( - dog_schema, - llm, - mode="openai-tools", - enforce_function_usage=True, - return_single=True - ) - structured_llm.invoke("Harry was a chubby brown beagle who loved chicken") - # -> {'name': 'Harry', 'color': 'brown', 'fav_food': 'chicken'} - - OpenAI functions example (mode="openai-functions"): - .. code-block:: python - - from typing import Optional - - from langchain_classic.chains import create_structured_output_runnable - from langchain_openai import ChatOpenAI - from pydantic import BaseModel, Field - - class Dog(BaseModel): - '''Identifying information about a dog.''' - - name: str = Field(..., description="The dog's name") - color: str = Field(..., description="The dog's color") - fav_food: str | None = Field(None, description="The dog's favorite food") - - llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0) - structured_llm = create_structured_output_runnable(Dog, llm, mode="openai-functions") - structured_llm.invoke("Harry was a chubby brown beagle who loved chicken") - # -> Dog(name="Harry", color="brown", fav_food="chicken") - - OpenAI functions with prompt example: - .. code-block:: python - - from typing import Optional - - from langchain_classic.chains import create_structured_output_runnable - from langchain_openai import ChatOpenAI - from langchain_core.prompts import ChatPromptTemplate - from pydantic import BaseModel, Field - - class Dog(BaseModel): - '''Identifying information about a dog.''' - - name: str = Field(..., description="The dog's name") - color: str = Field(..., description="The dog's color") - fav_food: str | None = Field(None, description="The dog's favorite food") - - llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0) - structured_llm = create_structured_output_runnable(Dog, llm, mode="openai-functions") - system = '''Extract information about any dogs mentioned in the user input.''' - prompt = ChatPromptTemplate.from_messages( - [("system", system), ("human", "{input}"),] - ) - chain = prompt | structured_llm - chain.invoke({"input": "Harry was a chubby brown beagle who loved chicken"}) - # -> Dog(name="Harry", color="brown", fav_food="chicken") - OpenAI json response format example (mode="openai-json"): - .. code-block:: python - - from typing import Optional - - from langchain_classic.chains import create_structured_output_runnable - from langchain_openai import ChatOpenAI - from langchain_core.prompts import ChatPromptTemplate - from pydantic import BaseModel, Field - - class Dog(BaseModel): - '''Identifying information about a dog.''' - - name: str = Field(..., description="The dog's name") - color: str = Field(..., description="The dog's color") - fav_food: str | None = Field(None, description="The dog's favorite food") - - llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0) - structured_llm = create_structured_output_runnable(Dog, llm, mode="openai-json") - system = '''You are a world class assistant for extracting information in structured JSON formats. \ - - Extract a valid JSON blob from the user input that matches the following JSON Schema: - - {output_schema}''' - prompt = ChatPromptTemplate.from_messages( - [("system", system), ("human", "{input}"),] - ) - chain = prompt | structured_llm - chain.invoke({"input": "Harry was a chubby brown beagle who loved chicken"}) - - """ # noqa: E501 - # for backwards compatibility - force_function_usage = kwargs.get( - "enforce_single_function_usage", - enforce_function_usage, - ) - - if mode == "openai-tools": - # Protect against typos in kwargs - keys_in_kwargs = set(kwargs.keys()) - # Backwards compatibility keys - unrecognized_keys = keys_in_kwargs - {"enforce_single_function_usage"} - if unrecognized_keys: - msg = f"Got an unexpected keyword argument(s): {unrecognized_keys}." - raise TypeError(msg) - - return _create_openai_tools_runnable( - output_schema, - llm, - prompt=prompt, - output_parser=output_parser, - enforce_tool_usage=force_function_usage, - first_tool_only=return_single, - ) - - if mode == "openai-functions": - return _create_openai_functions_structured_output_runnable( - output_schema, - llm, - prompt=prompt, - output_parser=output_parser, - enforce_single_function_usage=force_function_usage, - **kwargs, # llm-specific kwargs - ) - if mode == "openai-json": - if force_function_usage: - msg = ( - "enforce_single_function_usage is not supported for mode='openai-json'." - ) - raise ValueError(msg) - return _create_openai_json_runnable( - output_schema, - llm, - prompt=prompt, - output_parser=output_parser, - **kwargs, - ) - msg = ( # type: ignore[unreachable] - f"Invalid mode {mode}. Expected one of 'openai-tools', 'openai-functions', " - f"'openai-json'." - ) - raise ValueError(msg) - - def _create_openai_tools_runnable( tool: dict[str, Any] | type[BaseModel] | Callable, llm: Runnable, diff --git a/libs/langchain/langchain_classic/memory/summary.py b/libs/langchain/langchain_classic/memory/summary.py deleted file mode 100644 index 5b2ed54e56a70..0000000000000 --- a/libs/langchain/langchain_classic/memory/summary.py +++ /dev/null @@ -1,171 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from langchain_core._api import deprecated -from langchain_core.chat_history import BaseChatMessageHistory -from langchain_core.language_models import BaseLanguageModel -from langchain_core.messages import BaseMessage, SystemMessage, get_buffer_string -from langchain_core.prompts import BasePromptTemplate -from langchain_core.utils import pre_init -from pydantic import BaseModel -from typing_extensions import override - -from langchain_classic.chains.llm import LLMChain -from langchain_classic.memory.chat_memory import BaseChatMemory -from langchain_classic.memory.prompt import SUMMARY_PROMPT - - -@deprecated( - since="0.2.12", - removal="1.0", - message=( - "Refer here for how to incorporate summaries of conversation history: " - "https://langchain-ai.github.io/langgraph/how-tos/memory/add-summary-conversation-history/" - ), -) -class SummarizerMixin(BaseModel): - """Mixin for summarizer.""" - - human_prefix: str = "Human" - ai_prefix: str = "AI" - llm: BaseLanguageModel - prompt: BasePromptTemplate = SUMMARY_PROMPT - summary_message_cls: type[BaseMessage] = SystemMessage - - def predict_new_summary( - self, - messages: list[BaseMessage], - existing_summary: str, - ) -> str: - """Predict a new summary based on the messages and existing summary. - - Args: - messages: List of messages to summarize. - existing_summary: Existing summary to build upon. - - Returns: - A new summary string. - """ - new_lines = get_buffer_string( - messages, - human_prefix=self.human_prefix, - ai_prefix=self.ai_prefix, - ) - - chain = LLMChain(llm=self.llm, prompt=self.prompt) - return chain.predict(summary=existing_summary, new_lines=new_lines) - - async def apredict_new_summary( - self, - messages: list[BaseMessage], - existing_summary: str, - ) -> str: - """Predict a new summary based on the messages and existing summary. - - Args: - messages: List of messages to summarize. - existing_summary: Existing summary to build upon. - - Returns: - A new summary string. - """ - new_lines = get_buffer_string( - messages, - human_prefix=self.human_prefix, - ai_prefix=self.ai_prefix, - ) - - chain = LLMChain(llm=self.llm, prompt=self.prompt) - return await chain.apredict(summary=existing_summary, new_lines=new_lines) - - -@deprecated( - since="0.3.1", - removal="1.0.0", - message=( - "Please see the migration guide at: " - "https://python.langchain.com/docs/versions/migrating_memory/" - ), -) -class ConversationSummaryMemory(BaseChatMemory, SummarizerMixin): - """Continually summarizes the conversation history. - - The summary is updated after each conversation turn. - The implementations returns a summary of the conversation history which - can be used to provide context to the model. - """ - - buffer: str = "" - memory_key: str = "history" #: :meta private: - - @classmethod - def from_messages( - cls, - llm: BaseLanguageModel, - chat_memory: BaseChatMessageHistory, - *, - summarize_step: int = 2, - **kwargs: Any, - ) -> ConversationSummaryMemory: - """Create a ConversationSummaryMemory from a list of messages. - - Args: - llm: The language model to use for summarization. - chat_memory: The chat history to summarize. - summarize_step: Number of messages to summarize at a time. - **kwargs: Additional keyword arguments to pass to the class. - - Returns: - An instance of ConversationSummaryMemory with the summarized history. - """ - obj = cls(llm=llm, chat_memory=chat_memory, **kwargs) - for i in range(0, len(obj.chat_memory.messages), summarize_step): - obj.buffer = obj.predict_new_summary( - obj.chat_memory.messages[i : i + summarize_step], - obj.buffer, - ) - return obj - - @property - def memory_variables(self) -> list[str]: - """Will always return list of memory variables. - - :meta private: - """ - return [self.memory_key] - - @override - def load_memory_variables(self, inputs: dict[str, Any]) -> dict[str, Any]: - """Return history buffer.""" - if self.return_messages: - buffer: Any = [self.summary_message_cls(content=self.buffer)] - else: - buffer = self.buffer - return {self.memory_key: buffer} - - @pre_init - def validate_prompt_input_variables(cls, values: dict) -> dict: - """Validate that prompt input variables are consistent.""" - prompt_variables = values["prompt"].input_variables - expected_keys = {"summary", "new_lines"} - if expected_keys != set(prompt_variables): - msg = ( - "Got unexpected prompt input variables. The prompt expects " - f"{prompt_variables}, but it should have {expected_keys}." - ) - raise ValueError(msg) - return values - - def save_context(self, inputs: dict[str, Any], outputs: dict[str, str]) -> None: - """Save context from this conversation to buffer.""" - super().save_context(inputs, outputs) - self.buffer = self.predict_new_summary( - self.chat_memory.messages[-2:], - self.buffer, - ) - - def clear(self) -> None: - """Clear memory contents.""" - super().clear() - self.buffer = ""