diff --git a/libs/langchain/langchain/chains/llm_symbolic_math/__init__.py b/libs/langchain/langchain/chains/llm_symbolic_math/__init__.py index 6ba7b01959..64f6c7ac20 100644 --- a/libs/langchain/langchain/chains/llm_symbolic_math/__init__.py +++ b/libs/langchain/langchain/chains/llm_symbolic_math/__init__.py @@ -1,14 +1,12 @@ -"""Chain that interprets a prompt and executes python code to do math. +def raise_on_import() -> None: + """Raise an error on import since is deprecated.""" + raise ImportError( + "This module has been moved to langchain-experimental. " + "For more details: https://github.com/langchain-ai/langchain/discussions/11352." + "To access this code, install it with `pip install langchain-experimental`." + "`from langchain_experimental.llm_symbolic_math.base " + "import LLMSymbolicMathChain`" + ) -Heavily borrowed from llm_math, wrapper for SymPy -""" -from langchain._api import warn_deprecated -warn_deprecated( - since="0.0.304", - message=( - "On 2023-10-06 this module will be moved to langchain-experimental as " - "it relies on sympy https://github.com/sympy/sympy/issues/10805" - ), - pending=True, -) +raise_on_import() diff --git a/libs/langchain/langchain/chains/llm_symbolic_math/base.py b/libs/langchain/langchain/chains/llm_symbolic_math/base.py deleted file mode 100644 index 1ae92fa9d8..0000000000 --- a/libs/langchain/langchain/chains/llm_symbolic_math/base.py +++ /dev/null @@ -1,156 +0,0 @@ -"""Chain that interprets a prompt and executes python code to do symbolic math.""" -from __future__ import annotations - -import re -from typing import Any, Dict, List, Optional - -from langchain.base_language import BaseLanguageModel -from langchain.callbacks.manager import ( - AsyncCallbackManagerForChainRun, - CallbackManagerForChainRun, -) -from langchain.chains.base import Chain -from langchain.chains.llm import LLMChain -from langchain.chains.llm_symbolic_math.prompt import PROMPT -from langchain.prompts.base import BasePromptTemplate -from langchain.pydantic_v1 import Extra - - -class LLMSymbolicMathChain(Chain): - """Chain that interprets a prompt and executes python code to do symbolic math. - - Example: - .. code-block:: python - - from langchain.chains import LLMSymbolicMathChain - from langchain.llms import OpenAI - llm_symbolic_math = LLMSymbolicMathChain.from_llm(OpenAI()) - """ - - llm_chain: LLMChain - input_key: str = "question" #: :meta private: - output_key: str = "answer" #: :meta private: - - class Config: - """Configuration for this pydantic object.""" - - extra = Extra.forbid - arbitrary_types_allowed = True - - @property - def input_keys(self) -> List[str]: - """Expect input key. - - :meta private: - """ - return [self.input_key] - - @property - def output_keys(self) -> List[str]: - """Expect output key. - - :meta private: - """ - return [self.output_key] - - def _evaluate_expression(self, expression: str) -> str: - try: - import sympy - except ImportError as e: - raise ImportError( - "Unable to import sympy, please install it with `pip install sympy`." - ) from e - try: - output = str(sympy.sympify(expression, evaluate=True)) - except Exception as e: - raise ValueError( - f'LLMSymbolicMathChain._evaluate("{expression}") raised error: {e}.' - " Please try again with a valid numerical expression" - ) - - # Remove any leading and trailing brackets from the output - return re.sub(r"^\[|\]$", "", output) - - def _process_llm_result( - self, llm_output: str, run_manager: CallbackManagerForChainRun - ) -> Dict[str, str]: - run_manager.on_text(llm_output, color="green", verbose=self.verbose) - llm_output = llm_output.strip() - text_match = re.search(r"^```text(.*?)```", llm_output, re.DOTALL) - if text_match: - expression = text_match.group(1) - output = self._evaluate_expression(expression) - run_manager.on_text("\nAnswer: ", verbose=self.verbose) - run_manager.on_text(output, color="yellow", verbose=self.verbose) - answer = "Answer: " + output - elif llm_output.startswith("Answer:"): - answer = llm_output - elif "Answer:" in llm_output: - answer = "Answer: " + llm_output.split("Answer:")[-1] - else: - raise ValueError(f"unknown format from LLM: {llm_output}") - return {self.output_key: answer} - - async def _aprocess_llm_result( - self, - llm_output: str, - run_manager: AsyncCallbackManagerForChainRun, - ) -> Dict[str, str]: - await run_manager.on_text(llm_output, color="green", verbose=self.verbose) - llm_output = llm_output.strip() - text_match = re.search(r"^```text(.*?)```", llm_output, re.DOTALL) - if text_match: - expression = text_match.group(1) - output = self._evaluate_expression(expression) - await run_manager.on_text("\nAnswer: ", verbose=self.verbose) - await run_manager.on_text(output, color="yellow", verbose=self.verbose) - answer = "Answer: " + output - elif llm_output.startswith("Answer:"): - answer = llm_output - elif "Answer:" in llm_output: - answer = "Answer: " + llm_output.split("Answer:")[-1] - else: - raise ValueError(f"unknown format from LLM: {llm_output}") - return {self.output_key: answer} - - def _call( - self, - inputs: Dict[str, str], - run_manager: Optional[CallbackManagerForChainRun] = None, - ) -> Dict[str, str]: - _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager() - _run_manager.on_text(inputs[self.input_key]) - llm_output = self.llm_chain.predict( - question=inputs[self.input_key], - stop=["```output"], - callbacks=_run_manager.get_child(), - ) - return self._process_llm_result(llm_output, _run_manager) - - async def _acall( - self, - inputs: Dict[str, str], - run_manager: Optional[AsyncCallbackManagerForChainRun] = None, - ) -> Dict[str, str]: - _run_manager = run_manager or AsyncCallbackManagerForChainRun.get_noop_manager() - await _run_manager.on_text(inputs[self.input_key]) - llm_output = await self.llm_chain.apredict( - question=inputs[self.input_key], - stop=["```output"], - callbacks=_run_manager.get_child(), - ) - return await self._aprocess_llm_result(llm_output, _run_manager) - - @property - def _chain_type(self) -> str: - return "llm_symbolic_math_chain" - - @classmethod - def from_llm( - cls, - llm: BaseLanguageModel, - prompt: BasePromptTemplate = PROMPT, - **kwargs: Any, - ) -> LLMSymbolicMathChain: - llm_chain = LLMChain(llm=llm, prompt=prompt) - return cls(llm_chain=llm_chain, **kwargs) diff --git a/libs/langchain/langchain/chains/llm_symbolic_math/prompt.py b/libs/langchain/langchain/chains/llm_symbolic_math/prompt.py deleted file mode 100644 index 576dd1f9dc..0000000000 --- a/libs/langchain/langchain/chains/llm_symbolic_math/prompt.py +++ /dev/null @@ -1,51 +0,0 @@ -# flake8: noqa -from langchain.prompts.prompt import PromptTemplate - -_PROMPT_TEMPLATE = """Translate a math problem into a expression that can be executed using Python's SymPy library. Use the output of running this code to answer the question. - -Question: ${{Question with math problem.}} -```text -${{single line sympy expression that solves the problem}} -``` -...sympy.sympify(text, evaluate=True)... -```output -${{Output of running the code}} -``` -Answer: ${{Answer}} - -Begin. - -Question: What is the limit of sin(x) / x as x goes to 0 -```text -limit(sin(x)/x, x, 0) -``` -...sympy.sympify("limit(sin(x)/x, x, 0)")... -```output -1 -``` -Answer: 1 - -Question: What is the integral of e^-x from 0 to infinity -```text -integrate(exp(-x), (x, 0, oo)) -``` -...sympy.sympify("integrate(exp(-x), (x, 0, oo))")... -```output -1 -``` - -Question: What are the solutions to this equation x**2 - x? -```text -solveset(x**2 - x, x) -``` -...sympy.sympify("solveset(x**2 - x, x)")... -```output -[0, 1] -``` -Question: {question} -""" - -PROMPT = PromptTemplate( - input_variables=["question"], - template=_PROMPT_TEMPLATE, -) diff --git a/libs/langchain/tests/unit_tests/chains/test_llm_symbolic_math.py b/libs/langchain/tests/unit_tests/chains/test_llm_symbolic_math.py deleted file mode 100644 index 351dcbddfb..0000000000 --- a/libs/langchain/tests/unit_tests/chains/test_llm_symbolic_math.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Test LLM Math functionality.""" - -import pytest - -from langchain.chains.llm_symbolic_math.base import LLMSymbolicMathChain -from langchain.chains.llm_symbolic_math.prompt import _PROMPT_TEMPLATE -from tests.unit_tests.llms.fake_llm import FakeLLM - - -@pytest.fixture -@pytest.mark.requires("sympy") -def fake_llm_symbolic_math_chain() -> LLMSymbolicMathChain: - """Fake LLM Math chain for testing.""" - queries = { - _PROMPT_TEMPLATE.format(question="What is 1 plus 1?"): "Answer: 2", - _PROMPT_TEMPLATE.format( - question="What is the square root of 2?" - ): "```text\nsqrt(2)\n```", - _PROMPT_TEMPLATE.format( - question="What is the limit of sin(x) / x as x goes to 0?" - ): "```text\nlimit(sin(x)/x,x,0)\n```", - _PROMPT_TEMPLATE.format( - question="What is the integral of e^-x from 0 to infinity?" - ): "```text\nintegrate(exp(-x), (x, 0, oo))\n```", - _PROMPT_TEMPLATE.format( - question="What are the solutions to this equation x**2 - x?" - ): "```text\nsolveset(x**2 - x, x)\n```", - _PROMPT_TEMPLATE.format(question="foo"): "foo", - } - fake_llm = FakeLLM(queries=queries) - return LLMSymbolicMathChain.from_llm(fake_llm, input_key="q", output_key="a") - - -@pytest.mark.requires("sympy") -def test_simple_question(fake_llm_symbolic_math_chain: LLMSymbolicMathChain) -> None: - """Test simple question that should not need python.""" - question = "What is 1 plus 1?" - output = fake_llm_symbolic_math_chain.run(question) - assert output == "Answer: 2" - - -@pytest.mark.requires("sympy") -def test_root_question(fake_llm_symbolic_math_chain: LLMSymbolicMathChain) -> None: - """Test irrational number that should need sympy.""" - import sympy - - question = "What is the square root of 2?" - output = fake_llm_symbolic_math_chain.run(question) - assert output == f"Answer: {sympy.sqrt(2)}" - - -@pytest.mark.requires("sympy") -def test_limit_question(fake_llm_symbolic_math_chain: LLMSymbolicMathChain) -> None: - """Test question about limits that needs sympy""" - question = "What is the limit of sin(x) / x as x goes to 0?" - output = fake_llm_symbolic_math_chain.run(question) - assert output == "Answer: 1" - - -@pytest.mark.requires("sympy") -def test_integration_question( - fake_llm_symbolic_math_chain: LLMSymbolicMathChain, -) -> None: - """Test question about integration that needs sympy""" - question = "What is the integral of e^-x from 0 to infinity?" - output = fake_llm_symbolic_math_chain.run(question) - assert output == "Answer: 1" - - -@pytest.mark.requires("sympy") -def test_solver_question(fake_llm_symbolic_math_chain: LLMSymbolicMathChain) -> None: - """Test question about solving algebraic equations that needs sympy""" - question = "What are the solutions to this equation x**2 - x?" - output = fake_llm_symbolic_math_chain.run(question) - assert output == "Answer: {0, 1}" - - -@pytest.mark.requires("sympy") -def test_error(fake_llm_symbolic_math_chain: LLMSymbolicMathChain) -> None: - """Test question that raises error.""" - with pytest.raises(ValueError): - fake_llm_symbolic_math_chain.run("foo")