nodestorm-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -0
- package/dist/client.d.ts +170 -0
- package/dist/client.js +143 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +572 -0
- package/dist/tools/describe.d.ts +18 -0
- package/dist/tools/describe.js +284 -0
- package/dist/workflow-guide.md +1655 -0
- package/package.json +47 -0
|
@@ -0,0 +1,1655 @@
|
|
|
1
|
+
# Workflow Builder Agent Guide
|
|
2
|
+
|
|
3
|
+
**Purpose**: Enable an LLM agent to build executable NodeStorm workflow graphs from natural language prompts.
|
|
4
|
+
|
|
5
|
+
**Last Updated**: 2026-01-19
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Quick Reference
|
|
10
|
+
|
|
11
|
+
### Handle Naming Convention
|
|
12
|
+
Use these simple handle IDs (they are automatically normalized during import):
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
sourceHandle: "output" # Standard output (sends data)
|
|
16
|
+
targetHandle: "input" # Standard input (receives data)
|
|
17
|
+
sourceHandle: "true" # Boolean true branch
|
|
18
|
+
sourceHandle: "false" # Boolean false branch
|
|
19
|
+
sourceHandle: "each" # Iteration (list/table)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Note**: Both simple (`"output"`) and prefixed (`"{nodeId}-output-handle"`) formats are accepted.
|
|
23
|
+
|
|
24
|
+
### Edge Branch Types
|
|
25
|
+
| Branch Value | Used By | Purpose |
|
|
26
|
+
|-------------|---------|---------|
|
|
27
|
+
| `null` or omit | All nodes | Regular data flow |
|
|
28
|
+
| `"true"` | boolean | True condition path |
|
|
29
|
+
| `"false"` | boolean | False condition path |
|
|
30
|
+
| `"each"` | list, table | Iteration (run child for each item) |
|
|
31
|
+
| `"{case_name}"` | switch | Route to specific case |
|
|
32
|
+
|
|
33
|
+
### Valid Node Types (EXACT VALUES)
|
|
34
|
+
**CRITICAL**: The `type` field must be one of these exact lowercase values:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
text, list, table, search, scraper, file, prompt, boolean, json, switch, wait, aggregate, http_request, email, google_drive, transcription, image_generation
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**DO NOT use category names** like "Data Sources" or "AI/LLM" - use the exact type values above.
|
|
41
|
+
|
|
42
|
+
| Category | Valid `type` Values |
|
|
43
|
+
|----------|---------------------|
|
|
44
|
+
| Data | `text`, `list`, `table`, `file` |
|
|
45
|
+
| Web | `search`, `scraper` |
|
|
46
|
+
| AI | `prompt`, `boolean`, `json` |
|
|
47
|
+
| Control | `switch`, `wait`, `aggregate` |
|
|
48
|
+
| Actions | `http_request`, `email` |
|
|
49
|
+
| Integrations | `google_drive` |
|
|
50
|
+
| Media | `transcription`, `image_generation` |
|
|
51
|
+
|
|
52
|
+
### Minimal Valid Graph Template
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"nodes": [
|
|
56
|
+
{
|
|
57
|
+
"id": "text-1",
|
|
58
|
+
"type": "text",
|
|
59
|
+
"position": { "x": 100, "y": 100 },
|
|
60
|
+
"data": { "label": "text", "input": "Hello world" }
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"edges": []
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 2. Graph JSON Schema
|
|
70
|
+
|
|
71
|
+
### 2.1 Top-Level Structure
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"nodes": [ /* Array of Node objects */ ],
|
|
75
|
+
"edges": [ /* Array of Edge objects */ ]
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2.2 Node Schema
|
|
80
|
+
```typescript
|
|
81
|
+
interface Node {
|
|
82
|
+
id: string; // Unique identifier, format: "{type}-{uuid}" or custom
|
|
83
|
+
type: string; // One of the 19 supported node types
|
|
84
|
+
position: {
|
|
85
|
+
x: number; // Canvas X coordinate
|
|
86
|
+
y: number; // Canvas Y coordinate
|
|
87
|
+
};
|
|
88
|
+
data: {
|
|
89
|
+
label: string; // Display label (usually same as type)
|
|
90
|
+
// ... type-specific configuration fields
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2.3 Edge Schema
|
|
96
|
+
```typescript
|
|
97
|
+
interface Edge {
|
|
98
|
+
id: string; // Unique identifier, format: "edge-{uuid}"
|
|
99
|
+
source: string; // Source node ID
|
|
100
|
+
target: string; // Target node ID
|
|
101
|
+
sourceHandle: string; // Format: "{sourceNodeId}-{handleType}-handle"
|
|
102
|
+
targetHandle: string; // Format: "{targetNodeId}-input-handle"
|
|
103
|
+
branch?: string; // null | "true" | "false" | "each" | "{case_name}"
|
|
104
|
+
type: "custom"; // Required for ReactFlow rendering
|
|
105
|
+
markerEnd: { type: "arrowClosed" }; // Arrow at target end
|
|
106
|
+
style: { strokeWidth: 2 } | { strokeWidth: 2, strokeDasharray: "5,5" }; // Dashed for "each" edges
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 2.4 Position Guidelines
|
|
111
|
+
- **Horizontal spacing**: 300px between nodes
|
|
112
|
+
- **Vertical spacing**: 150px between parallel branches
|
|
113
|
+
- **Root nodes**: Start at x: 100, y: 100
|
|
114
|
+
- **Flow direction**: Left to right (increasing X)
|
|
115
|
+
|
|
116
|
+
### 2.5 ID Generation
|
|
117
|
+
- Use format: `{type}-{random}` (e.g., `prompt-abc123`)
|
|
118
|
+
- IDs must be unique within the graph
|
|
119
|
+
- Edge IDs: `edge-{random}` or `e{index}`
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 3. Complete Node Type Catalog
|
|
124
|
+
|
|
125
|
+
### 3.1 DATA NODES
|
|
126
|
+
|
|
127
|
+
#### `text` - Static Text Input
|
|
128
|
+
**Purpose**: Provide static text content to the workflow.
|
|
129
|
+
|
|
130
|
+
**Config Schema**:
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"label": "text",
|
|
134
|
+
"input": "The static text content goes here"
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Handles**:
|
|
139
|
+
- Input: `{id}-input-handle`
|
|
140
|
+
- Output: `{id}-output-handle`
|
|
141
|
+
|
|
142
|
+
**Output**: `{ text: "The static text content" }`
|
|
143
|
+
|
|
144
|
+
**Tip**: Most nodes (search, prompt, etc.) can work as root nodes with their own `input` field. Use `text` when you need a dedicated, editable text input at the start of a flow.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
#### `list` - List Extractor/Creator
|
|
149
|
+
**Purpose**: Extract list items using LLM OR enter items manually.
|
|
150
|
+
|
|
151
|
+
**Config Schema (Extract Mode)**:
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"label": "list",
|
|
155
|
+
"mode": "extract",
|
|
156
|
+
"input": "Extract all company names mentioned"
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Config Schema (Manual Mode)**:
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"label": "list",
|
|
164
|
+
"mode": "manual",
|
|
165
|
+
"input": "item1\nitem2\nitem3"
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Note**: Manual mode uses `mode: "manual"` and the `input` field (not `"manual_input"` or `"manual_list_input"`).
|
|
170
|
+
|
|
171
|
+
**Handles**:
|
|
172
|
+
- Input: `{id}-input-handle`
|
|
173
|
+
- Output: `{id}-output-handle`
|
|
174
|
+
- Each (iteration): `{id}-each-handle` (SOURCE)
|
|
175
|
+
|
|
176
|
+
**Output**: `{ items: [...], list: [...], count: N, text: "item1\nitem2..." }`
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
#### `table` - Tabular Data
|
|
181
|
+
**Purpose**: Store and iterate over tabular data. Import CSV files, paste from clipboard, or build tables manually.
|
|
182
|
+
|
|
183
|
+
**Config Schema**:
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"label": "table",
|
|
187
|
+
"columns": [
|
|
188
|
+
{ "id": "col_1", "name": "Name", "type": "text" },
|
|
189
|
+
{ "id": "col_2", "name": "Email", "type": "text" }
|
|
190
|
+
],
|
|
191
|
+
"rows": [
|
|
192
|
+
{ "col_1": "John Doe", "col_2": "john@example.com" },
|
|
193
|
+
{ "col_1": "Jane Smith", "col_2": "jane@example.com" }
|
|
194
|
+
],
|
|
195
|
+
"row_count": 2,
|
|
196
|
+
"storage_mode": "inline"
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Config Options**:
|
|
201
|
+
| Option | Type | Default | Description |
|
|
202
|
+
|--------|------|---------|-------------|
|
|
203
|
+
| `columns` | array | `[]` | Column definitions with id, name, and type |
|
|
204
|
+
| `rows` | array | `[]` | Row data (inline storage) |
|
|
205
|
+
| `row_count` | number | `0` | Total row count (used for file storage display) |
|
|
206
|
+
| `storage_mode` | enum | `"inline"` | `"inline"` (in config) or `"file"` (UserFile) |
|
|
207
|
+
| `file_id` | string | `null` | UserFile ID when using file storage |
|
|
208
|
+
|
|
209
|
+
**Column Types**: `text`, `number`, `date`, `boolean`
|
|
210
|
+
|
|
211
|
+
**Handles**:
|
|
212
|
+
- Input: `{id}-input-handle`
|
|
213
|
+
- Output: `{id}-output-handle`
|
|
214
|
+
- Each (iteration): `{id}-each-handle` (SOURCE)
|
|
215
|
+
|
|
216
|
+
**Output**: `{ rows: [...], list: [...], columns: [...], count: N, text: "N rows" }`
|
|
217
|
+
|
|
218
|
+
**Iteration**: Connect `{id}-each-handle` to child with `branch: "each"` to iterate over rows. Each row is passed as an object with column values accessible by column ID.
|
|
219
|
+
|
|
220
|
+
**Storage**: Tables under 500KB are stored inline in node config. Larger tables automatically use UserFile storage.
|
|
221
|
+
|
|
222
|
+
**Limits**: Max 5000 rows, 50 columns, 500KB inline (then file storage)
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
#### `search` - Web Search
|
|
227
|
+
**Purpose**: Search the web using Tavily AI Search (primary) or Brave Search (fallback).
|
|
228
|
+
|
|
229
|
+
**Config Schema**:
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"label": "search",
|
|
233
|
+
"input": "Find information about...",
|
|
234
|
+
"useLlmCleaning": true,
|
|
235
|
+
"topic": "general",
|
|
236
|
+
"searchDepth": "basic",
|
|
237
|
+
"timeRange": "",
|
|
238
|
+
"includeDomains": [],
|
|
239
|
+
"excludeDomains": [],
|
|
240
|
+
"maxResults": 8,
|
|
241
|
+
"includeAnswer": false,
|
|
242
|
+
"modelConfig": {
|
|
243
|
+
"chat_model": "gpt-5.4-mini",
|
|
244
|
+
"temperature": 0.7,
|
|
245
|
+
"maxTokens": 100
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Config Options**:
|
|
251
|
+
| Option | Type | Default | Description |
|
|
252
|
+
|--------|------|---------|-------------|
|
|
253
|
+
| `input` | string | "" | Search query or instructions |
|
|
254
|
+
| `useLlmCleaning` | boolean | `true` | AI optimizes query before search |
|
|
255
|
+
| `topic` | enum | `"general"` | `"general"`, `"news"`, `"finance"` |
|
|
256
|
+
| `searchDepth` | enum | `"basic"` | `"basic"` (1 credit), `"advanced"` (2 credits) |
|
|
257
|
+
| `timeRange` | enum | `""` | `""` (any), `"day"`, `"week"`, `"month"`, `"year"` |
|
|
258
|
+
| `includeDomains` | array | `[]` | Only include results from these domains |
|
|
259
|
+
| `excludeDomains` | array | `[]` | Exclude results from these domains |
|
|
260
|
+
| `maxResults` | number | `8` | Number of results (1-20) |
|
|
261
|
+
| `includeAnswer` | boolean | `false` | Include AI-generated answer summary |
|
|
262
|
+
|
|
263
|
+
**Handles**:
|
|
264
|
+
- Input: `{id}-input-handle`
|
|
265
|
+
- Output: `{id}-output-handle`
|
|
266
|
+
|
|
267
|
+
**Output**: `{ text: "formatted results", results: [...], query: "optimized query", provider: "tavily"|"brave" }`
|
|
268
|
+
|
|
269
|
+
**Notes**:
|
|
270
|
+
- Tavily is used as primary provider when `TAVILY_API_KEY` is configured
|
|
271
|
+
- Falls back to Brave Search if Tavily is unavailable or fails
|
|
272
|
+
- `useLlmCleaning: true` uses AI to optimize search query from natural language input
|
|
273
|
+
- `includeAnswer: true` adds an AI-generated summary at the top of results (Tavily only)
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
#### `scraper` - Web Page Scraper
|
|
278
|
+
**Purpose**: Extract content from web pages.
|
|
279
|
+
|
|
280
|
+
**Config Schema**:
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"label": "scraper",
|
|
284
|
+
"input": "https://example.com/page"
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Handles**:
|
|
289
|
+
- Input: `{id}-input-handle`
|
|
290
|
+
- Output: `{id}-output-handle`
|
|
291
|
+
|
|
292
|
+
**Output**: `{ text: "cleaned HTML", html: "...", url: "..." }`
|
|
293
|
+
|
|
294
|
+
**Note**: URL must start with `http://` or `https://`. May take up to 1 minute.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
#### `file` - Persistent File Storage
|
|
299
|
+
**Purpose**: Read, update, or create persistent files (CSV, JSON, TXT, MD) that persist across workflows.
|
|
300
|
+
|
|
301
|
+
**Mode Inference** (automatic based on configuration):
|
|
302
|
+
| Has Parent | Has File | Mode |
|
|
303
|
+
|------------|----------|------|
|
|
304
|
+
| No | Yes | **Read** - Load file content, pass to children |
|
|
305
|
+
| Yes | Yes | **Update** - Update file with parent data |
|
|
306
|
+
| Yes | No | **Create** - Create new file from parent data |
|
|
307
|
+
|
|
308
|
+
**Config Schema (Read Mode)**:
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"label": "file",
|
|
312
|
+
"file_id": "123",
|
|
313
|
+
"file_name": "contacts.csv",
|
|
314
|
+
"file_path": "/projects/contacts.csv"
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Config Schema (Update Mode)**:
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
"label": "file",
|
|
322
|
+
"file_id": "123",
|
|
323
|
+
"file_name": "contacts.csv",
|
|
324
|
+
"file_path": "/projects/contacts.csv",
|
|
325
|
+
"update_instructions": "Append the new rows to the CSV"
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Config Schema (Create Mode)**:
|
|
330
|
+
```json
|
|
331
|
+
{
|
|
332
|
+
"label": "file",
|
|
333
|
+
"create_instructions": "Create a CSV with columns: name, email, status",
|
|
334
|
+
"folder_path": "/projects/"
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Handles**:
|
|
339
|
+
- Input: `{id}-input-handle`
|
|
340
|
+
- Output: `{id}-output-handle`
|
|
341
|
+
|
|
342
|
+
**Output**:
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"text": "File content or operation summary",
|
|
346
|
+
"file_path": "/projects/contacts.csv",
|
|
347
|
+
"file_id": "123",
|
|
348
|
+
"metadata": {
|
|
349
|
+
"name": "contacts.csv",
|
|
350
|
+
"content_type": "text/csv",
|
|
351
|
+
"size_bytes": 1024
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Supported File Types**:
|
|
357
|
+
- `text/csv` - CSV files
|
|
358
|
+
- `application/json` - JSON files
|
|
359
|
+
- `text/plain` - Plain text files
|
|
360
|
+
- `text/markdown` - Markdown files
|
|
361
|
+
|
|
362
|
+
**Limits**: 10MB max file size, 1000 files per user
|
|
363
|
+
|
|
364
|
+
**Note**: File node differs from CSV node - file node persists data to storage, CSV node parses inline CSV text. Use file node when you need data to persist across workflow runs.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
### 3.3 AI/LLM NODES
|
|
369
|
+
|
|
370
|
+
#### `prompt` - LLM Prompt
|
|
371
|
+
**Purpose**: Send prompts to language models and get responses.
|
|
372
|
+
|
|
373
|
+
**Config Schema**:
|
|
374
|
+
```json
|
|
375
|
+
{
|
|
376
|
+
"label": "prompt",
|
|
377
|
+
"input": "Your prompt or instruction here",
|
|
378
|
+
"modelConfig": {
|
|
379
|
+
"provider": "openai",
|
|
380
|
+
"chat_model": "gpt-5.4-mini",
|
|
381
|
+
"systemMessage": "You are a helpful assistant.",
|
|
382
|
+
"temperature": 0.7,
|
|
383
|
+
"maxTokens": 400
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Supported Models**:
|
|
389
|
+
| Provider | Models |
|
|
390
|
+
|----------|--------|
|
|
391
|
+
| `openai` | `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano` |
|
|
392
|
+
| `anthropic` | `claude-3-5-sonnet-20241022`, `claude-3-5-haiku`, `claude-3-opus` |
|
|
393
|
+
| `google` | `gemini-1.5-pro`, `gemini-1.5-flash`, `gemini-2.0-flash` |
|
|
394
|
+
|
|
395
|
+
**Handles**:
|
|
396
|
+
- Input: `{id}-input-handle`
|
|
397
|
+
- Output: `{id}-output-handle`
|
|
398
|
+
|
|
399
|
+
**Output**: `{ messages: [{role, content}...], text: "LLM response" }`
|
|
400
|
+
|
|
401
|
+
**Auto-Chunking**: When input text exceeds 80,000 characters, the prompt node automatically:
|
|
402
|
+
1. Splits input into chunks (500 char overlap for context continuity)
|
|
403
|
+
2. Processes each chunk with the same prompt
|
|
404
|
+
3. Concatenates results with double-newline separators
|
|
405
|
+
|
|
406
|
+
This eliminates the need for a separate `chunk` node.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
#### `boolean` - Conditional Branching
|
|
411
|
+
**Purpose**: Evaluate a yes/no question and route to true or false branch.
|
|
412
|
+
|
|
413
|
+
**Config Schema**:
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"label": "boolean",
|
|
417
|
+
"input": "Is this a positive sentiment?",
|
|
418
|
+
"modelConfig": {
|
|
419
|
+
"chat_model": "gpt-5.4-mini"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Handles**:
|
|
425
|
+
- Input: `{id}-input-handle`
|
|
426
|
+
- True output: `{id}-true-handle` (SOURCE)
|
|
427
|
+
- False output: `{id}-false-handle` (SOURCE)
|
|
428
|
+
|
|
429
|
+
**Output**: `{ text: "true"|"false", boolean: true|false, messages: [...] }`
|
|
430
|
+
|
|
431
|
+
**Edge Usage**:
|
|
432
|
+
- True path: `sourceHandle: "{id}-true-handle", branch: "true"`
|
|
433
|
+
- False path: `sourceHandle: "{id}-false-handle", branch: "false"`
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
#### `json` - JSON Extractor
|
|
438
|
+
**Purpose**: Extract structured JSON from text using LLM.
|
|
439
|
+
|
|
440
|
+
**Config Schema**:
|
|
441
|
+
```json
|
|
442
|
+
{
|
|
443
|
+
"label": "json",
|
|
444
|
+
"input": "Extract name, email, and phone as JSON",
|
|
445
|
+
"modelConfig": {
|
|
446
|
+
"chat_model": "gpt-5.4-mini",
|
|
447
|
+
"temperature": 0.7,
|
|
448
|
+
"maxTokens": 1000
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Handles**:
|
|
454
|
+
- Input: `{id}-input-handle`
|
|
455
|
+
- Output: `{id}-output-handle`
|
|
456
|
+
|
|
457
|
+
**Output**: `{ json: {...}, text: "pretty printed JSON" }`
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### 3.4 LOGIC & CONTROL NODES
|
|
462
|
+
|
|
463
|
+
#### `switch` - Multi-Path Router
|
|
464
|
+
**Purpose**: Route execution to one of multiple paths based on conditions.
|
|
465
|
+
|
|
466
|
+
**Config Schema**:
|
|
467
|
+
```json
|
|
468
|
+
{
|
|
469
|
+
"label": "switch",
|
|
470
|
+
"condition_type": "llm|regex|contains|equals",
|
|
471
|
+
"cases": [
|
|
472
|
+
{
|
|
473
|
+
"name": "positive",
|
|
474
|
+
"description": "Positive sentiment",
|
|
475
|
+
"value": "positive",
|
|
476
|
+
"pattern": "\\b(good|great|excellent)\\b"
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"name": "negative",
|
|
480
|
+
"description": "Negative sentiment"
|
|
481
|
+
}
|
|
482
|
+
],
|
|
483
|
+
"default_branch": "neutral",
|
|
484
|
+
"input_value": "",
|
|
485
|
+
"llm_prompt": "Classify the sentiment"
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Condition Types**:
|
|
490
|
+
| Type | Description | Case Fields |
|
|
491
|
+
|------|-------------|-------------|
|
|
492
|
+
| `equals` | Exact match | `value` or `values` array |
|
|
493
|
+
| `contains` | Substring match | `value` or `values` array |
|
|
494
|
+
| `regex` | Pattern match | `pattern` |
|
|
495
|
+
| `llm` | AI classification | `name`, `description` |
|
|
496
|
+
|
|
497
|
+
**Handles**:
|
|
498
|
+
- Input: `{id}-input-handle`
|
|
499
|
+
- Case outputs: `{id}-output-handle` (one per case + default)
|
|
500
|
+
|
|
501
|
+
**Output**: `{ branch: "case_name", text: "Routed to: case_name", result: {...} }`
|
|
502
|
+
|
|
503
|
+
**Edge Usage**: `branch: "{case_name}"` routes to that case's path.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
#### `wait` - Execution Delay
|
|
508
|
+
**Purpose**: Pause workflow execution for a specified duration.
|
|
509
|
+
|
|
510
|
+
**Config Schema**:
|
|
511
|
+
```json
|
|
512
|
+
{
|
|
513
|
+
"label": "wait",
|
|
514
|
+
"delay_type": "seconds|minutes|hours|until",
|
|
515
|
+
"delay_amount": 30,
|
|
516
|
+
"until_time": "2025-01-01T12:00:00Z"
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**Handles**:
|
|
521
|
+
- Input: `{id}-input-handle`
|
|
522
|
+
- Output: `{id}-output-handle`
|
|
523
|
+
|
|
524
|
+
**Output**: `{ success: true, text: "Waited 30 seconds", result: {...} }`
|
|
525
|
+
|
|
526
|
+
**Limits**: Maximum 7 days (168 hours)
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
#### `aggregate` - Iteration Collector
|
|
531
|
+
**Purpose**: Collect all results from an iteration into a single array. **REQUIRED** to terminate iteration scope.
|
|
532
|
+
|
|
533
|
+
**Config Schema**:
|
|
534
|
+
```json
|
|
535
|
+
{
|
|
536
|
+
"label": "aggregate"
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**Handles**:
|
|
541
|
+
- Input: `{id}-input-handle`
|
|
542
|
+
- Output: `{id}-output-handle`
|
|
543
|
+
|
|
544
|
+
**Output**: `{ result: [...], count: N }`
|
|
545
|
+
|
|
546
|
+
**CRITICAL**: Every iteration (list/table with `each` edge) MUST end with an aggregate node.
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
### 3.5 ACTION NODES
|
|
551
|
+
|
|
552
|
+
#### `http_request` - HTTP API Calls
|
|
553
|
+
**Purpose**: Make HTTP requests to external APIs.
|
|
554
|
+
|
|
555
|
+
**Config Schema**:
|
|
556
|
+
```json
|
|
557
|
+
{
|
|
558
|
+
"label": "http_request",
|
|
559
|
+
"url": "https://api.example.com/endpoint",
|
|
560
|
+
"method": "GET|POST|PUT|PATCH|DELETE",
|
|
561
|
+
"headers": {
|
|
562
|
+
"Authorization": "Bearer token",
|
|
563
|
+
"Content-Type": "application/json"
|
|
564
|
+
},
|
|
565
|
+
"body": "{\"key\": \"value\"}",
|
|
566
|
+
"query_params": {
|
|
567
|
+
"param1": "value1"
|
|
568
|
+
},
|
|
569
|
+
"credential_id": null,
|
|
570
|
+
"timeout": 30
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Handles**:
|
|
575
|
+
- Input: `{id}-input-handle`
|
|
576
|
+
- Output: `{id}-output-handle`
|
|
577
|
+
|
|
578
|
+
**Output**: `{ body: {...}, status: 200, headers: {...}, success: true, text: "..." }`
|
|
579
|
+
|
|
580
|
+
**Limits**: Max 30 second timeout, 1MB response size
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
#### `email` - Send Email
|
|
585
|
+
**Purpose**: Send email notifications to the workflow user.
|
|
586
|
+
|
|
587
|
+
**Config Schema**:
|
|
588
|
+
```json
|
|
589
|
+
{
|
|
590
|
+
"label": "email",
|
|
591
|
+
"subject": "Flow Results: {{parent.text}}",
|
|
592
|
+
"body": "Here are your results:\n\n{{parent.result}}",
|
|
593
|
+
"format": "plain|html"
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Variable Interpolation**: Use `{{parent.key}}` to insert parent output values.
|
|
598
|
+
|
|
599
|
+
**Handles**:
|
|
600
|
+
- Input: `{id}-input-handle`
|
|
601
|
+
- Output: `{id}-output-handle`
|
|
602
|
+
|
|
603
|
+
**Output**: `{ sent: true, message_id: "...", recipient: "user@email.com", timestamp: "..." }`
|
|
604
|
+
|
|
605
|
+
**Security**: Email is ALWAYS sent to the authenticated user only.
|
|
606
|
+
**Limits**: 50 emails per day per user.
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
### 3.6 INTEGRATION NODES
|
|
611
|
+
|
|
612
|
+
#### `google_drive` - Google Drive
|
|
613
|
+
**Purpose**: Manage files and folders in Google Drive.
|
|
614
|
+
|
|
615
|
+
**Config Schema**:
|
|
616
|
+
```json
|
|
617
|
+
{
|
|
618
|
+
"label": "google_drive",
|
|
619
|
+
"operation": "list_files|get_metadata|download_file|upload_file|update_file|create_folder|copy_file|move_file|delete_file|search_files",
|
|
620
|
+
"folder_id": "...",
|
|
621
|
+
"file_id": "...",
|
|
622
|
+
"file_name": "...",
|
|
623
|
+
"file_content": "...",
|
|
624
|
+
"query": "..."
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
**Handles**:
|
|
629
|
+
- Input: `{id}-input-handle`
|
|
630
|
+
- Output: `{id}-output-handle`
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
### 3.7 MEDIA NODES
|
|
635
|
+
|
|
636
|
+
#### `transcription` - Audio to Text
|
|
637
|
+
**Purpose**: Convert audio to text using Whisper or Google STT.
|
|
638
|
+
|
|
639
|
+
**Config Schema**:
|
|
640
|
+
```json
|
|
641
|
+
{
|
|
642
|
+
"label": "transcription",
|
|
643
|
+
"provider": "openai|google",
|
|
644
|
+
"audio_source": "url|content",
|
|
645
|
+
"audio_url": "https://example.com/audio.mp3",
|
|
646
|
+
"language": "auto|en|es|fr|de|...",
|
|
647
|
+
"output_format": "text|json|verbose_json|srt|vtt",
|
|
648
|
+
"prompt": "Optional context prompt"
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
**Handles**:
|
|
653
|
+
- Input: `{id}-input-handle`
|
|
654
|
+
- Output: `{id}-output-handle`
|
|
655
|
+
|
|
656
|
+
**Output**: `{ text: "transcribed text", success: true, ... }`
|
|
657
|
+
|
|
658
|
+
**Supported Formats**: mp3, mp4, wav, m4a, webm, flac, ogg
|
|
659
|
+
**Limits**: 25MB max file size (OpenAI)
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
#### `image_generation` - AI Image Creation
|
|
664
|
+
**Purpose**: Generate images using DALL-E 3 or Imagen 3.
|
|
665
|
+
|
|
666
|
+
**Config Schema**:
|
|
667
|
+
```json
|
|
668
|
+
{
|
|
669
|
+
"label": "image_generation",
|
|
670
|
+
"provider": "openai|google",
|
|
671
|
+
"operation": "generate|edit|variation",
|
|
672
|
+
"prompt": "A serene mountain landscape at sunset",
|
|
673
|
+
"size": "1024x1024|1792x1024|1024x1792",
|
|
674
|
+
"aspect_ratio": "1:1|16:9|9:16|4:3|3:4",
|
|
675
|
+
"quality": "standard|hd",
|
|
676
|
+
"style": "vivid|natural",
|
|
677
|
+
"count": 1,
|
|
678
|
+
"negative_prompt": "blurry, low quality"
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
**Provider-Specific Options**:
|
|
683
|
+
| Option | OpenAI (DALL-E 3) | Google (Imagen 3) |
|
|
684
|
+
|--------|-------------------|-------------------|
|
|
685
|
+
| Size | `size` field | `aspect_ratio` field |
|
|
686
|
+
| Quality | `quality`, `style` | N/A |
|
|
687
|
+
| Negative | N/A | `negative_prompt` |
|
|
688
|
+
|
|
689
|
+
**Handles**:
|
|
690
|
+
- Input: `{id}-input-handle`
|
|
691
|
+
- Output: `{id}-output-handle`
|
|
692
|
+
|
|
693
|
+
**Output**: `{ images: [{url, revised_prompt}], image_count: N, success: true }`
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## 4. Edge Connection Rules
|
|
700
|
+
|
|
701
|
+
### 4.1 Handle Types
|
|
702
|
+
- **Source handles** (output): Can only connect TO target handles
|
|
703
|
+
- **Target handles** (input): Can only receive FROM source handles
|
|
704
|
+
|
|
705
|
+
### 4.2 Handle Naming Convention
|
|
706
|
+
```
|
|
707
|
+
Standard output: {nodeId}-output-handle
|
|
708
|
+
Standard input: {nodeId}-input-handle
|
|
709
|
+
Boolean true: {nodeId}-true-handle
|
|
710
|
+
Boolean false: {nodeId}-false-handle
|
|
711
|
+
Each (iteration): {nodeId}-each-handle
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### 4.3 Branch Property Rules
|
|
715
|
+
| Source Node Type | Branch Value | Meaning |
|
|
716
|
+
|------------------|--------------|---------|
|
|
717
|
+
| Any (regular) | `null` or omit | Standard data flow |
|
|
718
|
+
| `boolean` | `"true"` | True condition path |
|
|
719
|
+
| `boolean` | `"false"` | False condition path |
|
|
720
|
+
| `list`, `table` | `"each"` | Iterate over items |
|
|
721
|
+
| `switch` | `"{case_name}"` | Route to named case |
|
|
722
|
+
|
|
723
|
+
### 4.4 Multi-Input Nodes
|
|
724
|
+
- Multiple parent nodes CAN connect to the same target handle
|
|
725
|
+
- Messages from all parents are merged automatically
|
|
726
|
+
- Use for combining data from multiple sources
|
|
727
|
+
|
|
728
|
+
### 4.5 Edge Example
|
|
729
|
+
```json
|
|
730
|
+
{
|
|
731
|
+
"id": "edge-1",
|
|
732
|
+
"source": "prompt-1",
|
|
733
|
+
"target": "boolean-1",
|
|
734
|
+
"sourceHandle": "prompt-1-output-handle",
|
|
735
|
+
"targetHandle": "boolean-1-input-handle",
|
|
736
|
+
"type": "custom",
|
|
737
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
738
|
+
"style": { "strokeWidth": 2 }
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
For iteration edges (with `branch: "each"`), use dashed style:
|
|
743
|
+
```json
|
|
744
|
+
{
|
|
745
|
+
"id": "edge-iter",
|
|
746
|
+
"source": "list-1",
|
|
747
|
+
"target": "prompt-1",
|
|
748
|
+
"sourceHandle": "list-1-each-handle",
|
|
749
|
+
"targetHandle": "prompt-1-input-handle",
|
|
750
|
+
"branch": "each",
|
|
751
|
+
"type": "custom",
|
|
752
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
753
|
+
"style": { "strokeWidth": 2, "strokeDasharray": "5,5" }
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
---
|
|
758
|
+
|
|
759
|
+
## 5. Iteration Scope Rules
|
|
760
|
+
|
|
761
|
+
### 5.1 Iteration Sources
|
|
762
|
+
Only these node types can start an iteration:
|
|
763
|
+
- `list` (via `{id}-each-handle`)
|
|
764
|
+
- `table` (via `{id}-each-handle`)
|
|
765
|
+
|
|
766
|
+
### 5.2 Starting Iteration
|
|
767
|
+
```json
|
|
768
|
+
{
|
|
769
|
+
"id": "edge-iter",
|
|
770
|
+
"source": "list-1",
|
|
771
|
+
"target": "prompt-1",
|
|
772
|
+
"sourceHandle": "list-1-each-handle",
|
|
773
|
+
"targetHandle": "prompt-1-input-handle",
|
|
774
|
+
"branch": "each",
|
|
775
|
+
"type": "custom",
|
|
776
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
777
|
+
"style": { "strokeWidth": 2, "strokeDasharray": "5,5" }
|
|
778
|
+
}
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### 5.3 Iteration Scope
|
|
782
|
+
- **Starts**: When an edge with `branch: "each"` connects to a node
|
|
783
|
+
- **Continues**: All downstream nodes until an aggregate node
|
|
784
|
+
- **Ends**: At the `aggregate` node
|
|
785
|
+
|
|
786
|
+
### 5.4 Aggregate Node Requirement
|
|
787
|
+
**CRITICAL**: Every iteration MUST terminate with an `aggregate` node.
|
|
788
|
+
|
|
789
|
+
```
|
|
790
|
+
list --[each]--> prompt --> aggregate
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
Without aggregate:
|
|
794
|
+
- Results are lost
|
|
795
|
+
- Downstream nodes never execute
|
|
796
|
+
- Workflow hangs
|
|
797
|
+
|
|
798
|
+
### 5.5 What Happens During Iteration
|
|
799
|
+
1. List/table has 10 items
|
|
800
|
+
2. Child node runs 10 times (once per item)
|
|
801
|
+
3. Each run gets the current item's data as context
|
|
802
|
+
4. Aggregate collects all 10 results into an array
|
|
803
|
+
5. Downstream nodes receive the aggregated array
|
|
804
|
+
|
|
805
|
+
---
|
|
806
|
+
|
|
807
|
+
## 6. Common Workflow Patterns
|
|
808
|
+
|
|
809
|
+
### 6.1 Linear Chain
|
|
810
|
+
```
|
|
811
|
+
text --> prompt
|
|
812
|
+
```
|
|
813
|
+
Simple sequential processing. The last node's output is visible in execution results.
|
|
814
|
+
|
|
815
|
+
### 6.2 Conditional Branching
|
|
816
|
+
```
|
|
817
|
+
prompt --> boolean --[true]--> promptA
|
|
818
|
+
\--[false]--> promptB
|
|
819
|
+
```
|
|
820
|
+
Route based on yes/no decision.
|
|
821
|
+
|
|
822
|
+
### 6.3 Iteration Pattern
|
|
823
|
+
```
|
|
824
|
+
list --[each]--> prompt --> aggregate
|
|
825
|
+
```
|
|
826
|
+
Process each item and combine results.
|
|
827
|
+
|
|
828
|
+
### 6.4 Multi-Path Switch
|
|
829
|
+
```
|
|
830
|
+
prompt --> switch --[case1]--> action1
|
|
831
|
+
\--[case2]--> action2
|
|
832
|
+
\--[default]--> action3
|
|
833
|
+
```
|
|
834
|
+
Route to one of many paths.
|
|
835
|
+
|
|
836
|
+
### 6.5 Data Enrichment
|
|
837
|
+
```
|
|
838
|
+
table --[each]--> scraper --> prompt --> aggregate
|
|
839
|
+
```
|
|
840
|
+
Fetch data for each row, process, combine.
|
|
841
|
+
|
|
842
|
+
### 6.6 Multi-Input Merge
|
|
843
|
+
```
|
|
844
|
+
promptA ----\
|
|
845
|
+
--> prompt
|
|
846
|
+
promptB ----/
|
|
847
|
+
```
|
|
848
|
+
Combine outputs from multiple nodes.
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
## 7. Example Workflows
|
|
853
|
+
|
|
854
|
+
### 7.1 Simple Prompt Chain
|
|
855
|
+
**User Request**: "Create a workflow that takes a question and answers it"
|
|
856
|
+
|
|
857
|
+
```json
|
|
858
|
+
{
|
|
859
|
+
"nodes": [
|
|
860
|
+
{
|
|
861
|
+
"id": "text-1",
|
|
862
|
+
"type": "text",
|
|
863
|
+
"position": { "x": 100, "y": 100 },
|
|
864
|
+
"data": { "label": "text", "input": "What is machine learning?" }
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
"id": "prompt-1",
|
|
868
|
+
"type": "prompt",
|
|
869
|
+
"position": { "x": 400, "y": 100 },
|
|
870
|
+
"data": {
|
|
871
|
+
"label": "prompt",
|
|
872
|
+
"input": "Answer this question in simple terms",
|
|
873
|
+
"modelConfig": {
|
|
874
|
+
"provider": "openai",
|
|
875
|
+
"chat_model": "gpt-5.4-mini",
|
|
876
|
+
"systemMessage": "You are a helpful teacher.",
|
|
877
|
+
"temperature": 0.7,
|
|
878
|
+
"maxTokens": 400
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
],
|
|
883
|
+
"edges": [
|
|
884
|
+
{
|
|
885
|
+
"id": "e1",
|
|
886
|
+
"source": "text-1",
|
|
887
|
+
"target": "prompt-1",
|
|
888
|
+
"sourceHandle": "text-1-output-handle",
|
|
889
|
+
"targetHandle": "prompt-1-input-handle",
|
|
890
|
+
"type": "custom",
|
|
891
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
892
|
+
"style": { "strokeWidth": 2 }
|
|
893
|
+
}
|
|
894
|
+
]
|
|
895
|
+
}
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
### 7.2 URL Scraping with Iteration
|
|
901
|
+
**User Request**: "Scrape 3 URLs and summarize each page"
|
|
902
|
+
|
|
903
|
+
```json
|
|
904
|
+
{
|
|
905
|
+
"nodes": [
|
|
906
|
+
{
|
|
907
|
+
"id": "list-1",
|
|
908
|
+
"type": "list",
|
|
909
|
+
"position": { "x": 100, "y": 100 },
|
|
910
|
+
"data": {
|
|
911
|
+
"label": "list",
|
|
912
|
+
"mode": "manual",
|
|
913
|
+
"input": "https://example.com/page1\nhttps://example.com/page2\nhttps://example.com/page3"
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
"id": "scraper-1",
|
|
918
|
+
"type": "scraper",
|
|
919
|
+
"position": { "x": 400, "y": 100 },
|
|
920
|
+
"data": { "label": "scraper", "input": "" }
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
"id": "prompt-1",
|
|
924
|
+
"type": "prompt",
|
|
925
|
+
"position": { "x": 700, "y": 100 },
|
|
926
|
+
"data": {
|
|
927
|
+
"label": "prompt",
|
|
928
|
+
"input": "Summarize this web page content in 2-3 sentences",
|
|
929
|
+
"modelConfig": {
|
|
930
|
+
"provider": "openai",
|
|
931
|
+
"chat_model": "gpt-5.4-mini",
|
|
932
|
+
"temperature": 0.5,
|
|
933
|
+
"maxTokens": 200
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
"id": "aggregate-1",
|
|
939
|
+
"type": "aggregate",
|
|
940
|
+
"position": { "x": 1000, "y": 100 },
|
|
941
|
+
"data": { "label": "aggregate" }
|
|
942
|
+
}
|
|
943
|
+
],
|
|
944
|
+
"edges": [
|
|
945
|
+
{
|
|
946
|
+
"id": "e1",
|
|
947
|
+
"source": "list-1",
|
|
948
|
+
"target": "scraper-1",
|
|
949
|
+
"sourceHandle": "list-1-each-handle",
|
|
950
|
+
"targetHandle": "scraper-1-input-handle",
|
|
951
|
+
"branch": "each",
|
|
952
|
+
"type": "custom",
|
|
953
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
954
|
+
"style": { "strokeWidth": 2, "strokeDasharray": "5,5" }
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
"id": "e2",
|
|
958
|
+
"source": "scraper-1",
|
|
959
|
+
"target": "prompt-1",
|
|
960
|
+
"sourceHandle": "scraper-1-output-handle",
|
|
961
|
+
"targetHandle": "prompt-1-input-handle",
|
|
962
|
+
"type": "custom",
|
|
963
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
964
|
+
"style": { "strokeWidth": 2 }
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
"id": "e3",
|
|
968
|
+
"source": "prompt-1",
|
|
969
|
+
"target": "aggregate-1",
|
|
970
|
+
"sourceHandle": "prompt-1-output-handle",
|
|
971
|
+
"targetHandle": "aggregate-1-input-handle",
|
|
972
|
+
"type": "custom",
|
|
973
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
974
|
+
"style": { "strokeWidth": 2 }
|
|
975
|
+
}
|
|
976
|
+
]
|
|
977
|
+
}
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
---
|
|
981
|
+
|
|
982
|
+
### 7.3 Conditional Email Notification
|
|
983
|
+
**User Request**: "Analyze sentiment and send email only if positive"
|
|
984
|
+
|
|
985
|
+
```json
|
|
986
|
+
{
|
|
987
|
+
"nodes": [
|
|
988
|
+
{
|
|
989
|
+
"id": "text-1",
|
|
990
|
+
"type": "text",
|
|
991
|
+
"position": { "x": 100, "y": 100 },
|
|
992
|
+
"data": { "label": "text", "input": "I absolutely love this product! It exceeded my expectations." }
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
"id": "boolean-1",
|
|
996
|
+
"type": "boolean",
|
|
997
|
+
"position": { "x": 400, "y": 100 },
|
|
998
|
+
"data": {
|
|
999
|
+
"label": "boolean",
|
|
1000
|
+
"input": "Is this feedback positive?",
|
|
1001
|
+
"modelConfig": { "chat_model": "gpt-5.4-mini" }
|
|
1002
|
+
}
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
"id": "email-1",
|
|
1006
|
+
"type": "email",
|
|
1007
|
+
"position": { "x": 700, "y": 50 },
|
|
1008
|
+
"data": {
|
|
1009
|
+
"label": "email",
|
|
1010
|
+
"subject": "Positive Feedback Received!",
|
|
1011
|
+
"body": "Great news! We received positive feedback:\n\n{{parent.text}}",
|
|
1012
|
+
"format": "plain"
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
"id": "prompt-1",
|
|
1017
|
+
"type": "prompt",
|
|
1018
|
+
"position": { "x": 700, "y": 200 },
|
|
1019
|
+
"data": {
|
|
1020
|
+
"label": "prompt",
|
|
1021
|
+
"input": "Generate a response acknowledging this negative feedback",
|
|
1022
|
+
"modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 200 }
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
],
|
|
1026
|
+
"edges": [
|
|
1027
|
+
{
|
|
1028
|
+
"id": "e1",
|
|
1029
|
+
"source": "text-1",
|
|
1030
|
+
"target": "boolean-1",
|
|
1031
|
+
"sourceHandle": "text-1-output-handle",
|
|
1032
|
+
"targetHandle": "boolean-1-input-handle",
|
|
1033
|
+
"type": "custom",
|
|
1034
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1035
|
+
"style": { "strokeWidth": 2 }
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
"id": "e2",
|
|
1039
|
+
"source": "boolean-1",
|
|
1040
|
+
"target": "email-1",
|
|
1041
|
+
"sourceHandle": "boolean-1-true-handle",
|
|
1042
|
+
"targetHandle": "email-1-input-handle",
|
|
1043
|
+
"branch": "true",
|
|
1044
|
+
"type": "custom",
|
|
1045
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1046
|
+
"style": { "strokeWidth": 2 }
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
"id": "e3",
|
|
1050
|
+
"source": "boolean-1",
|
|
1051
|
+
"target": "prompt-1",
|
|
1052
|
+
"sourceHandle": "boolean-1-false-handle",
|
|
1053
|
+
"targetHandle": "prompt-1-input-handle",
|
|
1054
|
+
"branch": "false",
|
|
1055
|
+
"type": "custom",
|
|
1056
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1057
|
+
"style": { "strokeWidth": 2 }
|
|
1058
|
+
}
|
|
1059
|
+
]
|
|
1060
|
+
}
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
---
|
|
1064
|
+
|
|
1065
|
+
### 7.4 API Integration with HTTP Request
|
|
1066
|
+
**User Request**: "Fetch data from an API and process it"
|
|
1067
|
+
|
|
1068
|
+
```json
|
|
1069
|
+
{
|
|
1070
|
+
"nodes": [
|
|
1071
|
+
{
|
|
1072
|
+
"id": "http-1",
|
|
1073
|
+
"type": "http_request",
|
|
1074
|
+
"position": { "x": 100, "y": 100 },
|
|
1075
|
+
"data": {
|
|
1076
|
+
"label": "http_request",
|
|
1077
|
+
"url": "https://api.example.com/data",
|
|
1078
|
+
"method": "GET",
|
|
1079
|
+
"headers": { "Accept": "application/json" },
|
|
1080
|
+
"timeout": 30
|
|
1081
|
+
}
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
"id": "json-1",
|
|
1085
|
+
"type": "json",
|
|
1086
|
+
"position": { "x": 400, "y": 100 },
|
|
1087
|
+
"data": {
|
|
1088
|
+
"label": "json",
|
|
1089
|
+
"input": "Extract the name and status fields",
|
|
1090
|
+
"modelConfig": { "chat_model": "gpt-5.4-mini" }
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
],
|
|
1094
|
+
"edges": [
|
|
1095
|
+
{
|
|
1096
|
+
"id": "e1",
|
|
1097
|
+
"source": "http-1",
|
|
1098
|
+
"target": "json-1",
|
|
1099
|
+
"sourceHandle": "http-1-output-handle",
|
|
1100
|
+
"targetHandle": "json-1-input-handle",
|
|
1101
|
+
"type": "custom",
|
|
1102
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1103
|
+
"style": { "strokeWidth": 2 }
|
|
1104
|
+
}
|
|
1105
|
+
]
|
|
1106
|
+
}
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
---
|
|
1110
|
+
|
|
1111
|
+
### 7.5 Multi-Path Switch Routing
|
|
1112
|
+
**User Request**: "Classify customer inquiries into categories and handle each differently"
|
|
1113
|
+
|
|
1114
|
+
```json
|
|
1115
|
+
{
|
|
1116
|
+
"nodes": [
|
|
1117
|
+
{
|
|
1118
|
+
"id": "text-1",
|
|
1119
|
+
"type": "text",
|
|
1120
|
+
"position": { "x": 100, "y": 150 },
|
|
1121
|
+
"data": { "label": "text", "input": "I want to return my order #12345" }
|
|
1122
|
+
},
|
|
1123
|
+
{
|
|
1124
|
+
"id": "switch-1",
|
|
1125
|
+
"type": "switch",
|
|
1126
|
+
"position": { "x": 400, "y": 150 },
|
|
1127
|
+
"data": {
|
|
1128
|
+
"label": "switch",
|
|
1129
|
+
"condition_type": "llm",
|
|
1130
|
+
"cases": [
|
|
1131
|
+
{ "name": "refund", "description": "Request for refund or return" },
|
|
1132
|
+
{ "name": "shipping", "description": "Shipping or delivery question" },
|
|
1133
|
+
{ "name": "product", "description": "Product information inquiry" }
|
|
1134
|
+
],
|
|
1135
|
+
"default_branch": "general",
|
|
1136
|
+
"llm_prompt": "Classify this customer inquiry"
|
|
1137
|
+
}
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
"id": "prompt-refund",
|
|
1141
|
+
"type": "prompt",
|
|
1142
|
+
"position": { "x": 700, "y": 50 },
|
|
1143
|
+
"data": {
|
|
1144
|
+
"label": "prompt",
|
|
1145
|
+
"input": "Generate a helpful refund/return process response",
|
|
1146
|
+
"modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 200 }
|
|
1147
|
+
}
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
"id": "prompt-shipping",
|
|
1151
|
+
"type": "prompt",
|
|
1152
|
+
"position": { "x": 700, "y": 150 },
|
|
1153
|
+
"data": {
|
|
1154
|
+
"label": "prompt",
|
|
1155
|
+
"input": "Generate a shipping status response",
|
|
1156
|
+
"modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 200 }
|
|
1157
|
+
}
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
"id": "prompt-general",
|
|
1161
|
+
"type": "prompt",
|
|
1162
|
+
"position": { "x": 700, "y": 250 },
|
|
1163
|
+
"data": {
|
|
1164
|
+
"label": "prompt",
|
|
1165
|
+
"input": "Generate a general customer support response",
|
|
1166
|
+
"modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 200 }
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
],
|
|
1170
|
+
"edges": [
|
|
1171
|
+
{
|
|
1172
|
+
"id": "e1",
|
|
1173
|
+
"source": "text-1",
|
|
1174
|
+
"target": "switch-1",
|
|
1175
|
+
"sourceHandle": "text-1-output-handle",
|
|
1176
|
+
"targetHandle": "switch-1-input-handle",
|
|
1177
|
+
"type": "custom",
|
|
1178
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1179
|
+
"style": { "strokeWidth": 2 }
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
"id": "e2",
|
|
1183
|
+
"source": "switch-1",
|
|
1184
|
+
"target": "prompt-refund",
|
|
1185
|
+
"sourceHandle": "switch-1-output-handle",
|
|
1186
|
+
"targetHandle": "prompt-refund-input-handle",
|
|
1187
|
+
"branch": "refund",
|
|
1188
|
+
"type": "custom",
|
|
1189
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1190
|
+
"style": { "strokeWidth": 2 }
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
"id": "e3",
|
|
1194
|
+
"source": "switch-1",
|
|
1195
|
+
"target": "prompt-shipping",
|
|
1196
|
+
"sourceHandle": "switch-1-output-handle",
|
|
1197
|
+
"targetHandle": "prompt-shipping-input-handle",
|
|
1198
|
+
"branch": "shipping",
|
|
1199
|
+
"type": "custom",
|
|
1200
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1201
|
+
"style": { "strokeWidth": 2 }
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
"id": "e4",
|
|
1205
|
+
"source": "switch-1",
|
|
1206
|
+
"target": "prompt-general",
|
|
1207
|
+
"sourceHandle": "switch-1-output-handle",
|
|
1208
|
+
"targetHandle": "prompt-general-input-handle",
|
|
1209
|
+
"branch": "general",
|
|
1210
|
+
"type": "custom",
|
|
1211
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1212
|
+
"style": { "strokeWidth": 2 }
|
|
1213
|
+
}
|
|
1214
|
+
]
|
|
1215
|
+
}
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
---
|
|
1219
|
+
|
|
1220
|
+
### 7.6 Web Search and Summary
|
|
1221
|
+
**User Request**: "Search for information and summarize the results"
|
|
1222
|
+
|
|
1223
|
+
```json
|
|
1224
|
+
{
|
|
1225
|
+
"nodes": [
|
|
1226
|
+
{
|
|
1227
|
+
"id": "search-1",
|
|
1228
|
+
"type": "search",
|
|
1229
|
+
"position": { "x": 100, "y": 100 },
|
|
1230
|
+
"data": {
|
|
1231
|
+
"label": "search",
|
|
1232
|
+
"input": "Latest developments in artificial intelligence 2024",
|
|
1233
|
+
"useLlmCleaning": true,
|
|
1234
|
+
"maxResults": 5
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
"id": "prompt-1",
|
|
1239
|
+
"type": "prompt",
|
|
1240
|
+
"position": { "x": 400, "y": 100 },
|
|
1241
|
+
"data": {
|
|
1242
|
+
"label": "prompt",
|
|
1243
|
+
"input": "Summarize the key points from these search results in a concise paragraph",
|
|
1244
|
+
"modelConfig": {
|
|
1245
|
+
"chat_model": "gpt-5.4-mini",
|
|
1246
|
+
"temperature": 0.5,
|
|
1247
|
+
"maxTokens": 300
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
],
|
|
1252
|
+
"edges": [
|
|
1253
|
+
{
|
|
1254
|
+
"id": "e1",
|
|
1255
|
+
"source": "search-1",
|
|
1256
|
+
"target": "prompt-1",
|
|
1257
|
+
"sourceHandle": "search-1-output-handle",
|
|
1258
|
+
"targetHandle": "prompt-1-input-handle",
|
|
1259
|
+
"type": "custom",
|
|
1260
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1261
|
+
"style": { "strokeWidth": 2 }
|
|
1262
|
+
}
|
|
1263
|
+
]
|
|
1264
|
+
}
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
---
|
|
1268
|
+
|
|
1269
|
+
### 7.7 Image Generation
|
|
1270
|
+
**User Request**: "Generate an image based on a prompt"
|
|
1271
|
+
|
|
1272
|
+
```json
|
|
1273
|
+
{
|
|
1274
|
+
"nodes": [
|
|
1275
|
+
{
|
|
1276
|
+
"id": "text-1",
|
|
1277
|
+
"type": "text",
|
|
1278
|
+
"position": { "x": 100, "y": 100 },
|
|
1279
|
+
"data": { "label": "text", "input": "A serene mountain landscape at sunset with snow-capped peaks" }
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
"id": "image-1",
|
|
1283
|
+
"type": "image_generation",
|
|
1284
|
+
"position": { "x": 400, "y": 100 },
|
|
1285
|
+
"data": {
|
|
1286
|
+
"label": "image_generation",
|
|
1287
|
+
"provider": "openai",
|
|
1288
|
+
"operation": "generate",
|
|
1289
|
+
"prompt": "",
|
|
1290
|
+
"size": "1024x1024",
|
|
1291
|
+
"quality": "standard",
|
|
1292
|
+
"style": "vivid",
|
|
1293
|
+
"count": 1
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
],
|
|
1297
|
+
"edges": [
|
|
1298
|
+
{
|
|
1299
|
+
"id": "e1",
|
|
1300
|
+
"source": "text-1",
|
|
1301
|
+
"target": "image-1",
|
|
1302
|
+
"sourceHandle": "text-1-output-handle",
|
|
1303
|
+
"targetHandle": "image-1-input-handle",
|
|
1304
|
+
"type": "custom",
|
|
1305
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1306
|
+
"style": { "strokeWidth": 2 }
|
|
1307
|
+
}
|
|
1308
|
+
]
|
|
1309
|
+
}
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
---
|
|
1313
|
+
|
|
1314
|
+
### 7.8 Table-Based Data Pipeline
|
|
1315
|
+
**User Request**: "Process a table of data with AI and send results via email"
|
|
1316
|
+
|
|
1317
|
+
```json
|
|
1318
|
+
{
|
|
1319
|
+
"nodes": [
|
|
1320
|
+
{
|
|
1321
|
+
"id": "table-1",
|
|
1322
|
+
"type": "table",
|
|
1323
|
+
"position": { "x": 100, "y": 100 },
|
|
1324
|
+
"data": {
|
|
1325
|
+
"label": "table",
|
|
1326
|
+
"columns": [
|
|
1327
|
+
{ "id": "col_1", "name": "Company", "type": "text" },
|
|
1328
|
+
{ "id": "col_2", "name": "Description", "type": "text" }
|
|
1329
|
+
],
|
|
1330
|
+
"rows": [
|
|
1331
|
+
{ "col_1": "Acme Corp", "col_2": "Software development company" },
|
|
1332
|
+
{ "col_1": "TechStart", "col_2": "AI startup focused on automation" },
|
|
1333
|
+
{ "col_1": "DataFlow", "col_2": "Data analytics platform" }
|
|
1334
|
+
],
|
|
1335
|
+
"storage_mode": "inline"
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
"id": "prompt-1",
|
|
1340
|
+
"type": "prompt",
|
|
1341
|
+
"position": { "x": 400, "y": 100 },
|
|
1342
|
+
"data": {
|
|
1343
|
+
"label": "prompt",
|
|
1344
|
+
"input": "Analyze this company and provide a brief competitive analysis",
|
|
1345
|
+
"modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 300 }
|
|
1346
|
+
}
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
"id": "aggregate-1",
|
|
1350
|
+
"type": "aggregate",
|
|
1351
|
+
"position": { "x": 700, "y": 100 },
|
|
1352
|
+
"data": { "label": "aggregate" }
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
"id": "email-1",
|
|
1356
|
+
"type": "email",
|
|
1357
|
+
"position": { "x": 1000, "y": 100 },
|
|
1358
|
+
"data": {
|
|
1359
|
+
"label": "email",
|
|
1360
|
+
"subject": "Company Analysis Report",
|
|
1361
|
+
"body": "Here are the analysis results:\n\n{{parent.text}}",
|
|
1362
|
+
"format": "plain"
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
],
|
|
1366
|
+
"edges": [
|
|
1367
|
+
{
|
|
1368
|
+
"id": "e1",
|
|
1369
|
+
"source": "table-1",
|
|
1370
|
+
"target": "prompt-1",
|
|
1371
|
+
"sourceHandle": "table-1-each-handle",
|
|
1372
|
+
"targetHandle": "prompt-1-input-handle",
|
|
1373
|
+
"branch": "each",
|
|
1374
|
+
"type": "custom",
|
|
1375
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1376
|
+
"style": { "strokeWidth": 2, "strokeDasharray": "5,5" }
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
"id": "e2",
|
|
1380
|
+
"source": "prompt-1",
|
|
1381
|
+
"target": "aggregate-1",
|
|
1382
|
+
"sourceHandle": "prompt-1-output-handle",
|
|
1383
|
+
"targetHandle": "aggregate-1-input-handle",
|
|
1384
|
+
"type": "custom",
|
|
1385
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1386
|
+
"style": { "strokeWidth": 2 }
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
"id": "e3",
|
|
1390
|
+
"source": "aggregate-1",
|
|
1391
|
+
"target": "email-1",
|
|
1392
|
+
"sourceHandle": "aggregate-1-output-handle",
|
|
1393
|
+
"targetHandle": "email-1-input-handle",
|
|
1394
|
+
"type": "custom",
|
|
1395
|
+
"markerEnd": { "type": "arrowClosed" },
|
|
1396
|
+
"style": { "strokeWidth": 2 }
|
|
1397
|
+
}
|
|
1398
|
+
]
|
|
1399
|
+
}
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
---
|
|
1403
|
+
|
|
1404
|
+
## 8. Validation Checklist
|
|
1405
|
+
|
|
1406
|
+
### Server-Side Validation (FlowValidator)
|
|
1407
|
+
|
|
1408
|
+
**Important**: Validation is now enforced server-side before execution via `FlowValidator`. The validator runs automatically when a flow is executed and returns detailed error messages if validation fails.
|
|
1409
|
+
|
|
1410
|
+
**Location**: `backend/app/services/flow_validator.rb`
|
|
1411
|
+
|
|
1412
|
+
**Error Format**: Errors include node IDs for easy debugging:
|
|
1413
|
+
- `[node-abc123] Missing required config: url`
|
|
1414
|
+
- `[node-xyz789] Unknown node type: invalid_type`
|
|
1415
|
+
- `Flow has no nodes`
|
|
1416
|
+
- `Flow contains a cycle`
|
|
1417
|
+
|
|
1418
|
+
**Validation Categories**:
|
|
1419
|
+
1. **Structure**: Non-empty nodes array, unique IDs, required fields (id, type, data)
|
|
1420
|
+
2. **Node Types**: Must be in TASK_MAP (all 17 valid node types)
|
|
1421
|
+
3. **Connections**: Valid edges, no self-loops, no isolated nodes, DAG (no cycles), entry point exists
|
|
1422
|
+
4. **Iteration Scope**: Each edges must reach aggregate terminator
|
|
1423
|
+
5. **Config**: Truly required fields only; backend workers provide defaults for optional fields:
|
|
1424
|
+
- `prompt/boolean/json`: No required fields (backend defaults to gpt-5.4-mini)
|
|
1425
|
+
- `switch`: `cases` required (non-empty); `condition_type` validated only if present
|
|
1426
|
+
- `http_request`: `url` required; `method` validated only if present (defaults to GET)
|
|
1427
|
+
- `wait`: All fields optional (validated only if present, backend has defaults)
|
|
1428
|
+
- `list` (manual mode): `input` field required when `mode == "manual"`
|
|
1429
|
+
- `transcription/image_generation`: All fields optional (workers have defaults)
|
|
1430
|
+
6. **Credentials**: OAuth connections for google_drive, API credentials for http_request
|
|
1431
|
+
|
|
1432
|
+
### Pre-Output Checklist
|
|
1433
|
+
|
|
1434
|
+
Before outputting a graph, verify:
|
|
1435
|
+
|
|
1436
|
+
### Node Validation
|
|
1437
|
+
- [ ] All nodes have unique IDs
|
|
1438
|
+
- [ ] All node types are valid (from the 19 supported types)
|
|
1439
|
+
- [ ] All required config fields are present
|
|
1440
|
+
- [ ] Position coordinates are numbers
|
|
1441
|
+
- [ ] `data.label` matches node `type`
|
|
1442
|
+
|
|
1443
|
+
### Edge Validation
|
|
1444
|
+
- [ ] All edges have unique IDs
|
|
1445
|
+
- [ ] Source node exists in nodes array
|
|
1446
|
+
- [ ] Target node exists in nodes array
|
|
1447
|
+
- [ ] `sourceHandle` format: `{sourceNodeId}-{output|true|false|each}-handle`
|
|
1448
|
+
- [ ] `targetHandle` format: `{targetNodeId}-input-handle`
|
|
1449
|
+
- [ ] `type` is set to `"custom"` (required for rendering)
|
|
1450
|
+
- [ ] `markerEnd` is set to `{ "type": "arrowClosed" }`
|
|
1451
|
+
- [ ] `style` is set to `{ "strokeWidth": 2 }` (or with `"strokeDasharray": "5,5"` for iteration edges)
|
|
1452
|
+
- [ ] Branch value matches source node type:
|
|
1453
|
+
- `boolean`: `"true"` or `"false"`
|
|
1454
|
+
- `list`/`table` each: `"each"`
|
|
1455
|
+
- `switch`: case name or default branch
|
|
1456
|
+
- Other: `null` or omit
|
|
1457
|
+
|
|
1458
|
+
### Graph Structure
|
|
1459
|
+
- [ ] No orphan nodes (all nodes connected except starting nodes)
|
|
1460
|
+
- [ ] No cycles (DAG requirement - data flows one direction)
|
|
1461
|
+
- [ ] Every iteration scope has an aggregate terminator
|
|
1462
|
+
- [ ] At least one root node (no incoming edges)
|
|
1463
|
+
- [ ] Boolean nodes have both true and false paths handled
|
|
1464
|
+
|
|
1465
|
+
### Handle Compatibility
|
|
1466
|
+
- [ ] Each edge connects source handle to target handle
|
|
1467
|
+
- [ ] Boolean handles (`true-handle`, `false-handle`) only on boolean nodes
|
|
1468
|
+
- [ ] Each handles (`each-handle`) only on list/table nodes
|
|
1469
|
+
- [ ] Case handles (`case-{name}-handle`, `case-default-handle`) only on switch nodes
|
|
1470
|
+
|
|
1471
|
+
---
|
|
1472
|
+
|
|
1473
|
+
## 9. Common Pitfalls & Anti-Patterns
|
|
1474
|
+
|
|
1475
|
+
### 9.1 Handle Naming Errors
|
|
1476
|
+
```
|
|
1477
|
+
WRONG: sourceHandle: "output"
|
|
1478
|
+
RIGHT: sourceHandle: "prompt-1-output-handle"
|
|
1479
|
+
|
|
1480
|
+
WRONG: targetHandle: "prompt-1-output-handle"
|
|
1481
|
+
RIGHT: targetHandle: "prompt-1-input-handle"
|
|
1482
|
+
```
|
|
1483
|
+
|
|
1484
|
+
### 9.2 Missing Aggregate Node
|
|
1485
|
+
```
|
|
1486
|
+
WRONG: list --[each]--> prompt
|
|
1487
|
+
RIGHT: list --[each]--> prompt --> aggregate
|
|
1488
|
+
```
|
|
1489
|
+
**Always terminate iteration scopes with aggregate.**
|
|
1490
|
+
|
|
1491
|
+
### 9.3 Incorrect Branch Values
|
|
1492
|
+
```
|
|
1493
|
+
WRONG: branch: "True" (capital T)
|
|
1494
|
+
RIGHT: branch: "true" (lowercase)
|
|
1495
|
+
|
|
1496
|
+
WRONG: branch: "yes"
|
|
1497
|
+
RIGHT: branch: "true"
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
### 9.4 Orphan Branches
|
|
1501
|
+
```
|
|
1502
|
+
WRONG: Boolean with only true path, no false path handling
|
|
1503
|
+
RIGHT: Both paths should connect somewhere (even to same result)
|
|
1504
|
+
```
|
|
1505
|
+
|
|
1506
|
+
### 9.5 Using `text` Nodes Unnecessarily
|
|
1507
|
+
```
|
|
1508
|
+
WRONG: text --> search --> prompt
|
|
1509
|
+
RIGHT: search --> prompt (search can be root, prompt output is visible)
|
|
1510
|
+
```
|
|
1511
|
+
Most workflows work better without `text` as a prefix:
|
|
1512
|
+
- Start with the first meaningful node (search, prompt, list, etc.) as root
|
|
1513
|
+
- The last node's output is automatically visible in execution results
|
|
1514
|
+
- Use `text` only when you need to provide static input that users can edit
|
|
1515
|
+
|
|
1516
|
+
### 9.6 Circular Dependencies
|
|
1517
|
+
```
|
|
1518
|
+
WRONG: A --> B --> C --> A
|
|
1519
|
+
RIGHT: Ensure unidirectional flow (DAG)
|
|
1520
|
+
```
|
|
1521
|
+
|
|
1522
|
+
### 9.7 Each Edge Without Aggregate
|
|
1523
|
+
```
|
|
1524
|
+
WRONG: list --[each]--> prompt --[each]--> scraper (nested each)
|
|
1525
|
+
RIGHT: list --[each]--> prompt --> aggregate --> list --[each]--> ...
|
|
1526
|
+
```
|
|
1527
|
+
**Never nest iteration scopes. Always aggregate before starting new iteration.**
|
|
1528
|
+
|
|
1529
|
+
### 9.8 Wrong Handle for Iteration
|
|
1530
|
+
```
|
|
1531
|
+
WRONG: sourceHandle: "list-1-output-handle", branch: "each"
|
|
1532
|
+
RIGHT: sourceHandle: "list-1-each-handle", branch: "each"
|
|
1533
|
+
```
|
|
1534
|
+
|
|
1535
|
+
### 9.9 Model Config (Optional)
|
|
1536
|
+
```
|
|
1537
|
+
OPTIONAL: { "input": "..." } (backend uses default model: gpt-5.4-mini)
|
|
1538
|
+
EXPLICIT: { "input": "...", "modelConfig": { "chat_model": "gpt-4" } }
|
|
1539
|
+
```
|
|
1540
|
+
**Note**: Model config is optional for prompt/boolean/json nodes. The backend uses `gpt-5.4-mini` by default when not specified. Only specify `modelConfig` when you need a different model.
|
|
1541
|
+
|
|
1542
|
+
---
|
|
1543
|
+
|
|
1544
|
+
## 10. Natural Language Translation Guide
|
|
1545
|
+
|
|
1546
|
+
### 10.1 Intent Keywords to Node Types
|
|
1547
|
+
|
|
1548
|
+
#### Data
|
|
1549
|
+
| Keywords | Node Type |
|
|
1550
|
+
|----------|-----------|
|
|
1551
|
+
| "static text", "constant", "fixed value" | `text` |
|
|
1552
|
+
| "list of", "multiple items", "array", "extract items" | `list` |
|
|
1553
|
+
| "table", "tabular data", "rows and columns" | `table` |
|
|
1554
|
+
| "file", "persistent", "save to file", "load file", "update file" | `file` |
|
|
1555
|
+
|
|
1556
|
+
#### Web
|
|
1557
|
+
| Keywords | Node Type |
|
|
1558
|
+
|----------|-----------|
|
|
1559
|
+
| "scrape", "extract from website", "web page" | `scraper` |
|
|
1560
|
+
| "search", "look up", "find online" | `search` |
|
|
1561
|
+
|
|
1562
|
+
#### AI Processing
|
|
1563
|
+
| Keywords | Node Type |
|
|
1564
|
+
|----------|-----------|
|
|
1565
|
+
| "summarize", "explain", "write", "generate text" | `prompt` |
|
|
1566
|
+
| "true/false", "yes/no", "decide if", "check if" | `boolean` |
|
|
1567
|
+
| "JSON", "structured data", "extract fields" | `json` |
|
|
1568
|
+
| "classify", "route", "which category" | `switch` |
|
|
1569
|
+
|
|
1570
|
+
#### Logic & Control
|
|
1571
|
+
| Keywords | Node Type |
|
|
1572
|
+
|----------|-----------|
|
|
1573
|
+
| "wait", "delay", "pause", "schedule" | `wait` |
|
|
1574
|
+
| "combine", "collect all", "merge results" | `aggregate` |
|
|
1575
|
+
|
|
1576
|
+
#### Actions
|
|
1577
|
+
| Keywords | Node Type |
|
|
1578
|
+
|----------|-----------|
|
|
1579
|
+
| "send email", "notify", "email" | `email` |
|
|
1580
|
+
| "API call", "HTTP request", "webhook", "fetch" | `http_request` |
|
|
1581
|
+
|
|
1582
|
+
#### Integrations
|
|
1583
|
+
| Keywords | Node Type |
|
|
1584
|
+
|----------|-----------|
|
|
1585
|
+
| "Google Drive", "files", "folders" | `google_drive` |
|
|
1586
|
+
|
|
1587
|
+
#### Media
|
|
1588
|
+
| Keywords | Node Type |
|
|
1589
|
+
|----------|-----------|
|
|
1590
|
+
| "transcribe", "audio to text", "speech" | `transcription` |
|
|
1591
|
+
| "generate image", "create picture", "DALL-E" | `image_generation` |
|
|
1592
|
+
|
|
1593
|
+
### 10.2 Pattern Recognition
|
|
1594
|
+
|
|
1595
|
+
#### "For each X, do Y"
|
|
1596
|
+
```
|
|
1597
|
+
list/table --[each]--> Y --> aggregate
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
#### "If X then A, else B"
|
|
1601
|
+
```
|
|
1602
|
+
boolean --[true]--> A
|
|
1603
|
+
\--[false]--> B
|
|
1604
|
+
```
|
|
1605
|
+
|
|
1606
|
+
#### "Scrape and summarize"
|
|
1607
|
+
```
|
|
1608
|
+
scraper --> prompt
|
|
1609
|
+
```
|
|
1610
|
+
|
|
1611
|
+
#### "Search and process"
|
|
1612
|
+
```
|
|
1613
|
+
search --> prompt
|
|
1614
|
+
```
|
|
1615
|
+
|
|
1616
|
+
### 10.3 Example Translations
|
|
1617
|
+
|
|
1618
|
+
| User Request | Workflow Pattern |
|
|
1619
|
+
|--------------|------------------|
|
|
1620
|
+
| "Scrape 10 URLs and summarize each" | list (URLs) --[each]--> scraper --> prompt --> aggregate |
|
|
1621
|
+
| "Send email if sentiment is positive" | text --> boolean --[true]--> email |
|
|
1622
|
+
| "Process each item in my list" | list --[each]--> prompt --> aggregate |
|
|
1623
|
+
| "Generate an image from a description" | text --> image_generation |
|
|
1624
|
+
| "Search web and extract key facts" | search --> json |
|
|
1625
|
+
| "Wait 5 minutes then send email" | text --> wait --> email |
|
|
1626
|
+
| "Analyze each row in my table" | table --[each]--> prompt --> aggregate |
|
|
1627
|
+
|
|
1628
|
+
---
|
|
1629
|
+
|
|
1630
|
+
## Appendix: Supported Models Reference
|
|
1631
|
+
|
|
1632
|
+
### OpenAI Models
|
|
1633
|
+
| Model ID | Description | Best For |
|
|
1634
|
+
|----------|-------------|----------|
|
|
1635
|
+
| `gpt-5.4` | Most capable | Complex reasoning, multi-step tool orchestration |
|
|
1636
|
+
| `gpt-5.4-mini` | Fast & efficient | General tasks (default) |
|
|
1637
|
+
| `gpt-5.4-nano` | Cheapest, fastest | Simple/no-tool prompts |
|
|
1638
|
+
|
|
1639
|
+
### Anthropic Models
|
|
1640
|
+
| Model ID | Description |
|
|
1641
|
+
|----------|-------------|
|
|
1642
|
+
| `claude-3-5-sonnet-20241022` | Balanced performance (default) |
|
|
1643
|
+
| `claude-3-5-haiku-20241022` | Fast responses |
|
|
1644
|
+
| `claude-3-opus-20240229` | Most capable |
|
|
1645
|
+
|
|
1646
|
+
### Google Models
|
|
1647
|
+
| Model ID | Description |
|
|
1648
|
+
|----------|-------------|
|
|
1649
|
+
| `gemini-1.5-pro` | Most capable |
|
|
1650
|
+
| `gemini-1.5-flash` | Fast (default) |
|
|
1651
|
+
| `gemini-2.0-flash-exp` | Latest fast (experimental) |
|
|
1652
|
+
|
|
1653
|
+
---
|
|
1654
|
+
|
|
1655
|
+
**End of Guide**
|