diff --git a/README.MD b/README.MD
index dbf1deb..007b446 100644
--- a/README.MD
+++ b/README.MD
@@ -1,7 +1,10 @@
-# 📎ComfyUI-CrewAI
+# 📎ComfyUI-CrewAI
+Crafting high-quality prompts is essential for generating images or other content, but it can be challenging and time-consuming to find the right words, structure, or variations to achieve the desired result.
-
+Integrating CrewAI into ComfyUI allows users to automate and refine the prompt creation process using intelligent agents. CrewAI can generate, analyze, and iteratively improve text prompts based on user input or specific goals, streamlining the creative workflow.
+
+
[](https://github.com/luandev/ComfyUI-CrewAI/actions/workflows/Flake8.yml)
diff --git a/docs/demo.mp4 b/docs/demo.mp4
new file mode 100644
index 0000000..3ba3aa5
Binary files /dev/null and b/docs/demo.mp4 differ
diff --git a/examples/complex_crew.json b/examples/complex_crew.json
new file mode 100644
index 0000000..ff5e5a4
--- /dev/null
+++ b/examples/complex_crew.json
@@ -0,0 +1,482 @@
+{
+ "last_node_id": 27,
+ "last_link_id": 32,
+ "nodes": [
+ {
+ "id": 25,
+ "type": "📎CrewAI Agent",
+ "pos": [
+ 52.397254943847656,
+ -543.556396484375
+ ],
+ "size": [
+ 400,
+ 224
+ ],
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "llm",
+ "type": "CREWAI_LLM",
+ "link": 31,
+ "shape": 7
+ }
+ ],
+ "outputs": [
+ {
+ "name": "agent",
+ "type": "CREWAI_AGENT",
+ "links": [
+ 26
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "Creative Director",
+ "Creates and implements the vision of the project",
+ "",
+ true,
+ true
+ ]
+ },
+ {
+ "id": 26,
+ "type": "📎CrewAI Agent List",
+ "pos": [
+ 472.2691345214844,
+ -420.0502624511719
+ ],
+ "size": [
+ 159.60000610351562,
+ 66
+ ],
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "agent-1",
+ "type": "CREWAI_AGENT",
+ "link": 25
+ },
+ {
+ "name": "agent-2",
+ "type": "CREWAI_AGENT",
+ "link": 26
+ },
+ {
+ "name": "agent-3",
+ "type": "CREWAI_AGENT",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "agent",
+ "type": "CREWAI_AGENT",
+ "links": [
+ 23
+ ],
+ "shape": 6
+ }
+ ],
+ "properties": {},
+ "widgets_values": []
+ },
+ {
+ "id": 3,
+ "type": "📎CrewAI Task",
+ "pos": [
+ 642.90869140625,
+ -804.555908203125
+ ],
+ "size": [
+ 400,
+ 200
+ ],
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "agent",
+ "type": "CREWAI_AGENT",
+ "link": null,
+ "shape": 7
+ }
+ ],
+ "outputs": [
+ {
+ "name": "task",
+ "type": "CREWAI_TASK",
+ "links": [
+ 2
+ ]
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "Create images about {topic} using prompts",
+ "Positive and negative prompts for conditioning image generation"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "📎CrewAI Agent",
+ "pos": [
+ -391.1960754394531,
+ -821.2613525390625
+ ],
+ "size": [
+ 400,
+ 224
+ ],
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "llm",
+ "type": "CREWAI_LLM",
+ "link": 32,
+ "shape": 7
+ }
+ ],
+ "outputs": [
+ {
+ "name": "agent",
+ "type": "CREWAI_AGENT",
+ "links": [
+ 27
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "Creative Manager",
+ "make sure the task is completed in time with quality",
+ "",
+ true,
+ false
+ ]
+ },
+ {
+ "id": 24,
+ "type": "📎CrewAI Agent",
+ "pos": [
+ 53.63351058959961,
+ -826.7026977539062
+ ],
+ "size": [
+ 400,
+ 224
+ ],
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "llm",
+ "type": "CREWAI_LLM",
+ "link": 30,
+ "shape": 7
+ }
+ ],
+ "outputs": [
+ {
+ "name": "agent",
+ "type": "CREWAI_AGENT",
+ "links": [
+ 25
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "Prompt Creator",
+ "Creates high quality, performant, stable diffusion prompts",
+ "",
+ true,
+ true
+ ]
+ },
+ {
+ "id": 27,
+ "type": "📎CrewAI LLM Chat GPT",
+ "pos": [
+ -310.16619873046875,
+ -541.27490234375
+ ],
+ "size": [
+ 315,
+ 82
+ ],
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "llm",
+ "type": "CREWAI_LLM",
+ "links": [
+ 30,
+ 31,
+ 32
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "----------token",
+ "gpt-4o"
+ ]
+ },
+ {
+ "id": 22,
+ "type": "📎CrewAI Text",
+ "pos": [
+ 1111.2374267578125,
+ -799.2343139648438
+ ],
+ "size": [
+ 400,
+ 200
+ ],
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 20,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "",
+ "1950s advertisement for an innovative kitchen gadget, featuring a smiling housewife and her family in a pastel-colored kitchen. Use a bold Sans Serif or cursive script font for the tagline. Center the gadget with an emphasis on its modern design, surrounded by atomic pattern motifs. Ensure a dynamic composition typical of mid-century ads.",
+ "output",
+ "enabled",
+ "enabled"
+ ]
+ },
+ {
+ "id": 23,
+ "type": "📎CrewAI Text",
+ "pos": [
+ 1109.2076416015625,
+ -519.02294921875
+ ],
+ "size": [
+ 400,
+ 200
+ ],
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 22,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "",
+ "Avoid modern minimalist layouts or designs. Exclude dark or monochrome color schemes. Do not incorporate contemporary elements or sleek, futuristic fonts that attribute to modern technology styles.",
+ "output",
+ "enabled",
+ "enabled"
+ ]
+ },
+ {
+ "id": 1,
+ "type": "📎CrewAI Crew",
+ "pos": [
+ 643.4808959960938,
+ -537.2901000976562
+ ],
+ "size": [
+ 400,
+ 200
+ ],
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "tasks",
+ "type": "CREWAI_TASK",
+ "link": 2
+ },
+ {
+ "name": "agents",
+ "type": "CREWAI_AGENT",
+ "link": 23,
+ "shape": 7
+ },
+ {
+ "name": "manager_agent",
+ "type": "CREWAI_AGENT",
+ "link": 27,
+ "shape": 7
+ }
+ ],
+ "outputs": [
+ {
+ "name": "positive",
+ "type": "STRING",
+ "links": [
+ 20
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "negative",
+ "type": "STRING",
+ "links": [
+ 22
+ ],
+ "slot_index": 1
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ false,
+ "Impressive 50s style ad about a generic kitchen gadget ",
+ true
+ ]
+ }
+ ],
+ "links": [
+ [
+ 2,
+ 3,
+ 0,
+ 1,
+ 0,
+ "CREWAI_TASK"
+ ],
+ [
+ 20,
+ 1,
+ 0,
+ 22,
+ 0,
+ "STRING"
+ ],
+ [
+ 22,
+ 1,
+ 1,
+ 23,
+ 0,
+ "STRING"
+ ],
+ [
+ 23,
+ 26,
+ 0,
+ 1,
+ 1,
+ "CREWAI_AGENT"
+ ],
+ [
+ 25,
+ 24,
+ 0,
+ 26,
+ 0,
+ "CREWAI_AGENT"
+ ],
+ [
+ 26,
+ 25,
+ 0,
+ 26,
+ 1,
+ "CREWAI_AGENT"
+ ],
+ [
+ 27,
+ 4,
+ 0,
+ 1,
+ 2,
+ "CREWAI_AGENT"
+ ],
+ [
+ 30,
+ 27,
+ 0,
+ 24,
+ 0,
+ "CREWAI_LLM"
+ ],
+ [
+ 31,
+ 27,
+ 0,
+ 25,
+ 0,
+ "CREWAI_LLM"
+ ],
+ [
+ 32,
+ 27,
+ 0,
+ 4,
+ 0,
+ "CREWAI_LLM"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {
+ "ds": {
+ "scale": 1.1824237539421707,
+ "offset": [
+ 471.6987834460758,
+ 927.2935666308869
+ ]
+ },
+ "node_versions": {
+ "ComfyUI-CrewAI": "8b2fda591d4b4d9559adb573a58b8f437f0b978d"
+ },
+ "VHS_latentpreview": false,
+ "VHS_latentpreviewrate": 0
+ },
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/examples/simple_crew.json b/examples/simple_crew.json
new file mode 100644
index 0000000..4c43b80
--- /dev/null
+++ b/examples/simple_crew.json
@@ -0,0 +1,312 @@
+{
+ "last_node_id": 23,
+ "last_link_id": 22,
+ "nodes": [
+ {
+ "id": 5,
+ "type": "📎CrewAI LLM Ollama",
+ "pos": [
+ -481.0332336425781,
+ -435.5079650878906
+ ],
+ "size": [
+ 315,
+ 82
+ ],
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "llm",
+ "type": "CREWAI_LLM",
+ "links": [
+ 4
+ ]
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "ollama/llama3.2:3b",
+ "http://localhost:11434"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "📎CrewAI Agent",
+ "pos": [
+ -147.3191375732422,
+ -433.9649353027344
+ ],
+ "size": [
+ 400,
+ 224
+ ],
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "llm",
+ "type": "CREWAI_LLM",
+ "link": 4,
+ "shape": 7
+ }
+ ],
+ "outputs": [
+ {
+ "name": "agent",
+ "type": "CREWAI_AGENT",
+ "links": [
+ 3
+ ]
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "Creative Director",
+ "Create cool images",
+ "",
+ true,
+ false
+ ]
+ },
+ {
+ "id": 3,
+ "type": "📎CrewAI Task",
+ "pos": [
+ 278.6111145019531,
+ -434.6757507324219
+ ],
+ "size": [
+ 400,
+ 200
+ ],
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "agent",
+ "type": "CREWAI_AGENT",
+ "link": 3,
+ "shape": 7
+ }
+ ],
+ "outputs": [
+ {
+ "name": "task",
+ "type": "CREWAI_TASK",
+ "links": [
+ 2
+ ]
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "Create images about {topic} using prompts",
+ "Positive and negative prompts for conditioning a model"
+ ]
+ },
+ {
+ "id": 23,
+ "type": "📎CrewAI Text",
+ "pos": [
+ 1168.8687744140625,
+ -345.6724548339844
+ ],
+ "size": [
+ 400,
+ 200
+ ],
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 22,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "",
+ "A sad dog sitting alone in a dark room with a frown on its face",
+ "output",
+ "enabled",
+ "enabled"
+ ]
+ },
+ {
+ "id": 22,
+ "type": "📎CrewAI Text",
+ "pos": [
+ 1170.898681640625,
+ -625.8836669921875
+ ],
+ "size": [
+ 400,
+ 200
+ ],
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 20,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "",
+ "A happy dog running in a green field with a big smile on its face",
+ "output",
+ "enabled",
+ "enabled"
+ ]
+ },
+ {
+ "id": 1,
+ "type": "📎CrewAI Crew",
+ "pos": [
+ 716.1845092773438,
+ -433.09088134765625
+ ],
+ "size": [
+ 400,
+ 200
+ ],
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "tasks",
+ "type": "CREWAI_TASK",
+ "link": 2
+ },
+ {
+ "name": "agents",
+ "type": "CREWAI_AGENT",
+ "link": null,
+ "shape": 7
+ },
+ {
+ "name": "manager_agent",
+ "type": "CREWAI_AGENT",
+ "link": null,
+ "shape": 7
+ }
+ ],
+ "outputs": [
+ {
+ "name": "positive",
+ "type": "STRING",
+ "links": [
+ 20
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "negative",
+ "type": "STRING",
+ "links": [
+ 22
+ ],
+ "slot_index": 1
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ true,
+ "A happy dog",
+ true
+ ]
+ }
+ ],
+ "links": [
+ [
+ 2,
+ 3,
+ 0,
+ 1,
+ 0,
+ "CREWAI_TASK"
+ ],
+ [
+ 3,
+ 4,
+ 0,
+ 3,
+ 0,
+ "CREWAI_AGENT"
+ ],
+ [
+ 4,
+ 5,
+ 0,
+ 4,
+ 0,
+ "CREWAI_LLM"
+ ],
+ [
+ 20,
+ 1,
+ 0,
+ 22,
+ 0,
+ "STRING"
+ ],
+ [
+ 22,
+ 1,
+ 1,
+ 23,
+ 0,
+ "STRING"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {
+ "ds": {
+ "scale": 0.984973267580793,
+ "offset": [
+ 631.1795913675138,
+ 888.2631607603234
+ ]
+ },
+ "node_versions": {
+ "ComfyUI-CrewAI": "8b2fda591d4b4d9559adb573a58b8f437f0b978d"
+ },
+ "VHS_latentpreview": false,
+ "VHS_latentpreviewrate": 0
+ },
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/js/comfyui-crewai.js b/js/comfyui-crewai.js
index fef344d..0c23fe0 100644
--- a/js/comfyui-crewai.js
+++ b/js/comfyui-crewai.js
@@ -1,31 +1,51 @@
-import { app } from "../../scripts/app.js"
+import { app } from "../../scripts/app.js";
+import { ComfyWidgets } from "../../scripts/widgets.js";
+
+
app.registerExtension({
- name: "Comfy.CrewAI",
- async beforeRegisterNodeDef(nodeType, nodeData, app) {
- nodeType.prototype.onNodeCreated = function () {
- if (this.type.startsWith("📎CrewAI")) {
-
- this.onConnectionsChange = (type, index, connected, linkInfo, ioSlot) => {
- console.log(`\n\n📎`, {type, index, connected, linkInfo, ioSlot})
- const [propName, propIndex] = ioSlot.name.split('-');
- if (!isNaN(propIndex)) {
- console.log(this.inputs.every(item => item.link != undefined));
- if (this.inputs.every(item => item.link != undefined))
- this.addInput(`${propName}-${parseInt(propIndex) + 1}`, ioSlot.type);
-
- if (!connected && this.inputs.length > 0) {
- const inputIndex = this.inputs.findIndex(item => item.name === ioSlot.name);
- this.removeInput(inputIndex);
- }
-
- this.inputs.forEach((item, index) => {
- const [propName] = ioSlot.name.split('-');
- item.name = `${propName}-${index + 1}`;
- })
- }
- };
+ name: "Comfy.CrewAI",
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
+ nodeType.prototype.onNodeCreated = function () {
+ // Handles list type inputs
+ if (this.type.startsWith("📎CrewAI") && this.type.includes("List")) {
+ this.onConnectionsChange = (type, index, connected, linkInfo, ioSlot) => {
+ console.log(`\n\n📎`, { type, index, connected, linkInfo, ioSlot });
+ const [propName, propIndex] = ioSlot.name.split("-");
+ if (!isNaN(propIndex)) {
+ if (this.inputs.every((item) => item.link != undefined)) {
+ this.addInput(`${propName}-${parseInt(propIndex) + 1}`, ioSlot.type);
}
+
+ if (!connected && this.inputs.length > 0) {
+ const inputIndex = this.inputs.findIndex((item) => item.name === ioSlot.name);
+ this.removeInput(inputIndex);
+ }
+
+ this.inputs.forEach((item, index) => {
+ item.name = `${propName}-${index + 1}`;
+ });
+ }
};
- },
+ }
+
+ // Handles text display
+ if (this.type === "📎CrewAI Text") {
+ this.onExecuted = (message) => {
+ const callback = (value) => {
+ console.log(`📎 Text output changed`, value);
+ }
+ console.log(`📎 Text Node Executed`, message);
+ const text = Array.isArray(message.text) ? message.text.join("\n") : message.text || "";
+
+ let outputWidget = [...this.widgets].find((widget) => widget.name === "output");
+ if (!outputWidget) {
+ outputWidget = this.addWidget("customtext", `output`, text, callback, { multiline: true })
+ }
+ outputWidget.value = text;
+ };
+
+ }
+ };
+ },
});
diff --git a/nodes/llm_ollama_node.py b/nodes/llm_ollama_node.py
index 6dd1e34..ac8815a 100644
--- a/nodes/llm_ollama_node.py
+++ b/nodes/llm_ollama_node.py
@@ -11,7 +11,7 @@ class LlmOllama(BaseNode):
return {
"required": {
"model": ("STRING", {
- "default": "ollama/llama3:70b"
+ "default": "ollama/llama3.2:3b"
}),
"base_url": ("STRING", {
"default": "http://localhost:11434"
diff --git a/nodes/save_text_node.py b/nodes/save_text_node.py
index 3fbde6a..d944de0 100644
--- a/nodes/save_text_node.py
+++ b/nodes/save_text_node.py
@@ -11,18 +11,35 @@ class DisplayText:
def INPUT_TYPES(cls):
return {
"required": {
- "text": ("STRING", {"multiline": True}),
+ "text": ("STRING", {"forceInput": True}),
+ "output": ("STRING", {
+ "multiline": True, "default": ""
+ }),
"filename_prefix": ("STRING", {"default": "output"}),
"save_metadata": (["disabled", "enabled"], {"default": "enabled"}),
- }
+ "save_file": (["disabled", "enabled"], {"default": "enabled"}),
+ },
+ "hidden": {
+ "unique_id": "UNIQUE_ID",
+ "extra_pnginfo": "EXTRA_PNGINFO",
+ },
}
RETURN_TYPES = ("STRING",) # Indicates the node outputs text to the UI
FUNCTION = "save_and_display_text"
OUTPUT_NODE = True
+ OUTPUT_IS_LIST = (True,)
+ INPUT_IS_LIST = True
CATEGORY = "Custom Nodes"
- def save_and_display_text(self, text, filename_prefix, save_metadata):
+ def save_and_display_text(self,
+ text,
+ output,
+ filename_prefix,
+ save_metadata,
+ save_file,
+ unique_id=None,
+ extra_pnginfo=None):
# Ensure output directory exists
os.makedirs(self.output_dir, exist_ok=True)
@@ -31,9 +48,10 @@ class DisplayText:
filename = f"{filename_prefix}_{timestamp}.txt"
# Save text to file
- filepath = os.path.join(self.output_dir, filename)
- with open(filepath, "w", encoding="utf-8") as f:
- f.write(text)
+ if save_file == "enabled":
+ filepath = os.path.join(self.output_dir, filename)
+ with open(filepath, "w", encoding="utf-8") as f:
+ f.write(text)
# Optionally save metadata
if save_metadata == "enabled":
@@ -46,11 +64,32 @@ class DisplayText:
with open(metadata_path, "w", encoding="utf-8") as meta_file:
json.dump(metadata, meta_file, indent=4)
- # Return text for display in the UI
- return (f"Saved Text:\n{text}",) # Display the full text or a preview
+ # Update the UI
+ if unique_id is not None and extra_pnginfo is not None:
+ if not isinstance(extra_pnginfo, list):
+ print("Error: extra_pnginfo is not a list")
+ elif (
+ not isinstance(extra_pnginfo[0], dict)
+ or "workflow" not in extra_pnginfo[0]
+ ):
+ print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key")
+ else:
+ workflow = extra_pnginfo[0]["workflow"]
+ node = next(
+ (x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])),
+ None,
+ )
+ if node:
+ node["widgets_values"] = text
+
+ return {"ui": {"text": text}, "result": (text,)}
# Register the node in ComfyUI
NODE_CLASS_MAPPINGS = {
- "SaveAndDisplayTextNode": DisplayText,
+ "DisplayText": DisplayText,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "DisplayText": "Display and save text",
}
diff --git a/pyproject.toml b/pyproject.toml
index 00c1705..b8a79aa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "comfyui-crewai"
description = "ComfyUI-CrewAI aims to integrate Crew AI's multi-agent collaboration framework into the ComfyUI environment. By combining the strengths of Crew AI's role-based, collaborative AI agent system with ComfyUI's intuitive interface, we will create a robust platform for managing and executing complex AI tasks seamlessly"
-version = "1.1.0"
+version = "1.1.2"
license = { file = "LICENSE" }
dependencies = [
"crewai[tools]>=0.95.0,<1.0.0"