From c0a669d04b528d7b723df3f3f56377f4bef59976 Mon Sep 17 00:00:00 2001 From: Jason Schleifer Date: Tue, 31 Dec 2024 07:30:39 +1300 Subject: [PATCH 1/4] fixed cls and incorrect expression setting --- nodes/drivers/gtUIBlackForestImageGenerationDriver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nodes/drivers/gtUIBlackForestImageGenerationDriver.py b/nodes/drivers/gtUIBlackForestImageGenerationDriver.py index 9ccba1a..e814081 100644 --- a/nodes/drivers/gtUIBlackForestImageGenerationDriver.py +++ b/nodes/drivers/gtUIBlackForestImageGenerationDriver.py @@ -28,7 +28,7 @@ class gtUIBlackForestImageGenerationDriver(gtUIBaseImageGenerationDriver): DESCRIPTION = "Black Forest Image Generation Driver to use Flux models." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { @@ -183,7 +183,7 @@ class gtUIBlackForestImageGenerationDriver(gtUIBaseImageGenerationDriver): if model == "flux-pro-1.0-fill": if guidance is not None and guidance > 0: if guidance < 1.5: - guidance == 1.5 + guidance = 1.5 if guidance > 100: raise ValueError( f"When using {model}, guidance must be between 1.5 and 100." @@ -193,14 +193,14 @@ class gtUIBlackForestImageGenerationDriver(gtUIBaseImageGenerationDriver): if model in ["flux-pro-1.0-canny", "flux-pro-1.0-depth"]: if guidance is not None and guidance > 0: if guidance < 1: - guidance == 1 + guidance = 1 params["guidance"] = float(guidance) # Dev/Pro specific settings if model in ["flux-dev", "flux-pro"]: if guidance is not None and guidance > 0: if guidance < 1.5: - guidance == 1.5 + guidance = 1.5 if guidance > 5: raise ValueError( f"When using {model}, guidance must be between 1.5 and 5." From 4c67d7c35221ba825ccaab9f842dd7205eb33043 Mon Sep 17 00:00:00 2001 From: Jason Schleifer Date: Tue, 31 Dec 2024 11:22:20 +1300 Subject: [PATCH 2/4] major update to include spell checking, type checking, and removed unused code. --- Makefile | 18 +++ README.md | 14 +- __init__.py | 56 +++---- examples/README.md | 2 +- js/ColorNodes.js | 2 +- js/gtUIUtils.js | 2 +- js/nodeFixes.js | 54 ++++--- nodes/Separator.py | 2 +- nodes/agent/BaseAgent.py | 9 +- nodes/agent/RunAgent.py | 3 +- nodes/agent/gtUICreateAgentFromConfig.py | 2 +- nodes/agent/gtUIReplaceRulesetsOnAgent.py | 2 +- nodes/agent/gtUIReplaceToolsOnAgent.py | 2 +- nodes/agent/gtUIRunAgent.py | 142 ------------------ nodes/agent/gtUISetDefaultAgent.py | 5 +- nodes/combine/MergeTexts.py | 2 +- nodes/combine/RulesList.py | 2 +- nodes/combine/ToolList.py | 2 +- nodes/combine/gtUIMergeInputs.py | 2 +- nodes/combine/gtUIModuleList.py | 2 +- .../{depricated => deprecated}/__init__.py | 0 .../gtUIAmazonBedrockStructureConfig.py | 6 +- .../gtUIAnthropicStructureConfig.py | 2 +- .../gtUIAzureOpenAiStructureConfig.py | 23 ++- .../gtUIGoogleStructureConfig.py | 2 +- .../gtUIHuggingFaceStructureConfig.py | 2 +- .../gtUILMStudioStructureConfig.py | 2 +- .../gtUIOllamaStructureConfig.py | 2 +- .../gtUIOpenAiCompatibleConfig.py | 2 +- .../gtUIOpenAiStructureConfig.py | 2 +- .../config/gtUIAmazonBedrockDriversConfig.py | 6 +- nodes/config/gtUIAmazonBedrockSession.py | 96 ++++++------ nodes/config/gtUIAnthropicDriversConfig.py | 2 +- nodes/config/gtUIAzureOpenAiDriversConfig.py | 2 +- nodes/config/gtUIBaseConfig.py | 8 +- nodes/config/gtUIBaseDriversConfig.py | 6 +- nodes/config/gtUICohereDriversConfig.py | 2 +- nodes/config/gtUIEnvConfig.py | 2 +- nodes/config/gtUIGoogleDriversConfig.py | 10 +- nodes/config/gtUIHuggingFaceDriversConfig.py | 3 +- nodes/config/gtUILMStudioDriversConfig.py | 3 +- nodes/config/gtUIOllamaDriversConfig.py | 3 +- .../gtUIOpenAiCompatibleDriversConfig.py | 2 +- nodes/config/gtUIOpenAiDriversConfig.py | 2 +- nodes/config/gtUIStructureConfig.py | 2 +- nodes/convert/gtUITextToClipEncode.py | 2 +- nodes/convert/gtUITextToCombo.py | 2 +- nodes/custom_routes.py | 1 + nodes/display/gtUIOutputArtifactNode.py | 2 +- nodes/display/gtUIOutputDataNode.py | 4 +- nodes/display/gtUIOutputImageNode.py | 3 +- nodes/display/gtUIOutputStringNode.py | 2 +- nodes/display/gtUISaveImageNode.py | 3 +- .../drivers/gtUIAmazonBedrockPromptDriver.py | 2 +- ...ockStableDiffusionImageGenerationDriver.py | 2 +- .../gtUIAmazonBedrockTitanEmbeddingDriver.py | 18 ++- ...AmazonBedrockTitanImageGenerationDriver.py | 2 +- .../gtUIAmazonOpenSearchVectorStoreDriver.py | 4 +- ...AmazonSageMakerJumpstartEmbeddingDriver.py | 2 +- ...tUIAmazonSageMakerJumpstartPromptDriver.py | 2 +- nodes/drivers/gtUIAnthropicPromptDriver.py | 4 +- .../gtUIAzureMongoDbVectorStoreDriver.py | 2 +- .../gtUIAzureOpenAiChatPromptDriver.py | 2 +- .../drivers/gtUIAzureOpenAiEmbeddingDriver.py | 2 +- .../gtUIAzureOpenAiImageGenerationDriver.py | 4 +- .../gtUIBaseAudioTranscriptionDriver.py | 6 +- nodes/drivers/gtUIBaseDriver.py | 6 +- nodes/drivers/gtUIBaseEmbeddingDriver.py | 10 +- nodes/drivers/gtUIBaseImageDriver.py | 6 +- nodes/drivers/gtUIBasePromptDriver.py | 10 +- nodes/drivers/gtUIBaseRerankDriver.py | 10 +- nodes/drivers/gtUIBaseTextToSpeechDriver.py | 12 +- nodes/drivers/gtUIBaseVectorStoreDriver.py | 10 +- nodes/drivers/gtUIBaseWebSearchDriver.py | 10 +- .../gtUIBlackForestImageGenerationDriver.py | 3 +- nodes/drivers/gtUICohereEmbeddingDriver.py | 2 +- nodes/drivers/gtUICoherePromptDriver.py | 2 +- nodes/drivers/gtUICohereRerankDriver.py | 2 +- .../drivers/gtUIDuckDuckGoWebSearchDriver.py | 4 +- .../gtUIElevenLabsTextToSpeechDriver.py | 2 +- nodes/drivers/gtUIExaWebSearchDriver.py | 2 +- nodes/drivers/gtUIGoogleEmbeddingDriver.py | 2 +- nodes/drivers/gtUIGooglePromptDriver.py | 2 +- nodes/drivers/gtUIGoogleWebSearchDriver.py | 2 +- .../gtUIGriptapeCloudVectorStoreDriver.py | 4 +- .../gtUIHuggingFaceHubEmbeddingDriver.py | 3 +- .../drivers/gtUIHuggingFaceHubPromptDriver.py | 2 +- nodes/drivers/gtUILMStudioChatPromptDriver.py | 3 +- nodes/drivers/gtUILMStudioEmbeddingDriver.py | 2 +- .../gtUILeonardoImageGenerationDriver.py | 2 +- nodes/drivers/gtUILocalStructureRunDriver.py | 4 +- nodes/drivers/gtUILocalVectorStoreDriver.py | 2 +- nodes/drivers/gtUIMarqoVectorStoreDriver.py | 4 +- .../gtUIMongoDbAtlasVectorStoreDriver.py | 4 +- nodes/drivers/gtUIOllamaEmbeddingDriver.py | 4 +- nodes/drivers/gtUIOllamaPromptDriver.py | 2 +- .../gtUIOpenAiAudioTranscriptionDriver.py | 2 +- nodes/drivers/gtUIOpenAiChatPromptDriver.py | 2 +- .../gtUIOpenAiCompatibleChatPromptDriver.py | 2 +- .../gtUIOpenAiCompatibleEmbeddingDriver.py | 6 +- ...UIOpenAiCompatibleImageGenerationDriver.py | 2 +- nodes/drivers/gtUIOpenAiEmbeddingDriver.py | 2 +- .../gtUIOpenAiImageGenerationDriver.py | 4 +- nodes/drivers/gtUIOpenAiTextToSpeechDriver.py | 2 +- .../drivers/gtUIPgVectorVectorStoreDriver.py | 2 +- .../drivers/gtUIPineconeVectorStoreDriver.py | 4 +- nodes/drivers/gtUIQdrantVectorStoreDriver.py | 5 +- nodes/drivers/gtUIRedisVectorStoreDriver.py | 7 +- nodes/drivers/gtUITavilyWebSearchDriver.py | 2 +- nodes/drivers/gtUIVoyageAiEmbeddingDriver.py | 2 +- nodes/expand/ExpandAgent.py | 2 +- nodes/expand/gtUIExpandConfig.py | 20 +-- nodes/griptape_config.py | 76 ---------- nodes/gtUIBase.py | 7 +- nodes/loaders/gtUIBaseLoader.py | 6 +- nodes/loaders/gtUIFetchImage.py | 1 + nodes/loaders/gtUILoadAudio.py | 8 +- nodes/loaders/gtUILoadText.py | 4 +- nodes/rag/gtUIBaseQueryRagModule.py | 8 +- nodes/rag/gtUIBaseRagModule.py | 8 +- nodes/rag/gtUIBaseRerankRagModule.py | 6 +- ...Module.py => gtUIBaseResponseRagModule.py} | 4 +- nodes/rag/gtUIBaseRetrievalRagModule.py | 13 +- .../gtUIFootnotePromptResponseRagModule.py | 2 +- nodes/rag/gtUIPromptResponseRagModule.py | 8 +- nodes/rag/gtUIRagEngine.py | 2 +- nodes/rag/gtUITextChunksRerankRagModule.py | 4 +- nodes/rag/gtUITextChunksResponseRagModule.py | 6 +- nodes/rag/gtUITextLoaderRetrievalRagModule.py | 6 +- nodes/rag/gtUITranslateQueryRagModule.py | 6 +- .../rag/gtUIVectorStoreRetrievalRagModule.py | 6 +- nodes/rules/gtUIRule.py | 2 +- nodes/structures/BaseStructure.py | 28 ---- nodes/structures/CreatePipeline.py | 52 ------- nodes/structures/PipelineAddTask.py | 59 -------- nodes/structures/PipelineInsertTask.py | 64 -------- nodes/structures/RunStructure.py | 89 ----------- nodes/tasks/gtUIAudioTranscriptionTask.py | 4 +- nodes/tasks/gtUIBaseAudioTask.py | 4 +- nodes/tasks/gtUIBaseImageTask.py | 5 +- nodes/tasks/gtUIBaseTask.py | 10 +- nodes/tasks/gtUIBaseVectorStoreTask.py | 15 +- nodes/tasks/gtUIExtractionTask.py | 8 +- nodes/tasks/gtUIImageQueryTask.py | 2 +- .../gtUIInpaintingImageGenerationTask.py | 11 +- nodes/tasks/gtUIPromptImageGenerationTask.py | 23 +-- nodes/tasks/gtUIPromptImageVariationTask.py | 23 ++- nodes/tasks/gtUITextSummaryTask.py | 4 +- nodes/tasks/gtUITextToSpeechTask.py | 12 +- nodes/tasks/gtUIToolTask.py | 2 +- nodes/tasks/gtUIToolkitTask.py | 18 +-- nodes/tasks/gtUIVectorStoreQueryTask.py | 6 +- nodes/tasks/gtUIVectorStoreUpsertTextTask.py | 4 +- nodes/text/gtUIBaseSaveNode.py | 9 +- nodes/text/gtUICLIPTextEncode.py | 2 +- nodes/text/gtUIInputStringNode.py | 2 +- nodes/text/gtUISaveText.py | 6 +- nodes/tools/gtUIAudioTranscriptionClient.py | 6 +- nodes/tools/gtUIBaseTool.py | 7 +- nodes/tools/gtUICalculator.py | 3 +- nodes/tools/gtUIConvertAgentToTool.py | 2 +- nodes/tools/gtUIDateTime.py | 3 +- nodes/tools/gtUIExtractionTool.py | 3 +- nodes/tools/gtUIFileManager.py | 5 +- nodes/tools/gtUIKnowledgeBaseTool.py | 52 ++++--- nodes/tools/gtUIPromptSummaryTool.py | 2 +- nodes/tools/gtUIQueryTool.py | 2 +- nodes/tools/gtUIRagTool.py | 2 +- nodes/tools/gtUITextToSpeechClient.py | 10 +- nodes/tools/gtUIVectorStoreClient.py | 13 +- nodes/tools/gtUIWebScraper.py | 3 +- nodes/tools/gtUIWebSearch.py | 6 +- nodes/utilities.py | 9 +- nodes/utils.py | 2 +- nodes/utils/gtUICreateAgentModelfile.py | 4 +- nodes/utils/gtUICreateModelFromModelfile.py | 6 +- nodes/utils/gtUIRemoveOllamaModel.py | 4 +- py/griptape_settings.py | 1 + pyproject.toml | 10 ++ tests/audio/TranscriptionTest.mp3 | Bin 203759 -> 0 bytes tests/rag_workflow.py | 58 ------- typos.toml | 7 + workflow-tests/ConfigTests.json | 2 +- 183 files changed, 635 insertions(+), 1019 deletions(-) create mode 100644 Makefile delete mode 100644 nodes/agent/gtUIRunAgent.py rename nodes/config/{depricated => deprecated}/__init__.py (100%) rename nodes/config/{depricated => deprecated}/gtUIAmazonBedrockStructureConfig.py (95%) rename nodes/config/{depricated => deprecated}/gtUIAnthropicStructureConfig.py (98%) rename nodes/config/{depricated => deprecated}/gtUIAzureOpenAiStructureConfig.py (78%) rename nodes/config/{depricated => deprecated}/gtUIGoogleStructureConfig.py (98%) rename nodes/config/{depricated => deprecated}/gtUIHuggingFaceStructureConfig.py (98%) rename nodes/config/{depricated => deprecated}/gtUILMStudioStructureConfig.py (98%) rename nodes/config/{depricated => deprecated}/gtUIOllamaStructureConfig.py (98%) rename nodes/config/{depricated => deprecated}/gtUIOpenAiCompatibleConfig.py (99%) rename nodes/config/{depricated => deprecated}/gtUIOpenAiStructureConfig.py (98%) delete mode 100644 nodes/griptape_config.py rename nodes/rag/{gtUIBaseReponseRagModule.py => gtUIBaseResponseRagModule.py} (63%) delete mode 100644 nodes/structures/BaseStructure.py delete mode 100644 nodes/structures/CreatePipeline.py delete mode 100644 nodes/structures/PipelineAddTask.py delete mode 100644 nodes/structures/PipelineInsertTask.py delete mode 100644 nodes/structures/RunStructure.py delete mode 100644 tests/audio/TranscriptionTest.mp3 delete mode 100644 tests/rag_workflow.py create mode 100644 typos.toml diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b54128 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +.PHONY: check +check: check/format check/lint check/types check/spell ## Run all checks. + +.PHONY: check/format +check/format: + @poetry run ruff format --check + +.PHONY: check/lint +check/lint: + @poetry run ruff check + +.PHONY: check/types +check/types: + @poetry run pyright + +.PHONY: check/spell +check/spell: + @poetry run typos \ No newline at end of file diff --git a/README.md b/README.md index c1f5ffc..867ec57 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,9 @@ You can previous and download more examples [here](examples/README.md). ### Dec 31, 2024 * Removed manual version tracking in versions.js and now pulling the information from the `pyproject.toml`. To see what version you're running, choose RMB -> Griptape and look at the `Version` information. - + * Removed `griptape_config.py` as it's no longer being used + * Added testing to catch spelling, type, and other errors. _Note_: This was a massive effort - hopefully it didn't introduce any errors. + ### Dec 27, 2024 * Fixed missing classmethod for OllamaPromptDriver @@ -182,7 +184,7 @@ You can previous and download more examples [here](examples/README.md). * Upgrade to Griptape Framework v0.34.1 * Fix to `WebScraperTool` provides better results when using `off_prompt`. * Fixed bug where urls were dropping any text after the `:`. Example: "What is https://griptape.ai" was being converted to "What is https:". This is due to the `dynamicprompt` functionality of ComfyUI, so I've disabled that. -* Added context string to all BOOLEAN parameters to give the user a better idea as to what the particular boolean option does. For example, intead of just `True` or `False`, the tools now explain `off_prompt`. +* Added context string to all BOOLEAN parameters to give the user a better idea as to what the particular boolean option does. For example, instead of just `True` or `False`, the tools now explain `off_prompt`. ![WebsScraper tool with off_prompt](docs/images/off_prompt_parameter.png) @@ -248,7 +250,7 @@ You can previous and download more examples [here](examples/README.md). ![alt text](docs/images/release_030_2_config_nodes.png) -* Old `Griptape Agent Config` nodes still exist, but have been deprecated. They will be removed in a future release. Old workflows should automatically display the older nodes as deprecated. It's **highly recommended** to replace these old nodes with the new ones. I have tried to minimize breaking nodes, but if some may exist. I appologize for this if it happens. +* Old `Griptape Agent Config` nodes still exist, but have been deprecated. They will be removed in a future release. Old workflows should automatically display the older nodes as deprecated. It's **highly recommended** to replace these old nodes with the new ones. I have tried to minimize breaking nodes, but if some may exist. I apologize for this if it happens. ![alt text](docs/images/config_deprecated.png) @@ -347,11 +349,11 @@ You can previous and download more examples [here](examples/README.md). * **Drivers** * **Prompt Drivers** - Unique chat prompt drivers for `AmazonBedrock`, `Cohere`, `HuggingFace`, `Google`, `Ollama`, `LMStudio`, `Azure OpenAi`, `OpenAi`, `OpenAiCompatible` * **Image Generation Drivers** - These all existed before, but adding here for visibility: `Amazon Bedrock Stable Diffusion`, `Amazon Bedrock Titan`, `Leonardo AI`, `Azure OpenAi`, `OpenAi` - * **Embedding Drivers** - Agents can use these for generating embeddings, allowing them to extract relevant chunks of data from text. `Azure OpenAi`, `Voyage Ai`, `Cohere`, `Google`, `OpenAi`, `OpenAi compatable` + * **Embedding Drivers** - Agents can use these for generating embeddings, allowing them to extract relevant chunks of data from text. `Azure OpenAi`, `Voyage Ai`, `Cohere`, `Google`, `OpenAi`, `OpenAi compatible` * **Vector Store Drivers** - Allows agents to access Vector Stores to query data: ``Azure MongoDB`, `PGVector`, `Pinecone`, `Amazon OpenSearch`, `Qdrant`, `MongoDB Atlas`, `Redis`, `Local Vector Store` * **Text To Speech Drivers** - Gives agents the ability to convert text to speech. `OpenAi`, `ElevenLabs` * **Audio Transcription Driver** - Gives agents the ability to transcribe audio. `OpenAi` - * re-fixed spelling of Compatable to Compatible, because it's a common mistake. :) + * re-fixed spelling of `Compatible`, because it's a common mistake. :) * **Vector Store** - New Vector Store nodes - `Vector Store Add Text`, `Vector Store Query`, and `Griptape Tool: VectorStore` to allow you to work with various Vector Stores @@ -377,7 +379,7 @@ You can previous and download more examples [here](examples/README.md). ### July 12, 2024 * Updated to Griptape v0.28.2 -* **New Node** Griptape Config: OpenAI Compatible node. Allows you to connect to services like https://www.ohmygpt.com/ which are compatable with OpenAi's api. +* **New Node** Griptape Config: OpenAI Compatible node. Allows you to connect to services like https://www.ohmygpt.com/ which are compatible with OpenAi's api. * **New Node** HuggingFace Prompt Driver Config * Reorganized a few files * Removed unused DuckDuckGoTool now that Griptape supports drivers. diff --git a/__init__.py b/__init__.py index 50ff1b5..45b3a18 100644 --- a/__init__.py +++ b/__init__.py @@ -5,8 +5,6 @@ @description: This extension offers various nodes that allow you to work with LLMs using the Griptape Python Framework (https://griptape.ai) """ -import os - from dotenv import load_dotenv # AGENT @@ -25,31 +23,30 @@ from .nodes.combine.RulesList import RulesList from .nodes.combine.ToolList import ToolList # CONFIG -from .nodes.config.depricated.gtUIAmazonBedrockStructureConfig import ( +from .nodes.config.deprecated.gtUIAmazonBedrockStructureConfig import ( gtUIAmazonBedrockStructureConfig, ) -# CONFIG - DEPRICATED -from .nodes.config.depricated.gtUIAnthropicStructureConfig import ( +# CONFIG - DEPRECATED +from .nodes.config.deprecated.gtUIAnthropicStructureConfig import ( gtUIAnthropicStructureConfig, ) -from .nodes.config.depricated.gtUIAzureOpenAiStructureConfig import ( +from .nodes.config.deprecated.gtUIAzureOpenAiStructureConfig import ( gtUIAzureOpenAiStructureConfig, ) -from .nodes.config.depricated.gtUIGoogleStructureConfig import gtUIGoogleStructureConfig -from .nodes.config.depricated.gtUIHuggingFaceStructureConfig import ( +from .nodes.config.deprecated.gtUIGoogleStructureConfig import gtUIGoogleStructureConfig +from .nodes.config.deprecated.gtUIHuggingFaceStructureConfig import ( gtUIHuggingFaceStructureConfig, ) -from .nodes.config.depricated.gtUILMStudioStructureConfig import ( +from .nodes.config.deprecated.gtUILMStudioStructureConfig import ( gtUILMStudioStructureConfig, ) -from .nodes.config.depricated.gtUIOllamaStructureConfig import gtUIOllamaStructureConfig -from .nodes.config.depricated.gtUIOpenAiCompatibleConfig import ( +from .nodes.config.deprecated.gtUIOllamaStructureConfig import gtUIOllamaStructureConfig +from .nodes.config.deprecated.gtUIOpenAiCompatibleConfig import ( gtUIOpenAiCompatibleConfig, ) -from .nodes.config.depricated.gtUIOpenAiStructureConfig import gtUIOpenAiStructureConfig +from .nodes.config.deprecated.gtUIOpenAiStructureConfig import gtUIOpenAiStructureConfig from .nodes.config.gtUIAmazonBedrockDriversConfig import gtUIAmazonBedrockDriversConfig -from .nodes.config.gtUIAmazonBedrockSession import gtUIAmazonBedrockSession from .nodes.config.gtUIAnthropicDriversConfig import gtUIAnthropicDriversConfig from .nodes.config.gtUIAzureOpenAiDriversConfig import gtUIAzureOpenAiDriversConfig from .nodes.config.gtUICohereDriversConfig import gtUICohereDriversConfig @@ -253,7 +250,6 @@ from .nodes.tasks.gtUIInpaintingImageGenerationTask import ( from .nodes.tasks.gtUIParallelImageQueryTask import gtUIParallelImageQueryTask from .nodes.tasks.gtUIPromptImageGenerationTask import gtUIPromptImageGenerationTask from .nodes.tasks.gtUIPromptImageVariationTask import gtUIPromptImageVariationTask -from .nodes.tasks.gtUIPromptTask import gtUIPromptTask # TASKS # - Agent @@ -263,10 +259,6 @@ from .nodes.tasks.gtUITask import gtUITask from .nodes.tasks.gtUITextSummaryTask import gtUITextSummaryTask from .nodes.tasks.gtUITextToSpeechTask import gtUITextToSpeechTask -# - Tool -from .nodes.tasks.gtUIToolkitTask import gtUIToolkitTask -from .nodes.tasks.gtUIToolTask import gtUIToolTask - # - Vector Store from .nodes.tasks.gtUIVectorStoreQueryTask import gtUIVectorStoreQueryTask from .nodes.tasks.gtUIVectorStoreUpsertTextTask import gtUIVectorStoreUpsertTextTask @@ -296,7 +288,6 @@ from .nodes.tools.gtUIWebSearch import gtUIWebSearch from .nodes.utils.gtUICreateAgentModelfile import gtUICreateAgentModelfile from .nodes.utils.gtUICreateModelFromModelfile import gtUICreateModelFromModelfile from .nodes.utils.gtUIRemoveOllamaModel import gtUIRemoveOllamaModel -from .py.griptape_settings import GriptapeSettings # Load existing environment variables load_dotenv() @@ -319,7 +310,6 @@ NODE_CLASS_MAPPINGS = { # "Griptape Run: Prompt Task": gtUIPromptTask, # "Griptape Run: Tool Task": gtUIToolTask, # "Griptape Run: Toolkit Task": gtUIToolkitTask, - # "Gt Run Agent": gtUIRunAgent, "Griptape Expand: Agent Nodes": ExpandAgent, "Griptape Set: Default Agent": gtUISetDefaultAgent, # AGENT CONFIG @@ -336,16 +326,16 @@ NODE_CLASS_MAPPINGS = { "Griptape Agent Config: Ollama Drivers": gtUIOllamaDriversConfig, "Griptape Agent Config: OpenAI Drivers": gtUIOpenAiDriversConfig, "Griptape Agent Config: OpenAI Compatible Drivers": gtUIOpenAiCompatibleDriversConfig, - # DEPRICATED - "Griptape Agent Config: Amazon Bedrock [DEPRICATED]": gtUIAmazonBedrockStructureConfig, - "Griptape Agent Config: Anthropic [DEPRICATED]": gtUIAnthropicStructureConfig, - "Griptape Agent Config: Azure OpenAI [DEPRICATED]": gtUIAzureOpenAiStructureConfig, - "Griptape Agent Config: Google [DEPRICATED]": gtUIGoogleStructureConfig, - "Griptape Agent Config: HuggingFace [DEPRICATED]": gtUIHuggingFaceStructureConfig, - "Griptape Agent Config: LM Studio [DEPRICATED]": gtUILMStudioStructureConfig, - "Griptape Agent Config: Ollama [DEPRICATED]": gtUIOllamaStructureConfig, - "Griptape Agent Config: OpenAI [DEPRICATED]": gtUIOpenAiStructureConfig, - "Griptape Agent Config: OpenAI Compatible [DEPRICATED]": gtUIOpenAiCompatibleConfig, + # DEPRECATED + "Griptape Agent Config: Amazon Bedrock [DEPRECATED]": gtUIAmazonBedrockStructureConfig, + "Griptape Agent Config: Anthropic [DEPRECATED]": gtUIAnthropicStructureConfig, + "Griptape Agent Config: Azure OpenAI [DEPRECATED]": gtUIAzureOpenAiStructureConfig, + "Griptape Agent Config: Google [DEPRECATED]": gtUIGoogleStructureConfig, + "Griptape Agent Config: HuggingFace [DEPRECATED]": gtUIHuggingFaceStructureConfig, + "Griptape Agent Config: LM Studio [DEPRECATED]": gtUILMStudioStructureConfig, + "Griptape Agent Config: Ollama [DEPRECATED]": gtUIOllamaStructureConfig, + "Griptape Agent Config: OpenAI [DEPRECATED]": gtUIOpenAiStructureConfig, + "Griptape Agent Config: OpenAI Compatible [DEPRECATED]": gtUIOpenAiCompatibleConfig, # PROMPT DRIVER "Griptape Prompt Driver: Amazon Bedrock": gtUIAmazonBedrockPromptDriver, "Griptape Prompt Driver: Amazon SageMaker Jumpstart": gtUIAmazonSageMakerJumpstartPromptDriver, @@ -405,12 +395,6 @@ NODE_CLASS_MAPPINGS = { "Griptape Create: Rules": gtUIRule, "Griptape Combine: Rules List": RulesList, "Griptape Replace: Rulesets on Agent": gtUIReplaceRulesetsOnAgent, - # TASKS - # # STRUCTURES - # "Griptape Create: Pipeline": gtUICreatePipeline, - # "Griptape Run: Structure": gtUIRunStructure, - # "Griptape Pipeline: Add Task": gtUIPipelineAddTask, - # "Griptape Pipeline: Insert Task": gtUIPipelineInsertTask, # AGENT TOOLS "Griptape Convert: Agent to Tool": gtUIConvertAgentToTool, "Griptape Combine: Tool List": ToolList, diff --git a/examples/README.md b/examples/README.md index a929eb8..26ffc16 100644 --- a/examples/README.md +++ b/examples/README.md @@ -57,7 +57,7 @@ This example demonstrates how to use a local model (`llama3.1` in this case) to Download: [OffPrompt.json](OffPromopt.json) ## RAG (Retrieval Augmented Generation) -Use Retrieval Augmented Generation to have more control over the data your agents can query. Check out this example which demonstrates multiple data sources, reranking results, and controling the responses. +Use Retrieval Augmented Generation to have more control over the data your agents can query. Check out this example which demonstrates multiple data sources, reranking results, and controlling the responses. ![RAG](retrieval_augmented_generation.png) Download: [retrieval_augmented_generation.json](retrieval_augmented_generation.json) \ No newline at end of file diff --git a/js/ColorNodes.js b/js/ColorNodes.js index d687b72..bdebeff 100644 --- a/js/ColorNodes.js +++ b/js/ColorNodes.js @@ -63,7 +63,7 @@ export function setupNodeColors(nodeType, nodeData) { applyColor(this, nodeColors.red); } - if (nodeData.name.includes("[DEPRICATED]")){ + if (nodeData.name.includes("[DEPRECATED]")){ applyColor(this, nodeColors.yellow) } diff --git a/js/gtUIUtils.js b/js/gtUIUtils.js index 314584d..87ddcdc 100644 --- a/js/gtUIUtils.js +++ b/js/gtUIUtils.js @@ -67,7 +67,7 @@ export function formatPythonLikeObject(text) { export function chainCallback(object, property, callback) { if (object == undefined) { - console.error("Tried to add callback to non-existant object") + console.error("Tried to add callback to non-existent object") return; } if (property in object) { diff --git a/js/nodeFixes.js b/js/nodeFixes.js index c4c0294..ce81844 100644 --- a/js/nodeFixes.js +++ b/js/nodeFixes.js @@ -62,24 +62,42 @@ export const nodeFixes = { "Griptape Driver: Qdrant Vector Store": "Griptape Vector Store Driver: Qdrant", "Griptape Driver: DuckDuckGo WebSearch": "Griptape WebSearch Driver: DuckDuckGo", "Griptape Driver: Google WebSearch": "Griptape WebSearch Driver: Google", - "Griptape Agent Config: Amazon Bedrock [DEPRECIATED]": "Griptape Agent Config: Amazon Bedrock [DEPRICATED]", - "Griptape Agent Config: Anthropic [DEPRECIATED]": "Griptape Agent Config: Anthropic [DEPRICATED]", - "Griptape Agent Config: Azure OpenAI [DEPRECIATED]": "Griptape Agent Config: Azure OpenAI [DEPRICATED]", - "Griptape Agent Config: Google [DEPRECIATED]": "Griptape Agent Config: Google [DEPRICATED]", - "Griptape Agent Config: HuggingFace [DEPRECIATED]": "Griptape Agent Config: HuggingFace [DEPRICATED]", - "Griptape Agent Config: LM Studio [DEPRECIATED]": "Griptape Agent Config: LM Studio [DEPRICATED]", - "Griptape Agent Config: Ollama [DEPRECIATED]": "Griptape Agent Config: Ollama [DEPRICATED]", - "Griptape Agent Config: OpenAI Compatible [DEPRECIATED]": "Griptape Agent Config: OpenAI Compatible [DEPRICATED]", - "Griptape Agent Config: OpenAI [DEPRECIATED]": "Griptape Agent Config: OpenAI [DEPRICATED]", - "Griptape Agent Config: Amazon Bedrock": "Griptape Agent Config: Amazon Bedrock [DEPRICATED]", - "Griptape Agent Config: Anthropic": "Griptape Agent Config: Anthropic [DEPRICATED]", - "Griptape Agent Config: Azure OpenAI": "Griptape Agent Config: Azure OpenAI [DEPRICATED]", - "Griptape Agent Config: Google": "Griptape Agent Config: Google [DEPRICATED]", - "Griptape Agent Config: HuggingFace": "Griptape Agent Config: HuggingFace [DEPRICATED]", - "Griptape Agent Config: LM Studio": "Griptape Agent Config: LM Studio [DEPRICATED]", - "Griptape Agent Config: Ollama": "Griptape Agent Config: Ollama [DEPRICATED]", - "Griptape Agent Config: OpenAI Compatible": "Griptape Agent Config: OpenAI Compatible [DEPRICATED]", - "Griptape Agent Config: OpenAI": "Griptape Agent Config: OpenAI [DEPRICATED]", + "Griptape Agent Config: Amazon Bedrock [DEPRECIATED]": "Griptape Agent Config: Amazon Bedrock [DEPRECATED]", + "Griptape Agent Config: Anthropic [DEPRECIATED]": "Griptape Agent Config: Anthropic [DEPRECATED]", + "Griptape Agent Config: Azure OpenAI [DEPRECIATED]": "Griptape Agent Config: Azure OpenAI [DEPRECATED]", + "Griptape Agent Config: Google [DEPRECIATED]": "Griptape Agent Config: Google [DEPRECATED]", + "Griptape Agent Config: HuggingFace [DEPRECIATED]": "Griptape Agent Config: HuggingFace [DEPRECATED]", + "Griptape Agent Config: LM Studio [DEPRECIATED]": "Griptape Agent Config: LM Studio [DEPRECATED]", + "Griptape Agent Config: Ollama [DEPRECIATED]": "Griptape Agent Config: Ollama [DEPRECATED]", + "Griptape Agent Config: OpenAI Compatible [DEPRECIATED]": "Griptape Agent Config: OpenAI Compatible [DEPRECATED]", + "Griptape Agent Config: OpenAI [DEPRECIATED]": "Griptape Agent Config: OpenAI [DEPRECATED]", + "Griptape Agent Config: Amazon Bedrock": "Griptape Agent Config: Amazon Bedrock [DEPRECATED]", + "Griptape Agent Config: Anthropic": "Griptape Agent Config: Anthropic [DEPRECATED]", + "Griptape Agent Config: Azure OpenAI": "Griptape Agent Config: Azure OpenAI [DEPRECATED]", + "Griptape Agent Config: Google": "Griptape Agent Config: Google [DEPRECATED]", + "Griptape Agent Config: HuggingFace": "Griptape Agent Config: HuggingFace [DEPRECATED]", + "Griptape Agent Config: LM Studio": "Griptape Agent Config: LM Studio [DEPRECATED]", + "Griptape Agent Config: Ollama": "Griptape Agent Config: Ollama [DEPRECATED]", + "Griptape Agent Config: OpenAI Compatible": "Griptape Agent Config: OpenAI Compatible [DEPRECATED]", + "Griptape Agent Config: OpenAI": "Griptape Agent Config: OpenAI [DEPRECATED]", + "Griptape Agent Config: Amazon Bedrock [DEPRICATED]": "Griptape Agent Config: Amazon Bedrock [DEPRECATED]", + "Griptape Agent Config: Anthropic [DEPRICATED]": "Griptape Agent Config: Anthropic [DEPRECATED]", + "Griptape Agent Config: Azure OpenAI [DEPRICATED]": "Griptape Agent Config: Azure OpenAI [DEPRECATED]", + "Griptape Agent Config: Google [DEPRICATED]": "Griptape Agent Config: Google [DEPRECATED]", + "Griptape Agent Config: HuggingFace [DEPRICATED]": "Griptape Agent Config: HuggingFace [DEPRECATED]", + "Griptape Agent Config: LM Studio [DEPRICATED]": "Griptape Agent Config: LM Studio [DEPRECATED]", + "Griptape Agent Config: Ollama [DEPRICATED]": "Griptape Agent Config: Ollama [DEPRECATED]", + "Griptape Agent Config: OpenAI Compatible [DEPRICATED]": "Griptape Agent Config: OpenAI Compatible [DEPRECATED]", + "Griptape Agent Config: OpenAI [DEPRICATED]": "Griptape Agent Config: OpenAI [DEPRECATED]", + "Griptape Agent Config: Amazon Bedrock [DEPRICATED]": "Griptape Agent Config: Amazon Bedrock [DEPRECATED]", + "Griptape Agent Config: Anthropic [DEPRICATED]" : "Griptape Agent Config: Anthropic [DEPRECATED]", + "Griptape Agent Config: Azure OpenAI [DEPRICATED]" : "Griptape Agent Config: Azure OpenAI [DEPRECATED]", + "Griptape Agent Config: Google [DEPRICATED]" : "Griptape Agent Config: Google [DEPRECATED]", + "Griptape Agent Config: HuggingFace [DEPRICATED]" : "Griptape Agent Config: HuggingFace [DEPRECATED]", + "Griptape Agent Config: LM Studio [DEPRICATED]" : "Griptape Agent Config: LM Studio [DEPRECATED]", + "Griptape Agent Config: Ollama [DEPRICATED]" : "Griptape Agent Config: Ollama [DEPRECATED]", + "Griptape Agent Config: OpenAI Compatible [DEPRICATED]" : "Griptape Agent Config: OpenAI Compatible [DEPRECATED]", + "Griptape Agent Config: OpenAI [DEPRICATED]" : "Griptape Agent Config: OpenAI [DEPRECATED]", "Griptape Vector Store Driver: Griptape Cloud KnowledgeBase": "Griptape Vector Store Driver: Griptape Cloud" } } \ No newline at end of file diff --git a/nodes/Separator.py b/nodes/Separator.py index e5c9bdd..d84efe3 100644 --- a/nodes/Separator.py +++ b/nodes/Separator.py @@ -1,6 +1,6 @@ class Separator: @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return {"required": {}} RETURN_TYPES = () diff --git a/nodes/agent/BaseAgent.py b/nodes/agent/BaseAgent.py index 46dd6ba..4ce74a0 100644 --- a/nodes/agent/BaseAgent.py +++ b/nodes/agent/BaseAgent.py @@ -1,3 +1,5 @@ +# pyright: reportMissingImports=false + import logging from comfy_execution.graph import ExecutionBlocker @@ -29,7 +31,7 @@ class BaseAgent: self.agent = None @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { @@ -106,8 +108,9 @@ class BaseAgent: # See if there's an additional attribute on the RagTool if hasattr(tool, "use_rules"): # if it's true, get the ruleset - if tool.use_rules: - return tool.ruleset + if getattr(tool, "use_rules", False): + ruleset = getattr(tool, "ruleset", None) + return ruleset return None def tool_check(self, config, tools): diff --git a/nodes/agent/RunAgent.py b/nodes/agent/RunAgent.py index b837fee..b016e72 100644 --- a/nodes/agent/RunAgent.py +++ b/nodes/agent/RunAgent.py @@ -6,7 +6,7 @@ class RunAgent(BaseAgent): DESCRIPTION = "Run a simple Griptape Agent" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() del inputs["optional"]["config"] del inputs["optional"]["tools"] @@ -34,7 +34,6 @@ class RunAgent(BaseAgent): prompt_text = STRING else: prompt_text = STRING + "\n\n" + input_string - tools = self.agent.tools # if len(tools) > 0: # self.agent.add_task(ToolkitTask(prompt_text, tools=tools)) # else: diff --git a/nodes/agent/gtUICreateAgentFromConfig.py b/nodes/agent/gtUICreateAgentFromConfig.py index ec9d4fb..754e41d 100644 --- a/nodes/agent/gtUICreateAgentFromConfig.py +++ b/nodes/agent/gtUICreateAgentFromConfig.py @@ -11,7 +11,7 @@ class gtUICreateAgentFromConfig(BaseAgent): self.agent = None @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() del inputs["optional"]["tools"] del inputs["optional"]["rulesets"] diff --git a/nodes/agent/gtUIReplaceRulesetsOnAgent.py b/nodes/agent/gtUIReplaceRulesetsOnAgent.py index 9b122e4..032baa1 100644 --- a/nodes/agent/gtUIReplaceRulesetsOnAgent.py +++ b/nodes/agent/gtUIReplaceRulesetsOnAgent.py @@ -19,7 +19,7 @@ class gtUIReplaceRulesetsOnAgent(BaseAgent): self.agent = None @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { diff --git a/nodes/agent/gtUIReplaceToolsOnAgent.py b/nodes/agent/gtUIReplaceToolsOnAgent.py index 15c3b56..e792444 100644 --- a/nodes/agent/gtUIReplaceToolsOnAgent.py +++ b/nodes/agent/gtUIReplaceToolsOnAgent.py @@ -19,7 +19,7 @@ class gtUIReplaceToolsOnAgent(BaseAgent): self.agent = None @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { diff --git a/nodes/agent/gtUIRunAgent.py b/nodes/agent/gtUIRunAgent.py deleted file mode 100644 index fcf29cd..0000000 --- a/nodes/agent/gtUIRunAgent.py +++ /dev/null @@ -1,142 +0,0 @@ -from griptape.tasks import PromptTask, TextSummaryTask, ToolkitTask, ToolTask -from openai import OpenAIError - -# from server import PromptServer -from .gtComfyAgent import gtComfyAgent - -default_prompt = "{{ input_string }}" -max_attempts_default = 10 - - -class AnyType(str): - def __ne__(self, __value: object) -> bool: - return False - - -any = AnyType("*") - - -run_types = ["Default", "Prompt", "Tool(s)", "Summarize"] - - -class gtUIRunAgent: - """ - Run a Griptape Agent - """ - - def __init__(self): - self.default_prompt = default_prompt - self.agent = None - - @classmethod - def INPUT_TYPES(s): - return { - "required": {}, - "optional": { - "agent": ( - "AGENT", - { - "forceInput": True, - "tooltip": "An existing agent to use.\nIf not provided, a new agent will be created.", - }, - ), - "run_type": (run_types, {"default": run_types[0]}), - "tools": ("TOOL_LIST", {"forceInput": True, "INPUT_IS_LIST": True}), - "inputs": ("*",), - "text": ( - "STRING", - {"multiline": True, "dynamicPrompts": False}, - ), - }, - } - - @classmethod - def VALIDATE_INPUTS(s, input_types): - return True - - RETURN_TYPES = ( - any, - "AGENT", - ) - RETURN_NAMES = ( - "OUTPUT", - "AGENT", - ) - FUNCTION = "run" - - OUTPUT_NODE = True - - CATEGORY = "Griptape/Agent" - - def run(self, **kwargs): - run_type = kwargs.get("run_type", run_types[0]) - inputs = kwargs.get("inputs", []) - agent = kwargs.get("agent", None) - tools = kwargs.get("tools", []) - text = kwargs.get("text", None) - - create_dict = {} - - # Configuration - if agent: - config = agent.drivers_config - create_dict["config"] = agent.drivers_config.prompt_driver - - # Tools - create_dict["tools"] = self.tool_check(config, tools) - - if agent: - create_dict["rulesets"] = agent.rulesets - - # Memory - if agent: - create_dict["conversation_memory"] = agent.conversation_memory - create_dict["meta_memory"] = agent.meta_memory - create_dict["task_memory"] = agent.task_memory - - prompt = [] - try: - # Now create the agent - self.agent = gtComfyAgent(**create_dict) - - # Check for inputs. If none, then just create the agent - if len(inputs) == 0 and text.strip() == "": - output = "Agent created." - else: - # Get the prompt text - if len(inputs) == 0: - prompt = [text] - else: - prompt = [text] + inputs - - if run_type == "Default": - result = self.agent.run(prompt) - else: - if run_type == "Prompt" or ( - run_type == "Tool(s)" and len(tools) == 0 - ): - self.agent.add_task(PromptTask(prompt)) - elif run_type == "Tool(s)": - if len(tools) == 1: - self.agent.add_task(ToolTask(prompt, tool=tools[0])) - else: - self.agent.add_task(ToolkitTask(prompt, tools=tools)) - elif run_type == "Summarize": - self.agent.add_task(TextSummaryTask(prompt)) - result = self.agent.run() - output = result.output_task.output - return ( - output, - self.agent, - ) - - except OpenAIError as e: - if "api_key" in str(e).lower(): - return ( - "Error: OpenAI API key is missing. Please provide a valid API key.", - None, - ) - else: - return (f"OpenAI Error: {str(e)}", None) - except Exception as e: - return (f"Error creating agent: {str(e)}", None) diff --git a/nodes/agent/gtUISetDefaultAgent.py b/nodes/agent/gtUISetDefaultAgent.py index afc040b..5ccaef4 100644 --- a/nodes/agent/gtUISetDefaultAgent.py +++ b/nodes/agent/gtUISetDefaultAgent.py @@ -6,7 +6,7 @@ class gtUISetDefaultAgent(BaseAgent): DESCRIPTION = "Set the default agent." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() del inputs["optional"]["tools"] del inputs["optional"]["rulesets"] @@ -18,8 +18,9 @@ class gtUISetDefaultAgent(BaseAgent): RETURN_TYPES = ("CONFIG",) OUTPUT_NODE = True - def run(self, config=None): + def run(self, **kwargs): settings = GriptapeSettings() + config = kwargs.get("config", None) if config: config_dict = config.to_dict() settings.overwrite_settings_key("Griptape.default_config", config_dict) diff --git a/nodes/combine/MergeTexts.py b/nodes/combine/MergeTexts.py index f681962..21bdfd2 100644 --- a/nodes/combine/MergeTexts.py +++ b/nodes/combine/MergeTexts.py @@ -9,7 +9,7 @@ class MergeTexts: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { diff --git a/nodes/combine/RulesList.py b/nodes/combine/RulesList.py index dd1e987..7e4c421 100644 --- a/nodes/combine/RulesList.py +++ b/nodes/combine/RulesList.py @@ -9,7 +9,7 @@ class RulesList: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "optional": { "rules_1": ( diff --git a/nodes/combine/ToolList.py b/nodes/combine/ToolList.py index b0604ec..4d7b432 100644 --- a/nodes/combine/ToolList.py +++ b/nodes/combine/ToolList.py @@ -9,7 +9,7 @@ class ToolList: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "optional": { "tool_1": ( diff --git a/nodes/combine/gtUIMergeInputs.py b/nodes/combine/gtUIMergeInputs.py index 5547031..a206043 100644 --- a/nodes/combine/gtUIMergeInputs.py +++ b/nodes/combine/gtUIMergeInputs.py @@ -9,7 +9,7 @@ class gtUIMergeInputs: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { diff --git a/nodes/combine/gtUIModuleList.py b/nodes/combine/gtUIModuleList.py index 54253cd..fa17d41 100644 --- a/nodes/combine/gtUIModuleList.py +++ b/nodes/combine/gtUIModuleList.py @@ -9,7 +9,7 @@ class gtUIModuleList: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "optional": { "module_1": ( diff --git a/nodes/config/depricated/__init__.py b/nodes/config/deprecated/__init__.py similarity index 100% rename from nodes/config/depricated/__init__.py rename to nodes/config/deprecated/__init__.py diff --git a/nodes/config/depricated/gtUIAmazonBedrockStructureConfig.py b/nodes/config/deprecated/gtUIAmazonBedrockStructureConfig.py similarity index 95% rename from nodes/config/depricated/gtUIAmazonBedrockStructureConfig.py rename to nodes/config/deprecated/gtUIAmazonBedrockStructureConfig.py index f2a0e5f..47e80d9 100644 --- a/nodes/config/depricated/gtUIAmazonBedrockStructureConfig.py +++ b/nodes/config/deprecated/gtUIAmazonBedrockStructureConfig.py @@ -1,3 +1,5 @@ +from typing import Optional, Tuple + from griptape.configs.drivers import ( AmazonBedrockDriversConfig, ) @@ -33,7 +35,7 @@ class gtUIAmazonBedrockStructureConfig(gtUIBaseConfig): DESCRIPTION = "Amazon Bedrock Prompt Driver." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { @@ -64,7 +66,7 @@ class gtUIAmazonBedrockStructureConfig(gtUIBaseConfig): def create( self, **kwargs, - ): + ) -> Tuple[Optional[AmazonBedrockDriversConfig]]: params = {} prompt_model = kwargs.get("prompt_model", amazonBedrockPromptModels[0]) diff --git a/nodes/config/depricated/gtUIAnthropicStructureConfig.py b/nodes/config/deprecated/gtUIAnthropicStructureConfig.py similarity index 98% rename from nodes/config/depricated/gtUIAnthropicStructureConfig.py rename to nodes/config/deprecated/gtUIAnthropicStructureConfig.py index 5037555..2f74b2e 100644 --- a/nodes/config/depricated/gtUIAnthropicStructureConfig.py +++ b/nodes/config/deprecated/gtUIAnthropicStructureConfig.py @@ -29,7 +29,7 @@ class gtUIAnthropicStructureConfig(gtUIBaseConfig): ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { diff --git a/nodes/config/depricated/gtUIAzureOpenAiStructureConfig.py b/nodes/config/deprecated/gtUIAzureOpenAiStructureConfig.py similarity index 78% rename from nodes/config/depricated/gtUIAzureOpenAiStructureConfig.py rename to nodes/config/deprecated/gtUIAzureOpenAiStructureConfig.py index 9e36093..36cba90 100644 --- a/nodes/config/depricated/gtUIAzureOpenAiStructureConfig.py +++ b/nodes/config/deprecated/gtUIAzureOpenAiStructureConfig.py @@ -21,7 +21,7 @@ class gtUIAzureOpenAiStructureConfig(gtUIBaseConfig): DESCRIPTION = "Azure OpenAI Structure Config. Requires AZURE_OPENAI_ENDPOINT_3 and AZURE_OPENAI_API_KEY_3" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { @@ -75,13 +75,26 @@ class gtUIAzureOpenAiStructureConfig(gtUIBaseConfig): kwargs.get("azure_endpoint_env_var", DEFAULT_AZURE_OPENAI_ENDPOINT) ) - params["api_key"] = self.getenv("AZURE_OPENAI_API_KEY") - params["azure_endpoint"] = self.getenv("AZURE_OPENAI_ENDPOINT") + prompt_api_key = self.getenv(AZURE_OPENAI_API_KEY) + prompt_azure_endpoint = self.getenv(AZURE_OPENAI_ENDPOINT) + if not prompt_api_key: + raise ValueError("Azure OpenAI API Key is not set") + if not prompt_azure_endpoint: + raise ValueError("Azure OpenAI Endpoint is not set") + params["api_key"] = prompt_api_key + params["azure_endpoint"] = prompt_azure_endpoint prompt_driver = AzureOpenAiChatPromptDriver(**params) + + embedding_api_key = self.getenv(AZURE_OPENAI_API_KEY) + embedding_azure_endpoint = self.getenv(AZURE_OPENAI_ENDPOINT) + if not embedding_api_key: + raise ValueError("Azure OpenAI API Key is not set") + if not embedding_azure_endpoint: + raise ValueError("Azure OpenAI Endpoint is not set") embedding_driver = AzureOpenAiEmbeddingDriver( - api_key=self.getenv(AZURE_OPENAI_API_KEY), - azure_endpoint=self.getenv(AZURE_OPENAI_ENDPOINT), + api_key=embedding_api_key, + azure_endpoint=embedding_azure_endpoint, ) if not image_generation_driver: diff --git a/nodes/config/depricated/gtUIGoogleStructureConfig.py b/nodes/config/deprecated/gtUIGoogleStructureConfig.py similarity index 98% rename from nodes/config/depricated/gtUIGoogleStructureConfig.py rename to nodes/config/deprecated/gtUIGoogleStructureConfig.py index 42401dd..e08778c 100644 --- a/nodes/config/depricated/gtUIGoogleStructureConfig.py +++ b/nodes/config/deprecated/gtUIGoogleStructureConfig.py @@ -24,7 +24,7 @@ class gtUIGoogleStructureConfig(gtUIBaseConfig): """ @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( diff --git a/nodes/config/depricated/gtUIHuggingFaceStructureConfig.py b/nodes/config/deprecated/gtUIHuggingFaceStructureConfig.py similarity index 98% rename from nodes/config/depricated/gtUIHuggingFaceStructureConfig.py rename to nodes/config/deprecated/gtUIHuggingFaceStructureConfig.py index 752d7a6..7e7007b 100644 --- a/nodes/config/depricated/gtUIHuggingFaceStructureConfig.py +++ b/nodes/config/deprecated/gtUIHuggingFaceStructureConfig.py @@ -23,7 +23,7 @@ class gtUIHuggingFaceStructureConfig(gtUIBaseConfig): DESCRIPTION = "HuggingFace Structure Config." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { diff --git a/nodes/config/depricated/gtUILMStudioStructureConfig.py b/nodes/config/deprecated/gtUILMStudioStructureConfig.py similarity index 98% rename from nodes/config/depricated/gtUILMStudioStructureConfig.py rename to nodes/config/deprecated/gtUILMStudioStructureConfig.py index 50d8344..38fc85c 100644 --- a/nodes/config/depricated/gtUILMStudioStructureConfig.py +++ b/nodes/config/deprecated/gtUILMStudioStructureConfig.py @@ -23,7 +23,7 @@ class gtUILMStudioStructureConfig(gtUIBaseConfig): ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { diff --git a/nodes/config/depricated/gtUIOllamaStructureConfig.py b/nodes/config/deprecated/gtUIOllamaStructureConfig.py similarity index 98% rename from nodes/config/depricated/gtUIOllamaStructureConfig.py rename to nodes/config/deprecated/gtUIOllamaStructureConfig.py index 26de82a..543b5e5 100644 --- a/nodes/config/depricated/gtUIOllamaStructureConfig.py +++ b/nodes/config/deprecated/gtUIOllamaStructureConfig.py @@ -21,7 +21,7 @@ class gtUIOllamaStructureConfig(gtUIBaseConfig): DESCRIPTION = "Ollama Prompt Driver. Use local models with Ollama. Available at https://ollama.com" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { diff --git a/nodes/config/depricated/gtUIOpenAiCompatibleConfig.py b/nodes/config/deprecated/gtUIOpenAiCompatibleConfig.py similarity index 99% rename from nodes/config/depricated/gtUIOpenAiCompatibleConfig.py rename to nodes/config/deprecated/gtUIOpenAiCompatibleConfig.py index eb954b4..585b9e8 100644 --- a/nodes/config/depricated/gtUIOpenAiCompatibleConfig.py +++ b/nodes/config/deprecated/gtUIOpenAiCompatibleConfig.py @@ -25,7 +25,7 @@ class gtUIOpenAiCompatibleConfig(gtUIBaseConfig): DESCRIPTION = "OpenAI Compatible Structure Config." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { diff --git a/nodes/config/depricated/gtUIOpenAiStructureConfig.py b/nodes/config/deprecated/gtUIOpenAiStructureConfig.py similarity index 98% rename from nodes/config/depricated/gtUIOpenAiStructureConfig.py rename to nodes/config/deprecated/gtUIOpenAiStructureConfig.py index ca622e5..697b07a 100644 --- a/nodes/config/depricated/gtUIOpenAiStructureConfig.py +++ b/nodes/config/deprecated/gtUIOpenAiStructureConfig.py @@ -23,7 +23,7 @@ class gtUIOpenAiStructureConfig(gtUIBaseConfig): DESCRIPTION = "OpenAI Structure Config. Use OpenAI's models for prompt, embedding, image generation, and image query." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { diff --git a/nodes/config/gtUIAmazonBedrockDriversConfig.py b/nodes/config/gtUIAmazonBedrockDriversConfig.py index 468ee37..812fcb8 100644 --- a/nodes/config/gtUIAmazonBedrockDriversConfig.py +++ b/nodes/config/gtUIAmazonBedrockDriversConfig.py @@ -1,3 +1,5 @@ +from typing import Tuple + from griptape.configs import Defaults from griptape.configs.drivers import ( AmazonBedrockDriversConfig, @@ -56,7 +58,7 @@ class gtUIAmazonBedrockDriversConfig(gtUIBaseDriversConfig): DESCRIPTION = "Amazon Bedrock Prompt Driver." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} @@ -68,7 +70,7 @@ class gtUIAmazonBedrockDriversConfig(gtUIBaseDriversConfig): def create( self, **kwargs, - ): + ) -> Tuple[AmazonBedrockDriversConfig]: self.run_envs(kwargs) drivers_config_params = {} diff --git a/nodes/config/gtUIAmazonBedrockSession.py b/nodes/config/gtUIAmazonBedrockSession.py index 547e189..043e9a0 100644 --- a/nodes/config/gtUIAmazonBedrockSession.py +++ b/nodes/config/gtUIAmazonBedrockSession.py @@ -1,7 +1,6 @@ import boto3 from ...py.griptape_settings import GriptapeSettings -from ..drivers.gtUIBaseDriver import gtUIBaseDriver DEFAULT_AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID" DEFAULT_AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY" @@ -32,58 +31,59 @@ def start_session(aws_access_key_id=None, aws_secret_access_key=None, region_nam return None -class gtUIAmazonBedrockSession(gtUIBaseDriver): - DESCRIPTION = "Starts a session with Amazon" +# +# class gtUIAmazonBedrockSession(gtUIBaseDriver): +# DESCRIPTION = "Starts a session with Amazon" - @classmethod - def INPUT_TYPES(s): - return { - "optional": { - "aws_access_key_id_env_var": ( - "STRING", - {"default": DEFAULT_AWS_ACCESS_KEY_ID}, - ), - "aws_secret_access_key_env_var": ( - "STRING", - {"default": DEFAULT_AWS_SECRET_ACCESS_KEY}, - ), - "aws_default_region_env_var": ( - "STRING", - {"default": DEFAULT_AWS_DEFAULT_REGION}, - ), - } - } +# @classmethod +# def INPUT_TYPES(cls): +# return { +# "optional": { +# "aws_access_key_id_env_var": ( +# "STRING", +# {"default": DEFAULT_AWS_ACCESS_KEY_ID}, +# ), +# "aws_secret_access_key_env_var": ( +# "STRING", +# {"default": DEFAULT_AWS_SECRET_ACCESS_KEY}, +# ), +# "aws_default_region_env_var": ( +# "STRING", +# {"default": DEFAULT_AWS_DEFAULT_REGION}, +# ), +# } +# } - RETURN_TYPES = ("SESSION",) - RETURN_NAMES = ("SESSION",) +# RETURN_TYPES = ("SESSION",) +# RETURN_NAMES = ("SESSION",) - FUNCTION = "create" +# FUNCTION = "create" - CATEGORY = "Griptape/Agent Configs" +# CATEGORY = "Griptape/Agent Configs" - def build_params(self, **kwargs): - params = {} - aws_access_key_id = self.getenv( - kwargs.get("aws_access_key_id_env_var", DEFAULT_AWS_ACCESS_KEY_ID) - ) - aws_secret_access_key = self.getenv( - kwargs.get("aws_secret_access_key_env_var", DEFAULT_AWS_SECRET_ACCESS_KEY) - ) - region_name = self.getenv( - kwargs.get("aws_default_region_env_var", DEFAULT_AWS_DEFAULT_REGION) - ) +# def build_params(self, **kwargs): +# params = {} +# aws_access_key_id = self.getenv( +# kwargs.get("aws_access_key_id_env_var", DEFAULT_AWS_ACCESS_KEY_ID) +# ) +# aws_secret_access_key = self.getenv( +# kwargs.get("aws_secret_access_key_env_var", DEFAULT_AWS_SECRET_ACCESS_KEY) +# ) +# region_name = self.getenv( +# kwargs.get("aws_default_region_env_var", DEFAULT_AWS_DEFAULT_REGION) +# ) - params["aws_access_key_id"] = aws_access_key_id - params["aws_secret_access_key"] = aws_secret_access_key - params["region_name"] = region_name +# params["aws_access_key_id"] = aws_access_key_id +# params["aws_secret_access_key"] = aws_secret_access_key +# params["region_name"] = region_name - return params +# return params - def create(self, **kwargs): - params = self.build_params(**kwargs) - session = start_session( - aws_access_key_id=params.get("aws_access_key_id"), - aws_secret_access_key=params.get("aws_secret_access_key"), - region_name=params.get("region_name"), - ) - return (session,) +# def create(self, **kwargs): +# params = self.build_params(**kwargs) +# session = start_session( +# aws_access_key_id=params.get("aws_access_key_id"), +# aws_secret_access_key=params.get("aws_secret_access_key"), +# region_name=params.get("region_name"), +# ) +# return (session,) diff --git a/nodes/config/gtUIAnthropicDriversConfig.py b/nodes/config/gtUIAnthropicDriversConfig.py index 12970dc..2f55a9d 100644 --- a/nodes/config/gtUIAnthropicDriversConfig.py +++ b/nodes/config/gtUIAnthropicDriversConfig.py @@ -40,7 +40,7 @@ class gtUIAnthropicDriversConfig(gtUIBaseDriversConfig): ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} diff --git a/nodes/config/gtUIAzureOpenAiDriversConfig.py b/nodes/config/gtUIAzureOpenAiDriversConfig.py index ef54057..c7e227f 100644 --- a/nodes/config/gtUIAzureOpenAiDriversConfig.py +++ b/nodes/config/gtUIAzureOpenAiDriversConfig.py @@ -38,7 +38,7 @@ class gtUIAzureOpenAiDriversConfig(gtUIBaseDriversConfig): DESCRIPTION = "Azure OpenAI Structure Config. Requires AZURE_OPENAI_ENDPOINT_3 and AZURE_OPENAI_API_KEY_3" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} diff --git a/nodes/config/gtUIBaseConfig.py b/nodes/config/gtUIBaseConfig.py index 066c4b6..08f2287 100644 --- a/nodes/config/gtUIBaseConfig.py +++ b/nodes/config/gtUIBaseConfig.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.configs import Defaults from griptape.configs.drivers import OpenAiDriversConfig @@ -34,7 +36,7 @@ class gtUIBaseConfig: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { @@ -73,13 +75,13 @@ class gtUIBaseConfig: # OUTPUT_NODE = False - CATEGORY = "Griptape/Agent Configs [DEPRICATED]" + CATEGORY = "Griptape/Agent Configs [DEPRECATED]" def getenv(self, env): settings = GriptapeSettings() api_key = settings.get_settings_key_or_use_env(env) return api_key - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: Defaults.drivers_config = OpenAiDriversConfig() return (OpenAiDriversConfig(),) diff --git a/nodes/config/gtUIBaseDriversConfig.py b/nodes/config/gtUIBaseDriversConfig.py index c82afb1..4cd644f 100644 --- a/nodes/config/gtUIBaseDriversConfig.py +++ b/nodes/config/gtUIBaseDriversConfig.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.configs import Defaults from griptape.configs.drivers import OpenAiDriversConfig @@ -35,7 +37,7 @@ class gtUIBaseDriversConfig(gtUIBase): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() return inputs @@ -53,6 +55,6 @@ class gtUIBaseDriversConfig(gtUIBase): api_key = settings.get_settings_key_or_use_env(env) return api_key - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: Defaults.drivers_config = OpenAiDriversConfig() return (OpenAiDriversConfig(),) diff --git a/nodes/config/gtUICohereDriversConfig.py b/nodes/config/gtUICohereDriversConfig.py index 92e3bd5..bd4d3a8 100644 --- a/nodes/config/gtUICohereDriversConfig.py +++ b/nodes/config/gtUICohereDriversConfig.py @@ -33,7 +33,7 @@ class gtUICohereDriversConfig(gtUIBaseDriversConfig): DESCRIPTION = "OpenAI Structure Config. Use OpenAI's models for prompt, embedding, image generation, and image query." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} diff --git a/nodes/config/gtUIEnvConfig.py b/nodes/config/gtUIEnvConfig.py index 6750156..6190a07 100644 --- a/nodes/config/gtUIEnvConfig.py +++ b/nodes/config/gtUIEnvConfig.py @@ -12,7 +12,7 @@ class gtUIEnvConfig: """ @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { diff --git a/nodes/config/gtUIGoogleDriversConfig.py b/nodes/config/gtUIGoogleDriversConfig.py index 71f33d8..0741d06 100644 --- a/nodes/config/gtUIGoogleDriversConfig.py +++ b/nodes/config/gtUIGoogleDriversConfig.py @@ -1,3 +1,5 @@ +from typing import Optional, Tuple + from griptape.configs import Defaults from griptape.configs.drivers import ( GoogleDriversConfig, @@ -38,7 +40,7 @@ class gtUIGoogleDriversConfig(gtUIBaseDriversConfig): """ @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} @@ -52,9 +54,11 @@ class gtUIGoogleDriversConfig(gtUIBaseDriversConfig): "Google Structure Config. Use Google's models for prompt and image query." ) - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Optional[GoogleDriversConfig]]: self.run_envs(kwargs) + custom_config = None + drivers_config_params = {} # Create instances of the driver classes prompt_driver_builder = gtUIGooglePromptDriver() @@ -77,6 +81,8 @@ class gtUIGoogleDriversConfig(gtUIBaseDriversConfig): try: Defaults.drivers_config = GoogleDriversConfig(**drivers_config_params) custom_config = Defaults.drivers_config + if custom_config is None: + raise Exception("Failed to create GoogleDriversConfig") except Exception as e: print(e) diff --git a/nodes/config/gtUIHuggingFaceDriversConfig.py b/nodes/config/gtUIHuggingFaceDriversConfig.py index b623f22..30c6d8b 100644 --- a/nodes/config/gtUIHuggingFaceDriversConfig.py +++ b/nodes/config/gtUIHuggingFaceDriversConfig.py @@ -40,7 +40,7 @@ class gtUIHuggingFaceDriversConfig(gtUIBaseDriversConfig): DESCRIPTION = "HuggingFace Structure Config." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} @@ -50,6 +50,7 @@ class gtUIHuggingFaceDriversConfig(gtUIBaseDriversConfig): def create(self, **kwargs): self.run_envs(kwargs) + custom_config = None drivers_config_params = {} diff --git a/nodes/config/gtUILMStudioDriversConfig.py b/nodes/config/gtUILMStudioDriversConfig.py index 3b1a09d..4329fe5 100644 --- a/nodes/config/gtUILMStudioDriversConfig.py +++ b/nodes/config/gtUILMStudioDriversConfig.py @@ -1,3 +1,4 @@ +# pyright: reportMissingImports=false import logging from comfy_execution.graph import ExecutionBlocker @@ -38,7 +39,7 @@ class gtUILMStudioDriversConfig(gtUIBaseDriversConfig): ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} diff --git a/nodes/config/gtUIOllamaDriversConfig.py b/nodes/config/gtUIOllamaDriversConfig.py index 453a867..a5af988 100644 --- a/nodes/config/gtUIOllamaDriversConfig.py +++ b/nodes/config/gtUIOllamaDriversConfig.py @@ -33,7 +33,7 @@ class gtUIOllamaDriversConfig(gtUIBaseDriversConfig): DESCRIPTION = "Ollama Prompt Driver. Use local models with Ollama. Available at https://ollama.com" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} @@ -45,6 +45,7 @@ class gtUIOllamaDriversConfig(gtUIBaseDriversConfig): def create(self, **kwargs): self.run_envs(kwargs) + custom_config = None drivers_config_params = {} diff --git a/nodes/config/gtUIOpenAiCompatibleDriversConfig.py b/nodes/config/gtUIOpenAiCompatibleDriversConfig.py index 595fe8e..52462b4 100644 --- a/nodes/config/gtUIOpenAiCompatibleDriversConfig.py +++ b/nodes/config/gtUIOpenAiCompatibleDriversConfig.py @@ -56,7 +56,7 @@ class gtUIOpenAiCompatibleDriversConfig(gtUIBaseDriversConfig): DESCRIPTION = "OpenAI Compatible Structure Config." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} diff --git a/nodes/config/gtUIOpenAiDriversConfig.py b/nodes/config/gtUIOpenAiDriversConfig.py index 6018a01..8417bf3 100644 --- a/nodes/config/gtUIOpenAiDriversConfig.py +++ b/nodes/config/gtUIOpenAiDriversConfig.py @@ -49,7 +49,7 @@ class gtUIOpenAiDriversConfig(gtUIBaseDriversConfig): DESCRIPTION = "OpenAI Drivers Config. Use OpenAI's models for prompt, embedding, image generation, and image query." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = {} diff --git a/nodes/config/gtUIStructureConfig.py b/nodes/config/gtUIStructureConfig.py index 2fd670b..a1edf5d 100644 --- a/nodes/config/gtUIStructureConfig.py +++ b/nodes/config/gtUIStructureConfig.py @@ -25,7 +25,7 @@ class gtUIStructureConfig(gtUIBase): self.default_audio_transcription_driver = DummyAudioTranscriptionDriver() @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { diff --git a/nodes/convert/gtUITextToClipEncode.py b/nodes/convert/gtUITextToClipEncode.py index c0611ad..5b5265f 100644 --- a/nodes/convert/gtUITextToClipEncode.py +++ b/nodes/convert/gtUITextToClipEncode.py @@ -13,7 +13,7 @@ class gtUITextToClipEncode(gtUIBaseTask): DESCRIPTION = "Convert text to a CLIP conditioning" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "STRING": ("STRING", {"forceInput": True}), diff --git a/nodes/convert/gtUITextToCombo.py b/nodes/convert/gtUITextToCombo.py index 8f87e7c..ecccfaf 100644 --- a/nodes/convert/gtUITextToCombo.py +++ b/nodes/convert/gtUITextToCombo.py @@ -10,7 +10,7 @@ class gtUITextToCombo: DESCRIPTION = "Convert text to a Combo conditioning" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "STRING": ("STRING", {"forceInput": True}), diff --git a/nodes/custom_routes.py b/nodes/custom_routes.py index 47dcfc7..1afcc37 100644 --- a/nodes/custom_routes.py +++ b/nodes/custom_routes.py @@ -1,3 +1,4 @@ +# pyright: reportMissingImports=false from aiohttp import web from server import PromptServer diff --git a/nodes/display/gtUIOutputArtifactNode.py b/nodes/display/gtUIOutputArtifactNode.py index 6f06b16..17df690 100644 --- a/nodes/display/gtUIOutputArtifactNode.py +++ b/nodes/display/gtUIOutputArtifactNode.py @@ -5,7 +5,7 @@ class gtUIOutputArtifactNode: CATEGORY = "Griptape/Display" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {"INPUT": ("ARTIFACT", {"forceInput": True})}, diff --git a/nodes/display/gtUIOutputDataNode.py b/nodes/display/gtUIOutputDataNode.py index 659d15b..597473b 100644 --- a/nodes/display/gtUIOutputDataNode.py +++ b/nodes/display/gtUIOutputDataNode.py @@ -4,11 +4,11 @@ class gtUIOutputDataNode: CATEGORY = "Griptape/Display" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return {"required": {}, "optional": {"INPUT": ("*", {"forceInput": True})}} @classmethod - def VALIDATE_INPUTS(s, input_types): + def VALIDATE_INPUTS(cls, input_types): return True RETURN_TYPES = ("STRING",) diff --git a/nodes/display/gtUIOutputImageNode.py b/nodes/display/gtUIOutputImageNode.py index fa691b0..c6bfe3d 100644 --- a/nodes/display/gtUIOutputImageNode.py +++ b/nodes/display/gtUIOutputImageNode.py @@ -1,3 +1,4 @@ +# pyright: reportMissingImports=false import random import folder_paths @@ -20,7 +21,7 @@ class gtUIOutputImageNode(gtUISaveImageNode): CATEGORY = "Griptape/Display" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {"images": ("IMAGE", {"forceInput": True})}, diff --git a/nodes/display/gtUIOutputStringNode.py b/nodes/display/gtUIOutputStringNode.py index 7154d89..dc155fb 100644 --- a/nodes/display/gtUIOutputStringNode.py +++ b/nodes/display/gtUIOutputStringNode.py @@ -4,7 +4,7 @@ class gtUIOutputStringNode: CATEGORY = "Griptape/Display" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { diff --git a/nodes/display/gtUISaveImageNode.py b/nodes/display/gtUISaveImageNode.py index 61bd4ad..6494287 100644 --- a/nodes/display/gtUISaveImageNode.py +++ b/nodes/display/gtUISaveImageNode.py @@ -1,3 +1,4 @@ +# pyright: reportMissingImports=false import json import os @@ -7,7 +8,7 @@ from comfy.cli_args import args from PIL import Image from PIL.PngImagePlugin import PngInfo -from nodes import SaveImage +from nodes import SaveImage # type: ignore[reportAttributeAccessIssue] # From SaveImage diff --git a/nodes/drivers/gtUIAmazonBedrockPromptDriver.py b/nodes/drivers/gtUIAmazonBedrockPromptDriver.py index 6e4381b..25f4bab 100644 --- a/nodes/drivers/gtUIAmazonBedrockPromptDriver.py +++ b/nodes/drivers/gtUIAmazonBedrockPromptDriver.py @@ -20,7 +20,7 @@ DEFAULT_AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION" class gtUIAmazonBedrockPromptDriver(gtUIBasePromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Get the base required and optional inputs diff --git a/nodes/drivers/gtUIAmazonBedrockStableDiffusionImageGenerationDriver.py b/nodes/drivers/gtUIAmazonBedrockStableDiffusionImageGenerationDriver.py index 8064c76..4c51752 100644 --- a/nodes/drivers/gtUIAmazonBedrockStableDiffusionImageGenerationDriver.py +++ b/nodes/drivers/gtUIAmazonBedrockStableDiffusionImageGenerationDriver.py @@ -17,7 +17,7 @@ class gtUIAmazonBedrockStableDiffusionImageGenerationDriver( DESCRIPTION = "Amazon Bedrock Stable Diffusion Image Generation Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): style_presets = [ "", "3d-model", diff --git a/nodes/drivers/gtUIAmazonBedrockTitanEmbeddingDriver.py b/nodes/drivers/gtUIAmazonBedrockTitanEmbeddingDriver.py index dc32da3..767b011 100644 --- a/nodes/drivers/gtUIAmazonBedrockTitanEmbeddingDriver.py +++ b/nodes/drivers/gtUIAmazonBedrockTitanEmbeddingDriver.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.drivers import AmazonBedrockTitanEmbeddingDriver from ..config.gtUIAmazonBedrockSession import start_session @@ -18,7 +20,7 @@ class gtUIAmazonBedrockTitanEmbeddingDriver(gtUIBaseEmbeddingDriver): DESCRIPTION = "Amazon Bedrock Titan Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Get the base required and optional inputs @@ -39,6 +41,14 @@ class gtUIAmazonBedrockTitanEmbeddingDriver(gtUIBaseEmbeddingDriver): "tooltip": "Select the embedding model to use.", }, ), + "min_retry_delay": ( + "FLOAT", + {"default": 2, "tooltip": "Minimum retry delay"}, + ), + "max_retry_delay": ( + "FLOAT", + {"default": 10, "tooltip": "Minimum retry delay"}, + ), "aws_access_key_id_env_var": ( "STRING", { @@ -67,6 +77,8 @@ class gtUIAmazonBedrockTitanEmbeddingDriver(gtUIBaseEmbeddingDriver): def build_params(self, **kwargs): model = kwargs.get("embedding_model", None) + min_retry_delay = kwargs.get("min_retry_delay", 2) + max_retry_delay = kwargs.get("max_retry_delay", 10) api_key = self.getenv( kwargs.get("aws_access_key_id_env_var", DEFAULT_AWS_ACCESS_KEY_ID) ) @@ -81,13 +93,15 @@ class gtUIAmazonBedrockTitanEmbeddingDriver(gtUIBaseEmbeddingDriver): "aws_access_key_id": api_key, "aws_secret_access_key": secret_access_key, "region_name": region_name, + "min_retry_delay": min_retry_delay, + "max_retry_delay": max_retry_delay, } if model: params["model"] = model return params - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: params = self.build_params(**kwargs) start_session( aws_access_key_id=params.get("aws_access_key_id", None), diff --git a/nodes/drivers/gtUIAmazonBedrockTitanImageGenerationDriver.py b/nodes/drivers/gtUIAmazonBedrockTitanImageGenerationDriver.py index 288be5b..4e24007 100644 --- a/nodes/drivers/gtUIAmazonBedrockTitanImageGenerationDriver.py +++ b/nodes/drivers/gtUIAmazonBedrockTitanImageGenerationDriver.py @@ -17,7 +17,7 @@ class gtUIAmazonBedrockTitanImageGenerationDriver(gtUIBaseImageGenerationDriver) DESCRIPTION = "Amazon Bedrock Titan Image Generation Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): sizes = ["512x512", "1024x1024"] inputs = super().INPUT_TYPES() diff --git a/nodes/drivers/gtUIAmazonOpenSearchVectorStoreDriver.py b/nodes/drivers/gtUIAmazonOpenSearchVectorStoreDriver.py index aeb13dd..25589da 100644 --- a/nodes/drivers/gtUIAmazonOpenSearchVectorStoreDriver.py +++ b/nodes/drivers/gtUIAmazonOpenSearchVectorStoreDriver.py @@ -16,7 +16,7 @@ class gtUIAmazonOpenSearchVectorStoreDriver(gtUIBaseVectorStoreDriver): DESCRIPTION = "Griptape Open Search Vector Store Driver: https://aws.amazon.com/opensearch-service/" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Get the base required and optional inputs @@ -107,5 +107,5 @@ class gtUIAmazonOpenSearchVectorStoreDriver(gtUIBaseVectorStoreDriver): params.pop("aws_access_key_id") params.pop("aws_secret_access_key") params.pop("region_name") - driver = AmazonOpenSearchVectorStoreDriver(**params) + driver = AmazonOpenSearchVectorStoreDriver(**params) # type: ignore[reportArgumentType] return (driver,) diff --git a/nodes/drivers/gtUIAmazonSageMakerJumpstartEmbeddingDriver.py b/nodes/drivers/gtUIAmazonSageMakerJumpstartEmbeddingDriver.py index 813fd66..28b4ad4 100644 --- a/nodes/drivers/gtUIAmazonSageMakerJumpstartEmbeddingDriver.py +++ b/nodes/drivers/gtUIAmazonSageMakerJumpstartEmbeddingDriver.py @@ -14,7 +14,7 @@ class gtUIAmazonSageMakerJumpstartEmbeddingDriver(gtUIBaseEmbeddingDriver): DESCRIPTION = "OpenAI Compatible Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Get the base required and optional inputs base_required_inputs = inputs["required"] diff --git a/nodes/drivers/gtUIAmazonSageMakerJumpstartPromptDriver.py b/nodes/drivers/gtUIAmazonSageMakerJumpstartPromptDriver.py index f973cc4..a68294d 100644 --- a/nodes/drivers/gtUIAmazonSageMakerJumpstartPromptDriver.py +++ b/nodes/drivers/gtUIAmazonSageMakerJumpstartPromptDriver.py @@ -12,7 +12,7 @@ DEFAULT_AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION" class gtUIAmazonSageMakerJumpstartPromptDriver(gtUIBasePromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() diff --git a/nodes/drivers/gtUIAnthropicPromptDriver.py b/nodes/drivers/gtUIAnthropicPromptDriver.py index 4d1080e..e940070 100644 --- a/nodes/drivers/gtUIAnthropicPromptDriver.py +++ b/nodes/drivers/gtUIAnthropicPromptDriver.py @@ -11,7 +11,7 @@ DEFAULT_API_KEY = "ANTHROPIC_API_KEY" class gtUIAnthropicPromptDriver(gtUIBasePromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Get the base required and optional inputs @@ -58,7 +58,7 @@ class gtUIAnthropicPromptDriver(gtUIBasePromptDriver): "min": 0, "max": 500, "step": 1, - "tooltip": "Limits the number of tokens considered for each step of the generation. Pevents the model from focusing too narrowly on the top choices.", + "tooltip": "Limits the number of tokens considered for each step of the generation. Prevents the model from focusing too narrowly on the top choices.", }, ), } diff --git a/nodes/drivers/gtUIAzureMongoDbVectorStoreDriver.py b/nodes/drivers/gtUIAzureMongoDbVectorStoreDriver.py index 84bf6de..007a9dd 100644 --- a/nodes/drivers/gtUIAzureMongoDbVectorStoreDriver.py +++ b/nodes/drivers/gtUIAzureMongoDbVectorStoreDriver.py @@ -17,7 +17,7 @@ class gtUIAzureMongoDbVectorStoreDriver(gtUIBaseVectorStoreDriver): DESCRIPTION = "Griptape Azure Mongodb Atlas Vector Store Driver: https://www.mongodb.com/products/platform/atlas-database" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( diff --git a/nodes/drivers/gtUIAzureOpenAiChatPromptDriver.py b/nodes/drivers/gtUIAzureOpenAiChatPromptDriver.py index c500604..ebcdde4 100644 --- a/nodes/drivers/gtUIAzureOpenAiChatPromptDriver.py +++ b/nodes/drivers/gtUIAzureOpenAiChatPromptDriver.py @@ -10,7 +10,7 @@ DEFAULT_API_KEY_ENV_VAR = "AZURE_OPENAI_API_KEY" class gtUIAzureOpenAiChatPromptDriver(gtUIBasePromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Get the base required and optional inputs base_required_inputs = inputs["required"] diff --git a/nodes/drivers/gtUIAzureOpenAiEmbeddingDriver.py b/nodes/drivers/gtUIAzureOpenAiEmbeddingDriver.py index ea23a41..69b0d1b 100644 --- a/nodes/drivers/gtUIAzureOpenAiEmbeddingDriver.py +++ b/nodes/drivers/gtUIAzureOpenAiEmbeddingDriver.py @@ -11,7 +11,7 @@ class gtUIAzureOpenAiEmbeddingDriver(gtUIBaseEmbeddingDriver): DESCRIPTION = "Azure OpenAI Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() diff --git a/nodes/drivers/gtUIAzureOpenAiImageGenerationDriver.py b/nodes/drivers/gtUIAzureOpenAiImageGenerationDriver.py index 38f81d2..4447a20 100644 --- a/nodes/drivers/gtUIAzureOpenAiImageGenerationDriver.py +++ b/nodes/drivers/gtUIAzureOpenAiImageGenerationDriver.py @@ -14,7 +14,7 @@ class gtUIAzureOpenAiImageGenerationDriver(gtUIBaseImageGenerationDriver): DESCRIPTION = "Azire OpenAI Image Generation Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { @@ -58,7 +58,7 @@ class gtUIAzureOpenAiImageGenerationDriver(gtUIBaseImageGenerationDriver): return inputs def adjust_size_based_on_model(self, model, size): - # pick the approprite size based on the model + # pick the appropriate size based on the model if model == "dall-e-2": if size in ["1024x1792", "1792x1024"]: size = "1024x1024" diff --git a/nodes/drivers/gtUIBaseAudioTranscriptionDriver.py b/nodes/drivers/gtUIBaseAudioTranscriptionDriver.py index 29edb81..7d1d4d4 100644 --- a/nodes/drivers/gtUIBaseAudioTranscriptionDriver.py +++ b/nodes/drivers/gtUIBaseAudioTranscriptionDriver.py @@ -8,7 +8,7 @@ DEFAULT_MODEL = "whisper-1" class gtUIBaseAudioTranscriptionDriver(gtUIBaseDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {}, @@ -21,9 +21,7 @@ class gtUIBaseAudioTranscriptionDriver(gtUIBaseDriver): CATEGORY = "Griptape/Agent Drivers/Audio Transcription" - def create( - self, - ): + def create(self, **kwargs): api_key = self.getenv(DEFAULT_API_KEY) params = {} diff --git a/nodes/drivers/gtUIBaseDriver.py b/nodes/drivers/gtUIBaseDriver.py index f152294..a71e83e 100644 --- a/nodes/drivers/gtUIBaseDriver.py +++ b/nodes/drivers/gtUIBaseDriver.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.drivers import DummyPromptDriver from ...py.griptape_settings import GriptapeSettings @@ -7,7 +9,7 @@ class gtUIBaseDriver: DESCRIPTION = "Griptape Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {}, @@ -25,6 +27,6 @@ class gtUIBaseDriver: api_key = settings.get_settings_key_or_use_env(env) return api_key - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: driver = DummyPromptDriver() return (driver,) diff --git a/nodes/drivers/gtUIBaseEmbeddingDriver.py b/nodes/drivers/gtUIBaseEmbeddingDriver.py index 14ba98e..4bf55fd 100644 --- a/nodes/drivers/gtUIBaseEmbeddingDriver.py +++ b/nodes/drivers/gtUIBaseEmbeddingDriver.py @@ -1,4 +1,6 @@ -from griptape.drivers import BaseEmbeddingDriver +from typing import Any, Tuple + +from griptape.drivers import DummyEmbeddingDriver from .gtUIBaseDriver import gtUIBaseDriver @@ -7,7 +9,7 @@ class gtUIBaseEmbeddingDriver(gtUIBaseDriver): DESCRIPTION = "Griptape Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update() @@ -18,6 +20,6 @@ class gtUIBaseEmbeddingDriver(gtUIBaseDriver): RETURN_TYPES = ("EMBEDDING_DRIVER",) - def create(self, **kwargs): - driver = BaseEmbeddingDriver() + def create(self, **kwargs) -> Tuple[Any, ...]: + driver = DummyEmbeddingDriver() return (driver,) diff --git a/nodes/drivers/gtUIBaseImageDriver.py b/nodes/drivers/gtUIBaseImageDriver.py index d255cdc..1f796f7 100644 --- a/nodes/drivers/gtUIBaseImageDriver.py +++ b/nodes/drivers/gtUIBaseImageDriver.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.drivers import OpenAiImageGenerationDriver from .gtUIBaseDriver import gtUIBaseDriver @@ -14,7 +16,7 @@ class gtUIBaseImageGenerationDriver(gtUIBaseDriver): """ @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {}, @@ -28,7 +30,7 @@ class gtUIBaseImageGenerationDriver(gtUIBaseDriver): CATEGORY = "Griptape/Agent Drivers/Image Generation" - def create(self, prompt): + def create(self, **kwargs) -> Tuple[Any, ...]: api_key = self.getenv(DEFAULT_API_KEY) params = {} if api_key: diff --git a/nodes/drivers/gtUIBasePromptDriver.py b/nodes/drivers/gtUIBasePromptDriver.py index 86a566e..ef4c6a6 100644 --- a/nodes/drivers/gtUIBasePromptDriver.py +++ b/nodes/drivers/gtUIBasePromptDriver.py @@ -1,11 +1,13 @@ -from griptape.drivers import BasePromptDriver +from typing import Any, Tuple + +from griptape.drivers import DummyPromptDriver from .gtUIBaseDriver import gtUIBaseDriver class gtUIBasePromptDriver(gtUIBaseDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( @@ -64,6 +66,6 @@ class gtUIBasePromptDriver(gtUIBaseDriver): CATEGORY = "Griptape/Agent Drivers/Prompt" - def create(self, **kwargs): - driver = BasePromptDriver() + def create(self, **kwargs) -> Tuple[Any, ...]: + driver = DummyPromptDriver() return (driver,) diff --git a/nodes/drivers/gtUIBaseRerankDriver.py b/nodes/drivers/gtUIBaseRerankDriver.py index a2d0bf2..bb4a9f2 100644 --- a/nodes/drivers/gtUIBaseRerankDriver.py +++ b/nodes/drivers/gtUIBaseRerankDriver.py @@ -1,4 +1,6 @@ -from griptape.drivers import BaseRerankDriver +from typing import Any, Tuple + +from griptape.drivers import DummyPromptDriver from .gtUIBaseDriver import gtUIBaseDriver @@ -7,7 +9,7 @@ class gtUIBaseRerankDriver(gtUIBaseDriver): DESCRIPTION = "Griptape Rerank Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update() @@ -18,6 +20,6 @@ class gtUIBaseRerankDriver(gtUIBaseDriver): RETURN_TYPES = ("RERANK_DRIVER",) - def create(self, **kwargs): - driver = BaseRerankDriver() + def create(self, **kwargs) -> Tuple[Any, ...]: + driver = DummyPromptDriver() return (driver,) diff --git a/nodes/drivers/gtUIBaseTextToSpeechDriver.py b/nodes/drivers/gtUIBaseTextToSpeechDriver.py index 6ef9e3b..6bc835e 100644 --- a/nodes/drivers/gtUIBaseTextToSpeechDriver.py +++ b/nodes/drivers/gtUIBaseTextToSpeechDriver.py @@ -1,4 +1,6 @@ -from griptape.drivers import BaseTextToSpeechDriver +from typing import Any, Tuple + +from griptape.drivers import DummyTextToSpeechDriver from .gtUIBaseDriver import gtUIBaseDriver @@ -7,7 +9,7 @@ class gtUIBaseTextToSpeechDriver(gtUIBaseDriver): DESCRIPTION = "Griptape Text to Speech Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { @@ -35,8 +37,6 @@ class gtUIBaseTextToSpeechDriver(gtUIBaseDriver): CATEGORY = "Griptape/Agent Drivers/Text to Speech" - def create(self, **kwargs): - model = kwargs.get("text_to_speech_model", "eleven_multilingual_v2") - voice = kwargs.get("voice", "Matilda") - driver = BaseTextToSpeechDriver(model=model, voice=voice) + def create(self, **kwargs) -> Tuple[Any, ...]: + driver = DummyTextToSpeechDriver() return (driver,) diff --git a/nodes/drivers/gtUIBaseVectorStoreDriver.py b/nodes/drivers/gtUIBaseVectorStoreDriver.py index 46c4eb8..b75127b 100644 --- a/nodes/drivers/gtUIBaseVectorStoreDriver.py +++ b/nodes/drivers/gtUIBaseVectorStoreDriver.py @@ -1,6 +1,8 @@ +from typing import Any, Tuple + from griptape.drivers import ( - BaseVectorStoreDriver, DummyEmbeddingDriver, + DummyVectorStoreDriver, OpenAiEmbeddingDriver, ) @@ -14,7 +16,7 @@ class gtUIBaseVectorStoreDriver(gtUIBaseDriver): DESCRIPTION = "Griptape Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( @@ -41,7 +43,7 @@ class gtUIBaseVectorStoreDriver(gtUIBaseDriver): else: return DummyEmbeddingDriver() - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: embedding_driver = kwargs.get("embedding_driver", None) params = {} @@ -50,5 +52,5 @@ class gtUIBaseVectorStoreDriver(gtUIBaseDriver): else: params["embedding_driver"] = self.get_default_embedding_driver() - driver = BaseVectorStoreDriver(**params) + driver = DummyVectorStoreDriver() return (driver,) diff --git a/nodes/drivers/gtUIBaseWebSearchDriver.py b/nodes/drivers/gtUIBaseWebSearchDriver.py index 576809f..2b7b986 100644 --- a/nodes/drivers/gtUIBaseWebSearchDriver.py +++ b/nodes/drivers/gtUIBaseWebSearchDriver.py @@ -1,11 +1,11 @@ -from griptape.drivers import BaseWebSearchDriver +from typing import Any, Tuple from .gtUIBaseDriver import gtUIBaseDriver class gtUIBaseWebSearchDriver(gtUIBaseDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {}, @@ -18,8 +18,6 @@ class gtUIBaseWebSearchDriver(gtUIBaseDriver): CATEGORY = "Griptape/Agent Drivers/Web Search" - def create( - self, - ): - driver = BaseWebSearchDriver() + def create(self, **kwargs) -> Tuple[Any, ...]: + driver = None return (driver,) diff --git a/nodes/drivers/gtUIBlackForestImageGenerationDriver.py b/nodes/drivers/gtUIBlackForestImageGenerationDriver.py index e814081..91e22fe 100644 --- a/nodes/drivers/gtUIBlackForestImageGenerationDriver.py +++ b/nodes/drivers/gtUIBlackForestImageGenerationDriver.py @@ -237,8 +237,9 @@ class gtUIBlackForestImageGenerationDriver(gtUIBaseImageGenerationDriver): def create(self, **kwargs): params = self.build_params(**kwargs) params["max_attempts"] = 1 + driver = None try: - driver = BlackForestImageGenerationDriver(**params) + driver = BlackForestImageGenerationDriver(**params) # type: ignore[reportPossiblyUnboundVariable] except Exception as e: print(f"Error creating BlackForestImageGenerationDriver: {e}") return (None,) diff --git a/nodes/drivers/gtUICohereEmbeddingDriver.py b/nodes/drivers/gtUICohereEmbeddingDriver.py index b9afb08..4d87085 100644 --- a/nodes/drivers/gtUICohereEmbeddingDriver.py +++ b/nodes/drivers/gtUICohereEmbeddingDriver.py @@ -11,7 +11,7 @@ class gtUICohereEmbeddingDriver(gtUIBaseEmbeddingDriver): DESCRIPTION = "Cohere Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() diff --git a/nodes/drivers/gtUICoherePromptDriver.py b/nodes/drivers/gtUICoherePromptDriver.py index f39b03d..bd5e595 100644 --- a/nodes/drivers/gtUICoherePromptDriver.py +++ b/nodes/drivers/gtUICoherePromptDriver.py @@ -16,7 +16,7 @@ DEFAULT_API_KEY_ENV_VAR = "COHERE_API_KEY" class gtUICoherePromptDriver(gtUIBasePromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( diff --git a/nodes/drivers/gtUICohereRerankDriver.py b/nodes/drivers/gtUICohereRerankDriver.py index 0056462..1521cad 100644 --- a/nodes/drivers/gtUICohereRerankDriver.py +++ b/nodes/drivers/gtUICohereRerankDriver.py @@ -10,7 +10,7 @@ class gtUICohereRerankDriver(gtUIBaseRerankDriver): DESCRIPTION = "Cohere Rerank Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( diff --git a/nodes/drivers/gtUIDuckDuckGoWebSearchDriver.py b/nodes/drivers/gtUIDuckDuckGoWebSearchDriver.py index eee62cf..9678ff6 100644 --- a/nodes/drivers/gtUIDuckDuckGoWebSearchDriver.py +++ b/nodes/drivers/gtUIDuckDuckGoWebSearchDriver.py @@ -6,8 +6,6 @@ from .gtUIBaseWebSearchDriver import gtUIBaseWebSearchDriver class gtUIDuckDuckGoWebSearchDriver(gtUIBaseWebSearchDriver): DESCRIPTION = "DuckDuckGo Web Search Driver" - def create( - self, - ): + def create(self, **kwargs): driver = DuckDuckGoWebSearchDriver() return (driver,) diff --git a/nodes/drivers/gtUIElevenLabsTextToSpeechDriver.py b/nodes/drivers/gtUIElevenLabsTextToSpeechDriver.py index f654a41..4a9cdae 100644 --- a/nodes/drivers/gtUIElevenLabsTextToSpeechDriver.py +++ b/nodes/drivers/gtUIElevenLabsTextToSpeechDriver.py @@ -11,7 +11,7 @@ class gtUIElevenLabsTextToSpeechDriver(gtUIBaseTextToSpeechDriver): DESCRIPTION = "Griptape ElevenLabs to Speech Driver: https://elevenlabs.io/apps/" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( diff --git a/nodes/drivers/gtUIExaWebSearchDriver.py b/nodes/drivers/gtUIExaWebSearchDriver.py index 3c71445..c4f186d 100644 --- a/nodes/drivers/gtUIExaWebSearchDriver.py +++ b/nodes/drivers/gtUIExaWebSearchDriver.py @@ -11,7 +11,7 @@ class gtUIExaWebSearchDriver(gtUIBaseWebSearchDriver): DESCRIPTION = "Exa Web Search Driver. Requires environment variables EXA_API_KEY." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { diff --git a/nodes/drivers/gtUIGoogleEmbeddingDriver.py b/nodes/drivers/gtUIGoogleEmbeddingDriver.py index db3ad1d..4ff42b9 100644 --- a/nodes/drivers/gtUIGoogleEmbeddingDriver.py +++ b/nodes/drivers/gtUIGoogleEmbeddingDriver.py @@ -19,7 +19,7 @@ class gtUIGoogleEmbeddingDriver(gtUIBaseEmbeddingDriver): DESCRIPTION = "Google Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( diff --git a/nodes/drivers/gtUIGooglePromptDriver.py b/nodes/drivers/gtUIGooglePromptDriver.py index 3991982..dcc16ae 100644 --- a/nodes/drivers/gtUIGooglePromptDriver.py +++ b/nodes/drivers/gtUIGooglePromptDriver.py @@ -9,7 +9,7 @@ DEFAULT_API_KEY_ENV_VAR = "GOOGLE_API_KEY" class gtUIGooglePromptDriver(gtUIBasePromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( diff --git a/nodes/drivers/gtUIGoogleWebSearchDriver.py b/nodes/drivers/gtUIGoogleWebSearchDriver.py index b8bff65..816b8e7 100644 --- a/nodes/drivers/gtUIGoogleWebSearchDriver.py +++ b/nodes/drivers/gtUIGoogleWebSearchDriver.py @@ -10,7 +10,7 @@ class gtUIGoogleWebSearchDriver(gtUIBaseWebSearchDriver): DESCRIPTION = "Google Web Search Driver. Requires environment variables GOOGLE_API_KEY and GOOGLE_API_SEARCH_ID." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { diff --git a/nodes/drivers/gtUIGriptapeCloudVectorStoreDriver.py b/nodes/drivers/gtUIGriptapeCloudVectorStoreDriver.py index 3010a27..d9892c6 100644 --- a/nodes/drivers/gtUIGriptapeCloudVectorStoreDriver.py +++ b/nodes/drivers/gtUIGriptapeCloudVectorStoreDriver.py @@ -12,7 +12,7 @@ class gtUIGriptapeCloudVectorStoreDriver(gtUIBaseVectorStoreDriver): DESCRIPTION = "Griptape Cloud Vector Store Driver: https://cloud.griptape.ai" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() del inputs["optional"]["embedding_driver"] inputs["required"].update() @@ -48,7 +48,7 @@ class gtUIGriptapeCloudVectorStoreDriver(gtUIBaseVectorStoreDriver): api_key_env_var = kwargs.get("api_key_env_var", API_KEY_ENV) base_url = kwargs.get("base_url", BASE_URL) knowledge_base_id = kwargs.get("knowledge_base_id", "12345-abcde-1434") - + api_key = None if api_key_env_var: api_key = self.getenv(api_key_env_var) diff --git a/nodes/drivers/gtUIHuggingFaceHubEmbeddingDriver.py b/nodes/drivers/gtUIHuggingFaceHubEmbeddingDriver.py index ee8e0ea..e93ce82 100644 --- a/nodes/drivers/gtUIHuggingFaceHubEmbeddingDriver.py +++ b/nodes/drivers/gtUIHuggingFaceHubEmbeddingDriver.py @@ -12,7 +12,7 @@ class gtUIHuggingFaceHubEmbeddingDriver(gtUIBaseEmbeddingDriver): DESCRIPTION = "Hugging Face Hub Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( @@ -68,6 +68,7 @@ class gtUIHuggingFaceHubEmbeddingDriver(gtUIBaseEmbeddingDriver): def create(self, **kwargs): params = self.build_params(**kwargs) + driver = None try: driver = HuggingFaceHubEmbeddingDriver(**params) except Exception as e: diff --git a/nodes/drivers/gtUIHuggingFaceHubPromptDriver.py b/nodes/drivers/gtUIHuggingFaceHubPromptDriver.py index ac62684..7f94b71 100644 --- a/nodes/drivers/gtUIHuggingFaceHubPromptDriver.py +++ b/nodes/drivers/gtUIHuggingFaceHubPromptDriver.py @@ -11,7 +11,7 @@ class gtUIHuggingFaceHubPromptDriver(gtUIBasePromptDriver): DESCRIPTION = "Hugging Face Hub Prompt Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update({}) diff --git a/nodes/drivers/gtUILMStudioChatPromptDriver.py b/nodes/drivers/gtUILMStudioChatPromptDriver.py index b229b39..859468c 100644 --- a/nodes/drivers/gtUILMStudioChatPromptDriver.py +++ b/nodes/drivers/gtUILMStudioChatPromptDriver.py @@ -1,3 +1,4 @@ +# pyright: reportMissingImports=false from comfy_execution.graph import ExecutionBlocker from griptape.drivers import OpenAiChatPromptDriver @@ -10,7 +11,7 @@ DEFAULT_API_KEY = "lm_studio" class gtUILMStudioChatPromptDriver(gtUIOpenAiCompatibleChatPromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() del inputs["optional"]["api_key_env_var"] diff --git a/nodes/drivers/gtUILMStudioEmbeddingDriver.py b/nodes/drivers/gtUILMStudioEmbeddingDriver.py index 3f41204..8e29c0d 100644 --- a/nodes/drivers/gtUILMStudioEmbeddingDriver.py +++ b/nodes/drivers/gtUILMStudioEmbeddingDriver.py @@ -9,7 +9,7 @@ DEFAULT_API_KEY = "lm_studio" class gtUILMStudioEmbeddingDriver(gtUIOpenAiCompatibleEmbeddingDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() del inputs["optional"]["api_key_env_var"] diff --git a/nodes/drivers/gtUILeonardoImageGenerationDriver.py b/nodes/drivers/gtUILeonardoImageGenerationDriver.py index 663176e..32fd06d 100644 --- a/nodes/drivers/gtUILeonardoImageGenerationDriver.py +++ b/nodes/drivers/gtUILeonardoImageGenerationDriver.py @@ -70,7 +70,7 @@ class gtUILeonardoImageGenerationDriver(gtUIBaseImageGenerationDriver): ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): models = [] for model in leonardo_models: models.append(model["name"]) diff --git a/nodes/drivers/gtUILocalStructureRunDriver.py b/nodes/drivers/gtUILocalStructureRunDriver.py index 2d2a8f2..d785b3c 100644 --- a/nodes/drivers/gtUILocalStructureRunDriver.py +++ b/nodes/drivers/gtUILocalStructureRunDriver.py @@ -8,7 +8,7 @@ class gtUILocalStructureRunDriver: DESCRIPTION = "Local Structure Run Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {"agent": ("AGENT", {"forceInput": True, "default": None})}, @@ -28,7 +28,7 @@ class gtUILocalStructureRunDriver: ): if agent: # Create a local structure function - driver = LocalStructureRunDriver(structure_factory_fn=lambda: agent) + driver = LocalStructureRunDriver(create_structure=lambda: agent) return (driver,) else: return None diff --git a/nodes/drivers/gtUILocalVectorStoreDriver.py b/nodes/drivers/gtUILocalVectorStoreDriver.py index bd1cbdb..79e1bc8 100644 --- a/nodes/drivers/gtUILocalVectorStoreDriver.py +++ b/nodes/drivers/gtUILocalVectorStoreDriver.py @@ -11,7 +11,7 @@ class gtUILocalVectorStoreDriver(gtUIBaseVectorStoreDriver): DESCRIPTION = "Griptape Local Vector Store Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( diff --git a/nodes/drivers/gtUIMarqoVectorStoreDriver.py b/nodes/drivers/gtUIMarqoVectorStoreDriver.py index 9929751..7cc12b8 100644 --- a/nodes/drivers/gtUIMarqoVectorStoreDriver.py +++ b/nodes/drivers/gtUIMarqoVectorStoreDriver.py @@ -13,7 +13,7 @@ class gtUIMarqoVectorStoreDriver(gtUIBaseVectorStoreDriver): DESCRIPTION = "Griptape Marqo Vector Store Driver: https://www.marqo.ai/" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( @@ -65,5 +65,5 @@ class gtUIMarqoVectorStoreDriver(gtUIBaseVectorStoreDriver): def create(self, **kwargs): params = self.build_params(**kwargs) - driver = MarqoVectorStoreDriver(**params) + driver = MarqoVectorStoreDriver(**params) # type: ignore[reportArgumentType] return (driver,) diff --git a/nodes/drivers/gtUIMongoDbAtlasVectorStoreDriver.py b/nodes/drivers/gtUIMongoDbAtlasVectorStoreDriver.py index e3c612a..fe9d1e4 100644 --- a/nodes/drivers/gtUIMongoDbAtlasVectorStoreDriver.py +++ b/nodes/drivers/gtUIMongoDbAtlasVectorStoreDriver.py @@ -17,7 +17,7 @@ class gtUIMongoDbAtlasVectorStoreDriver(gtUIBaseVectorStoreDriver): DESCRIPTION = "Griptape Mongodb Atlas Vector Store Driver: https://www.mongodb.com/products/platform/atlas-database" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( @@ -112,5 +112,5 @@ class gtUIMongoDbAtlasVectorStoreDriver(gtUIBaseVectorStoreDriver): def create(self, **kwargs): params = self.build_params(**kwargs) - driver = MongoDbAtlasVectorStoreDriver(**params) + driver = MongoDbAtlasVectorStoreDriver(**params) # type: ignore[reportArgumentType] return (driver,) diff --git a/nodes/drivers/gtUIOllamaEmbeddingDriver.py b/nodes/drivers/gtUIOllamaEmbeddingDriver.py index cd6741a..86c4941 100644 --- a/nodes/drivers/gtUIOllamaEmbeddingDriver.py +++ b/nodes/drivers/gtUIOllamaEmbeddingDriver.py @@ -15,7 +15,7 @@ default_base_url = "http://127.0.0.1" class gtUIOllamaEmbeddingDriver(gtUIBaseEmbeddingDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( @@ -48,7 +48,7 @@ class gtUIOllamaEmbeddingDriver(gtUIBaseEmbeddingDriver): return inputs @classmethod - def VALIDATE_INPUTS(s, embedding_model): + def VALIDATE_INPUTS(cls, embedding_model): if not embedding_model: return """ diff --git a/nodes/drivers/gtUIOllamaPromptDriver.py b/nodes/drivers/gtUIOllamaPromptDriver.py index 1082fcb..c894e01 100644 --- a/nodes/drivers/gtUIOllamaPromptDriver.py +++ b/nodes/drivers/gtUIOllamaPromptDriver.py @@ -13,7 +13,7 @@ default_base_url = "http://127.0.0.1" class gtUIOllamaPromptDriver(gtUIBasePromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Get the base required and optional inputs base_required_inputs = inputs["required"] diff --git a/nodes/drivers/gtUIOpenAiAudioTranscriptionDriver.py b/nodes/drivers/gtUIOpenAiAudioTranscriptionDriver.py index 4dd0d4b..0235414 100644 --- a/nodes/drivers/gtUIOpenAiAudioTranscriptionDriver.py +++ b/nodes/drivers/gtUIOpenAiAudioTranscriptionDriver.py @@ -15,7 +15,7 @@ class gtUIOpenAiAudioTranscriptionDriver(gtUIBaseAudioTranscriptionDriver): DESCRIPTION = "OpenAI Audio Transcription Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( diff --git a/nodes/drivers/gtUIOpenAiChatPromptDriver.py b/nodes/drivers/gtUIOpenAiChatPromptDriver.py index c3d05ec..5b26da4 100644 --- a/nodes/drivers/gtUIOpenAiChatPromptDriver.py +++ b/nodes/drivers/gtUIOpenAiChatPromptDriver.py @@ -11,7 +11,7 @@ DEFAULT_API_KEY = "OPENAI_API_KEY" class gtUIOpenAiChatPromptDriver(gtUIBasePromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Get the base required and optional inputs diff --git a/nodes/drivers/gtUIOpenAiCompatibleChatPromptDriver.py b/nodes/drivers/gtUIOpenAiCompatibleChatPromptDriver.py index a5c532c..a648d79 100644 --- a/nodes/drivers/gtUIOpenAiCompatibleChatPromptDriver.py +++ b/nodes/drivers/gtUIOpenAiCompatibleChatPromptDriver.py @@ -9,7 +9,7 @@ DEFAULT_API_KEY_ENV = "OPENAI_API_KEY" class gtUIOpenAiCompatibleChatPromptDriver(gtUIOpenAiChatPromptDriver): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( diff --git a/nodes/drivers/gtUIOpenAiCompatibleEmbeddingDriver.py b/nodes/drivers/gtUIOpenAiCompatibleEmbeddingDriver.py index 8c722eb..fcca18d 100644 --- a/nodes/drivers/gtUIOpenAiCompatibleEmbeddingDriver.py +++ b/nodes/drivers/gtUIOpenAiCompatibleEmbeddingDriver.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.drivers import OpenAiEmbeddingDriver from .gtUIBaseEmbeddingDriver import gtUIBaseEmbeddingDriver @@ -11,7 +13,7 @@ class gtUIOpenAiCompatibleEmbeddingDriver(gtUIBaseEmbeddingDriver): DESCRIPTION = "OpenAI Compatible Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( @@ -60,7 +62,7 @@ class gtUIOpenAiCompatibleEmbeddingDriver(gtUIBaseEmbeddingDriver): return params - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: params = self.build_params(**kwargs) driver = OpenAiEmbeddingDriver(**params) return (driver,) diff --git a/nodes/drivers/gtUIOpenAiCompatibleImageGenerationDriver.py b/nodes/drivers/gtUIOpenAiCompatibleImageGenerationDriver.py index 48de314..8651bc1 100644 --- a/nodes/drivers/gtUIOpenAiCompatibleImageGenerationDriver.py +++ b/nodes/drivers/gtUIOpenAiCompatibleImageGenerationDriver.py @@ -14,7 +14,7 @@ class gtUIOpenAiCompatibleImageGenerationDriver(gtUIOpenAiImageGenerationDriver) DESCRIPTION = "OpenAI Image Generation Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { diff --git a/nodes/drivers/gtUIOpenAiEmbeddingDriver.py b/nodes/drivers/gtUIOpenAiEmbeddingDriver.py index 29b1611..c1ee744 100644 --- a/nodes/drivers/gtUIOpenAiEmbeddingDriver.py +++ b/nodes/drivers/gtUIOpenAiEmbeddingDriver.py @@ -16,7 +16,7 @@ class gtUIOpenAiEmbeddingDriver(gtUIBaseEmbeddingDriver): DESCRIPTION = "OpenAI Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() diff --git a/nodes/drivers/gtUIOpenAiImageGenerationDriver.py b/nodes/drivers/gtUIOpenAiImageGenerationDriver.py index c5cd563..8edaf41 100644 --- a/nodes/drivers/gtUIOpenAiImageGenerationDriver.py +++ b/nodes/drivers/gtUIOpenAiImageGenerationDriver.py @@ -15,7 +15,7 @@ class gtUIOpenAiImageGenerationDriver(gtUIBaseImageGenerationDriver): DESCRIPTION = "OpenAI Image Generation Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { @@ -43,7 +43,7 @@ class gtUIOpenAiImageGenerationDriver(gtUIBaseImageGenerationDriver): return inputs def adjust_size_based_on_model(self, model, size): - # pick the approprite size based on the model + # pick the appropriate size based on the model if model == "dall-e-2": if size in ["1024x1792", "1792x1024"]: size = "1024x1024" diff --git a/nodes/drivers/gtUIOpenAiTextToSpeechDriver.py b/nodes/drivers/gtUIOpenAiTextToSpeechDriver.py index ae59747..4f07503 100644 --- a/nodes/drivers/gtUIOpenAiTextToSpeechDriver.py +++ b/nodes/drivers/gtUIOpenAiTextToSpeechDriver.py @@ -14,7 +14,7 @@ class gtUIOpenAiTextToSpeechDriver(gtUIBaseTextToSpeechDriver): DESCRIPTION = "Griptape OpenAi Text to Speech Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( diff --git a/nodes/drivers/gtUIPgVectorVectorStoreDriver.py b/nodes/drivers/gtUIPgVectorVectorStoreDriver.py index 971f8bc..935b0de 100644 --- a/nodes/drivers/gtUIPgVectorVectorStoreDriver.py +++ b/nodes/drivers/gtUIPgVectorVectorStoreDriver.py @@ -16,7 +16,7 @@ class gtUIPgVectorVectorStoreDriver(gtUIBaseVectorStoreDriver): ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( diff --git a/nodes/drivers/gtUIPineconeVectorStoreDriver.py b/nodes/drivers/gtUIPineconeVectorStoreDriver.py index 6901a0d..127ccab 100644 --- a/nodes/drivers/gtUIPineconeVectorStoreDriver.py +++ b/nodes/drivers/gtUIPineconeVectorStoreDriver.py @@ -11,7 +11,7 @@ class gtUIPineconeVectorStoreDriver(gtUIBaseVectorStoreDriver): DESCRIPTION = "Griptape Pinecone Vector Store Driver: https://www.pinecone.io/" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( @@ -66,5 +66,5 @@ class gtUIPineconeVectorStoreDriver(gtUIBaseVectorStoreDriver): def create(self, **kwargs): params = self.build_params(**kwargs) - driver = PineconeVectorStoreDriver(**params) + driver = PineconeVectorStoreDriver(**params) # type: ignore[reportArgumentType] return (driver,) diff --git a/nodes/drivers/gtUIQdrantVectorStoreDriver.py b/nodes/drivers/gtUIQdrantVectorStoreDriver.py index 6394dc4..aaf3927 100644 --- a/nodes/drivers/gtUIQdrantVectorStoreDriver.py +++ b/nodes/drivers/gtUIQdrantVectorStoreDriver.py @@ -12,7 +12,7 @@ class gtUIQdrantVectorStoreDriver(gtUIBaseVectorStoreDriver): DESCRIPTION = "Griptape Qdrant Vector Store Driver: https://qdrant.tech/" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( @@ -57,6 +57,9 @@ class gtUIQdrantVectorStoreDriver(gtUIBaseVectorStoreDriver): collection_name = kwargs.get("collection_name", DEFAULT_COLLECTION_NAME) content_payload_key = kwargs.get("content_payload_key", DEFAULT_PAYLOAD_KEY) + url = None + api_key = None + if url_env: url = self.getenv(url_env) if api_key_env: diff --git a/nodes/drivers/gtUIRedisVectorStoreDriver.py b/nodes/drivers/gtUIRedisVectorStoreDriver.py index dd3b49c..e320d3e 100644 --- a/nodes/drivers/gtUIRedisVectorStoreDriver.py +++ b/nodes/drivers/gtUIRedisVectorStoreDriver.py @@ -12,7 +12,7 @@ class gtUIRedisVectorStoreDriver(gtUIBaseVectorStoreDriver): DESCRIPTION = "Griptape Redis Atlas Vector Store Driver: https://redis.io/" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update() inputs["optional"].update( @@ -57,6 +57,11 @@ class gtUIRedisVectorStoreDriver(gtUIBaseVectorStoreDriver): password_env = kwargs.get("password_env", DEFAULT_PASSWORD_ENV) index_env_var = kwargs.get("index_name_env", DEFAULT_INDEX_ENV) + password = None + host = None + port = None + index = None + if password_env: password = self.getenv(password_env) if host_env: diff --git a/nodes/drivers/gtUITavilyWebSearchDriver.py b/nodes/drivers/gtUITavilyWebSearchDriver.py index 8ef31a8..fac291e 100644 --- a/nodes/drivers/gtUITavilyWebSearchDriver.py +++ b/nodes/drivers/gtUITavilyWebSearchDriver.py @@ -13,7 +13,7 @@ class gtUITavilyWebSearchDriver(gtUIBaseWebSearchDriver): ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { diff --git a/nodes/drivers/gtUIVoyageAiEmbeddingDriver.py b/nodes/drivers/gtUIVoyageAiEmbeddingDriver.py index a2726fe..d8c1faa 100644 --- a/nodes/drivers/gtUIVoyageAiEmbeddingDriver.py +++ b/nodes/drivers/gtUIVoyageAiEmbeddingDriver.py @@ -9,7 +9,7 @@ class gtUIVoyageAiEmbeddingDriver(gtUIBaseEmbeddingDriver): DESCRIPTION = "Voyage AI Embedding Driver" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Get the base required and optional inputs diff --git a/nodes/expand/ExpandAgent.py b/nodes/expand/ExpandAgent.py index 899340d..d488dcd 100644 --- a/nodes/expand/ExpandAgent.py +++ b/nodes/expand/ExpandAgent.py @@ -5,7 +5,7 @@ class ExpandAgent: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "agent": ( diff --git a/nodes/expand/gtUIExpandConfig.py b/nodes/expand/gtUIExpandConfig.py index 47576c3..a642495 100644 --- a/nodes/expand/gtUIExpandConfig.py +++ b/nodes/expand/gtUIExpandConfig.py @@ -7,7 +7,7 @@ class gtUIExpandConfig: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "config": ( @@ -42,15 +42,17 @@ class gtUIExpandConfig: OUTPUT_NODE = True def expand(self, **kwargs): - config = kwargs.get("config") - - prompt_driver = config.prompt_driver - image_generation_driver = config.image_generation_driver - embedding_driver = config.embedding_driver - vector_store_driver = config.vector_store_driver - text_to_speech_driver = config.text_to_speech_driver - audio_transcription_driver = config.audio_transcription_driver + config = kwargs.get("config", None) + if config is not None: + prompt_driver = config.prompt_driver + image_generation_driver = config.image_generation_driver + embedding_driver = config.embedding_driver + vector_store_driver = config.vector_store_driver + text_to_speech_driver = config.text_to_speech_driver + audio_transcription_driver = config.audio_transcription_driver + else: + raise ValueError("No config provided") return ( prompt_driver, image_generation_driver, diff --git a/nodes/griptape_config.py b/nodes/griptape_config.py deleted file mode 100644 index 664f087..0000000 --- a/nodes/griptape_config.py +++ /dev/null @@ -1,76 +0,0 @@ -# This config code from rgthree-comfy - an incredibly helpful library! -# https://github.com/rgthree/rgthree-comfy -import os -import json -import re - -from .utils import get_dict_value, set_dict_value, dict_has_key - - -def get_config_value(key): - return get_dict_value(GRIPTAPE_CONFIG, key) - - -def extend_config(default_config, user_config): - """Returns a new config dict combining user_config into defined keys for default_config.""" - cfg = {} - for key, value in default_config.items(): - if key not in user_config: - cfg[key] = value - elif isinstance(value, dict): - cfg[key] = extend_config(value, user_config[key]) - else: - cfg[key] = user_config[key] if key in user_config else value - return cfg - - -def set_user_config(data: dict): - """Sets the user configuration.""" - count = 0 - for key, value in data.items(): - if dict_has_key(DEFAULT_CONFIG, key): - set_dict_value(USER_CONFIG, key, value) - set_dict_value(GRIPTAPE_CONFIG, key, value) - count += 1 - if count > 0: - write_user_config() - - -def get_griptape_default_config(): - """Gets the default configuration.""" - with open(DEFAULT_CONFIG_FILE, "r", encoding="UTF-8") as file: - config = re.sub(r"(?:^|\s)//.*", "", file.read(), flags=re.MULTILINE) - return json.loads(config) - - -def get_griptape_user_config(): - """Gets the user configuration.""" - if os.path.exists(USER_CONFIG_FILE): - with open(USER_CONFIG_FILE, "r", encoding="UTF-8") as file: - config = re.sub(r"(?:^|\s)//.*", "", file.read(), flags=re.MULTILINE) - return json.loads(config) - else: - return {} - - -def write_user_config(): - """Writes the user configuration.""" - with open(USER_CONFIG_FILE, "w+", encoding="UTF-8") as file: - json.dump(USER_CONFIG, file, sort_keys=True, indent=2, separators=(",", ": ")) - - -THIS_DIR = os.path.dirname(os.path.abspath(__file__)) -DEFAULT_CONFIG_FILE = os.path.join(THIS_DIR, "..", "griptape_config.json.default") -USER_CONFIG_FILE = os.path.join(THIS_DIR, "..", "griptape_config.json") -DEFAULT_CONFIG = get_griptape_default_config() - -USER_CONFIG = get_griptape_user_config() - -# Migrate old config options into "features" -needs_to_write_user_config = False - -if needs_to_write_user_config is True: - print("writing new user config.") - write_user_config() - -GRIPTAPE_CONFIG = extend_config(DEFAULT_CONFIG, USER_CONFIG) diff --git a/nodes/gtUIBase.py b/nodes/gtUIBase.py index 7c25f07..2892c9c 100644 --- a/nodes/gtUIBase.py +++ b/nodes/gtUIBase.py @@ -1,4 +1,5 @@ import os +from typing import Any, Tuple class gtUIBase: @@ -10,7 +11,7 @@ class gtUIBase: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {}, @@ -49,6 +50,6 @@ class gtUIBase: os.environ[key] = value print(f"Set environment variable: {key}") - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: self.run_envs(kwargs) - return () + return (None,) diff --git a/nodes/loaders/gtUIBaseLoader.py b/nodes/loaders/gtUIBaseLoader.py index 49d1ab6..93d6409 100644 --- a/nodes/loaders/gtUIBaseLoader.py +++ b/nodes/loaders/gtUIBaseLoader.py @@ -1,4 +1,4 @@ -from griptape.artifacts import BaseArtifact +from griptape.artifacts import TextArtifact class gtUIBaseLoader: @@ -8,7 +8,7 @@ class gtUIBaseLoader: DESCRIPTION = "Loader" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {}, @@ -23,5 +23,5 @@ class gtUIBaseLoader: CATEGORY = "Griptape/Loaders" def run(self, **kwargs): - artifact = BaseArtifact() + artifact = TextArtifact(value="") return (artifact,) diff --git a/nodes/loaders/gtUIFetchImage.py b/nodes/loaders/gtUIFetchImage.py index 7e4cba7..ecc0e63 100644 --- a/nodes/loaders/gtUIFetchImage.py +++ b/nodes/loaders/gtUIFetchImage.py @@ -1,3 +1,4 @@ +# pyright: reportMissingImports=false from io import BytesIO import numpy as np diff --git a/nodes/loaders/gtUILoadAudio.py b/nodes/loaders/gtUILoadAudio.py index 6c4a932..ec8e119 100644 --- a/nodes/loaders/gtUILoadAudio.py +++ b/nodes/loaders/gtUILoadAudio.py @@ -1,9 +1,10 @@ +# pyright: reportMissingImports=false + import mimetypes import os -import torchaudio - import folder_paths +import torchaudio def is_audio_file(filepath): @@ -16,7 +17,7 @@ class gtUILoadAudio: SUPPORTED_FORMATS = (".wav", ".mp3", ".ogg", ".flac", ".aiff", ".aif") @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): input_dir = folder_paths.get_input_directory() files = [ f @@ -37,7 +38,6 @@ class gtUILoadAudio: def gt_load_audio(self, audio): audio_path = folder_paths.get_annotated_filepath(audio) waveform, sample_rate = torchaudio.load(audio_path) - multiplier = 1.0 audio = {"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate} return ( audio_path, diff --git a/nodes/loaders/gtUILoadText.py b/nodes/loaders/gtUILoadText.py index f01a2fd..f05eded 100644 --- a/nodes/loaders/gtUILoadText.py +++ b/nodes/loaders/gtUILoadText.py @@ -1,3 +1,5 @@ +# pyright: reportMissingImports=false + import os import folder_paths @@ -23,7 +25,7 @@ class gtUILoadText: ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): input_dir = folder_paths.get_input_directory() files = [ f diff --git a/nodes/rag/gtUIBaseQueryRagModule.py b/nodes/rag/gtUIBaseQueryRagModule.py index 5789b51..3a9be50 100644 --- a/nodes/rag/gtUIBaseQueryRagModule.py +++ b/nodes/rag/gtUIBaseQueryRagModule.py @@ -1,5 +1,3 @@ -from griptape.engines.rag.modules import BaseQueryRagModule - from .gtUIBaseRagModule import gtUIBaseRagModule @@ -12,10 +10,10 @@ class gtUIBaseQueryRagModule(gtUIBaseRagModule): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() return inputs - def create(self): - return ([BaseQueryRagModule()],) + def create(self, **kwargs): + return ([None],) diff --git a/nodes/rag/gtUIBaseRagModule.py b/nodes/rag/gtUIBaseRagModule.py index 75c0bcb..b40aa77 100644 --- a/nodes/rag/gtUIBaseRagModule.py +++ b/nodes/rag/gtUIBaseRagModule.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.engines.rag.modules import BaseRagModule @@ -10,7 +12,7 @@ class gtUIBaseRagModule: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": {}, @@ -23,7 +25,7 @@ class gtUIBaseRagModule: CATEGORY = "Griptape/RAG" - def ensure_dict(params): + def ensure_dict(self, params): if isinstance(params, dict): return params elif isinstance(params, str): @@ -48,5 +50,5 @@ class gtUIBaseRagModule: except Exception: raise ValueError(f"Could not convert {type(params)} to dictionary") - def create(self): + def create(self) -> Tuple[Any, ...]: return ([BaseRagModule()],) diff --git a/nodes/rag/gtUIBaseRerankRagModule.py b/nodes/rag/gtUIBaseRerankRagModule.py index b5bc374..f01f6e9 100644 --- a/nodes/rag/gtUIBaseRerankRagModule.py +++ b/nodes/rag/gtUIBaseRerankRagModule.py @@ -1,5 +1,3 @@ -from griptape.engines.rag.modules import BaseRerankRagModule - from .gtUIBaseRagModule import gtUIBaseRagModule @@ -12,7 +10,7 @@ class gtUIBaseRerankRagModule(gtUIBaseRagModule): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( @@ -26,4 +24,4 @@ class gtUIBaseRerankRagModule(gtUIBaseRagModule): RETURN_NAMES = ("MODULE",) def create(self, **kwargs): - return (BaseRerankRagModule(),) + return (None,) diff --git a/nodes/rag/gtUIBaseReponseRagModule.py b/nodes/rag/gtUIBaseResponseRagModule.py similarity index 63% rename from nodes/rag/gtUIBaseReponseRagModule.py rename to nodes/rag/gtUIBaseResponseRagModule.py index e12f722..eadc720 100644 --- a/nodes/rag/gtUIBaseReponseRagModule.py +++ b/nodes/rag/gtUIBaseResponseRagModule.py @@ -1,5 +1,3 @@ -from griptape.engines.rag.modules import BaseResponseRagModule - from .gtUIBaseRagModule import gtUIBaseRagModule @@ -9,4 +7,4 @@ class gtUIBaseResponseRagModule(gtUIBaseRagModule): """ def create(self, **kwargs): - return ([BaseResponseRagModule()],) + return ([None],) diff --git a/nodes/rag/gtUIBaseRetrievalRagModule.py b/nodes/rag/gtUIBaseRetrievalRagModule.py index 9de0b7a..7b715e8 100644 --- a/nodes/rag/gtUIBaseRetrievalRagModule.py +++ b/nodes/rag/gtUIBaseRetrievalRagModule.py @@ -1,5 +1,8 @@ -from griptape.drivers import DummyVectorStoreDriver, LocalVectorStoreDriver -from griptape.engines.rag.modules import BaseRetrievalRagModule +from griptape.drivers import ( + DummyEmbeddingDriver, + DummyVectorStoreDriver, + LocalVectorStoreDriver, +) from .gtUIBaseRagModule import gtUIBaseRagModule @@ -13,7 +16,7 @@ class gtUIBaseRetrievalRagModule(gtUIBaseRagModule): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = { @@ -46,10 +49,10 @@ class gtUIBaseRetrievalRagModule(gtUIBaseRagModule): if not vector_store_driver or isinstance( vector_store_driver, DummyVectorStoreDriver ): - return LocalVectorStoreDriver() + return LocalVectorStoreDriver(embedding_driver=DummyEmbeddingDriver()) else: return vector_store_driver return None def create(self, **kwargs): - return ([BaseRetrievalRagModule()],) + return ([None],) diff --git a/nodes/rag/gtUIFootnotePromptResponseRagModule.py b/nodes/rag/gtUIFootnotePromptResponseRagModule.py index 919d0a2..c52f9f0 100644 --- a/nodes/rag/gtUIFootnotePromptResponseRagModule.py +++ b/nodes/rag/gtUIFootnotePromptResponseRagModule.py @@ -12,7 +12,7 @@ class gtUIFootnotePromptResponseRagModule(gtUIPromptResponseRagModule): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() return inputs diff --git a/nodes/rag/gtUIPromptResponseRagModule.py b/nodes/rag/gtUIPromptResponseRagModule.py index bb542da..4c1e6ba 100644 --- a/nodes/rag/gtUIPromptResponseRagModule.py +++ b/nodes/rag/gtUIPromptResponseRagModule.py @@ -1,6 +1,8 @@ +from typing import Any, Tuple + from griptape.engines.rag.modules import PromptResponseRagModule -from .gtUIBaseReponseRagModule import gtUIBaseResponseRagModule +from .gtUIBaseResponseRagModule import gtUIBaseResponseRagModule class gtUIPromptResponseRagModule(gtUIBaseResponseRagModule): @@ -12,7 +14,7 @@ class gtUIPromptResponseRagModule(gtUIBaseResponseRagModule): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = { @@ -30,7 +32,7 @@ class gtUIPromptResponseRagModule(gtUIBaseResponseRagModule): } return inputs - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: params = {} prompt_driver = kwargs.get("prompt_driver", None) rulesets = kwargs.get("rulesets", []) diff --git a/nodes/rag/gtUIRagEngine.py b/nodes/rag/gtUIRagEngine.py index a038660..0d07f2c 100644 --- a/nodes/rag/gtUIRagEngine.py +++ b/nodes/rag/gtUIRagEngine.py @@ -10,7 +10,7 @@ class gtUIRagEngine: DESCRIPTION = "Griptape Rag Tool" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { diff --git a/nodes/rag/gtUITextChunksRerankRagModule.py b/nodes/rag/gtUITextChunksRerankRagModule.py index bae2851..6771b3f 100644 --- a/nodes/rag/gtUITextChunksRerankRagModule.py +++ b/nodes/rag/gtUITextChunksRerankRagModule.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.engines.rag.modules import TextChunksRerankRagModule from .gtUIBaseRerankRagModule import gtUIBaseRerankRagModule @@ -11,7 +13,7 @@ class gtUITextChunksRerankRagModule(gtUIBaseRerankRagModule): def __init__(self): pass - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: params = {} rerank_driver = kwargs.get("rerank_driver", None) if rerank_driver: diff --git a/nodes/rag/gtUITextChunksResponseRagModule.py b/nodes/rag/gtUITextChunksResponseRagModule.py index e50e980..5bbd20b 100644 --- a/nodes/rag/gtUITextChunksResponseRagModule.py +++ b/nodes/rag/gtUITextChunksResponseRagModule.py @@ -1,6 +1,8 @@ +from typing import Any, Tuple + from griptape.engines.rag.modules import TextChunksResponseRagModule -from .gtUIBaseReponseRagModule import gtUIBaseResponseRagModule +from .gtUIBaseResponseRagModule import gtUIBaseResponseRagModule class gtUITextChunksResponseRagModule(gtUIBaseResponseRagModule): @@ -11,6 +13,6 @@ class gtUITextChunksResponseRagModule(gtUIBaseResponseRagModule): def __init__(self): pass - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: module = TextChunksResponseRagModule() return ([module],) diff --git a/nodes/rag/gtUITextLoaderRetrievalRagModule.py b/nodes/rag/gtUITextLoaderRetrievalRagModule.py index 3d55e96..17d52db 100644 --- a/nodes/rag/gtUITextLoaderRetrievalRagModule.py +++ b/nodes/rag/gtUITextLoaderRetrievalRagModule.py @@ -1,5 +1,7 @@ +# pyright: reportMissingImports=false import os import tempfile +from typing import Any, Tuple import folder_paths from griptape.engines.rag.modules import TextLoaderRetrievalRagModule @@ -36,7 +38,7 @@ class gtUITextLoaderRetrievalRagModule(gtUIBaseRetrievalRagModule): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): input_dir = folder_paths.get_input_directory() files = [ f @@ -93,7 +95,7 @@ class gtUITextLoaderRetrievalRagModule(gtUIBaseRetrievalRagModule): ) return inputs - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: vector_store_driver = self.get_vector_store_driver( kwargs.get("vector_store_driver", None) ) diff --git a/nodes/rag/gtUITranslateQueryRagModule.py b/nodes/rag/gtUITranslateQueryRagModule.py index 63f4acd..5f11414 100644 --- a/nodes/rag/gtUITranslateQueryRagModule.py +++ b/nodes/rag/gtUITranslateQueryRagModule.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.drivers import OpenAiChatPromptDriver from griptape.engines.rag.modules import TranslateQueryRagModule @@ -13,7 +15,7 @@ class gtUITranslateQueryRagModule(gtUIBaseQueryRagModule): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"] = { @@ -31,7 +33,7 @@ class gtUITranslateQueryRagModule(gtUIBaseQueryRagModule): } return inputs - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: prompt_driver = kwargs.get("prompt_driver", None) if not prompt_driver: prompt_driver = OpenAiChatPromptDriver(model="gpt-4o") diff --git a/nodes/rag/gtUIVectorStoreRetrievalRagModule.py b/nodes/rag/gtUIVectorStoreRetrievalRagModule.py index f54281d..773c0f4 100644 --- a/nodes/rag/gtUIVectorStoreRetrievalRagModule.py +++ b/nodes/rag/gtUIVectorStoreRetrievalRagModule.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.engines.rag.modules import VectorStoreRetrievalRagModule from .gtUIBaseRetrievalRagModule import gtUIBaseRetrievalRagModule @@ -12,7 +14,7 @@ class gtUIVectorStoreRetrievalRagModule(gtUIBaseRetrievalRagModule): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( @@ -34,7 +36,7 @@ class gtUIVectorStoreRetrievalRagModule(gtUIBaseRetrievalRagModule): ) return inputs - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: vector_store_driver = self.get_vector_store_driver( kwargs.get("vector_store_driver", None) ) diff --git a/nodes/rules/gtUIRule.py b/nodes/rules/gtUIRule.py index dac1335..0ccfe7e 100644 --- a/nodes/rules/gtUIRule.py +++ b/nodes/rules/gtUIRule.py @@ -8,7 +8,7 @@ class gtUIRule: DESCRIPTION = "Create a set of rules for an agent" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "name": ("STRING", {"default": "My rules"}), diff --git a/nodes/structures/BaseStructure.py b/nodes/structures/BaseStructure.py deleted file mode 100644 index e98e4ad..0000000 --- a/nodes/structures/BaseStructure.py +++ /dev/null @@ -1,28 +0,0 @@ -from griptape.structures import Structure - - -class gtUIBaseStructure: - def __init__(self): - pass - - DESCRIPTION = "Create a Structure." - - @classmethod - def INPUT_TYPES(s): - return { - "required": {}, - "optional": {}, - } - - RETURN_TYPES = ("STRUCTURE",) - RETURN_NAMES = ("STRUCTURE",) - - FUNCTION = "run" - # OUTPUT_NODE = True - - CATEGORY = "Griptape/Structure" - - def run( - self, - ): - return (Structure(),) diff --git a/nodes/structures/CreatePipeline.py b/nodes/structures/CreatePipeline.py deleted file mode 100644 index ae6b655..0000000 --- a/nodes/structures/CreatePipeline.py +++ /dev/null @@ -1,52 +0,0 @@ -from random import randint - -from griptape.structures import Pipeline - -from .BaseStructure import gtUIBaseStructure - - -class AnyType(str): - def __ne__(self, __value: object) -> bool: - return False - - -any = AnyType("*") - - -class gtUICreatePipeline(gtUIBaseStructure): - def __init__(self): - pass - - DESCRIPTION = "Create a Pipeline." - - @classmethod - def INPUT_TYPES(s): - inputs = super().INPUT_TYPES() - inputs["optional"].update({"task_1": ("TASK",)}) - del inputs["required"]["STRING"] - del inputs["optional"]["input_string"] - - return inputs - - RETURN_TYPES = ("STRUCTURE",) - RETURN_NAMES = ("STRUCTURE",) - - FUNCTION = "run" - OUTPUT_NODE = False - - @classmethod - def IS_CHANGED(s, int_field): - return randint(0, 1000) - - def run( - self, - **kwargs, - ): - tasks = [] - for i, (key, value) in enumerate(kwargs.items()): - if value is not None: - if i == 0: - # append " {{ args[0] }}" to the first task if it's not there. - tasks.append(value) - structure = Pipeline(tasks=tasks) - return (structure,) diff --git a/nodes/structures/PipelineAddTask.py b/nodes/structures/PipelineAddTask.py deleted file mode 100644 index 85f8c7d..0000000 --- a/nodes/structures/PipelineAddTask.py +++ /dev/null @@ -1,59 +0,0 @@ -from random import randint - -from griptape.tasks import PromptTask - -from .BaseStructure import gtUIBaseStructure - - -class AnyType(str): - def __ne__(self, __value: object) -> bool: - return False - - -any = AnyType("*") - - -class gtUIPipelineAddTask(gtUIBaseStructure): - DESCRIPTION = "Add a task to the end of a pipeline." - - @classmethod - def INPUT_TYPES(s): - inputs = super().INPUT_TYPES() - inputs["required"].update({"structure": ("STRUCTURE",)}) - inputs["optional"].update( - { - "task": ("TASK", {}), - "append_parent_output": ("BOOLEAN", {"default": True}), - } - ) - return inputs - - @classmethod - def IS_CHANGED(s, int_field): - return randint(0, 1000) - - RETURN_TYPES = ("STRUCTURE",) - RETURN_NAMES = ("STRUCTURE",) - - FUNCTION = "run" - # OUTPUT_NODE = Tru - - def run(self, **kwargs): - structure = kwargs.get("structure") - append_parent_output = kwargs.get("append_parent_output", True) - task = kwargs.get("task") - - if task: - if isinstance(task, PromptTask): - if append_parent_output: - prev_value = task.input.value - task = PromptTask( - "{{ prev_value}}\n\n{{ parent_output }}", - context={"prev_value": prev_value}, - ) - # print(task.input.value) - # print(type(task.input.value)) - - # task.prompt = task.prompt.value + "\n\n" + task.input.value - structure.add_task(task) - return (structure,) diff --git a/nodes/structures/PipelineInsertTask.py b/nodes/structures/PipelineInsertTask.py deleted file mode 100644 index 755a8be..0000000 --- a/nodes/structures/PipelineInsertTask.py +++ /dev/null @@ -1,64 +0,0 @@ -from random import randint - -from griptape.tasks import PromptTask - -from .BaseStructure import gtUIBaseStructure - - -class AnyType(str): - def __ne__(self, __value: object) -> bool: - return False - - -any = AnyType("*") - - -class gtUIPipelineInsertTask(gtUIBaseStructure): - DESCRIPTION = "Insert a task into a pipeline." - - @classmethod - def INPUT_TYPES(s): - inputs = super().INPUT_TYPES() - inputs["required"].update({"structure": ("STRUCTURE",)}) - inputs["optional"].update( - { - "parent_task": ("TASK", {}), - "task": ("TASK", {}), - "append_parent_output": ("BOOLEAN", {"default": True}), - }, - ) - return inputs - - @classmethod - def IS_CHANGED(s, int_field): - return randint(0, 1000) - - RETURN_TYPES = ("STRUCTURE",) - RETURN_NAMES = ("STRUCTURE",) - - FUNCTION = "run" - # OUTPUT_NODE = True - - def run( - self, - **kwargs, - ): - structure = kwargs.get("structure") - parent_task = kwargs.get("parent_task") - task = kwargs.get("task") - append_parent_output = kwargs.get("append_parent_output", True) - - if task and parent_task: - if isinstance(task, PromptTask): - if append_parent_output: - prev_value = task.input.value - task = PromptTask( - "{{ prev_value}}\n\n{{ parent_output }}", - context={"prev_value": prev_value}, - ) - # print(task.input.value) - # print(type(task.input.value)) - - # task.prompt = task.prompt.value + "\n\n" + task.input.value - structure.insert_task(parent_task, task) - return (structure,) diff --git a/nodes/structures/RunStructure.py b/nodes/structures/RunStructure.py deleted file mode 100644 index d5f5957..0000000 --- a/nodes/structures/RunStructure.py +++ /dev/null @@ -1,89 +0,0 @@ -from griptape.artifacts import AudioArtifact, ImageArtifact, TextArtifact - -from ..utilities import image_to_comfyui -from .BaseStructure import gtUIBaseStructure - - -class AnyType(str): - def __ne__(self, __value: object) -> bool: - return False - - -any = AnyType("*") - - -class gtUIRunStructure(gtUIBaseStructure): - def __init__(self): - pass - - DESCRIPTION = "Run a Structure." - - @classmethod - def INPUT_TYPES(s): - inputs = super().INPUT_TYPES() - inputs["required"].update({"structure": ("STRUCTURE",)}) - inputs["optional"].update( - { - "STRING": ("STRING", {"default": "", "multiline": True}), - } - ) - return inputs - - RETURN_TYPES = ( - any, - "STRING", - "STRUCTURE", - ) - RETURN_NAMES = ( - "OUTPUT", - "TASK_OUTPUTS", - "STRUCTURE", - ) - - FUNCTION = "run" - OUTPUT_NODE = True - - def append_prompt_text(self, structure, prompt_text): - if prompt_text.strip() != "": - # Check and see if the first task has an input - if structure.tasks: - first_task = structure.tasks[0] - if isinstance(first_task.input, TextArtifact): - new_artifact = TextArtifact( - first_task.input.value + "\n\n {{ args[0] }}" - ) - first_task.input = new_artifact - structure.tasks[0] = first_task - - return structure - - def run(self, **kwargs): - STRING = kwargs.get("STRING", "") - structure = kwargs.get("structure") - input_string = kwargs.get("input_string", "") - - prompt_text = self.get_prompt_text(STRING, input_string) - - # If prompt_text isn't empty, we'll need to add it to the first task if we can. - structure = self.append_prompt_text(structure, prompt_text) - # Run the structure - result = structure.run(prompt_text) - - # Handle the results - task_outputs = repr( - [ - {"input": task.input.value, "output": task.output.value} - for task in structure.tasks - ] - ) - - # Depending on the type of output, we'll need to adjust it - if isinstance(result.output_task.output, str): - output = result.output_task.output - elif isinstance(result.output_task.output, TextArtifact): - output = result.output_task.output.value - elif isinstance(result.output_task.output, ImageArtifact): - output, output_mask = image_to_comfyui(result.output_task.output.value) - elif isinstance(result.output_task.output, AudioArtifact): - output = type(result.output_task.output) - return (output, task_outputs, structure) diff --git a/nodes/tasks/gtUIAudioTranscriptionTask.py b/nodes/tasks/gtUIAudioTranscriptionTask.py index 541fd3c..6447541 100644 --- a/nodes/tasks/gtUIAudioTranscriptionTask.py +++ b/nodes/tasks/gtUIAudioTranscriptionTask.py @@ -43,6 +43,8 @@ class gtUIAudioTranscriptionTask(gtUIBaseAudioTask): audio_filepath = kwargs.get("audio_filepath", None) driver = kwargs.get("driver", None) agent = kwargs.get("agent", None) + + result = None if not agent: agent = Agent() @@ -100,7 +102,7 @@ class gtUIAudioTranscriptionTask(gtUIBaseAudioTask): result = pipeline.run() except Exception as e: print(e) - output = result.output_task.output.value + output = result.output_task.output.value # type: ignore[reportOptionalMemberAccess] else: output = "No audio provided" return (output,) diff --git a/nodes/tasks/gtUIBaseAudioTask.py b/nodes/tasks/gtUIBaseAudioTask.py index 0a78821..27dec52 100644 --- a/nodes/tasks/gtUIBaseAudioTask.py +++ b/nodes/tasks/gtUIBaseAudioTask.py @@ -1,5 +1,7 @@ +# pyright: reportMissingImports=false import io import tempfile +from typing import Any, Tuple import torchaudio from griptape.drivers import OpenAiAudioTranscriptionDriver @@ -55,7 +57,7 @@ class gtUIBaseAudioTask(gtUIBaseTask): return temp_files - def run(self, **kwargs): + def run(self, **kwargs) -> Tuple[Any, ...]: driver = kwargs.get("driver", None) if not driver: diff --git a/nodes/tasks/gtUIBaseImageTask.py b/nodes/tasks/gtUIBaseImageTask.py index cf054a1..aec29aa 100644 --- a/nodes/tasks/gtUIBaseImageTask.py +++ b/nodes/tasks/gtUIBaseImageTask.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from .gtUIBaseTask import gtUIBaseTask @@ -16,6 +18,7 @@ class gtUIBaseImageTask(gtUIBaseTask): CATEGORY = "Griptape/Image" - def run(self, STRING, image, input_string=None, agent=None): + def run(self, **kwargs) -> Tuple[Any, ...]: + agent = kwargs.get("agent", None) output = "Output" return (output, agent) diff --git a/nodes/tasks/gtUIBaseTask.py b/nodes/tasks/gtUIBaseTask.py index d729dbd..f7986d2 100644 --- a/nodes/tasks/gtUIBaseTask.py +++ b/nodes/tasks/gtUIBaseTask.py @@ -1,7 +1,5 @@ import re -from griptape.tasks import PromptTask - from ..agent.gtComfyAgent import gtComfyAgent as Agent @@ -12,7 +10,7 @@ class gtUIBaseTask: DESCRIPTION = "Run a PromptTask." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { # "deferred_evaluation": ("BOOLEAN", {"default": False}), @@ -88,12 +86,11 @@ class gtUIBaseTask: input_string = kwargs.get("input_string") agent = kwargs.get("agent") deferred_evaluation = kwargs.get("deferred_evaluation", False) - + result = None if not agent: agent = Agent() prompt_text = self.get_prompt_text(STRING, input_string) - task = PromptTask(prompt_text) if deferred_evaluation: try: return ( @@ -110,7 +107,6 @@ class gtUIBaseTask: print(e) # result = agent.run() return ( - result.output_task.output.value, + result.output_task.output.value, # type: ignore[reportOptionalMemberAccess] agent, - # task ) diff --git a/nodes/tasks/gtUIBaseVectorStoreTask.py b/nodes/tasks/gtUIBaseVectorStoreTask.py index 7907d3d..fb36a4d 100644 --- a/nodes/tasks/gtUIBaseVectorStoreTask.py +++ b/nodes/tasks/gtUIBaseVectorStoreTask.py @@ -1,4 +1,4 @@ -from griptape.drivers import LocalVectorStoreDriver +from griptape.drivers import DummyEmbeddingDriver, LocalVectorStoreDriver from ..agent.gtComfyAgent import gtComfyAgent as Agent @@ -11,7 +11,7 @@ class gtUIBaseVectorStoreTask: DESCRIPTION = "Operate on a Vector Store." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): # inputs = super().INPUT_TYPES() inputs = { "required": {}, @@ -45,15 +45,12 @@ class gtUIBaseVectorStoreTask: elif not driver and agent: vector_store_driver = agent.drivers_config.vector_store_driver else: - vector_store_driver = LocalVectorStoreDriver() + vector_store_driver = LocalVectorStoreDriver( + embedding_driver=DummyEmbeddingDriver() + ) return vector_store_driver def run(self, **kwargs): agent = kwargs.get("agent", Agent()) - driver = kwargs.get("driver", None) - vector_store_driver = self.get_vector_store_driver(agent, driver) - return ( - agent, - # vector_store_driver, - ) + return (agent,) diff --git a/nodes/tasks/gtUIExtractionTask.py b/nodes/tasks/gtUIExtractionTask.py index 32deb07..1e05981 100644 --- a/nodes/tasks/gtUIExtractionTask.py +++ b/nodes/tasks/gtUIExtractionTask.py @@ -13,7 +13,7 @@ class gtUIExtractionTask(gtUIBaseTask): """ @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { @@ -69,8 +69,8 @@ class gtUIExtractionTask(gtUIBaseTask): column_name.strip() for column_name in column_names_string.split(",") ] template_schema = kwargs.get("template_schema", "") - params = {} - + engine = None + output = None if not agent: agent = Agent() prompt_driver = agent.drivers_config.prompt_driver @@ -87,7 +87,7 @@ class gtUIExtractionTask(gtUIBaseTask): prompt_text = self.get_prompt_text(STRING, input_string) - task = ExtractionTask(extraction_engine=engine) + task = ExtractionTask(extraction_engine=engine) # type: ignore[reportArgumentType] try: agent.add_task(task) except Exception as e: diff --git a/nodes/tasks/gtUIImageQueryTask.py b/nodes/tasks/gtUIImageQueryTask.py index ee98ae9..5b12974 100644 --- a/nodes/tasks/gtUIImageQueryTask.py +++ b/nodes/tasks/gtUIImageQueryTask.py @@ -45,7 +45,7 @@ class gtUIImageQueryTask(gtUIBaseImageTask): ImageLoader().parse(base64.b64decode(base64Image)) ) except Exception as e: - raise (f"Couldn't load image {e}") + raise RuntimeError(f"Couldn't load image {e}") # Depending on the model, we might need to use a workflow instead of a simple prompt rulesets = agent.rulesets tasks = [] diff --git a/nodes/tasks/gtUIInpaintingImageGenerationTask.py b/nodes/tasks/gtUIInpaintingImageGenerationTask.py index e2e7338..210f6ec 100644 --- a/nodes/tasks/gtUIInpaintingImageGenerationTask.py +++ b/nodes/tasks/gtUIInpaintingImageGenerationTask.py @@ -1,3 +1,4 @@ +# pyright: reportMissingImports=false import base64 import os @@ -9,6 +10,8 @@ try: ) except ImportError: print("BlackForestImageGenerationDriver not found") + BlackForestImageGenerationDriver = None # Set it to None if import fails + from griptape.drivers import ( AmazonBedrockImageGenerationDriver, AzureOpenAiImageGenerationDriver, @@ -35,7 +38,7 @@ class gtUIInpaintingImageGenerationTask(gtUIBaseImageTask): ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { @@ -102,7 +105,7 @@ class gtUIInpaintingImageGenerationTask(gtUIBaseImageTask): mask = kwargs.get("mask") driver = kwargs.get("driver", None) input_string = kwargs.get("input_string", None) - + result = None agent = Agent() final_image = convert_tensor_to_base_64(image) final_mask = convert_tensor_to_base_64(mask) @@ -126,7 +129,7 @@ class gtUIInpaintingImageGenerationTask(gtUIBaseImageTask): raise ValueError(msg) # Quick fix for flux-pro-1.0-fill - if "BlackForestImageGenerationDriver" in globals() and isinstance( + if BlackForestImageGenerationDriver and isinstance( driver, BlackForestImageGenerationDriver ): # check the model to make sure it's one that can handle Variation Image Generation @@ -150,7 +153,7 @@ class gtUIInpaintingImageGenerationTask(gtUIBaseImageTask): result = agent.run() except Exception as e: print(e) - filename = result.output_task.output.name + filename = result.output_task.output.name # type: ignore[reportOptionalMemberAccess] image_path = os.path.join(output_dir, filename) # Get the image in a format ComfyUI can read output_image, output_mask = image_path_to_output(image_path) diff --git a/nodes/tasks/gtUIPromptImageGenerationTask.py b/nodes/tasks/gtUIPromptImageGenerationTask.py index b23f4fa..fa7624d 100644 --- a/nodes/tasks/gtUIPromptImageGenerationTask.py +++ b/nodes/tasks/gtUIPromptImageGenerationTask.py @@ -1,4 +1,6 @@ +# pyright: reportMissingImports=false import os +from typing import Any, Tuple import folder_paths from griptape.drivers import ( @@ -24,7 +26,7 @@ class gtUIPromptImageGenerationTask(gtUIBaseTask): DESCRIPTION = "Generate an image from a text prompt." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update({"driver": ("DRIVER",)}) return inputs @@ -41,7 +43,7 @@ class gtUIPromptImageGenerationTask(gtUIBaseTask): ) CATEGORY = "Griptape/Image" - def run(self, **kwargs): + def run(self, **kwargs) -> Tuple[Any, ...]: STRING = kwargs.get("STRING") driver = kwargs.get("driver", None) input_string = kwargs.get("input_string", None) @@ -51,7 +53,7 @@ class gtUIPromptImageGenerationTask(gtUIBaseTask): agent = Agent() prompt_text = self.get_prompt_text(STRING, input_string) - + pipeline = None if not driver: # Check and see if the agent.config has an image_generation_driver if isinstance( @@ -84,11 +86,14 @@ class gtUIPromptImageGenerationTask(gtUIBaseTask): except Exception as e: print(e) - result = pipeline.run() - filename = result.output_task.output.name - image_path = os.path.join(output_dir, filename) + if pipeline is not None: + result = pipeline.run() + filename = result.output_task.output.name + image_path = os.path.join(output_dir, filename) - # Get the image in a format ComfyUI can read - output_image, output_mask = image_path_to_output(image_path) + # Get the image in a format ComfyUI can read + output_image, output_mask = image_path_to_output(image_path) - return (output_image, agent, image_path) + return (output_image, agent, image_path) + else: + return ("Error", agent, None) diff --git a/nodes/tasks/gtUIPromptImageVariationTask.py b/nodes/tasks/gtUIPromptImageVariationTask.py index 3b86762..37db76d 100644 --- a/nodes/tasks/gtUIPromptImageVariationTask.py +++ b/nodes/tasks/gtUIPromptImageVariationTask.py @@ -1,3 +1,5 @@ +# pyright: reportMissingImports=false + import base64 import os @@ -9,6 +11,8 @@ try: ) except ImportError: print("BlackForestImageGenerationDriver not found") + BlackForestImageGenerationDriver = None # Set it to None if import fails + from griptape.drivers import ( OpenAiImageGenerationDriver, ) @@ -32,7 +36,7 @@ class gtUIPromptImageVariationTask(gtUIBaseImageTask): DESCRIPTION = "Generate a variation of an image from a text prompt and an image." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update({"driver": ("DRIVER",)}) del inputs["optional"]["agent"] @@ -53,6 +57,7 @@ class gtUIPromptImageVariationTask(gtUIBaseImageTask): driver = kwargs.get("driver", None) input_string = kwargs.get("input_string", None) + result = None agent = Agent() final_image = convert_tensor_to_base_64(image) if not final_image: @@ -68,7 +73,7 @@ class gtUIPromptImageVariationTask(gtUIBaseImageTask): model="dall-e-2", ) # Check if driver is BlackForestImageGenerationDriver - if "BlackForestImageGenerationDriver" in globals() and isinstance( + if BlackForestImageGenerationDriver and isinstance( driver, BlackForestImageGenerationDriver ): # check the model to make sure it's one that can handle Variation Image Generation @@ -105,9 +110,13 @@ class gtUIPromptImageVariationTask(gtUIBaseImageTask): result = agent.run() except Exception as e: print(e) - filename = result.output_task.output.name - image_path = os.path.join(output_dir, filename) - # Get the image in a format ComfyUI can read - output_image, output_mask = image_path_to_output(image_path) + if result is not None: + filename = result.output_task.output.name - return (output_image, image_path) + image_path = os.path.join(output_dir, filename) + # Get the image in a format ComfyUI can read + output_image, output_mask = image_path_to_output(image_path) + + return (output_image, image_path) + else: + return ("Error", None) diff --git a/nodes/tasks/gtUITextSummaryTask.py b/nodes/tasks/gtUITextSummaryTask.py index 6351ffa..57af5d7 100644 --- a/nodes/tasks/gtUITextSummaryTask.py +++ b/nodes/tasks/gtUITextSummaryTask.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.tasks import ( TextSummaryTask, ) @@ -10,7 +12,7 @@ class gtUITextSummaryTask(gtUIBaseTask): DESCRIPTION = "Summarize a text prompt." CATEGORY = "Griptape/Text" - def run(self, **kwargs): + def run(self, **kwargs) -> Tuple[Any, ...]: STRING = kwargs.get("STRING") input_string = kwargs.get("input_string", None) agent = kwargs.get("agent", None) diff --git a/nodes/tasks/gtUITextToSpeechTask.py b/nodes/tasks/gtUITextToSpeechTask.py index 5e16dc8..1671e80 100644 --- a/nodes/tasks/gtUITextToSpeechTask.py +++ b/nodes/tasks/gtUITextToSpeechTask.py @@ -1,3 +1,6 @@ +# pyright: reportMissingImports=false +from typing import Any, Tuple + from comfy_execution.graph import ExecutionBlocker from griptape.artifacts import AudioArtifact, ErrorArtifact from griptape.drivers import DummyTextToSpeechDriver, ElevenLabsTextToSpeechDriver @@ -16,7 +19,7 @@ default_prompt = "{{ input_string }}" class gtUITextToSpeechTask(gtUIBaseTask): @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( @@ -30,7 +33,7 @@ class gtUITextToSpeechTask(gtUIBaseTask): RETURN_TYPES = ("AUDIO",) RETURN_NAMES = ("AUDIO",) - def run(self, **kwargs): + def run(self, **kwargs) -> Tuple[Any, ...]: # try: STRING = kwargs.get("STRING", "") input_string = kwargs.get("input_string", "") @@ -41,14 +44,15 @@ class gtUITextToSpeechTask(gtUIBaseTask): ELEVEN_LABS_API_KEY = settings.get_settings_key_or_use_env( "ELEVEN_LABS_API_KEY" ) - + if not ELEVEN_LABS_API_KEY: + ELEVEN_LABS_API_KEY = "" driver = agent.drivers_config.text_to_speech_driver if isinstance(driver, DummyTextToSpeechDriver): driver = ElevenLabsTextToSpeechDriver( api_key=ELEVEN_LABS_API_KEY, model="eleven_multilingual_v2", voice="Matilda", - ) + ) # type: ignore[reportArgumentType] prompt_text = self.get_prompt_text(STRING, input_string) task = TextToSpeechTask( diff --git a/nodes/tasks/gtUIToolTask.py b/nodes/tasks/gtUIToolTask.py index ae41b0f..4bccc05 100644 --- a/nodes/tasks/gtUIToolTask.py +++ b/nodes/tasks/gtUIToolTask.py @@ -11,7 +11,7 @@ class gtUIToolTask(gtUIBaseTask): CATEGORY = "Griptape/Agent" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() # Update optional inputs to include 'tool' and adjust others as necessary diff --git a/nodes/tasks/gtUIToolkitTask.py b/nodes/tasks/gtUIToolkitTask.py index c9a9be2..b8e7f99 100644 --- a/nodes/tasks/gtUIToolkitTask.py +++ b/nodes/tasks/gtUIToolkitTask.py @@ -1,6 +1,4 @@ -from griptape.tasks import ( - ToolkitTask, -) +from griptape.tasks import PromptTask, ToolkitTask from ..agent.gtComfyAgent import gtComfyAgent as Agent from .gtUIBaseTask import gtUIBaseTask @@ -29,9 +27,6 @@ class gtUIToolkitTask(gtUIBaseTask): agent = kwargs.get("agent", None) prompt_text = self.get_prompt_text(STRING, input_string) - if len(tools) == 0: - return super().run(STRING, input_string, agent) - if prompt_text.strip() == "": return ("No prompt provided", agent) # if the tool is provided, keep going @@ -43,10 +38,13 @@ class gtUIToolkitTask(gtUIBaseTask): response = agent.model_response(model) return (response, agent) - task = ToolkitTask( - prompt_text, - tools=tools, - ) + if len(tools) == 0: + task = PromptTask(prompt_text) + else: + task = ToolkitTask( + prompt_text, + tools=tools, + ) # if deferred_evaluation: # return ("Toolkit Task Created.", task) try: diff --git a/nodes/tasks/gtUIVectorStoreQueryTask.py b/nodes/tasks/gtUIVectorStoreQueryTask.py index 3614ae4..db08e15 100644 --- a/nodes/tasks/gtUIVectorStoreQueryTask.py +++ b/nodes/tasks/gtUIVectorStoreQueryTask.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from ..agent.gtComfyAgent import gtComfyAgent as Agent from .gtUIBaseVectorStoreTask import gtUIBaseVectorStoreTask @@ -8,7 +10,7 @@ class gtUIVectorStoreQueryTask(gtUIBaseVectorStoreTask): DESCRIPTION = "Query a Vector Store." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( @@ -47,7 +49,7 @@ class gtUIVectorStoreQueryTask(gtUIBaseVectorStoreTask): CATEGORY = "Griptape/Text" - def run(self, **kwargs): + def run(self, **kwargs) -> Tuple[Any, ...]: STRING = kwargs.get("STRING", "") input_string = kwargs.get("input_string", "") agent = kwargs.get("agent", Agent()) diff --git a/nodes/tasks/gtUIVectorStoreUpsertTextTask.py b/nodes/tasks/gtUIVectorStoreUpsertTextTask.py index bc5b542..1b7d2d1 100644 --- a/nodes/tasks/gtUIVectorStoreUpsertTextTask.py +++ b/nodes/tasks/gtUIVectorStoreUpsertTextTask.py @@ -18,7 +18,7 @@ class gtUIVectorStoreUpsertTextTask(gtUIBaseVectorStoreTask): DESCRIPTION = "Operate on a Vector Store." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( @@ -31,7 +31,7 @@ class gtUIVectorStoreUpsertTextTask(gtUIBaseVectorStoreTask): return inputs @classmethod - def VALIDATE_INPUTS(s, input_types): + def VALIDATE_INPUTS(cls, input_types): return True RETURN_TYPES = ( diff --git a/nodes/text/gtUIBaseSaveNode.py b/nodes/text/gtUIBaseSaveNode.py index 6ad4813..2309d99 100644 --- a/nodes/text/gtUIBaseSaveNode.py +++ b/nodes/text/gtUIBaseSaveNode.py @@ -1,4 +1,6 @@ +# pyright: reportMissingImports=false import os +from typing import Any, Tuple import folder_paths @@ -18,7 +20,7 @@ class gtUIBaseSaveNode: DESCRIPTION = "Save a file." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = { "required": { "text": ("STRING", {"default": "", "forceInput": True}), @@ -57,7 +59,8 @@ class gtUIBaseSaveNode: return full_output_path - def save(self, **kwargs): + def save(self, **kwargs) -> Tuple[Any, ...]: print(f": {kwargs['text']=}") print(f"Saving file: {kwargs['filename_prefix']}") - return {"ui": {"message": f"Saved file: {kwargs['filename_prefix']}"}} + filename_prefix = kwargs.get("filename_prefix", None) + return (filename_prefix,) diff --git a/nodes/text/gtUICLIPTextEncode.py b/nodes/text/gtUICLIPTextEncode.py index ba26102..44bcaec 100644 --- a/nodes/text/gtUICLIPTextEncode.py +++ b/nodes/text/gtUICLIPTextEncode.py @@ -13,7 +13,7 @@ class gtUICLIPTextEncode(gtUIBaseTask): DESCRIPTION = "Create a text string and convert it to a CLIP conditioning." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update({"clip": ("CLIP",)}) del inputs["optional"]["agent"] diff --git a/nodes/text/gtUIInputStringNode.py b/nodes/text/gtUIInputStringNode.py index e331358..d21858a 100644 --- a/nodes/text/gtUIInputStringNode.py +++ b/nodes/text/gtUIInputStringNode.py @@ -14,7 +14,7 @@ class gtUIInputStringNode: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": {}, "optional": { diff --git a/nodes/text/gtUISaveText.py b/nodes/text/gtUISaveText.py index 86a39b3..d6a529f 100644 --- a/nodes/text/gtUISaveText.py +++ b/nodes/text/gtUISaveText.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from .gtUIBaseSaveNode import gtUIBaseSaveNode @@ -6,7 +8,7 @@ class gtUISaveText(gtUIBaseSaveNode): CATEGORY = "Griptape/Text" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { @@ -15,7 +17,7 @@ class gtUISaveText(gtUIBaseSaveNode): ) return inputs - def save(self, **kwargs): + def save(self, **kwargs) -> Tuple[Any, ...]: text = kwargs.get("text", None) filename_prefix = kwargs.get("filename_prefix", None) diff --git a/nodes/tools/gtUIAudioTranscriptionClient.py b/nodes/tools/gtUIAudioTranscriptionClient.py index 37aca62..66bad94 100644 --- a/nodes/tools/gtUIAudioTranscriptionClient.py +++ b/nodes/tools/gtUIAudioTranscriptionClient.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.drivers import OpenAiAudioTranscriptionDriver from griptape.tools.audio_transcription.tool import AudioTranscriptionTool @@ -12,7 +14,7 @@ class gtUIAudioTranscriptionClient(gtUIBaseTool): DESCRIPTION = "Transcribe audio to text" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "off_prompt": ( @@ -27,7 +29,7 @@ class gtUIAudioTranscriptionClient(gtUIBaseTool): "optional": {"driver": ("DRIVER", {"default": None})}, } - def create(self, **kwargs): + def create(self, **kwargs) -> Tuple[Any, ...]: off_prompt = kwargs.get("off_prompt", True) driver = kwargs.get("driver", None) diff --git a/nodes/tools/gtUIBaseTool.py b/nodes/tools/gtUIBaseTool.py index db4224b..fcc5a80 100644 --- a/nodes/tools/gtUIBaseTool.py +++ b/nodes/tools/gtUIBaseTool.py @@ -1,3 +1,5 @@ +from typing import Any, Tuple + from griptape.tools import BaseTool @@ -10,7 +12,7 @@ class gtUIBaseTool: pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "off_prompt": ( @@ -32,5 +34,6 @@ class gtUIBaseTool: CATEGORY = "Griptape/Agent Tools" - def create(self, off_prompt): + def create(self, **kwargs) -> Tuple[Any, ...]: + off_prompt = kwargs.get("off_prompt", False) return ([BaseTool(off_prompt=off_prompt)],) diff --git a/nodes/tools/gtUICalculator.py b/nodes/tools/gtUICalculator.py index 25e21c1..fa9164e 100644 --- a/nodes/tools/gtUICalculator.py +++ b/nodes/tools/gtUICalculator.py @@ -10,6 +10,7 @@ class gtUICalculator(gtUIBaseTool): DESCRIPTION = "Perform calculations." - def create(self, off_prompt): + def create(self, **kwargs): + off_prompt = kwargs.get("off_prompt", False) tool = CalculatorTool(off_prompt=off_prompt) return ([tool],) diff --git a/nodes/tools/gtUIConvertAgentToTool.py b/nodes/tools/gtUIConvertAgentToTool.py index a2467b6..af0d1e9 100644 --- a/nodes/tools/gtUIConvertAgentToTool.py +++ b/nodes/tools/gtUIConvertAgentToTool.py @@ -12,7 +12,7 @@ class gtUIConvertAgentToTool(gtUIBaseTool): DESCRIPTION = "Convert Agent to Tool" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { diff --git a/nodes/tools/gtUIDateTime.py b/nodes/tools/gtUIDateTime.py index 50fb491..5e65819 100644 --- a/nodes/tools/gtUIDateTime.py +++ b/nodes/tools/gtUIDateTime.py @@ -12,6 +12,7 @@ class gtUIDateTime(gtUIBaseTool): DESCRIPTION = "Get the current date and time." - def create(self, off_prompt): + def create(self, **kwargs): + off_prompt = kwargs.get("off_prompt", False) tool = DateTimeTool(off_prompt=off_prompt) return ([tool],) diff --git a/nodes/tools/gtUIExtractionTool.py b/nodes/tools/gtUIExtractionTool.py index 1a56fc7..942354e 100644 --- a/nodes/tools/gtUIExtractionTool.py +++ b/nodes/tools/gtUIExtractionTool.py @@ -14,7 +14,7 @@ class gtUIExtractionTool(gtUIBaseTool): """ @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( @@ -52,6 +52,7 @@ class gtUIExtractionTool(gtUIBaseTool): column_names_string = kwargs.get("column_names", "") column_names = [column_name.strip() for column_name in column_names_string] template_schema = kwargs.get("template_schema", "") + engine = None params = {} if not prompt_driver: diff --git a/nodes/tools/gtUIFileManager.py b/nodes/tools/gtUIFileManager.py index 3332292..33b1ea3 100644 --- a/nodes/tools/gtUIFileManager.py +++ b/nodes/tools/gtUIFileManager.py @@ -13,7 +13,7 @@ class gtUIFileManager(gtUIBaseTool): DESCRIPTION = "Access files on disk." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "off_prompt": ( @@ -27,6 +27,7 @@ class gtUIFileManager(gtUIBaseTool): }, } - def create(self, off_prompt, workdir=""): + def create(self, **kwargs): + off_prompt = kwargs.get("off_prompt", True) tool = FileManagerTool(off_prompt=off_prompt) return ([tool],) diff --git a/nodes/tools/gtUIKnowledgeBaseTool.py b/nodes/tools/gtUIKnowledgeBaseTool.py index 3b73ef8..d7b712c 100644 --- a/nodes/tools/gtUIKnowledgeBaseTool.py +++ b/nodes/tools/gtUIKnowledgeBaseTool.py @@ -1,7 +1,10 @@ +from typing import List, cast + import requests from griptape.drivers import GriptapeCloudVectorStoreDriver from griptape.engines.rag import RagEngine from griptape.engines.rag.modules import ( + BaseRetrievalRagModule, PromptResponseRagModule, VectorStoreRetrievalRagModule, ) @@ -24,7 +27,7 @@ class gtUIKnowledgeBaseTool(gtUIBaseTool): DESCRIPTION = "Access a Griptape Cloud Knowledge Base. Learn more at https://cloud.griptape.ai" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "off_prompt": ( @@ -63,35 +66,40 @@ class gtUIKnowledgeBaseTool(gtUIBaseTool): else: print(f"Failed to get knowledge base info: {response.status_code}") - def create( - self, - off_prompt, - api_key_environment_variable, - base_url, - knowledge_base_id, - ): + def create(self, **kwargs): + off_prompt = kwargs.get("off_prompt", False) + api_key_environment_variable = kwargs.get( + "api_key_environment_variable", "GRIPTAPE_CLOUD_API_KEY" + ) + base_url = kwargs.get("base_url", "https://cloud.griptape.ai") + knowledge_base_id = kwargs.get("knowledge_base_id", "12345-abcde-1434") + settings = GriptapeSettings() api_key = settings.get_settings_key_or_use_env(api_key_environment_variable) - + if api_key is None: + api_key = "" # Use the Griptape API to grab the name and description of the knowledge base data = self.getKnowledgeBaseInfo(api_key, base_url, knowledge_base_id) - name = data.get("name", "Griptape Knowledge Base") - description = data.get("description", "Contains helpful information") + name = data.get("name", "Griptape Knowledge Base") # type: ignore[reportOptionalMemberAccess] + description = data.get("description", "Contains helpful information") # type: ignore[reportOptionalMemberAccess] name = to_pascal_case(name) - engine = RagEngine( - retrieval_stage=RetrievalRagStage( - retrieval_modules=[ - VectorStoreRetrievalRagModule( - driver=GriptapeCloudVectorStoreDriver( - api_key=api_key, - base_url=base_url, - knowledge_base_id=knowledge_base_id, - ) + retrieval_modules: List[BaseRetrievalRagModule] = cast( + List[BaseRetrievalRagModule], + [ + VectorStoreRetrievalRagModule( + vector_store_driver=GriptapeCloudVectorStoreDriver( + api_key=api_key, + base_url=base_url, + knowledge_base_id=knowledge_base_id, ) - ] - ), + ) + ], + ) + + engine = RagEngine( + retrieval_stage=RetrievalRagStage(retrieval_modules=retrieval_modules), response_stage=ResponseRagStage( response_modules=[ PromptResponseRagModule(), diff --git a/nodes/tools/gtUIPromptSummaryTool.py b/nodes/tools/gtUIPromptSummaryTool.py index 379071a..788b920 100644 --- a/nodes/tools/gtUIPromptSummaryTool.py +++ b/nodes/tools/gtUIPromptSummaryTool.py @@ -10,7 +10,7 @@ class gtUIPromptSummaryTool(gtUIBaseTool): """ @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( diff --git a/nodes/tools/gtUIQueryTool.py b/nodes/tools/gtUIQueryTool.py index 98875ad..aa5fd1a 100644 --- a/nodes/tools/gtUIQueryTool.py +++ b/nodes/tools/gtUIQueryTool.py @@ -9,7 +9,7 @@ class gtUIQueryTool(gtUIBaseTool): """ @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( diff --git a/nodes/tools/gtUIRagTool.py b/nodes/tools/gtUIRagTool.py index 9399e05..c504930 100644 --- a/nodes/tools/gtUIRagTool.py +++ b/nodes/tools/gtUIRagTool.py @@ -8,7 +8,7 @@ class gtUIRagTool(gtUIBaseTool): DESCRIPTION = "Griptape Rag Tool" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): return { "required": { "description": ( diff --git a/nodes/tools/gtUITextToSpeechClient.py b/nodes/tools/gtUITextToSpeechClient.py index 43525a0..8d13490 100644 --- a/nodes/tools/gtUITextToSpeechClient.py +++ b/nodes/tools/gtUITextToSpeechClient.py @@ -13,7 +13,7 @@ class gtUITextToSpeechClient(gtUIBaseTool): DESCRIPTION = "Transcribe audio to text" @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): # inputs = super().INPUT_TYPES() # inputs["optional"].update({"driver": ("DRIVER",)}) # return inputs @@ -32,11 +32,15 @@ class gtUITextToSpeechClient(gtUIBaseTool): "optional": {"driver": ("TEXT_TO_SPEECH_DRIVER", {"default": None})}, } - def create(self, off_prompt, driver=None): + def create(self, **kwargs): + off_prompt = kwargs.get("off_prompt", True) + driver = kwargs.get("driver", None) if not driver: settings = GriptapeSettings() api_key = settings.get_settings_key_or_use_env("ELEVEN_LABS_API_KEY") - + if not api_key: + print("Could not get Eleven Labs API key") + api_key = "" driver = ElevenLabsTextToSpeechDriver( api_key=api_key, model="eleven_multilingual_v2", voice="Matilda" ) diff --git a/nodes/tools/gtUIVectorStoreClient.py b/nodes/tools/gtUIVectorStoreClient.py index d44ef33..ce0c65c 100644 --- a/nodes/tools/gtUIVectorStoreClient.py +++ b/nodes/tools/gtUIVectorStoreClient.py @@ -13,7 +13,7 @@ class gtUIVectorStoreClient(gtUIBaseTool): """ @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["required"].update( { @@ -49,12 +49,11 @@ class gtUIVectorStoreClient(gtUIBaseTool): pass # Try key-value pair format - if ":" in s: - return dict( - map(str.strip, item.split(":", 1)) - for item in s.split("\n") - if ":" in item - ) + return dict( + (str.strip(item.split(":", 1)[0]), str.strip(item.split(":", 1)[1])) + for item in s.split("\n") + if ":" in item + ) # If all else fails, return an empty dict return {} diff --git a/nodes/tools/gtUIWebScraper.py b/nodes/tools/gtUIWebScraper.py index e2dedc0..e298407 100644 --- a/nodes/tools/gtUIWebScraper.py +++ b/nodes/tools/gtUIWebScraper.py @@ -12,7 +12,8 @@ class gtUIWebScraper(gtUIBaseTool): DESCRIPTION = "Scrape the web for information." - def create(self, off_prompt): + def create(self, **kwargs): + off_prompt = kwargs.get("off_prompt", False) tool = WebScraperTool( off_prompt=off_prompt, ) diff --git a/nodes/tools/gtUIWebSearch.py b/nodes/tools/gtUIWebSearch.py index b56ec9c..843d4cc 100644 --- a/nodes/tools/gtUIWebSearch.py +++ b/nodes/tools/gtUIWebSearch.py @@ -14,7 +14,7 @@ class gtUIWebSearch(gtUIBaseTool): DESCRIPTION = "Search the web." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = super().INPUT_TYPES() inputs["optional"].update( { @@ -23,7 +23,9 @@ class gtUIWebSearch(gtUIBaseTool): ) return inputs - def create(self, off_prompt, driver=None): + def create(self, **kwargs): + off_prompt = kwargs.get("off_prompt", False) + driver = kwargs.get("driver", None) if not driver: driver = DuckDuckGoWebSearchDriver() tool = WebSearchTool( diff --git a/nodes/utilities.py b/nodes/utilities.py index f150125..259ba48 100644 --- a/nodes/utilities.py +++ b/nodes/utilities.py @@ -1,6 +1,10 @@ +# pyright: reportMissingImports=false +# pyright: reportOptionalMemberAccess=false + import base64 import re from io import BytesIO +from typing import Optional from urllib.parse import urlparse, urlunparse import numpy as np @@ -71,7 +75,10 @@ def get_prompt_text(string_prompt, input_string): def image_path_to_output(image_path): - img = Image.open(image_path) + img: Optional[Image.Image] = Image.open(image_path) + if img is None: + raise ValueError(f"Failed to open image at path: {image_path}") + output_images = [] output_masks = [] for i in ImageSequence.Iterator(img): diff --git a/nodes/utils.py b/nodes/utils.py index 5e7cf93..1c90317 100644 --- a/nodes/utils.py +++ b/nodes/utils.py @@ -34,7 +34,7 @@ def set_dict_value(data: dict, dict_key: str, value, create_missing_objects=True keys = dict_key.split(".") key = keys.pop(0) if len(keys) > 0 else None if key not in data: - if create_missing_objects == False: + if not create_missing_objects: return data[key] = {} if len(keys) == 0: diff --git a/nodes/utils/gtUICreateAgentModelfile.py b/nodes/utils/gtUICreateAgentModelfile.py index b3330e7..62e162c 100644 --- a/nodes/utils/gtUICreateAgentModelfile.py +++ b/nodes/utils/gtUICreateAgentModelfile.py @@ -13,7 +13,7 @@ class gtUICreateAgentModelfile: DESCRIPTION = "Creates a Modelfile to build a new Model for Ollama." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = { "required": { "base_url": ( @@ -47,7 +47,7 @@ class gtUICreateAgentModelfile: return inputs @classmethod - def VALIDATE_INPUTS(s): + def VALIDATE_INPUTS(cls): if not check_ollama_installed(): return "You must have ollama installed on your machine to use this node." return True diff --git a/nodes/utils/gtUICreateModelFromModelfile.py b/nodes/utils/gtUICreateModelFromModelfile.py index a36ef7f..3895315 100644 --- a/nodes/utils/gtUICreateModelFromModelfile.py +++ b/nodes/utils/gtUICreateModelFromModelfile.py @@ -7,7 +7,7 @@ class gtUICreateModelFromModelfile: ) @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = { "required": { "modelfile": ( @@ -25,7 +25,7 @@ class gtUICreateModelFromModelfile: return inputs @classmethod - def VALIDATE_INPUTS(s): + def VALIDATE_INPUTS(cls): if not check_ollama_installed(): return "You must have ollama installed on your machine to use this node." return True @@ -57,7 +57,7 @@ class gtUICreateModelFromModelfile: modelfile = kwargs.get("modelfile", None) base_model = self.get_base_model(modelfile) if ":" in str(base_model): - base_model = base_model.split(":")[0] + base_model = str(base_model).split(":")[0] new_model_name = str(kwargs.get("new_model_name", "new_model")) # Save the contents of modelfile to a file named Modelfile. diff --git a/nodes/utils/gtUIRemoveOllamaModel.py b/nodes/utils/gtUIRemoveOllamaModel.py index 21022b6..2cb0eea 100644 --- a/nodes/utils/gtUIRemoveOllamaModel.py +++ b/nodes/utils/gtUIRemoveOllamaModel.py @@ -17,7 +17,7 @@ class gtUIRemoveOllamaModel: DESCRIPTION = "Ollama can sometimes be overrun with models. This node allows you to remove a model by name." @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): inputs = { "required": { "base_url": ( @@ -43,7 +43,7 @@ class gtUIRemoveOllamaModel: return inputs @classmethod - def VALIDATE_INPUTS(s): + def VALIDATE_INPUTS(cls): if not check_ollama_installed(): return "You must have ollama installed on your machine to use this node." return True diff --git a/py/griptape_settings.py b/py/griptape_settings.py index 26286df..a1eaf71 100644 --- a/py/griptape_settings.py +++ b/py/griptape_settings.py @@ -1,3 +1,4 @@ +# pyright: reportMissingImports=false import json import os import re diff --git a/pyproject.toml b/pyproject.toml index 483c152..22df260 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,18 @@ python-dotenv = "^1.0.1" griptape = { version = "^1.0.2", extras = ["all"]} griptape-black-forest = {git = "https://github.com/griptape-ai/griptape-black-forest.git"} + +[tool.poetry.group.dev] +optional = true + + [tool.poetry.group.dev.dependencies] icecream = "^2.1.3" +ruff = "^0.8.0" +pyright = "^1.1.376" +pre-commit = "^4.0.0" +typos = "^1.22.9" + #torch = {version = "^2.3.1+cu121", source = "pytorch-gpu-src"} #torchvision = {version = "^0.18.1+cu121", source = "pytorch-gpu-src"} #torchaudio = {version = "^2.3.1+cu121", source = "pytorch-gpu-src"} diff --git a/tests/audio/TranscriptionTest.mp3 b/tests/audio/TranscriptionTest.mp3 deleted file mode 100644 index d874e54fa56b793a6c19c87a74b59ae5e45b932d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203759 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%0D3f|HF7e$qI7vp6B1jKCWb0qBu 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¼ogY}HJ&E<>wI*$Vd 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 diff --git a/tests/rag_workflow.py b/tests/rag_workflow.py deleted file mode 100644 index ba0c009..0000000 --- a/tests/rag_workflow.py +++ /dev/null @@ -1,58 +0,0 @@ -# use tempfile to create a temporary file - -from dotenv import load_dotenv -from griptape.chunkers import TextChunker -from griptape.drivers import LocalVectorStoreDriver, OpenAiEmbeddingDriver -from griptape.engines.rag import RagEngine -from griptape.engines.rag.modules import TextLoaderRetrievalRagModule -from griptape.engines.rag.stages import RetrievalRagStage -from griptape.loaders import TextLoader -from griptape.structures import Agent -from griptape.tools import QueryTool, RagTool - -load_dotenv() - -text = """ -Wonderbread.ai is a fictitional company that specializes in creating balloon animals for the future. -They are often confused with the bread company, Wonder Bread, but they are not the same. -Some of the services that Wonderbread.ai offers include: -- Balloon animal creation -- Balloon animal delivery -- Balloon animal training -- Balloon animal parties -- Balloon animal events -- Balloon animal consulting -- Balloon animal workshops -- Balloon animal classes -- Balloon animal seminars -- Balloon animal webinars -- Balloon animal conferences - -Wonderbread.ai is a leader in the balloon animal industry and has been in business for over 100 years. -They have a team of experts who are dedicated to creating the best balloon animals in the world. -If you are interested in learning more about Wonderbread.ai, please visit their website at www.wonderbread.ai/balloonanimals -""" - -chunker = TextChunker() -chunks = chunker.chunk(text) -module = TextLoaderRetrievalRagModule( - query_params={"namespace": "wonderbread"}, - vector_store_driver=LocalVectorStoreDriver( - embedding_driver=OpenAiEmbeddingDriver() - ), - loader=TextLoader().parse(text), -) - -retrieval_modules = [module] - -rag_engine = RagEngine( - retrieval_stage=RetrievalRagStage(retrieval_modules=retrieval_modules), -) - -tool = RagTool( - off_prompt=True, description="Contains info about a company", rag_engine=rag_engine -) - -agent = Agent(tools=[tool, QueryTool()]) - -agent.run("What does the company Wonderbread.ai do?") diff --git a/typos.toml b/typos.toml new file mode 100644 index 0000000..cb92d55 --- /dev/null +++ b/typos.toml @@ -0,0 +1,7 @@ +[files] +extend-exclude = [ + "*.svg", # Ignore all .svg files + "js/nodeFixes.js", # Ignore a specific file + "examples/retrieval_augmented_generation.json", # it's trying to fix "ue_links" which is fine + "nodes/drivers/gtUILeonardoImageGenerationDriver.py" # having trouble with french prefere +] diff --git a/workflow-tests/ConfigTests.json b/workflow-tests/ConfigTests.json index 5e3b26e..1830e0c 100644 --- a/workflow-tests/ConfigTests.json +++ b/workflow-tests/ConfigTests.json @@ -340,7 +340,7 @@ }, "widgets_values": [ "", - "What modle are you?" + "What model are you?" ] }, { From 80bd657380fd951076276f80863588e5620ab8a1 Mon Sep 17 00:00:00 2001 From: Jason Schleifer Date: Tue, 31 Dec 2024 11:33:26 +1300 Subject: [PATCH 3/4] updated pyproject & added release.yml --- .github/workflows/release.yml | 52 +++++++++++++++++++++++++++++++++++ pyproject.toml | 22 ++++++++------- 2 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e2af6bb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Create Release with Changelog + +on: + push: + branches: + - main + +jobs: + create-release: + name: Create Release + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install poetry + + - name: Read version from pyproject.toml + id: get_version + run: | + VERSION=$(poetry version -s) + echo "version=$VERSION" >> $GITHUB_ENV + + - name: Install Changelog Generator + run: | + sudo apt-get install ruby-full + gem install github_changelog_generator + + - name: Generate Changelog + run: | + github_changelog_generator --user --project --token ${{ secrets.GITHUB_TOKEN }} + + - name: Create GitHub Release + uses: actions/create-release@v1 + with: + tag_name: "v${{ env.version }}" + release_name: "v${{ env.version }}" + body: | + $(cat CHANGELOG.md) + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index 22df260..29b55c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,18 @@ description = "Griptape LLM(Large Language Model) Nodes for ComfyUI." authors = ["Jason Schleifer "] readme = "README.md" +# Used by Comfy Registry https://comfyregistry.org +[project] +name = "comfyui-griptape" +description = "Griptape LLM(Large Language Model) Nodes for ComfyUI." +version = "2.0.10" +license = {file = "LICENSE"} +dependencies = ["griptape[all]==^1.0.2", "python-dotenv", "poetry==1.8.4", "griptape-black-forest @ git+https://github.com/griptape-ai/griptape-black-forest.git"] + +[project.urls] +Repository = "https://github.com/griptape-ai/ComfyUI-Griptape" + + [tool.poetry.dependencies] python = "^3.11" python-dotenv = "^1.0.1" @@ -33,16 +45,6 @@ name = "pytorch-gpu-src" url = "https://download.pytorch.org/whl/cu121" priority = "explicit" -# Used by Comfy Registry https://comfyregistry.org -[project] -name = "comfyui-griptape" -description = "Griptape LLM(Large Language Model) Nodes for ComfyUI." -version = "2.0.10" -license = {file = "LICENSE"} -dependencies = ["griptape[all]==^1.0.2", "python-dotenv", "poetry==1.8.4", "griptape-black-forest @ git+https://github.com/griptape-ai/griptape-black-forest.git"] - -[project.urls] -Repository = "https://github.com/griptape-ai/ComfyUI-Griptape" [tool.comfy] PublisherId = "shhlife" From 9c52b26b418d4ad0249e5a4fb213ffb660fa6361 Mon Sep 17 00:00:00 2001 From: Jason Schleifer Date: Tue, 31 Dec 2024 11:34:00 +1300 Subject: [PATCH 4/4] updated version number --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 29b55c5..af2fef8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "comfyui-griptape" -version = "2.0.10" +version = "2.0.11" description = "Griptape LLM(Large Language Model) Nodes for ComfyUI." authors = ["Jason Schleifer "] readme = "README.md" @@ -9,7 +9,7 @@ readme = "README.md" [project] name = "comfyui-griptape" description = "Griptape LLM(Large Language Model) Nodes for ComfyUI." -version = "2.0.10" +version = "2.0.11" license = {file = "LICENSE"} dependencies = ["griptape[all]==^1.0.2", "python-dotenv", "poetry==1.8.4", "griptape-black-forest @ git+https://github.com/griptape-ai/griptape-black-forest.git"]