forked from Archives/langchain
Add Other File Utilities (#3209)
Add other File Utilities, include - List Directory - Search for file - Move - Copy - Remove file Bundle as toolkit Add a notebook that connects to the Chat Agent, which somewhat supports multi-arg input tools Update original read/write files to return the original dir paths and better handle unsupported file paths. Add unit testsfix_agent_callbacks
parent
491c27f861
commit
334c162f16
@ -0,0 +1,190 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# File System Tools\n",
|
||||||
|
"\n",
|
||||||
|
"LangChain provides tools for interacting with a local file system out of the box. This notebook walks through some of them.\n",
|
||||||
|
"\n",
|
||||||
|
"Note: these tools are not recommended for use outside a sandboxed environment! "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"First, we'll import the tools."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from langchain.tools.file_management import (\n",
|
||||||
|
" ReadFileTool,\n",
|
||||||
|
" CopyFileTool,\n",
|
||||||
|
" DeleteFileTool,\n",
|
||||||
|
" MoveFileTool,\n",
|
||||||
|
" WriteFileTool,\n",
|
||||||
|
" ListDirectoryTool,\n",
|
||||||
|
")\n",
|
||||||
|
"from langchain.agents.agent_toolkits import FileManagementToolkit\n",
|
||||||
|
"from tempfile import TemporaryDirectory\n",
|
||||||
|
"\n",
|
||||||
|
"# We'll make a temporary directory to avoid clutter\n",
|
||||||
|
"working_directory = TemporaryDirectory()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## The FileManagementToolkit\n",
|
||||||
|
"\n",
|
||||||
|
"If you want to provide all the file tooling to your agent, it's easy to do so with the toolkit. We'll pass the temporary directory in as a root directory as a workspace for the LLM.\n",
|
||||||
|
"\n",
|
||||||
|
"It's recommended to always pass in a root directory, since without one, it's easy for the LLM to pollute the working directory, and without one, there isn't any validation against\n",
|
||||||
|
"straightforward prompt injection."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[CopyFileTool(name='copy_file', description='Create a copy of a file in a specified location', args_schema=<class 'langchain.tools.file_management.copy.FileCopyInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug'),\n",
|
||||||
|
" DeleteFileTool(name='file_delete', description='Delete a file', args_schema=<class 'langchain.tools.file_management.delete.FileDeleteInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug'),\n",
|
||||||
|
" FileSearchTool(name='file_search', description='Recursively search for files in a subdirectory that match the regex pattern', args_schema=<class 'langchain.tools.file_management.file_search.FileSearchInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug'),\n",
|
||||||
|
" MoveFileTool(name='move_file', description='Move or rename a file from one location to another', args_schema=<class 'langchain.tools.file_management.move.FileMoveInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug'),\n",
|
||||||
|
" ReadFileTool(name='read_file', description='Read file from disk', args_schema=<class 'langchain.tools.file_management.read.ReadFileInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug'),\n",
|
||||||
|
" WriteFileTool(name='write_file', description='Write file to disk', args_schema=<class 'langchain.tools.file_management.write.WriteFileInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug'),\n",
|
||||||
|
" ListDirectoryTool(name='list_directory', description='List files and directories in a specified folder', args_schema=<class 'langchain.tools.file_management.list_dir.DirectoryListingInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug')]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 2,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"toolkit = FileManagementToolkit(root_dir=str(working_directory.name)) # If you don't provide a root_dir, operations will default to the current working directory\n",
|
||||||
|
"toolkit.get_tools()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Selecting File System Tools\n",
|
||||||
|
"\n",
|
||||||
|
"If you only want to select certain tools, you can pass them in as arguments when initializing the toolkit, or you can individually initialize the desired tools."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[ReadFileTool(name='read_file', description='Read file from disk', args_schema=<class 'langchain.tools.file_management.read.ReadFileInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug'),\n",
|
||||||
|
" WriteFileTool(name='write_file', description='Write file to disk', args_schema=<class 'langchain.tools.file_management.write.WriteFileInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug'),\n",
|
||||||
|
" ListDirectoryTool(name='list_directory', description='List files and directories in a specified folder', args_schema=<class 'langchain.tools.file_management.list_dir.DirectoryListingInput'>, return_direct=False, verbose=False, callback_manager=<langchain.callbacks.shared.SharedCallbackManager object at 0x1156f4350>, root_dir='/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/tmpxb8c3aug')]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 3,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"tools = FileManagementToolkit(root_dir=str(working_directory.name), selected_tools=[\"read_file\", \"write_file\", \"list_directory\"]).get_tools()\n",
|
||||||
|
"tools"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"'File written successfully to example.txt.'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 4,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"read_tool, write_tool, list_tool = tools\n",
|
||||||
|
"write_tool.run({\"file_path\": \"example.txt\", \"text\": \"Hello World!\"})"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 5,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"'example.txt'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 5,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# List files in the working directory\n",
|
||||||
|
"list_tool.run({})"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3 (ipykernel)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.11.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 4
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
"""Local file management toolkit."""
|
||||||
|
|
||||||
|
from langchain.agents.agent_toolkits.file_management.toolkit import (
|
||||||
|
FileManagementToolkit,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ["FileManagementToolkit"]
|
@ -0,0 +1,61 @@
|
|||||||
|
"""Toolkit for interacting with the local filesystem."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import root_validator
|
||||||
|
|
||||||
|
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||||
|
from langchain.tools import BaseTool
|
||||||
|
from langchain.tools.file_management.copy import CopyFileTool
|
||||||
|
from langchain.tools.file_management.delete import DeleteFileTool
|
||||||
|
from langchain.tools.file_management.file_search import FileSearchTool
|
||||||
|
from langchain.tools.file_management.list_dir import ListDirectoryTool
|
||||||
|
from langchain.tools.file_management.move import MoveFileTool
|
||||||
|
from langchain.tools.file_management.read import ReadFileTool
|
||||||
|
from langchain.tools.file_management.write import WriteFileTool
|
||||||
|
|
||||||
|
_FILE_TOOLS = {
|
||||||
|
tool_cls.__fields__["name"].default: tool_cls
|
||||||
|
for tool_cls in [
|
||||||
|
CopyFileTool,
|
||||||
|
DeleteFileTool,
|
||||||
|
FileSearchTool,
|
||||||
|
MoveFileTool,
|
||||||
|
ReadFileTool,
|
||||||
|
WriteFileTool,
|
||||||
|
ListDirectoryTool,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FileManagementToolkit(BaseToolkit):
|
||||||
|
"""Toolkit for interacting with a Local Files."""
|
||||||
|
|
||||||
|
root_dir: Optional[str] = None
|
||||||
|
"""If specified, all file operations are made relative to root_dir."""
|
||||||
|
selected_tools: Optional[List[str]] = None
|
||||||
|
"""If provided, only provide the selected tools. Defaults to all."""
|
||||||
|
|
||||||
|
@root_validator
|
||||||
|
def validate_tools(cls, values: dict) -> dict:
|
||||||
|
selected_tools = values.get("selected_tools") or []
|
||||||
|
for tool_name in selected_tools:
|
||||||
|
if tool_name not in _FILE_TOOLS:
|
||||||
|
raise ValueError(
|
||||||
|
f"File Tool of name {tool_name} not supported."
|
||||||
|
f" Permitted tools: {list(_FILE_TOOLS)}"
|
||||||
|
)
|
||||||
|
return values
|
||||||
|
|
||||||
|
def get_tools(self) -> List[BaseTool]:
|
||||||
|
"""Get the tools in the toolkit."""
|
||||||
|
allowed_tools = self.selected_tools or _FILE_TOOLS.keys()
|
||||||
|
tools: List[BaseTool] = []
|
||||||
|
for tool in allowed_tools:
|
||||||
|
tool_cls = _FILE_TOOLS[tool]
|
||||||
|
tools.append(tool_cls(root_dir=self.root_dir))
|
||||||
|
return tools
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["FileManagementToolkit"]
|
@ -0,0 +1,19 @@
|
|||||||
|
"""File Management Tools."""
|
||||||
|
|
||||||
|
from langchain.tools.file_management.copy import CopyFileTool
|
||||||
|
from langchain.tools.file_management.delete import DeleteFileTool
|
||||||
|
from langchain.tools.file_management.file_search import FileSearchTool
|
||||||
|
from langchain.tools.file_management.list_dir import ListDirectoryTool
|
||||||
|
from langchain.tools.file_management.move import MoveFileTool
|
||||||
|
from langchain.tools.file_management.read import ReadFileTool
|
||||||
|
from langchain.tools.file_management.write import WriteFileTool
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"CopyFileTool",
|
||||||
|
"DeleteFileTool",
|
||||||
|
"FileSearchTool",
|
||||||
|
"MoveFileTool",
|
||||||
|
"ReadFileTool",
|
||||||
|
"WriteFileTool",
|
||||||
|
"ListDirectoryTool",
|
||||||
|
]
|
@ -0,0 +1,46 @@
|
|||||||
|
import shutil
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from langchain.tools.file_management.utils import (
|
||||||
|
INVALID_PATH_TEMPLATE,
|
||||||
|
BaseFileTool,
|
||||||
|
FileValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FileCopyInput(BaseModel):
|
||||||
|
"""Input for CopyFileTool."""
|
||||||
|
|
||||||
|
source_path: str = Field(..., description="Path of the file to copy")
|
||||||
|
destination_path: str = Field(..., description="Path to save the copied file")
|
||||||
|
|
||||||
|
|
||||||
|
class CopyFileTool(BaseFileTool):
|
||||||
|
name: str = "copy_file"
|
||||||
|
args_schema: Type[BaseModel] = FileCopyInput
|
||||||
|
description: str = "Create a copy of a file in a specified location"
|
||||||
|
|
||||||
|
def _run(self, source_path: str, destination_path: str) -> str:
|
||||||
|
try:
|
||||||
|
source_path_ = self.get_relative_path(source_path)
|
||||||
|
except FileValidationError:
|
||||||
|
return INVALID_PATH_TEMPLATE.format(
|
||||||
|
arg_name="source_path", value=source_path
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
destination_path_ = self.get_relative_path(destination_path)
|
||||||
|
except FileValidationError:
|
||||||
|
return INVALID_PATH_TEMPLATE.format(
|
||||||
|
arg_name="destination_path", value=destination_path
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
shutil.copy2(source_path_, destination_path_, follow_symlinks=False)
|
||||||
|
return f"File copied successfully from {source_path} to {destination_path}."
|
||||||
|
except Exception as e:
|
||||||
|
return "Error: " + str(e)
|
||||||
|
|
||||||
|
async def _arun(self, source_path: str, destination_path: str) -> str:
|
||||||
|
# TODO: Add aiofiles method
|
||||||
|
raise NotImplementedError
|
@ -0,0 +1,39 @@
|
|||||||
|
import os
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from langchain.tools.file_management.utils import (
|
||||||
|
INVALID_PATH_TEMPLATE,
|
||||||
|
BaseFileTool,
|
||||||
|
FileValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FileDeleteInput(BaseModel):
|
||||||
|
"""Input for DeleteFileTool."""
|
||||||
|
|
||||||
|
file_path: str = Field(..., description="Path of the file to delete")
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteFileTool(BaseFileTool):
|
||||||
|
name: str = "file_delete"
|
||||||
|
args_schema: Type[BaseModel] = FileDeleteInput
|
||||||
|
description: str = "Delete a file"
|
||||||
|
|
||||||
|
def _run(self, file_path: str) -> str:
|
||||||
|
try:
|
||||||
|
file_path_ = self.get_relative_path(file_path)
|
||||||
|
except FileValidationError:
|
||||||
|
return INVALID_PATH_TEMPLATE.format(arg_name="file_path", value=file_path)
|
||||||
|
if not file_path_.exists():
|
||||||
|
return f"Error: no such file or directory: {file_path}"
|
||||||
|
try:
|
||||||
|
os.remove(file_path_)
|
||||||
|
return f"File deleted successfully: {file_path}."
|
||||||
|
except Exception as e:
|
||||||
|
return "Error: " + str(e)
|
||||||
|
|
||||||
|
async def _arun(self, file_path: str) -> str:
|
||||||
|
# TODO: Add aiofiles method
|
||||||
|
raise NotImplementedError
|
@ -0,0 +1,55 @@
|
|||||||
|
import fnmatch
|
||||||
|
import os
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from langchain.tools.file_management.utils import (
|
||||||
|
INVALID_PATH_TEMPLATE,
|
||||||
|
BaseFileTool,
|
||||||
|
FileValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FileSearchInput(BaseModel):
|
||||||
|
"""Input for FileSearchTool."""
|
||||||
|
|
||||||
|
dir_path: str = Field(
|
||||||
|
default=".",
|
||||||
|
description="Subdirectory to search in.",
|
||||||
|
)
|
||||||
|
pattern: str = Field(
|
||||||
|
...,
|
||||||
|
description="Unix shell regex, where * matches everything.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FileSearchTool(BaseFileTool):
|
||||||
|
name: str = "file_search"
|
||||||
|
args_schema: Type[BaseModel] = FileSearchInput
|
||||||
|
description: str = (
|
||||||
|
"Recursively search for files in a subdirectory that match the regex pattern"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run(self, pattern: str, dir_path: str = ".") -> str:
|
||||||
|
try:
|
||||||
|
dir_path_ = self.get_relative_path(dir_path)
|
||||||
|
except FileValidationError:
|
||||||
|
return INVALID_PATH_TEMPLATE.format(arg_name="dir_path", value=dir_path)
|
||||||
|
matches = []
|
||||||
|
try:
|
||||||
|
for root, _, filenames in os.walk(dir_path_):
|
||||||
|
for filename in fnmatch.filter(filenames, pattern):
|
||||||
|
absolute_path = os.path.join(root, filename)
|
||||||
|
relative_path = os.path.relpath(absolute_path, dir_path_)
|
||||||
|
matches.append(relative_path)
|
||||||
|
if matches:
|
||||||
|
return "\n".join(matches)
|
||||||
|
else:
|
||||||
|
return f"No files found for pattern {pattern} in directory {dir_path}"
|
||||||
|
except Exception as e:
|
||||||
|
return "Error: " + str(e)
|
||||||
|
|
||||||
|
async def _arun(self, dir_path: str, pattern: str) -> str:
|
||||||
|
# TODO: Add aiofiles method
|
||||||
|
raise NotImplementedError
|
@ -0,0 +1,40 @@
|
|||||||
|
import os
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from langchain.tools.file_management.utils import (
|
||||||
|
INVALID_PATH_TEMPLATE,
|
||||||
|
BaseFileTool,
|
||||||
|
FileValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DirectoryListingInput(BaseModel):
|
||||||
|
"""Input for ListDirectoryTool."""
|
||||||
|
|
||||||
|
dir_path: str = Field(default=".", description="Subdirectory to list.")
|
||||||
|
|
||||||
|
|
||||||
|
class ListDirectoryTool(BaseFileTool):
|
||||||
|
name: str = "list_directory"
|
||||||
|
args_schema: Type[BaseModel] = DirectoryListingInput
|
||||||
|
description: str = "List files and directories in a specified folder"
|
||||||
|
|
||||||
|
def _run(self, dir_path: str = ".") -> str:
|
||||||
|
try:
|
||||||
|
dir_path_ = self.get_relative_path(dir_path)
|
||||||
|
except FileValidationError:
|
||||||
|
return INVALID_PATH_TEMPLATE.format(arg_name="dir_path", value=dir_path)
|
||||||
|
try:
|
||||||
|
entries = os.listdir(dir_path_)
|
||||||
|
if entries:
|
||||||
|
return "\n".join(entries)
|
||||||
|
else:
|
||||||
|
return f"No files found in directory {dir_path}"
|
||||||
|
except Exception as e:
|
||||||
|
return "Error: " + str(e)
|
||||||
|
|
||||||
|
async def _arun(self, dir_path: str) -> str:
|
||||||
|
# TODO: Add aiofiles method
|
||||||
|
raise NotImplementedError
|
@ -0,0 +1,49 @@
|
|||||||
|
import shutil
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from langchain.tools.file_management.utils import (
|
||||||
|
INVALID_PATH_TEMPLATE,
|
||||||
|
BaseFileTool,
|
||||||
|
FileValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FileMoveInput(BaseModel):
|
||||||
|
"""Input for MoveFileTool."""
|
||||||
|
|
||||||
|
source_path: str = Field(..., description="Path of the file to move")
|
||||||
|
destination_path: str = Field(..., description="New path for the moved file")
|
||||||
|
|
||||||
|
|
||||||
|
class MoveFileTool(BaseFileTool):
|
||||||
|
name: str = "move_file"
|
||||||
|
args_schema: Type[BaseModel] = FileMoveInput
|
||||||
|
description: str = "Move or rename a file from one location to another"
|
||||||
|
|
||||||
|
def _run(self, source_path: str, destination_path: str) -> str:
|
||||||
|
try:
|
||||||
|
source_path_ = self.get_relative_path(source_path)
|
||||||
|
except FileValidationError:
|
||||||
|
return INVALID_PATH_TEMPLATE.format(
|
||||||
|
arg_name="source_path", value=source_path
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
destination_path_ = self.get_relative_path(destination_path)
|
||||||
|
except FileValidationError:
|
||||||
|
return INVALID_PATH_TEMPLATE.format(
|
||||||
|
arg_name="destination_path_", value=destination_path_
|
||||||
|
)
|
||||||
|
if not source_path_.exists():
|
||||||
|
return f"Error: no such file or directory {source_path}"
|
||||||
|
try:
|
||||||
|
# shutil.move expects str args in 3.8
|
||||||
|
shutil.move(str(source_path_), destination_path_)
|
||||||
|
return f"File moved successfully from {source_path} to {destination_path}."
|
||||||
|
except Exception as e:
|
||||||
|
return "Error: " + str(e)
|
||||||
|
|
||||||
|
async def _arun(self, source_path: str, destination_path: str) -> str:
|
||||||
|
# TODO: Add aiofiles method
|
||||||
|
raise NotImplementedError
|
@ -0,0 +1,54 @@
|
|||||||
|
"""Test the FileCopy tool."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from langchain.tools.file_management.copy import CopyFileTool
|
||||||
|
from langchain.tools.file_management.utils import (
|
||||||
|
INVALID_PATH_TEMPLATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_copy_file_with_root_dir() -> None:
|
||||||
|
"""Test the FileCopy tool when a root dir is specified."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = CopyFileTool(root_dir=temp_dir)
|
||||||
|
source_file = Path(temp_dir) / "source.txt"
|
||||||
|
destination_file = Path(temp_dir) / "destination.txt"
|
||||||
|
source_file.write_text("Hello, world!")
|
||||||
|
tool.run({"source_path": "source.txt", "destination_path": "destination.txt"})
|
||||||
|
assert source_file.exists()
|
||||||
|
assert destination_file.exists()
|
||||||
|
assert source_file.read_text() == "Hello, world!"
|
||||||
|
assert destination_file.read_text() == "Hello, world!"
|
||||||
|
|
||||||
|
|
||||||
|
def test_copy_file_errs_outside_root_dir() -> None:
|
||||||
|
"""Test the FileCopy tool when a root dir is specified."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = CopyFileTool(root_dir=temp_dir)
|
||||||
|
result = tool.run(
|
||||||
|
{
|
||||||
|
"source_path": "../source.txt",
|
||||||
|
"destination_path": "../destination.txt",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert result == INVALID_PATH_TEMPLATE.format(
|
||||||
|
arg_name="source_path", value="../source.txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_copy_file() -> None:
|
||||||
|
"""Test the FileCopy tool."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = CopyFileTool()
|
||||||
|
source_file = Path(temp_dir) / "source.txt"
|
||||||
|
destination_file = Path(temp_dir) / "destination.txt"
|
||||||
|
source_file.write_text("Hello, world!")
|
||||||
|
tool.run(
|
||||||
|
{"source_path": str(source_file), "destination_path": str(destination_file)}
|
||||||
|
)
|
||||||
|
assert source_file.exists()
|
||||||
|
assert destination_file.exists()
|
||||||
|
assert source_file.read_text() == "Hello, world!"
|
||||||
|
assert destination_file.read_text() == "Hello, world!"
|
@ -0,0 +1,43 @@
|
|||||||
|
"""Test the FileSearch tool."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from langchain.tools.file_management.file_search import FileSearchTool
|
||||||
|
from langchain.tools.file_management.utils import (
|
||||||
|
INVALID_PATH_TEMPLATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_search_with_root_dir() -> None:
|
||||||
|
"""Test the FileSearch tool when a root dir is specified."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = FileSearchTool(root_dir=temp_dir)
|
||||||
|
file_1 = Path(temp_dir) / "file1.txt"
|
||||||
|
file_2 = Path(temp_dir) / "file2.log"
|
||||||
|
file_1.write_text("File 1 content")
|
||||||
|
file_2.write_text("File 2 content")
|
||||||
|
matches = tool.run({"dir_path": ".", "pattern": "*.txt"}).split("\n")
|
||||||
|
assert len(matches) == 1
|
||||||
|
assert Path(matches[0]).name == "file1.txt"
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_search_errs_outside_root_dir() -> None:
|
||||||
|
"""Test the FileSearch tool when a root dir is specified."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = FileSearchTool(root_dir=temp_dir)
|
||||||
|
result = tool.run({"dir_path": "..", "pattern": "*.txt"})
|
||||||
|
assert result == INVALID_PATH_TEMPLATE.format(arg_name="dir_path", value="..")
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_search() -> None:
|
||||||
|
"""Test the FileSearch tool."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = FileSearchTool()
|
||||||
|
file_1 = Path(temp_dir) / "file1.txt"
|
||||||
|
file_2 = Path(temp_dir) / "file2.log"
|
||||||
|
file_1.write_text("File 1 content")
|
||||||
|
file_2.write_text("File 2 content")
|
||||||
|
matches = tool.run({"dir_path": temp_dir, "pattern": "*.txt"}).split("\n")
|
||||||
|
assert len(matches) == 1
|
||||||
|
assert Path(matches[0]).name == "file1.txt"
|
@ -0,0 +1,41 @@
|
|||||||
|
"""Test the DirectoryListing tool."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from langchain.tools.file_management.list_dir import ListDirectoryTool
|
||||||
|
from langchain.tools.file_management.utils import (
|
||||||
|
INVALID_PATH_TEMPLATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_directory_with_root_dir() -> None:
|
||||||
|
"""Test the DirectoryListing tool when a root dir is specified."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = ListDirectoryTool(root_dir=temp_dir)
|
||||||
|
file_1 = Path(temp_dir) / "file1.txt"
|
||||||
|
file_2 = Path(temp_dir) / "file2.txt"
|
||||||
|
file_1.write_text("File 1 content")
|
||||||
|
file_2.write_text("File 2 content")
|
||||||
|
entries = tool.run({"dir_path": "."}).split("\n")
|
||||||
|
assert set(entries) == {"file1.txt", "file2.txt"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_directory_errs_outside_root_dir() -> None:
|
||||||
|
"""Test the DirectoryListing tool when a root dir is specified."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = ListDirectoryTool(root_dir=temp_dir)
|
||||||
|
result = tool.run({"dir_path": ".."})
|
||||||
|
assert result == INVALID_PATH_TEMPLATE.format(arg_name="dir_path", value="..")
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_directory() -> None:
|
||||||
|
"""Test the DirectoryListing tool."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = ListDirectoryTool()
|
||||||
|
file_1 = Path(temp_dir) / "file1.txt"
|
||||||
|
file_2 = Path(temp_dir) / "file2.txt"
|
||||||
|
file_1.write_text("File 1 content")
|
||||||
|
file_2.write_text("File 2 content")
|
||||||
|
entries = tool.run({"dir_path": temp_dir}).split("\n")
|
||||||
|
assert set(entries) == {"file1.txt", "file2.txt"}
|
@ -0,0 +1,52 @@
|
|||||||
|
"""Test the FileMove tool."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from langchain.tools.file_management.move import MoveFileTool
|
||||||
|
from langchain.tools.file_management.utils import (
|
||||||
|
INVALID_PATH_TEMPLATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_move_file_with_root_dir() -> None:
|
||||||
|
"""Test the FileMove tool when a root dir is specified."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = MoveFileTool(root_dir=temp_dir)
|
||||||
|
source_file = Path(temp_dir) / "source.txt"
|
||||||
|
destination_file = Path(temp_dir) / "destination.txt"
|
||||||
|
source_file.write_text("Hello, world!")
|
||||||
|
tool.run({"source_path": "source.txt", "destination_path": "destination.txt"})
|
||||||
|
assert not source_file.exists()
|
||||||
|
assert destination_file.exists()
|
||||||
|
assert destination_file.read_text() == "Hello, world!"
|
||||||
|
|
||||||
|
|
||||||
|
def test_move_file_errs_outside_root_dir() -> None:
|
||||||
|
"""Test the FileMove tool when a root dir is specified."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = MoveFileTool(root_dir=temp_dir)
|
||||||
|
result = tool.run(
|
||||||
|
{
|
||||||
|
"source_path": "../source.txt",
|
||||||
|
"destination_path": "../destination.txt",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert result == INVALID_PATH_TEMPLATE.format(
|
||||||
|
arg_name="source_path", value="../source.txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_move_file() -> None:
|
||||||
|
"""Test the FileMove tool."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
tool = MoveFileTool()
|
||||||
|
source_file = Path(temp_dir) / "source.txt"
|
||||||
|
destination_file = Path(temp_dir) / "destination.txt"
|
||||||
|
source_file.write_text("Hello, world!")
|
||||||
|
tool.run(
|
||||||
|
{"source_path": str(source_file), "destination_path": str(destination_file)}
|
||||||
|
)
|
||||||
|
assert not source_file.exists()
|
||||||
|
assert destination_file.exists()
|
||||||
|
assert destination_file.read_text() == "Hello, world!"
|
@ -0,0 +1,48 @@
|
|||||||
|
"""Test the FileManagementToolkit."""
|
||||||
|
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from langchain.agents.agent_toolkits.file_management.toolkit import (
|
||||||
|
FileManagementToolkit,
|
||||||
|
)
|
||||||
|
from langchain.tools.base import BaseTool
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_toolkit_get_tools() -> None:
|
||||||
|
"""Test the get_tools method of FileManagementToolkit."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
toolkit = FileManagementToolkit(root_dir=temp_dir)
|
||||||
|
tools = toolkit.get_tools()
|
||||||
|
assert len(tools) > 0
|
||||||
|
assert all(isinstance(tool, BaseTool) for tool in tools)
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_toolkit_get_tools_with_selection() -> None:
|
||||||
|
"""Test the get_tools method of FileManagementToolkit with selected_tools."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
toolkit = FileManagementToolkit(
|
||||||
|
root_dir=temp_dir, selected_tools=["read_file", "write_file"]
|
||||||
|
)
|
||||||
|
tools = toolkit.get_tools()
|
||||||
|
assert len(tools) == 2
|
||||||
|
tool_names = [tool.name for tool in tools]
|
||||||
|
assert "read_file" in tool_names
|
||||||
|
assert "write_file" in tool_names
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_toolkit_invalid_tool() -> None:
|
||||||
|
"""Test the FileManagementToolkit with an invalid tool."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
FileManagementToolkit(root_dir=temp_dir, selected_tools=["invalid_tool"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_toolkit_root_dir() -> None:
|
||||||
|
"""Test the FileManagementToolkit root_dir handling."""
|
||||||
|
with TemporaryDirectory() as temp_dir:
|
||||||
|
toolkit = FileManagementToolkit(root_dir=temp_dir)
|
||||||
|
tools = toolkit.get_tools()
|
||||||
|
root_dirs = [tool.root_dir for tool in tools if hasattr(tool, "root_dir")]
|
||||||
|
assert all(root_dir == temp_dir for root_dir in root_dirs)
|
Loading…
Reference in New Issue