__all__ = ['AudioTranscriptionTool', 'BaseImageGenerationTool', 'BaseTool', 'CalculatorTool', 'ComputerTool', 'DateTimeTool', 'EmailTool', 'ExtractionTool', 'FileManagerTool', 'GriptapeCloudToolTool', 'ImageQueryTool', 'InpaintingImageGenerationTool', 'OutpaintingImageGenerationTool', 'PromptImageGenerationTool', 'PromptSummaryTool', 'QueryTool', 'RagTool', 'RestApiTool', 'SqlTool', 'StructureRunTool', 'StructuredOutputTool', 'TextToSpeechTool', 'VariationImageGenerationTool', 'VectorStoreTool', 'WebScraperTool', 'WebSearchTool']module-attribute
Bases:
BaseTool
Source Code in griptape/tools/audio_transcription/tool.py
@define class AudioTranscriptionTool(BaseTool): """A tool that can be used to generate transcriptions from input audio.""" audio_transcription_driver: BaseAudioTranscriptionDriver = field(kw_only=True) audio_loader: AudioLoader = field(default=Factory(lambda: AudioLoader()), kw_only=True) @activity( config={ "description": "This tool can be used to generate transcriptions of audio files on disk.", "schema": Schema({Literal("path", description="The paths to an audio file on disk."): str}), }, ) def transcribe_audio_from_disk(self, params: dict) -> TextArtifact | ErrorArtifact: audio_path = params["values"]["path"] audio_artifact = self.audio_loader.load(audio_path) return self.audio_transcription_driver.run(audio_artifact) @activity( config={ "description": "This tool can be used to generate the transcription of an audio artifact in memory.", "schema": Schema({"schema": Schema({"memory_name": str, "artifact_namespace": str, "artifact_name": str})}), }, ) def transcribe_audio_from_memory(self, params: dict[str, Any]) -> TextArtifact | ErrorArtifact: memory = self.find_input_memory(params["values"]["memory_name"]) artifact_namespace = params["values"]["artifact_namespace"] artifact_name = params["values"]["artifact_name"] if memory is None: return ErrorArtifact("memory not found") audio_artifact = cast( "AudioArtifact", load_artifact_from_memory(memory, artifact_namespace, artifact_name, AudioArtifact), ) return self.audio_transcription_driver.run(audio_artifact)
audio_loader = field(default=Factory(lambda: AudioLoader()), kw_only=True)class-attribute instance-attributeaudio_transcription_driver = field(kw_only=True)class-attribute instance-attribute
transcribe_audio_from_disk(params)
Source Code in griptape/tools/audio_transcription/tool.py
@activity( config={ "description": "This tool can be used to generate transcriptions of audio files on disk.", "schema": Schema({Literal("path", description="The paths to an audio file on disk."): str}), }, ) def transcribe_audio_from_disk(self, params: dict) -> TextArtifact | ErrorArtifact: audio_path = params["values"]["path"] audio_artifact = self.audio_loader.load(audio_path) return self.audio_transcription_driver.run(audio_artifact)
transcribe_audio_from_memory(params)
Source Code in griptape/tools/audio_transcription/tool.py
@activity( config={ "description": "This tool can be used to generate the transcription of an audio artifact in memory.", "schema": Schema({"schema": Schema({"memory_name": str, "artifact_namespace": str, "artifact_name": str})}), }, ) def transcribe_audio_from_memory(self, params: dict[str, Any]) -> TextArtifact | ErrorArtifact: memory = self.find_input_memory(params["values"]["memory_name"]) artifact_namespace = params["values"]["artifact_namespace"] artifact_name = params["values"]["artifact_name"] if memory is None: return ErrorArtifact("memory not found") audio_artifact = cast( "AudioArtifact", load_artifact_from_memory(memory, artifact_namespace, artifact_name, AudioArtifact), ) return self.audio_transcription_driver.run(audio_artifact)
BaseImageGenerationTool
Bases:
ArtifactFileOutputMixin
, BaseTool
Source Code in griptape/tools/base_image_generation_tool.py
@define class BaseImageGenerationTool(ArtifactFileOutputMixin, BaseTool): """A base class for tools that generate images from text prompts.""" PROMPT_DESCRIPTION = "Features and qualities to include in the generated image, descriptive and succinct." NEGATIVE_PROMPT_DESCRIPTION = ( "Features and qualities to avoid in the generated image. Affirmatively describe " "what to avoid, for example: to avoid the color red, include 'red' " "rather than 'no red'." )
NEGATIVE_PROMPT_DESCRIPTION = "Features and qualities to avoid in the generated image. Affirmatively describe what to avoid, for example: to avoid the color red, include 'red' rather than 'no red'."class-attribute instance-attributePROMPT_DESCRIPTION = 'Features and qualities to include in the generated image, descriptive and succinct.'class-attribute instance-attribute
BaseTool
Bases:
ActivityMixin
, SerializableMixin
, RunnableMixin['BaseTool']
, ABC
Attributes
| Name | Type | Description |
|---|---|---|
name | str | Tool name. |
input_memory | Optional[list[TaskMemory]] | TaskMemory available in tool activities. Gets automatically set if None. |
output_memory | Optional[dict[str, list[TaskMemory]]] | TaskMemory that activities write to be default. Gets automatically set if None. |
install_dependencies_on_init | bool | Determines whether dependencies from the tool requirements.txt file are installed in init. |
dependencies_install_directory | Optional[str] | Custom dependency install directory. |
verbose | bool | Determines whether tool operations (such as dependency installation) should be verbose. |
off_prompt | bool | Determines whether tool activity output goes to the output memory. |
Source Code in griptape/tools/base_tool.py
@define class BaseTool(ActivityMixin, SerializableMixin, RunnableMixin["BaseTool"], ABC): """Abstract class for all tools to inherit from for. Attributes: name: Tool name. input_memory: TaskMemory available in tool activities. Gets automatically set if None. output_memory: TaskMemory that activities write to be default. Gets automatically set if None. install_dependencies_on_init: Determines whether dependencies from the tool requirements.txt file are installed in init. dependencies_install_directory: Custom dependency install directory. verbose: Determines whether tool operations (such as dependency installation) should be verbose. off_prompt: Determines whether tool activity output goes to the output memory. """ REQUIREMENTS_FILE = "requirements.txt" name: str = field( default=Factory(lambda self: self.__class__.__name__, takes_self=True), kw_only=True, metadata={"serializable": True}, ) input_memory: Optional[list[TaskMemory]] = field(default=None, kw_only=True, metadata={"serializable": True}) output_memory: Optional[dict[str, list[TaskMemory]]] = field( default=None, kw_only=True, metadata={"serializable": True} ) install_dependencies_on_init: bool = field(default=True, kw_only=True, metadata={"serializable": True}) dependencies_install_directory: Optional[str] = field(default=None, kw_only=True, metadata={"serializable": True}) verbose: bool = field(default=False, kw_only=True, metadata={"serializable": True}) off_prompt: bool = field(default=False, kw_only=True, metadata={"serializable": True}) def __attrs_post_init__(self) -> None: if ( self.install_dependencies_on_init and self.has_requirements and not self.are_requirements_met(self.requirements_path) ): self.install_dependencies(os.environ.copy()) @output_memory.validator # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess] def validate_output_memory(self, _: Attribute, output_memory: dict[str, Optional[list[TaskMemory]]]) -> None: if output_memory: for activity_name, memory_list in output_memory.items(): if not self.find_activity(activity_name): raise ValueError(f"activity {activity_name} doesn't exist") if memory_list is None: raise ValueError(f"memory list for activity '{activity_name}' can't be None") output_memory_names = [memory.name for memory in memory_list] if len(output_memory_names) > len(set(output_memory_names)): raise ValueError(f"memory names have to be unique in activity '{activity_name}' output") @property def requirements_path(self) -> str: return os.path.join(self.abs_dir_path, self.REQUIREMENTS_FILE) @property def abs_file_path(self) -> str: return os.path.abspath(inspect.getfile(self.__class__)) @property def abs_dir_path(self) -> str: return os.path.dirname(self.abs_file_path) @property def has_requirements(self) -> bool: return os.path.exists(self.requirements_path) # This method has to remain a method and can't be decorated with @property because # of the max depth recursion issue in `self.activities`. def schema(self) -> dict: full_schema = Schema(Or(*self.activity_schemas()), description=f"{self.name} action schema.") return full_schema.json_schema(f"{self.name} ToolAction Schema") def activity_schemas(self) -> list[Schema]: schemas = [] for activity in self.activities(): schema_dict: dict[Literal | schema.Optional, Any] = { Literal("name"): self.name, Literal("path", description=self.activity_description(activity)): self.activity_name(activity), } activity_schema = self.activity_schema(activity) # If no schema is defined, we just make `input` optional instead of omitting it. # This works better with lower-end models that may accidentally pass in an empty dict. if activity_schema is None: schema_dict[schema.Optional("input")] = {} else: schema_dict[Literal("input")] = activity_schema.schema schemas.append(Schema(schema_dict)) return schemas def run(self, activity: Callable, subtask: ActionsSubtask, action: ToolAction) -> BaseArtifact: try: output = self.before_run(activity, subtask, action) output = self.try_run(activity, subtask, action, output) output = self.after_run(activity, subtask, action, output) except Exception as e: logging.debug(traceback.format_exc()) output = ErrorArtifact(str(e), exception=e) return output def before_run(self, activity: Callable, subtask: ActionsSubtask, action: ToolAction) -> Optional[dict]: super().before_run() return action.input @observable(tags=["Tool.run()"]) def try_run( self, activity: Callable, subtask: ActionsSubtask, action: ToolAction, value: Optional[dict], ) -> BaseArtifact: activity_result = activity(deepcopy(value)) if isinstance(activity_result, BaseArtifact): result = activity_result else: logging.warning("Activity result is not an artifact; converting result to InfoArtifact") if activity_result is None: result = InfoArtifact("Tool returned an empty value") else: result = InfoArtifact(activity_result) return result def after_run( self, activity: Callable, subtask: ActionsSubtask, action: ToolAction, value: BaseArtifact, ) -> BaseArtifact: super().after_run() if self.output_memory: output_memories = self.output_memory[getattr(activity, "name")] or [] for memory in output_memories: value = memory.process_output(activity, subtask, value) return value return value def validate(self) -> bool: if not os.path.exists(self.requirements_path): raise Exception(f"{self.REQUIREMENTS_FILE} not found") return True def tool_dir(self) -> str: class_file = inspect.getfile(self.__class__) return os.path.dirname(os.path.abspath(class_file)) def install_dependencies(self, env: Optional[dict[str, str]] = None) -> None: env = env or {} command = [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"] if self.dependencies_install_directory is None: command.extend(["-U"]) else: command.extend(["-t", self.dependencies_install_directory]) subprocess.run( command, env=env, cwd=self.tool_dir(), stdout=None if self.verbose else subprocess.DEVNULL, stderr=None if self.verbose else subprocess.DEVNULL, check=False, ) def find_input_memory(self, memory_name: str) -> Optional[TaskMemory]: if self.input_memory: return next((m for m in self.input_memory if m.name == memory_name), None) return None def to_native_tool_name(self, activity: Callable) -> str: """Converts a Tool's name and an Activity into to a native tool name. The native tool name is a combination of the Tool's name and the Activity's name. The Tool's name may only contain letters and numbers, and the Activity's name may only contain letters, numbers, and underscores. Args: activity: Activity to convert Returns: str: Native tool name. """ tool_name = self.name if re.match(r"^[a-zA-Z0-9]+$", tool_name) is None: raise ValueError("Tool name can only contain letters and numbers.") activity_name = self.activity_name(activity) if re.match(r"^[a-zA-Z0-9_]+$", activity_name) is None: raise ValueError("Activity name can only contain letters, numbers, and underscores.") return f"{tool_name}_{activity_name}" def are_requirements_met(self, requirements_path: str) -> bool: requirements = Path(requirements_path).read_text().splitlines() try: for requirement in requirements: importlib.metadata.version(requirement) return True except importlib.metadata.PackageNotFoundError: return False
REQUIREMENTS_FILE = 'requirements.txt'class-attribute instance-attributeabs_dir_pathpropertyabs_file_pathpropertydependencies_install_directory = field(default=None, kw_only=True, metadata={'serializable': True})class-attribute instance-attributehas_requirementspropertyinput_memory = field(default=None, kw_only=True, metadata={'serializable': True})class-attribute instance-attributeinstall_dependencies_on_init = field(default=True, kw_only=True, metadata={'serializable': True})class-attribute instance-attributename = field(default=Factory(lambda self: self.__class__.__name__, takes_self=True), kw_only=True, metadata={'serializable': True})class-attribute instance-attributeoff_prompt = field(default=False, kw_only=True, metadata={'serializable': True})class-attribute instance-attributeoutput_memory = field(default=None, kw_only=True, metadata={'serializable': True})class-attribute instance-attributerequirements_pathpropertyverbose = field(default=False, kw_only=True, metadata={'serializable': True})class-attribute instance-attribute
attrs_post_init()
Source Code in griptape/tools/base_tool.py
def __attrs_post_init__(self) -> None: if ( self.install_dependencies_on_init and self.has_requirements and not self.are_requirements_met(self.requirements_path) ): self.install_dependencies(os.environ.copy())
activity_schemas()
Source Code in griptape/tools/base_tool.py
def activity_schemas(self) -> list[Schema]: schemas = [] for activity in self.activities(): schema_dict: dict[Literal | schema.Optional, Any] = { Literal("name"): self.name, Literal("path", description=self.activity_description(activity)): self.activity_name(activity), } activity_schema = self.activity_schema(activity) # If no schema is defined, we just make `input` optional instead of omitting it. # This works better with lower-end models that may accidentally pass in an empty dict. if activity_schema is None: schema_dict[schema.Optional("input")] = {} else: schema_dict[Literal("input")] = activity_schema.schema schemas.append(Schema(schema_dict)) return schemas
after_run(activity, subtask, action, value)
Source Code in griptape/tools/base_tool.py
def after_run( self, activity: Callable, subtask: ActionsSubtask, action: ToolAction, value: BaseArtifact, ) -> BaseArtifact: super().after_run() if self.output_memory: output_memories = self.output_memory[getattr(activity, "name")] or [] for memory in output_memories: value = memory.process_output(activity, subtask, value) return value return value
are_requirements_met(requirements_path)
Source Code in griptape/tools/base_tool.py
def are_requirements_met(self, requirements_path: str) -> bool: requirements = Path(requirements_path).read_text().splitlines() try: for requirement in requirements: importlib.metadata.version(requirement) return True except importlib.metadata.PackageNotFoundError: return False
before_run(activity, subtask, action)
Source Code in griptape/tools/base_tool.py
def before_run(self, activity: Callable, subtask: ActionsSubtask, action: ToolAction) -> Optional[dict]: super().before_run() return action.input
find_input_memory(memory_name)
Source Code in griptape/tools/base_tool.py
def find_input_memory(self, memory_name: str) -> Optional[TaskMemory]: if self.input_memory: return next((m for m in self.input_memory if m.name == memory_name), None) return None
install_dependencies(env=None)
Source Code in griptape/tools/base_tool.py
def install_dependencies(self, env: Optional[dict[str, str]] = None) -> None: env = env or {} command = [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"] if self.dependencies_install_directory is None: command.extend(["-U"]) else: command.extend(["-t", self.dependencies_install_directory]) subprocess.run( command, env=env, cwd=self.tool_dir(), stdout=None if self.verbose else subprocess.DEVNULL, stderr=None if self.verbose else subprocess.DEVNULL, check=False, )
run(activity, subtask, action)
Source Code in griptape/tools/base_tool.py
def run(self, activity: Callable, subtask: ActionsSubtask, action: ToolAction) -> BaseArtifact: try: output = self.before_run(activity, subtask, action) output = self.try_run(activity, subtask, action, output) output = self.after_run(activity, subtask, action, output) except Exception as e: logging.debug(traceback.format_exc()) output = ErrorArtifact(str(e), exception=e) return output
schema()
Source Code in griptape/tools/base_tool.py
def schema(self) -> dict: full_schema = Schema(Or(*self.activity_schemas()), description=f"{self.name} action schema.") return full_schema.json_schema(f"{self.name} ToolAction Schema")
to_native_tool_name(activity)
Converts a Tool's name and an Activity into to a native tool name.
The native tool name is a combination of the Tool's name and the Activity's name. The Tool's name may only contain letters and numbers, and the Activity's name may only contain letters, numbers, and underscores.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
activity | Callable | Activity to convert | required |
Returns
| Name | Type | Description |
|---|---|---|
str | str | Native tool name. |
Source Code in griptape/tools/base_tool.py
def to_native_tool_name(self, activity: Callable) -> str: """Converts a Tool's name and an Activity into to a native tool name. The native tool name is a combination of the Tool's name and the Activity's name. The Tool's name may only contain letters and numbers, and the Activity's name may only contain letters, numbers, and underscores. Args: activity: Activity to convert Returns: str: Native tool name. """ tool_name = self.name if re.match(r"^[a-zA-Z0-9]+$", tool_name) is None: raise ValueError("Tool name can only contain letters and numbers.") activity_name = self.activity_name(activity) if re.match(r"^[a-zA-Z0-9_]+$", activity_name) is None: raise ValueError("Activity name can only contain letters, numbers, and underscores.") return f"{tool_name}_{activity_name}"
tool_dir()
Source Code in griptape/tools/base_tool.py
def tool_dir(self) -> str: class_file = inspect.getfile(self.__class__) return os.path.dirname(os.path.abspath(class_file))
try_run(activity, subtask, action, value)
Source Code in griptape/tools/base_tool.py
@observable(tags=["Tool.run()"]) def try_run( self, activity: Callable, subtask: ActionsSubtask, action: ToolAction, value: Optional[dict], ) -> BaseArtifact: activity_result = activity(deepcopy(value)) if isinstance(activity_result, BaseArtifact): result = activity_result else: logging.warning("Activity result is not an artifact; converting result to InfoArtifact") if activity_result is None: result = InfoArtifact("Tool returned an empty value") else: result = InfoArtifact(activity_result) return result
validate()
Source Code in griptape/tools/base_tool.py
def validate(self) -> bool: if not os.path.exists(self.requirements_path): raise Exception(f"{self.REQUIREMENTS_FILE} not found") return True
validateoutput_memory(, output_memory)
Source Code in griptape/tools/base_tool.py
@output_memory.validator # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess] def validate_output_memory(self, _: Attribute, output_memory: dict[str, Optional[list[TaskMemory]]]) -> None: if output_memory: for activity_name, memory_list in output_memory.items(): if not self.find_activity(activity_name): raise ValueError(f"activity {activity_name} doesn't exist") if memory_list is None: raise ValueError(f"memory list for activity '{activity_name}' can't be None") output_memory_names = [memory.name for memory in memory_list] if len(output_memory_names) > len(set(output_memory_names)): raise ValueError(f"memory names have to be unique in activity '{activity_name}' output")
CalculatorTool
Bases:
BaseTool
Source Code in griptape/tools/calculator/tool.py
class CalculatorTool(BaseTool): @activity( config={ "description": "Can be used for computing simple numerical or algebraic calculations in Python", "schema": Schema( { Literal( "expression", description="Arithmetic expression parsable in pure Python. Single line only. " "Don't use variables. Don't use any imports or external libraries", ): str, }, ), }, ) def calculate(self, params: dict) -> BaseArtifact: import numexpr # pyright: ignore[reportMissingImports] try: expression = params["values"]["expression"] return TextArtifact(numexpr.evaluate(expression)) except Exception as e: return ErrorArtifact(f"error calculating: {e}")
calculate(params)
Source Code in griptape/tools/calculator/tool.py
@activity( config={ "description": "Can be used for computing simple numerical or algebraic calculations in Python", "schema": Schema( { Literal( "expression", description="Arithmetic expression parsable in pure Python. Single line only. " "Don't use variables. Don't use any imports or external libraries", ): str, }, ), }, ) def calculate(self, params: dict) -> BaseArtifact: import numexpr # pyright: ignore[reportMissingImports] try: expression = params["values"]["expression"] return TextArtifact(numexpr.evaluate(expression)) except Exception as e: return ErrorArtifact(f"error calculating: {e}")
ComputerTool
Bases:
BaseTool
Source Code in griptape/tools/computer/tool.py
@define class ComputerTool(BaseTool): local_workdir: Optional[str] = field(default=None, kw_only=True) container_workdir: str = field(default="/griptape", kw_only=True) env_vars: dict = field(factory=dict, kw_only=True) dockerfile_path: str = field( default=Factory(lambda self: f"{os.path.join(self.tool_dir(), 'resources/Dockerfile')}", takes_self=True), kw_only=True, ) requirements_txt_path: str = field( default=Factory(lambda self: f"{os.path.join(self.tool_dir(), 'resources/requirements.txt')}", takes_self=True), kw_only=True, ) docker_client: DockerClient = field( default=Factory(lambda self: self.default_docker_client(), takes_self=True), kw_only=True, ) _tempdir: Optional[tempfile.TemporaryDirectory] = field(default=None, kw_only=True) def __attrs_post_init__(self) -> None: super().__attrs_post_init__() if self.local_workdir: Path(self.local_workdir).mkdir(parents=True, exist_ok=True) else: self._tempdir = tempfile.TemporaryDirectory() self.local_workdir = self._tempdir.name @docker_client.validator # pyright: ignore[reportAttributeAccessIssue] def validate_docker_client(self, _: Attribute, docker_client: DockerClient) -> None: if not docker_client: raise ValueError("Docker client can't be initialized: make sure the Docker daemon is running") def install_dependencies(self, env: Optional[dict[str, str]] = None) -> None: super().install_dependencies(env) self.remove_existing_container(self.container_name(self)) self.build_image(self) @activity( config={ "description": "Can be used to execute Python code to solve any programmatic tasks and access and analyze" " files in the file system. If you need to use code output use `print` statements. " "You have access to the following external Python libraries: " "{{ _self.dependencies() }}", "schema": Schema( { Literal("code", description="Python code to execute"): str, Literal( "filename", description="name of the file to put the Python code in before executing it", ): str, }, ), }, ) def execute_code(self, params: dict) -> BaseArtifact: code = params["values"]["code"] filename = params["values"]["filename"] return self.execute_code_in_container(filename, code) @activity( config={ "description": "Can be used to execute shell commands in Linux", "schema": Schema({Literal("command", description="shell command to execute"): str}), }, ) def execute_command(self, params: dict) -> BaseArtifact: command = params["values"]["command"] return self.execute_command_in_container(command) def execute_command_in_container(self, command: str) -> BaseArtifact: from docker.models.containers import Container try: binds = {self.local_workdir: {"bind": self.container_workdir, "mode": "rw"}} if self.local_workdir else None container = self.docker_client.containers.run( # pyright: ignore[reportCallIssue] self.image_name(self), environment=self.env_vars, command=command, name=self.container_name(self), volumes=binds, # pyright: ignore[reportArgumentType] According to the [docs](https://docker-py.readthedocs.io/en/stable/containers.html), the type of `volumes` is dict[str, dict[str, str]]. stdout=True, stderr=True, detach=True, ) if isinstance(container, Container): container.wait() stderr = container.logs(stdout=False, stderr=True).decode().strip() stdout = container.logs(stdout=True, stderr=False).decode().strip() container.stop() container.remove() if stderr: return ErrorArtifact(stderr) return TextArtifact(stdout) return ErrorArtifact("error running container") except Exception as e: return ErrorArtifact(f"error executing command: {e}") def execute_code_in_container(self, filename: str, code: str) -> BaseArtifact: container_file_path = os.path.join(self.container_workdir, filename) if self.local_workdir: tempdir = None local_workdir = self.local_workdir else: tempdir = tempfile.TemporaryDirectory() local_workdir = tempdir.name local_file_path = os.path.join(local_workdir, filename) try: Path(local_file_path).write_text(code) return self.execute_command_in_container(f"python {container_file_path}") except Exception as e: return ErrorArtifact(f"error executing code: {e}") finally: if tempdir: tempdir.cleanup() def default_docker_client(self) -> Optional[DockerClient]: import docker try: return docker.from_env() except Exception as e: logging.exception(e) return None def image_name(self, tool: BaseTool) -> str: import stringcase # pyright: ignore[reportMissingImports] return f"{stringcase.snakecase(tool.name)}_image" def container_name(self, tool: BaseTool) -> str: import stringcase # pyright: ignore[reportMissingImports] return f"{stringcase.snakecase(tool.name)}_container" def remove_existing_container(self, name: str) -> None: from docker.errors import NotFound from docker.models.containers import Container try: existing_container = self.docker_client.containers.get(name) if isinstance(existing_container, Container): existing_container.remove(force=True) logging.info("Removed existing container %s", name) except NotFound: pass def build_image(self, tool: BaseTool) -> None: with tempfile.TemporaryDirectory() as temp_dir: shutil.copy(self.dockerfile_path, temp_dir) shutil.copy(self.requirements_txt_path, temp_dir) image = self.docker_client.images.build(path=temp_dir, tag=self.image_name(tool), rm=True, forcerm=True) if isinstance(image, tuple): logging.info("Built image: %s", image[0].short_id) def dependencies(self) -> list[str]: with open(self.requirements_txt_path) as file: return [line.strip() for line in file] def __del__(self) -> None: if self._tempdir: self._tempdir.cleanup()
_tempdir = field(default=None, kw_only=True)class-attribute instance-attributecontainer_workdir = field(default='/griptape', kw_only=True)class-attribute instance-attributedocker_client = field(default=Factory(lambda self: self.default_docker_client(), takes_self=True), kw_only=True)class-attribute instance-attributedockerfile_path = field(default=Factory(lambda self: f'{os.path.join(self.tool_dir(), 'resources/Dockerfile')}', takes_self=True), kw_only=True)class-attribute instance-attributeenv_vars = field(factory=dict, kw_only=True)class-attribute instance-attributelocal_workdir = field(default=None, kw_only=True)class-attribute instance-attributerequirements_txt_path = field(default=Factory(lambda self: f'{os.path.join(self.tool_dir(), 'resources/requirements.txt')}', takes_self=True), kw_only=True)class-attribute instance-attribute
attrs_post_init()
Source Code in griptape/tools/computer/tool.py
def __attrs_post_init__(self) -> None: super().__attrs_post_init__() if self.local_workdir: Path(self.local_workdir).mkdir(parents=True, exist_ok=True) else: self._tempdir = tempfile.TemporaryDirectory() self.local_workdir = self._tempdir.name
del()
Source Code in griptape/tools/computer/tool.py
def __del__(self) -> None: if self._tempdir: self._tempdir.cleanup()
build_image(tool)
Source Code in griptape/tools/computer/tool.py
def build_image(self, tool: BaseTool) -> None: with tempfile.TemporaryDirectory() as temp_dir: shutil.copy(self.dockerfile_path, temp_dir) shutil.copy(self.requirements_txt_path, temp_dir) image = self.docker_client.images.build(path=temp_dir, tag=self.image_name(tool), rm=True, forcerm=True) if isinstance(image, tuple): logging.info("Built image: %s", image[0].short_id)
container_name(tool)
Source Code in griptape/tools/computer/tool.py
def container_name(self, tool: BaseTool) -> str: import stringcase # pyright: ignore[reportMissingImports] return f"{stringcase.snakecase(tool.name)}_container"
default_docker_client()
Source Code in griptape/tools/computer/tool.py
def default_docker_client(self) -> Optional[DockerClient]: import docker try: return docker.from_env() except Exception as e: logging.exception(e) return None
dependencies()
Source Code in griptape/tools/computer/tool.py
def dependencies(self) -> list[str]: with open(self.requirements_txt_path) as file: return [line.strip() for line in file]
execute_code(params)
Source Code in griptape/tools/computer/tool.py
@activity( config={ "description": "Can be used to execute Python code to solve any programmatic tasks and access and analyze" " files in the file system. If you need to use code output use `print` statements. " "You have access to the following external Python libraries: " "{{ _self.dependencies() }}", "schema": Schema( { Literal("code", description="Python code to execute"): str, Literal( "filename", description="name of the file to put the Python code in before executing it", ): str, }, ), }, ) def execute_code(self, params: dict) -> BaseArtifact: code = params["values"]["code"] filename = params["values"]["filename"] return self.execute_code_in_container(filename, code)
execute_code_in_container(filename, code)
Source Code in griptape/tools/computer/tool.py
def execute_code_in_container(self, filename: str, code: str) -> BaseArtifact: container_file_path = os.path.join(self.container_workdir, filename) if self.local_workdir: tempdir = None local_workdir = self.local_workdir else: tempdir = tempfile.TemporaryDirectory() local_workdir = tempdir.name local_file_path = os.path.join(local_workdir, filename) try: Path(local_file_path).write_text(code) return self.execute_command_in_container(f"python {container_file_path}") except Exception as e: return ErrorArtifact(f"error executing code: {e}") finally: if tempdir: tempdir.cleanup()
execute_command(params)
Source Code in griptape/tools/computer/tool.py
@activity( config={ "description": "Can be used to execute shell commands in Linux", "schema": Schema({Literal("command", description="shell command to execute"): str}), }, ) def execute_command(self, params: dict) -> BaseArtifact: command = params["values"]["command"] return self.execute_command_in_container(command)
execute_command_in_container(command)
Source Code in griptape/tools/computer/tool.py
def execute_command_in_container(self, command: str) -> BaseArtifact: from docker.models.containers import Container try: binds = {self.local_workdir: {"bind": self.container_workdir, "mode": "rw"}} if self.local_workdir else None container = self.docker_client.containers.run( # pyright: ignore[reportCallIssue] self.image_name(self), environment=self.env_vars, command=command, name=self.container_name(self), volumes=binds, # pyright: ignore[reportArgumentType] According to the [docs](https://docker-py.readthedocs.io/en/stable/containers.html), the type of `volumes` is dict[str, dict[str, str]]. stdout=True, stderr=True, detach=True, ) if isinstance(container, Container): container.wait() stderr = container.logs(stdout=False, stderr=True).decode().strip() stdout = container.logs(stdout=True, stderr=False).decode().strip() container.stop() container.remove() if stderr: return ErrorArtifact(stderr) return TextArtifact(stdout) return ErrorArtifact("error running container") except Exception as e: return ErrorArtifact(f"error executing command: {e}")
image_name(tool)
Source Code in griptape/tools/computer/tool.py
def image_name(self, tool: BaseTool) -> str: import stringcase # pyright: ignore[reportMissingImports] return f"{stringcase.snakecase(tool.name)}_image"
install_dependencies(env=None)
Source Code in griptape/tools/computer/tool.py
def install_dependencies(self, env: Optional[dict[str, str]] = None) -> None: super().install_dependencies(env) self.remove_existing_container(self.container_name(self)) self.build_image(self)
remove_existing_container(name)
Source Code in griptape/tools/computer/tool.py
def remove_existing_container(self, name: str) -> None: from docker.errors import NotFound from docker.models.containers import Container try: existing_container = self.docker_client.containers.get(name) if isinstance(existing_container, Container): existing_container.remove(force=True) logging.info("Removed existing container %s", name) except NotFound: pass
validatedocker_client(, docker_client)
Source Code in griptape/tools/computer/tool.py
@docker_client.validator # pyright: ignore[reportAttributeAccessIssue] def validate_docker_client(self, _: Attribute, docker_client: DockerClient) -> None: if not docker_client: raise ValueError("Docker client can't be initialized: make sure the Docker daemon is running")
DateTimeTool
Bases:
BaseTool
Source Code in griptape/tools/date_time/tool.py
@define class DateTimeTool(BaseTool): denylist: list[str] = field(default=Factory(lambda: ["get_relative_datetime"]), kw_only=True) @activity(config={"description": "Can be used to return current date and time."}) def get_current_datetime(self) -> BaseArtifact: return TextArtifact(str(datetime.now())) @activity( config={ "description": "Can be used to return a relative date and time.", "schema": schema.Schema( { schema.Literal( "relative_date_string", description='Relative date in English compatible with the dateparser library. For example, "now EST", "20 minutes ago", ' '"in 2 days", "3 months, 1 week and 1 day ago", or "yesterday at 2pm"', ): str, }, ), }, ) def get_relative_datetime(self, params: dict) -> BaseArtifact: from dateparser import parse try: date_string = params["values"]["relative_date_string"] relative_datetime = parse(date_string) if relative_datetime: return TextArtifact(str(relative_datetime)) return ErrorArtifact("invalid date string") except Exception as e: return ErrorArtifact(f"error getting current datetime: {e}") @activity( config={ "description": "Can be used to add a timedelta to a datetime.", "schema": schema.Schema( { schema.Literal( "timedelta_kwargs", description='A dictionary of keyword arguments to pass to the timedelta function. For example, {"days": -1, "hours": 2}', ): dict, schema.Optional( schema.Literal( "iso_datetime", description='Datetime represented as a string in ISO 8601 format. For example, "2021-01-01T00:00:00". Defaults to the current datetime if not provided.', ) ): str, }, ), }, ) def add_timedelta(self, timedelta_kwargs: dict, iso_datetime: Optional[str] = None) -> BaseArtifact: if iso_datetime is None: iso_datetime = datetime.now().isoformat() return TextArtifact((datetime.fromisoformat(iso_datetime) + timedelta(**timedelta_kwargs)).isoformat()) @activity( config={ "description": "Can be used to calculate the difference between two datetimes. The difference is calculated as end_datetime - start_datetime.", "schema": schema.Schema( { schema.Literal( "start_datetime", description='Datetime represented as a string in ISO 8601 format. For example, "2021-01-01T00:00:00"', ): str, schema.Literal( "end_datetime", description='Datetime represented as a string in ISO 8601 format. For example, "2021-01-02T00:00:00"', ): str, } ), } ) def get_datetime_diff(self, start_datetime: str, end_datetime: str) -> BaseArtifact: return TextArtifact(str(datetime.fromisoformat(end_datetime) - datetime.fromisoformat(start_datetime)))
denylist = field(default=Factory(lambda: ['get_relative_datetime']), kw_only=True)class-attribute instance-attribute
add_timedelta(timedelta_kwargs, iso_datetime=None)
Source Code in griptape/tools/date_time/tool.py
@activity( config={ "description": "Can be used to add a timedelta to a datetime.", "schema": schema.Schema( { schema.Literal( "timedelta_kwargs", description='A dictionary of keyword arguments to pass to the timedelta function. For example, {"days": -1, "hours": 2}', ): dict, schema.Optional( schema.Literal( "iso_datetime", description='Datetime represented as a string in ISO 8601 format. For example, "2021-01-01T00:00:00". Defaults to the current datetime if not provided.', ) ): str, }, ), }, ) def add_timedelta(self, timedelta_kwargs: dict, iso_datetime: Optional[str] = None) -> BaseArtifact: if iso_datetime is None: iso_datetime = datetime.now().isoformat() return TextArtifact((datetime.fromisoformat(iso_datetime) + timedelta(**timedelta_kwargs)).isoformat())
get_current_datetime()
Source Code in griptape/tools/date_time/tool.py
@activity(config={"description": "Can be used to return current date and time."}) def get_current_datetime(self) -> BaseArtifact: return TextArtifact(str(datetime.now()))
get_datetime_diff(start_datetime, end_datetime)
Source Code in griptape/tools/date_time/tool.py
@activity( config={ "description": "Can be used to calculate the difference between two datetimes. The difference is calculated as end_datetime - start_datetime.", "schema": schema.Schema( { schema.Literal( "start_datetime", description='Datetime represented as a string in ISO 8601 format. For example, "2021-01-01T00:00:00"', ): str, schema.Literal( "end_datetime", description='Datetime represented as a string in ISO 8601 format. For example, "2021-01-02T00:00:00"', ): str, } ), } ) def get_datetime_diff(self, start_datetime: str, end_datetime: str) -> BaseArtifact: return TextArtifact(str(datetime.fromisoformat(end_datetime) - datetime.fromisoformat(start_datetime)))
get_relative_datetime(params)
Source Code in griptape/tools/date_time/tool.py
@activity( config={ "description": "Can be used to return a relative date and time.", "schema": schema.Schema( { schema.Literal( "relative_date_string", description='Relative date in English compatible with the dateparser library. For example, "now EST", "20 minutes ago", ' '"in 2 days", "3 months, 1 week and 1 day ago", or "yesterday at 2pm"', ): str, }, ), }, ) def get_relative_datetime(self, params: dict) -> BaseArtifact: from dateparser import parse try: date_string = params["values"]["relative_date_string"] relative_datetime = parse(date_string) if relative_datetime: return TextArtifact(str(relative_datetime)) return ErrorArtifact("invalid date string") except Exception as e: return ErrorArtifact(f"error getting current datetime: {e}")
EmailTool
Bases:
BaseTool
Attributes
| Name | Type | Description |
|---|---|---|
username | Optional[str] | Username/email address used to send email via the SMTP protocol and retrieve email via the IMAP protocol. Example: bender@futurama.com. |
password | Optional[str] | Password used to send email via the SMTP protocol and retrieve email via the IMAP protocol. If using gmail, this would be an App Password. |
email_max_retrieve_count | Optional[int] | Used to limit the number of messages retrieved during any given activities. |
smtp_host | Optional[str] | Hostname or url of the SMTP server. Example: smtp.gmail.com. Required when using the send activity. |
smtp_port | Optional[int] | Port of the SMTP server. Example: 465. Required when using the send activity. |
smtp_use_ssl | bool | Whether to use SSL when sending email via the SMTP protocol. |
smtp_user | Optional[str] | Username/email address used to send email via the SMTP protocol. Overrides username for SMTP only. Required when using the send activity. |
smtp_password | Optional[str] | Password to send email via the SMTP protocol. Overrides password for SMTP only. Required when using the send activity. |
imap_url | Optional[str] | Hostname or url of the IMAP server. Example: imap.gmail.com. Required when using the retrieve activity. |
imap_user | Optional[str] | Username/email address used to retrieve email via the IMAP protocol. Overrides username for IMAP only. Required when using the retrieve activity. |
imap_password | Optional[str] | Password to retrieve email via the IMAP protocol. Overrides password for IMAP only. Required when using the retrieve activity. |
mailboxes | Optional[dict[str, Optional[str]]] | Descriptions of mailboxes available for retrieving email via the IMAP protocol. Required when using the retrieve activity. Example: {'INBOX': 'default mailbox for incoming email', 'SENT': 'default mailbox for sent email'} |
email_loader | EmailLoader | Instance of EmailLoader. |
Source Code in griptape/tools/email/tool.py
@define class EmailTool(BaseTool): """Tool for working with email. Attributes: username: Username/email address used to send email via the SMTP protocol and retrieve email via the IMAP protocol. Example: bender@futurama.com. password: Password used to send email via the SMTP protocol and retrieve email via the IMAP protocol. If using gmail, this would be an App Password. email_max_retrieve_count: Used to limit the number of messages retrieved during any given activities. smtp_host: Hostname or url of the SMTP server. Example: smtp.gmail.com. Required when using the `send` activity. smtp_port: Port of the SMTP server. Example: 465. Required when using the `send` activity. smtp_use_ssl: Whether to use SSL when sending email via the SMTP protocol. smtp_user: Username/email address used to send email via the SMTP protocol. Overrides username for SMTP only. Required when using the `send` activity. smtp_password: Password to send email via the SMTP protocol. Overrides password for SMTP only. Required when using the `send` activity. imap_url: Hostname or url of the IMAP server. Example: imap.gmail.com. Required when using the `retrieve` activity. imap_user: Username/email address used to retrieve email via the IMAP protocol. Overrides username for IMAP only. Required when using the `retrieve` activity. imap_password: Password to retrieve email via the IMAP protocol. Overrides password for IMAP only. Required when using the `retrieve` activity. mailboxes: Descriptions of mailboxes available for retrieving email via the IMAP protocol. Required when using the `retrieve` activity. Example: {'INBOX': 'default mailbox for incoming email', 'SENT': 'default mailbox for sent email'} email_loader: Instance of `EmailLoader`. """ username: Optional[str] = field(default=None, kw_only=True) password: Optional[str] = field(default=None, kw_only=True) email_max_retrieve_count: Optional[int] = field(default=None, kw_only=True) smtp_host: Optional[str] = field(default=None, kw_only=True) smtp_port: Optional[int] = field(default=None, kw_only=True) smtp_use_ssl: bool = field(default=True, kw_only=True) smtp_user: Optional[str] = field(default=Factory(lambda self: self.username, takes_self=True), kw_only=True) smtp_password: Optional[str] = field(default=Factory(lambda self: self.password, takes_self=True), kw_only=True) imap_url: Optional[str] = field(default=None, kw_only=True) imap_user: Optional[str] = field(default=Factory(lambda self: self.username, takes_self=True), kw_only=True) imap_password: Optional[str] = field(default=Factory(lambda self: self.password, takes_self=True), kw_only=True) mailboxes: Optional[dict[str, Optional[str]]] = field(default=None, kw_only=True) email_loader: EmailLoader = field( default=Factory( lambda self: EmailLoader(imap_url=self.imap_url, username=self.imap_user, password=self.imap_password), takes_self=True, ), kw_only=True, ) @activity( config={ "description": "Can be used to retrieve emails." "{% if _self.mailboxes %} Available mailboxes: {{ _self.mailboxes }}{% endif %}", "schema": Schema( { Literal("label", description="Label to retrieve emails from such as 'INBOX' or 'SENT'"): str, schema.Optional( Literal("key", description="Optional key for filtering such as 'FROM' or 'SUBJECT'"), ): str, schema.Optional( Literal("search_criteria", description="Optional search criteria to filter emails by key"), ): str, schema.Optional(Literal("max_count", description="Optional max email count")): int, }, ), }, ) def retrieve(self, params: dict) -> ListArtifact | ErrorArtifact: if self.mailboxes is None: return ErrorArtifact("mailboxes is required") values = params["values"] max_count = int(values["max_count"]) if values.get("max_count") is not None else self.email_max_retrieve_count return self.email_loader.load( EmailLoader.EmailQuery( label=values["label"], key=values.get("key"), search_criteria=values.get("search_criteria"), max_count=max_count, ), ) @activity( config={ "description": "Can be used to send emails", "schema": Schema( { Literal("to", description="Recipient's email address"): str, Literal("subject", description="Email subject"): str, Literal("body", description="Email body"): str, }, ), }, ) def send(self, params: dict) -> InfoArtifact | ErrorArtifact: values = params["values"] if self.smtp_user is None: return ErrorArtifact("smtp_user is required") if self.smtp_password is None: return ErrorArtifact("smtp_password is required") if self.smtp_host is None: return ErrorArtifact("smtp_host is required") if self.smtp_port is None: return ErrorArtifact("smtp_port is required") msg = MIMEText(values["body"]) msg["Subject"] = values["subject"] msg["From"] = self.smtp_user msg["To"] = values["to"] try: with self._create_smtp_client(self.smtp_host, self.smtp_port) as client: client.login(self.smtp_user, self.smtp_password) client.sendmail(msg["From"], [msg["To"]], msg.as_string()) return InfoArtifact("email was successfully sent") except Exception as e: logging.exception(e) return ErrorArtifact(f"error sending email: {e}") def _create_smtp_client(self, smtp_host: str, smtp_port: int) -> smtplib.SMTP | smtplib.SMTP_SSL: if self.smtp_use_ssl: return smtplib.SMTP_SSL(smtp_host, smtp_port) return smtplib.SMTP(smtp_host, smtp_port)
email_loader = field(default=Factory(lambda self: EmailLoader(imap_url=self.imap_url, username=self.imap_user, password=self.imap_password), takes_self=True), kw_only=True)class-attribute instance-attributeemail_max_retrieve_count = field(default=None, kw_only=True)class-attribute instance-attributeimap_password = field(default=Factory(lambda self: self.password, takes_self=True), kw_only=True)class-attribute instance-attributeimap_url = field(default=None, kw_only=True)class-attribute instance-attributeimap_user = field(default=Factory(lambda self: self.username, takes_self=True), kw_only=True)class-attribute instance-attributemailboxes = field(default=None, kw_only=True)class-attribute instance-attributepassword = field(default=None, kw_only=True)class-attribute instance-attributesmtp_host = field(default=None, kw_only=True)class-attribute instance-attributesmtp_password = field(default=Factory(lambda self: self.password, takes_self=True), kw_only=True)class-attribute instance-attributesmtp_port = field(default=None, kw_only=True)class-attribute instance-attributesmtp_use_ssl = field(default=True, kw_only=True)class-attribute instance-attributesmtp_user = field(default=Factory(lambda self: self.username, takes_self=True), kw_only=True)class-attribute instance-attributeusername = field(default=None, kw_only=True)class-attribute instance-attribute
_create_smtp_client(smtp_host, smtp_port)
Source Code in griptape/tools/email/tool.py
def _create_smtp_client(self, smtp_host: str, smtp_port: int) -> smtplib.SMTP | smtplib.SMTP_SSL: if self.smtp_use_ssl: return smtplib.SMTP_SSL(smtp_host, smtp_port) return smtplib.SMTP(smtp_host, smtp_port)
retrieve(params)
Source Code in griptape/tools/email/tool.py
@activity( config={ "description": "Can be used to retrieve emails." "{% if _self.mailboxes %} Available mailboxes: {{ _self.mailboxes }}{% endif %}", "schema": Schema( { Literal("label", description="Label to retrieve emails from such as 'INBOX' or 'SENT'"): str, schema.Optional( Literal("key", description="Optional key for filtering such as 'FROM' or 'SUBJECT'"), ): str, schema.Optional( Literal("search_criteria", description="Optional search criteria to filter emails by key"), ): str, schema.Optional(Literal("max_count", description="Optional max email count")): int, }, ), }, ) def retrieve(self, params: dict) -> ListArtifact | ErrorArtifact: if self.mailboxes is None: return ErrorArtifact("mailboxes is required") values = params["values"] max_count = int(values["max_count"]) if values.get("max_count") is not None else self.email_max_retrieve_count return self.email_loader.load( EmailLoader.EmailQuery( label=values["label"], key=values.get("key"), search_criteria=values.get("search_criteria"), max_count=max_count, ), )
send(params)
Source Code in griptape/tools/email/tool.py
@activity( config={ "description": "Can be used to send emails", "schema": Schema( { Literal("to", description="Recipient's email address"): str, Literal("subject", description="Email subject"): str, Literal("body", description="Email body"): str, }, ), }, ) def send(self, params: dict) -> InfoArtifact | ErrorArtifact: values = params["values"] if self.smtp_user is None: return ErrorArtifact("smtp_user is required") if self.smtp_password is None: return ErrorArtifact("smtp_password is required") if self.smtp_host is None: return ErrorArtifact("smtp_host is required") if self.smtp_port is None: return ErrorArtifact("smtp_port is required") msg = MIMEText(values["body"]) msg["Subject"] = values["subject"] msg["From"] = self.smtp_user msg["To"] = values["to"] try: with self._create_smtp_client(self.smtp_host, self.smtp_port) as client: client.login(self.smtp_user, self.smtp_password) client.sendmail(msg["From"], [msg["To"]], msg.as_string()) return InfoArtifact("email was successfully sent") except Exception as e: logging.exception(e) return ErrorArtifact(f"error sending email: {e}")
ExtractionTool
Attributes
| Name | Type | Description |
|---|---|---|
extraction_engine | BaseExtractionEngine | ExtractionEngine. |
Source Code in griptape/tools/extraction/tool.py
@define(kw_only=True) class ExtractionTool(BaseTool, RuleMixin): """Tool for using an Extraction Engine. Attributes: extraction_engine: `ExtractionEngine`. """ extraction_engine: BaseExtractionEngine = field() @activity( config={ "description": "Can be used extract structured text from data.", "schema": Schema( { Literal("data"): Or( str, Schema( { "memory_name": str, "artifact_namespace": str, } ), ), } ), }, ) def extract(self, params: dict) -> ListArtifact | InfoArtifact | ErrorArtifact: data = params["values"]["data"] if isinstance(data, str): artifacts = ListArtifact([TextArtifact(data)]) else: memory = self.find_input_memory(data["memory_name"]) artifact_namespace = data["artifact_namespace"] if memory is not None: artifacts = memory.load_artifacts(artifact_namespace) else: return ErrorArtifact("memory not found") return self.extraction_engine.extract_artifacts(artifacts)
extraction_engine = field()class-attribute instance-attribute
extract(params)
Source Code in griptape/tools/extraction/tool.py
@activity( config={ "description": "Can be used extract structured text from data.", "schema": Schema( { Literal("data"): Or( str, Schema( { "memory_name": str, "artifact_namespace": str, } ), ), } ), }, ) def extract(self, params: dict) -> ListArtifact | InfoArtifact | ErrorArtifact: data = params["values"]["data"] if isinstance(data, str): artifacts = ListArtifact([TextArtifact(data)]) else: memory = self.find_input_memory(data["memory_name"]) artifact_namespace = data["artifact_namespace"] if memory is not None: artifacts = memory.load_artifacts(artifact_namespace) else: return ErrorArtifact("memory not found") return self.extraction_engine.extract_artifacts(artifacts)
FileManagerTool
Bases:
BaseTool
Attributes
| Name | Type | Description |
|---|---|---|
file_manager_driver | BaseFileManagerDriver | File Manager Driver to use to list, load, and save files. |