import importlib import os from pathlib import Path from typing import IO, Any import yaml class Loader(yaml.SafeLoader): """ A custom YAML loader that adds support for various custom tags: - !include: includes a YAML file as a subdocument - !prompt: returns a prompt class based on the specified string - !tool: returns a tool class based on the specified string - !env: returns the value of an environment variable - !file: returns the contents of a file """ def __init__(self, stream: IO[Any]) -> None: """ Initializes a new instance of the Loader class. :param stream: The stream to load YAML from. :type stream: IOBase """ self._root = Path(stream.name).resolve().parent super(Loader, self).__init__(stream) self.add_constructor("!include", Loader.include) self.add_constructor("!prompt", Loader.prompt) self.add_constructor("!tool", Loader.tool) self.add_constructor("!env", Loader.env) self.add_constructor("!file", Loader.file) def include(self, node: yaml.Node) -> Any: """ Loads a YAML file from a path relative to the current file. Use this tag to include other agent configs as plugins. :param node: The YAML node to be loaded. :type node: yaml.Node :return: The loaded YAML file. :rtype: Any """ filename = Path(self.construct_scalar(node)) if not filename.is_absolute(): filename = self._root / filename with open(filename, 'r') as f: return yaml.load(f, Loader) def prompt(self, node: yaml.Node) -> Any: """ Returns a PromptTemplate class based on the specified string. :param node: The YAML node representing the prompt string. :type node: yaml.Node :return: The prompt class. :rtype: type :raises AssertionError: If the resolved prompt class is not a subclass of PromptTemplate. """ from ..prompt import PromptTemplate, SimpleReactPrompt, ZeroShotReactPrompt prompt = self.construct_scalar(node) if '.' in prompt: _path = prompt.split('.') module = importlib.import_module('.'.join(_path[:-1])) prompt_cls = getattr(module, _path[-1]) else: prompt_cls = eval(prompt) assert issubclass(prompt_cls.__class__, PromptTemplate) return prompt_cls def tool(self, node: yaml.Node) -> Any: """ Loads a Custom BaseTool class from a path relative to the current file. :param node: The YAML node to be loaded. :type node: yaml.Node :return: The loaded BaseTool class. :rtype: Any """ from ..tools import BaseTool, PythonSandBoxTool tool = self.construct_scalar(node) if '.' in tool: _path = tool.split('.') module = importlib.import_module('.'.join(_path[:-1])) tool_cls = getattr(module, _path[-1]) else: tool_cls = eval(tool) assert issubclass(tool_cls, BaseTool) return tool_cls def env(self, node: yaml.Node) -> Any: """ Loads an environment variable from the current environment, defaults to an empty string if the variable is not set. :param node: The YAML node to be loaded. :type node: yaml.Node :return: The loaded environment variable. :rtype: Any """ return os.environ.get(self.construct_scalar(node), "") def file(self, node: yaml.Node) -> Any: """ Loads any readable file from a path relative to the current file. :param node: The YAML node to be loaded. :type node: yaml.Node :return: The loaded file. :rtype: Any """ filename = Path(self.construct_scalar(node)) if not filename.is_absolute(): filename = self._root / filename with open(filename, 'r') as f: return f.read().strip()