diff --git a/__init__.py b/__init__.py index d18fc28..87c560a 100644 --- a/__init__.py +++ b/__init__.py @@ -11,7 +11,7 @@ import os from dotenv import load_dotenv # Load the griptape_config.json data -from .nodes.agent import CreateAgent, ExpandAgent +from .nodes.agent import CreateAgent, ExpandAgent, RunAgent from .nodes.combine_nodes import MergeTexts, ToolList from .nodes.config_nodes import ( gtUIAmazonBedrockStructureConfig, @@ -32,9 +32,7 @@ from .nodes.image_nodes import ( ) from .nodes.rules import gtUIRule from .nodes.tasks import ( - gtUICsvExtractionTask, gtUIImageQueryTask, - gtUIJsonExtractionTask, gtUIPromptImageGenerationTask, gtUIPromptImageVariationTask, gtUIPromptTask, @@ -53,6 +51,7 @@ from .nodes.tools import ( gtUIFileManager, gtUIKnowledgeBaseTool, gtUIWebScraper, + gtUIWebSearch, ) from .py.griptape_config import ( load_and_prepare_config, @@ -92,6 +91,7 @@ NODE_CLASS_MAPPINGS = { "gtUILeonardoImageGenerationDriver": gtUILeonardoImageGenerationDriver, "gtUIOutputStringNode": gtUIOutputStringNode, "gtUIOutputImageNode": gtUIOutputImageNode, + "RunAgent": RunAgent, "CreateAgent": CreateAgent, "ExpandAgent": ExpandAgent, "gtUIOpenAiStructureConfig": gtUIOpenAiStructureConfig, @@ -108,6 +108,7 @@ NODE_CLASS_MAPPINGS = { "ToolTask": gtUIToolTask, "ToolkitTask": gtUIToolkitTask, "gtUIKnowledgeBaseTool": gtUIKnowledgeBaseTool, + "gtUIWebSearch": gtUIWebSearch, "ImageQueryTask": gtUIImageQueryTask, "PromptImageGenerationTask": gtUIPromptImageGenerationTask, "PromptImageVariationTask": gtUIPromptImageVariationTask, @@ -122,6 +123,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "gtUITextToClipEncode": "Griptape Convert: Text to CLIP Encode", "gtUIFetchImage": "Griptape Load: Image From URL", "CreateAgent": "Griptape Create: Agent", + "RunAgent": "Griptape Run: Agent", "PromptImageGenerationTask": "Griptape Create: Image from Text", "PromptImageVariationTask": "Griptape Create: Image Variation", "Rule": "Griptape Create: Rules", @@ -146,6 +148,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "WebScraper": "Griptape Tool: WebScraper", "gtUIFileManager": "Griptape Tool: FileManager", "gtUIKnowledgeBaseTool": "Griptape Tool: Griptape Cloud KnowledgeBase", + "gtUIWebSearch": "Griptape Tool: WebSearch", "ToolList": "Griptape Combine: Tool List", "MergeTexts": "Griptape Combine: Merge Texts", "EnvironmentConfig": "Griptape Config: Environment Variables", diff --git a/griptape_config.json.default b/griptape_config.json.default index 827a545..6a4c9e5 100644 --- a/griptape_config.json.default +++ b/griptape_config.json.default @@ -2,6 +2,7 @@ "env": { "OPENAI_API_KEY": "", "GOOGLE_API_KEY": "", + "GOOGLE_API_SEARCH_ID": "", "AWS_ACCESS_KEY_ID": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_DEFAULT_REGION": "us-east-1", diff --git a/nodes/agent.py b/nodes/agent.py index d0d699a..2356d71 100644 --- a/nodes/agent.py +++ b/nodes/agent.py @@ -44,6 +44,7 @@ class CreateAgent(BaseAgent): def run( self, STRING, + agent=None, config=None, input_string=None, tools=[], @@ -62,7 +63,8 @@ class CreateAgent(BaseAgent): agent_rulesets = [] for ruleset in rulesets: agent_rulesets.append(ruleset) - agent = gtAgent(config=config, tools=agent_tools, rulesets=agent_rulesets) + if not agent: + agent = gtAgent(config=config, tools=agent_tools, rulesets=agent_rulesets) # Run the agent if there's a prompt if input_string or STRING not in [default_prompt, ""]: diff --git a/nodes/duckduckgo_client/__init__.py b/nodes/duckduckgo_client/__init__.py new file mode 100644 index 0000000..c35025f --- /dev/null +++ b/nodes/duckduckgo_client/__init__.py @@ -0,0 +1,3 @@ +from .tool import DuckDuckGoTool + +__all__ = ["DuckDuckGoTool"] diff --git a/nodes/duckduckgo_client/manifest.yml b/nodes/duckduckgo_client/manifest.yml new file mode 100644 index 0000000..3d4a083 --- /dev/null +++ b/nodes/duckduckgo_client/manifest.yml @@ -0,0 +1,5 @@ +version: "v1" +name: Duck Duck Go Client +description: client for duckduckgo search engine +contact_email: hello@griptape.ai +legal_info_url: https://www.example.com/legal diff --git a/nodes/duckduckgo_client/requirements.txt b/nodes/duckduckgo_client/requirements.txt new file mode 100644 index 0000000..83ca02f --- /dev/null +++ b/nodes/duckduckgo_client/requirements.txt @@ -0,0 +1,3 @@ +duckduckgo_search +griptape +python_dotenv \ No newline at end of file diff --git a/nodes/duckduckgo_client/tool.py b/nodes/duckduckgo_client/tool.py new file mode 100644 index 0000000..205fc46 --- /dev/null +++ b/nodes/duckduckgo_client/tool.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +import schema +from attr import define +from duckduckgo_search import DDGS +from griptape.artifacts import ErrorArtifact, ListArtifact, TextArtifact +from griptape.tools import BaseTool +from griptape.utils.decorators import activity +from schema import Literal, Schema + + +@define +class DuckDuckGoTool(BaseTool): + @activity( + config={ + "description": "Can be used to perform a text search on DuckDuckGo", + "schema": Schema( + { + Literal("keywords", description="The keywords to search for"): str, + schema.Optional( + "max_results", + default=None, + description="The maximum number of results to return", + ): int, + schema.Optional( + "region", + default="wt-wt", + description="The region to search in (wt-wt, us-en, uk-en, ru-ru, etc)", + ): str, + schema.Optional( + "safe_search", + default="moderate", + description="The safe search level to use (on, moderate, off)", + ): str, + schema.Optional( + "timelimit", + default=None, + description="The time limit for the search (d, w, m, y)", + ): str, + schema.Optional( + "backend", + default="api", + description="The backend to use for the search (api, html, lite)", + ): str, + }, + ), + } + ) + def text(self, params: dict) -> ListArtifact | ErrorArtifact: + keyword_value = params["values"].get("keywords") + max_results = params["values"].get("max_results") + region = params["values"].get("region") + safe_search = params["values"].get("safe_search") + timelimit = params["values"].get("timelimit") + backend = params["values"].get("backend") + + # Initialize a dictionary with the keyword argument + params = {} + + # Add parameters to the dictionary only if they are not None + if max_results is not None: + params["max_results"] = max_results + if region is not None: + params["region"] = region + if safe_search is not None: + params["safe_search"] = safe_search + if timelimit is not None: + params["timelimit"] = timelimit + if backend is not None: + params["backend"] = backend + try: + results = DDGS().text(keyword_value, **params) + return ListArtifact([TextArtifact(result) for result in results]) + + except Exception as e: + return ErrorArtifact(str(e)) + + @activity( + config={ + "description": "Can be used to get instant answers from DuckDuckGo", + "schema": Schema( + { + Literal("keywords", description="The keywords to search for"): str, + }, + ), + } + ) + def answers(self, params: dict) -> ListArtifact | ErrorArtifact: + keyword_value = params["values"].get("keywords") + + try: + results = DDGS().answers(keyword_value) + return ListArtifact([TextArtifact(result) for result in results]) + + except Exception as e: + return ErrorArtifact(str(e)) diff --git a/nodes/tasks.py b/nodes/tasks.py index c2be328..832ce8d 100644 --- a/nodes/tasks.py +++ b/nodes/tasks.py @@ -2,6 +2,7 @@ import base64 import os from textwrap import dedent +import folder_paths from griptape.drivers import ( AmazonBedrockImageQueryDriver, AnthropicImageQueryDriver, @@ -28,8 +29,6 @@ from griptape.tasks import ( ) from schema import Schema -import folder_paths - from ..py.griptape_config import get_config from .base_image_task import gtUIBaseImageTask from .base_task import gtUIBaseTask @@ -328,7 +327,7 @@ class gtUIToolTask(gtUIBaseTask): # Update optional inputs to include 'tool' and adjust others as necessary inputs["optional"].update( { - "tool": ("TOOL",), + "tool": ("TOOL_LIST",), } ) return inputs @@ -336,7 +335,7 @@ class gtUIToolTask(gtUIBaseTask): def run( self, STRING, - tool=None, + tool=[], input_string=None, agent=None, ): @@ -344,10 +343,25 @@ class gtUIToolTask(gtUIBaseTask): agent = Agent() prompt_text = self.get_prompt_text(STRING, input_string) - if tool: - task = ToolTask(prompt_text, tool=tool) + + # Figure out what tool to use. + # If none are provided, check the agent for tools + # if the agent doesn't have any, then we won't use any tools. + agent_tools = [] + agent_tools = agent.tools + + if len(tool) > 0: + agent_tool = tool[0] + elif len(agent_tools) > 0: + agent_tool = agent_tools[0] + else: + agent_tool = None + + if agent_tool: + task = ToolTask(prompt_text, tool=agent_tool) else: task = PromptTask(prompt_text) + try: agent.add_task(task) except Exception as e: @@ -376,6 +390,8 @@ class gtUIToolkitTask(gtUIBaseTask): input_string=None, agent=None, ): + prompt_text = self.get_prompt_text(STRING, input_string) + if len(tools) == 0: return super().run(STRING, input_string, agent) @@ -383,8 +399,6 @@ class gtUIToolkitTask(gtUIBaseTask): if not agent: agent = Agent() - prompt_text = self.get_prompt_text(STRING, input_string) - task = ToolkitTask(prompt_text, tools=tools) try: agent.add_task(task) diff --git a/nodes/tools.py b/nodes/tools.py index 48229d7..89e3064 100644 --- a/nodes/tools.py +++ b/nodes/tools.py @@ -13,6 +13,7 @@ from griptape.tools import ( from ..py.griptape_config import get_config from .base_tool import gtUIBaseTool +from .duckduckgo_client import DuckDuckGoTool class gtUIFileManager(gtUIBaseTool): @@ -43,6 +44,18 @@ class gtUICalculator(gtUIBaseTool): return ([tool],) +class gtUIWebSearch(gtUIBaseTool): + """ + The Griptape Web Search Tool + """ + + def create(self, off_prompt): + tool = DuckDuckGoTool( + off_prompt=off_prompt, + ) + return ([tool],) + + # class gtUIImageQueryClient(gtUIBaseTool): # """ # The Griptape Image Query Tool diff --git a/requirements.txt b/requirements.txt index f8ef66d..7289e2b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ griptape[all] -python-dotenv \ No newline at end of file +python-dotenv +duckduckgo_search \ No newline at end of file