Files
langchain-python/langchain/tools/python/tool.py
T
KullTC 64596b23b9 Return output of PythonAstREPLTool when falling back to exec() (#2780)
When the code ran by the PythonAstREPLTool contains multiple statements
it will fallback to exec() instead of using eval(). With this change, it
will also return the output of the code in the same way the
PythonREPLTool will.
2023-04-12 21:22:46 -07:00

97 lines
3.4 KiB
Python

"""A tool for running python code in a REPL."""
import ast
import sys
from io import StringIO
from typing import Dict, Optional
from pydantic import Field, root_validator
from langchain.python import PythonREPL
from langchain.tools.base import BaseTool
def _get_default_python_repl() -> PythonREPL:
return PythonREPL(_globals=globals(), _locals=None)
class PythonREPLTool(BaseTool):
"""A tool for running python code in a REPL."""
name = "Python REPL"
description = (
"A Python shell. Use this to execute python commands. "
"Input should be a valid python command. "
"If you want to see the output of a value, you should print it out "
"with `print(...)`."
)
python_repl: PythonREPL = Field(default_factory=_get_default_python_repl)
sanitize_input: bool = True
def _run(self, query: str) -> str:
"""Use the tool."""
if self.sanitize_input:
query = query.strip().strip("```")
return self.python_repl.run(query)
async def _arun(self, query: str) -> str:
"""Use the tool asynchronously."""
raise NotImplementedError("PythonReplTool does not support async")
class PythonAstREPLTool(BaseTool):
"""A tool for running python code in a REPL."""
name = "python_repl_ast"
description = (
"A Python shell. Use this to execute python commands. "
"Input should be a valid python command. "
"When using this tool, sometimes output is abbreviated - "
"make sure it does not look abbreviated before using it in your answer."
)
globals: Optional[Dict] = Field(default_factory=dict)
locals: Optional[Dict] = Field(default_factory=dict)
sanitize_input: bool = True
@root_validator(pre=True)
def validate_python_version(cls, values: Dict) -> Dict:
"""Validate valid python version."""
if sys.version_info < (3, 9):
raise ValueError(
"This tool relies on Python 3.9 or higher "
"(as it uses new functionality in the `ast` module, "
f"you have Python version: {sys.version}"
)
return values
def _run(self, query: str) -> str:
"""Use the tool."""
try:
if self.sanitize_input:
# Remove the triple backticks from the query.
query = query.strip().strip("```")
tree = ast.parse(query)
module = ast.Module(tree.body[:-1], type_ignores=[])
exec(ast.unparse(module), self.globals, self.locals) # type: ignore
module_end = ast.Module(tree.body[-1:], type_ignores=[])
module_end_str = ast.unparse(module_end) # type: ignore
try:
return eval(module_end_str, self.globals, self.locals)
except Exception:
old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()
try:
exec(module_end_str, self.globals, self.locals)
sys.stdout = old_stdout
output = mystdout.getvalue()
except Exception as e:
sys.stdout = old_stdout
output = str(e)
return output
except Exception as e:
return str(e)
async def _arun(self, query: str) -> str:
"""Use the tool asynchronously."""
raise NotImplementedError("PythonReplTool does not support async")