Skip to content

Commit 3efffee

Browse files
committed
feat: add capability for users run crew using sample payloads
1 parent 428810b commit 3efffee

File tree

8 files changed

+261
-4
lines changed

8 files changed

+261
-4
lines changed

lib/crewai/src/crewai/cli/cli.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .run_crew import run_crew
2929
from .tools.main import ToolCommand
3030
from .train_crew import train_crew
31+
from .triggers.main import TriggersCommand
3132
from .update_crew import update_crew
3233

3334

@@ -392,6 +393,26 @@ def flow_add_crew(crew_name):
392393
add_crew_to_flow(crew_name)
393394

394395

396+
@crewai.group()
397+
def triggers():
398+
"""Trigger related commands. Use 'crewai triggers list' to see available triggers, or 'crewai triggers run app_slug/trigger_slug' to execute."""
399+
400+
401+
@triggers.command(name="list")
402+
def triggers_list():
403+
"""List all available triggers from integrations."""
404+
triggers_cmd = TriggersCommand()
405+
triggers_cmd.list_triggers()
406+
407+
408+
@triggers.command(name="run")
409+
@click.argument("trigger_path")
410+
def triggers_run(trigger_path: str):
411+
"""Execute crew with trigger payload. Format: app_slug/trigger_slug"""
412+
triggers_cmd = TriggersCommand()
413+
triggers_cmd.execute_with_trigger(trigger_path)
414+
415+
395416
@crewai.command()
396417
def chat():
397418
"""

lib/crewai/src/crewai/cli/plus_api.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class PlusAPI:
1818
AGENTS_RESOURCE = "/crewai_plus/api/v1/agents"
1919
TRACING_RESOURCE = "/crewai_plus/api/v1/tracing"
2020
EPHEMERAL_TRACING_RESOURCE = "/crewai_plus/api/v1/tracing/ephemeral"
21+
INTEGRATIONS_RESOURCE = "/crewai_plus/api/v1/integrations"
2122

2223
def __init__(self, api_key: str) -> None:
2324
self.api_key = api_key
@@ -176,3 +177,13 @@ def mark_trace_batch_as_failed(
176177
json={"status": "failed", "failure_reason": error_message},
177178
timeout=30,
178179
)
180+
181+
def get_triggers(self) -> requests.Response:
182+
"""Get all available triggers from integrations."""
183+
return self._make_request("GET", f"{self.INTEGRATIONS_RESOURCE}/triggers")
184+
185+
def get_trigger_payload(self, app_slug: str, trigger_slug: str) -> requests.Response:
186+
"""Get sample payload for a specific trigger."""
187+
return self._make_request(
188+
"GET", f"{self.INTEGRATIONS_RESOURCE}/{app_slug}/{trigger_slug}/payload"
189+
)

lib/crewai/src/crewai/cli/templates/crew/main.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def run():
2121
'topic': 'AI LLMs',
2222
'current_year': str(datetime.now().year)
2323
}
24-
24+
2525
try:
2626
{{crew_name}}().crew().kickoff(inputs=inputs)
2727
except Exception as e:
@@ -60,9 +60,33 @@ def test():
6060
"topic": "AI LLMs",
6161
"current_year": str(datetime.now().year)
6262
}
63-
63+
6464
try:
6565
{{crew_name}}().crew().test(n_iterations=int(sys.argv[1]), eval_llm=sys.argv[2], inputs=inputs)
6666

6767
except Exception as e:
6868
raise Exception(f"An error occurred while testing the crew: {e}")
69+
70+
def run_with_trigger():
71+
"""
72+
Run the crew with trigger payload.
73+
"""
74+
import json
75+
76+
if len(sys.argv) < 2:
77+
raise Exception("No trigger payload provided. Please provide JSON payload as argument.")
78+
79+
try:
80+
trigger_payload = json.loads(sys.argv[1])
81+
except json.JSONDecodeError:
82+
raise Exception("Invalid JSON payload provided as argument")
83+
84+
inputs = {
85+
"crewai_trigger_payload": trigger_payload
86+
}
87+
88+
try:
89+
result = {{crew_name}}().crew().kickoff(inputs=inputs)
90+
return result
91+
except Exception as e:
92+
raise Exception(f"An error occurred while running the crew with trigger: {e}")

lib/crewai/src/crewai/cli/templates/crew/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ run_crew = "{{folder_name}}.main:run"
1414
train = "{{folder_name}}.main:train"
1515
replay = "{{folder_name}}.main:replay"
1616
test = "{{folder_name}}.main:test"
17+
run_with_trigger = "{{folder_name}}.main:run_with_trigger"
1718

1819
[build-system]
1920
requires = ["hatchling"]

lib/crewai/src/crewai/cli/templates/flow/main.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@ class PoemState(BaseModel):
1616
class PoemFlow(Flow[PoemState]):
1717

1818
@start()
19-
def generate_sentence_count(self):
19+
def generate_sentence_count(self, crewai_trigger_payload: dict = None):
2020
print("Generating sentence count")
21-
self.state.sentence_count = randint(1, 5)
21+
22+
# Use trigger payload if available
23+
if crewai_trigger_payload:
24+
# Example: use trigger data to influence sentence count
25+
self.state.sentence_count = crewai_trigger_payload.get('sentence_count', randint(1, 5))
26+
print(f"Using trigger payload: {crewai_trigger_payload}")
27+
else:
28+
self.state.sentence_count = randint(1, 5)
2229

2330
@listen(generate_sentence_count)
2431
def generate_poem(self):
@@ -49,5 +56,32 @@ def plot():
4956
poem_flow.plot()
5057

5158

59+
def run_with_trigger():
60+
"""
61+
Run the flow with trigger payload.
62+
"""
63+
import json
64+
import sys
65+
66+
# Get trigger payload from command line argument
67+
if len(sys.argv) < 2:
68+
raise Exception("No trigger payload provided. Please provide JSON payload as argument.")
69+
70+
try:
71+
trigger_payload = json.loads(sys.argv[1])
72+
except json.JSONDecodeError:
73+
raise Exception("Invalid JSON payload provided as argument")
74+
75+
# Create flow and kickoff with trigger payload
76+
# The @start() methods will automatically receive crewai_trigger_payload parameter
77+
poem_flow = PoemFlow()
78+
79+
try:
80+
result = poem_flow.kickoff({"crewai_trigger_payload": trigger_payload})
81+
return result
82+
except Exception as e:
83+
raise Exception(f"An error occurred while running the flow with trigger: {e}")
84+
85+
5286
if __name__ == "__main__":
5387
kickoff()

lib/crewai/src/crewai/cli/templates/flow/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies = [
1212
kickoff = "{{folder_name}}.main:kickoff"
1313
run_crew = "{{folder_name}}.main:kickoff"
1414
plot = "{{folder_name}}.main:plot"
15+
run_with_trigger = "{{folder_name}}.main:run_with_trigger"
1516

1617
[build-system]
1718
requires = ["hatchling"]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Triggers command module for CrewAI CLI."""
2+
3+
from .main import TriggersCommand
4+
5+
__all__ = ["TriggersCommand"]
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import json
2+
import subprocess
3+
import sys
4+
from typing import Any, Dict
5+
6+
import click
7+
from rich.console import Console
8+
from rich.table import Table
9+
10+
from crewai.cli.command import BaseCommand, PlusAPIMixin
11+
from crewai.cli.utils import read_toml
12+
13+
console = Console()
14+
15+
16+
class TriggersCommand(BaseCommand, PlusAPIMixin):
17+
"""
18+
A class to handle trigger-related operations for CrewAI projects.
19+
"""
20+
21+
def __init__(self):
22+
BaseCommand.__init__(self)
23+
PlusAPIMixin.__init__(self, telemetry=self._telemetry)
24+
25+
def list_triggers(self) -> None:
26+
"""List all available triggers from integrations."""
27+
try:
28+
console.print("[bold blue]Fetching available triggers...[/bold blue]")
29+
response = self.plus_api_client.get_triggers()
30+
self._validate_response(response)
31+
32+
triggers_data = response.json()
33+
self._display_triggers(triggers_data)
34+
35+
except Exception as e:
36+
console.print(f"[bold red]Error fetching triggers: {e}[/bold red]")
37+
raise SystemExit(1)
38+
39+
def execute_with_trigger(self, trigger_path: str) -> None:
40+
"""Execute crew with trigger payload."""
41+
try:
42+
# Parse app_slug/trigger_slug
43+
if "/" not in trigger_path:
44+
console.print(
45+
"[bold red]Error: Trigger path must be in format 'app_slug/trigger_slug'[/bold red]"
46+
)
47+
raise SystemExit(1)
48+
49+
app_slug, trigger_slug = trigger_path.split("/", 1)
50+
51+
console.print(f"[bold blue]Fetching trigger payload for {app_slug}/{trigger_slug}...[/bold blue]")
52+
response = self.plus_api_client.get_trigger_payload(app_slug, trigger_slug)
53+
54+
if response.status_code == 404:
55+
error_data = response.json()
56+
console.print(f"[bold red]Error: {error_data.get('error', 'Trigger not found')}[/bold red]")
57+
raise SystemExit(1)
58+
59+
self._validate_response(response)
60+
61+
trigger_data = response.json()
62+
self._display_trigger_info(trigger_data)
63+
64+
# Ask for confirmation
65+
if not click.confirm("Do you want to run the crew with this trigger payload?"):
66+
console.print("[yellow]Operation cancelled.[/yellow]")
67+
return
68+
69+
# Run crew with trigger payload
70+
self._run_crew_with_payload(trigger_data.get("sample_payload", {}))
71+
72+
except Exception as e:
73+
console.print(f"[bold red]Error executing crew with trigger: {e}[/bold red]")
74+
raise SystemExit(1)
75+
76+
def _display_triggers(self, triggers_data: Dict[str, Any]) -> None:
77+
"""Display triggers in a formatted table."""
78+
apps = triggers_data.get("apps", [])
79+
80+
if not apps:
81+
console.print("[yellow]No triggers found.[/yellow]")
82+
return
83+
84+
for app in apps:
85+
app_name = app.get("name", "Unknown App")
86+
app_slug = app.get("slug", "unknown")
87+
is_connected = app.get("is_connected", False)
88+
connection_status = "[green]✓ Connected[/green]" if is_connected else "[red]✗ Not Connected[/red]"
89+
90+
console.print(f"\n[bold cyan]{app_name}[/bold cyan] ({app_slug}) - {connection_status}")
91+
console.print(f"[dim]{app.get('description', 'No description available')}[/dim]")
92+
93+
triggers = app.get("triggers", [])
94+
if triggers:
95+
table = Table(show_header=True, header_style="bold magenta")
96+
table.add_column("Trigger Path", style="cyan")
97+
table.add_column("Name", style="green")
98+
table.add_column("Description", style="dim")
99+
100+
for trigger in triggers:
101+
trigger_path = f"{app_slug}/{trigger.get('slug', 'unknown')}"
102+
table.add_row(
103+
trigger_path,
104+
trigger.get("name", "Unknown"),
105+
trigger.get("description", "No description")
106+
)
107+
108+
console.print(table)
109+
else:
110+
console.print("[dim] No triggers available[/dim]")
111+
112+
def _display_trigger_info(self, trigger_data: Dict[str, Any]) -> None:
113+
"""Display trigger information before execution."""
114+
console.print("\n[bold green]Trigger Information:[/bold green]")
115+
console.print(f"App: [cyan]{trigger_data.get('app', 'Unknown')}[/cyan]")
116+
console.print(f"Trigger: [cyan]{trigger_data.get('trigger', 'Unknown')}[/cyan]")
117+
console.print(f"Name: [green]{trigger_data.get('trigger_name', 'Unknown')}[/green]")
118+
console.print(f"Description: [dim]{trigger_data.get('description', 'No description')}[/dim]")
119+
120+
sample_payload = trigger_data.get("sample_payload", {})
121+
if sample_payload:
122+
console.print("\n[bold yellow]Sample Payload:[/bold yellow]")
123+
console.print(json.dumps(sample_payload, indent=2))
124+
125+
console.print("\n[bold cyan]Usage in Crew:[/bold cyan]")
126+
console.print("The payload will be available in your crew inputs as:")
127+
console.print("- [green]crewai_trigger_payload[/green]: Full payload object")
128+
console.print("\nExample: Access payload data in your YAML configs using {crewai_trigger_payload}")
129+
130+
def _run_crew_with_payload(self, payload: Dict[str, Any]) -> None:
131+
"""Run the crew with the trigger payload using the run_with_trigger method."""
132+
try:
133+
# Check if we're in a crew project
134+
pyproject_data = read_toml()
135+
is_flow = pyproject_data.get("tool", {}).get("crewai", {}).get("type") == "flow"
136+
137+
console.print(f"[bold blue]Running {'Flow' if is_flow else 'Crew'} with trigger payload...[/bold blue]")
138+
139+
# Execute using the run_with_trigger method with JSON payload as argument
140+
result = subprocess.run(
141+
["uv", "run", "run_with_trigger", json.dumps(payload)],
142+
capture_output=False,
143+
text=True,
144+
check=True
145+
)
146+
147+
console.print("[bold green]Execution completed successfully![/bold green]")
148+
149+
except subprocess.CalledProcessError as e:
150+
console.print(f"[bold red]Execution failed with exit code {e.returncode}[/bold red]")
151+
raise SystemExit(e.returncode)
152+
except FileNotFoundError:
153+
console.print(
154+
"[bold red]Error: Not in a CrewAI project directory. "
155+
"Please run this command from a CrewAI project root.[/bold red]"
156+
)
157+
raise SystemExit(1)
158+
except Exception as e:
159+
console.print(f"[bold red]Unexpected error running project: {e}[/bold red]")
160+
raise SystemExit(1)

0 commit comments

Comments
 (0)