flowcraft 1.0.0-beta.1

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.
Files changed (148) hide show
  1. package/.editorconfig +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +249 -0
  4. package/config/tsconfig.json +21 -0
  5. package/config/tsup.config.ts +11 -0
  6. package/config/vitest.config.ts +11 -0
  7. package/docs/.vitepress/config.ts +105 -0
  8. package/docs/api-reference/builder.md +158 -0
  9. package/docs/api-reference/fn.md +142 -0
  10. package/docs/api-reference/index.md +38 -0
  11. package/docs/api-reference/workflow.md +126 -0
  12. package/docs/guide/advanced-guides/cancellation.md +117 -0
  13. package/docs/guide/advanced-guides/composition.md +68 -0
  14. package/docs/guide/advanced-guides/custom-executor.md +180 -0
  15. package/docs/guide/advanced-guides/error-handling.md +135 -0
  16. package/docs/guide/advanced-guides/logging.md +106 -0
  17. package/docs/guide/advanced-guides/middleware.md +106 -0
  18. package/docs/guide/advanced-guides/observability.md +175 -0
  19. package/docs/guide/best-practices/debugging.md +182 -0
  20. package/docs/guide/best-practices/state-management.md +120 -0
  21. package/docs/guide/best-practices/sub-workflow-data.md +95 -0
  22. package/docs/guide/best-practices/testing.md +187 -0
  23. package/docs/guide/builders.md +157 -0
  24. package/docs/guide/functional-api.md +133 -0
  25. package/docs/guide/index.md +178 -0
  26. package/docs/guide/recipes/creating-a-loop.md +113 -0
  27. package/docs/guide/recipes/data-processing-pipeline.md +123 -0
  28. package/docs/guide/recipes/fan-out-fan-in.md +112 -0
  29. package/docs/guide/recipes/index.md +15 -0
  30. package/docs/guide/recipes/resilient-api-call.md +110 -0
  31. package/docs/guide/tooling/graph-validation.md +160 -0
  32. package/docs/guide/tooling/mermaid.md +156 -0
  33. package/docs/index.md +56 -0
  34. package/eslint.config.js +16 -0
  35. package/package.json +40 -0
  36. package/pnpm-workspace.yaml +2 -0
  37. package/sandbox/1.basic/README.md +45 -0
  38. package/sandbox/1.basic/package.json +16 -0
  39. package/sandbox/1.basic/src/flow.ts +17 -0
  40. package/sandbox/1.basic/src/main.ts +22 -0
  41. package/sandbox/1.basic/src/nodes.ts +112 -0
  42. package/sandbox/1.basic/src/utils.ts +35 -0
  43. package/sandbox/1.basic/tsconfig.json +3 -0
  44. package/sandbox/2.research/README.md +46 -0
  45. package/sandbox/2.research/package.json +16 -0
  46. package/sandbox/2.research/src/flow.ts +14 -0
  47. package/sandbox/2.research/src/main.ts +31 -0
  48. package/sandbox/2.research/src/nodes.ts +108 -0
  49. package/sandbox/2.research/src/utils.ts +45 -0
  50. package/sandbox/2.research/src/visualize.ts +29 -0
  51. package/sandbox/2.research/tsconfig.json +3 -0
  52. package/sandbox/3.parallel/README.md +65 -0
  53. package/sandbox/3.parallel/package.json +16 -0
  54. package/sandbox/3.parallel/src/main.ts +45 -0
  55. package/sandbox/3.parallel/src/nodes.ts +43 -0
  56. package/sandbox/3.parallel/src/utils.ts +25 -0
  57. package/sandbox/3.parallel/tsconfig.json +3 -0
  58. package/sandbox/4.dag/README.md +179 -0
  59. package/sandbox/4.dag/data/1.blog-post/100.json +60 -0
  60. package/sandbox/4.dag/data/1.blog-post/README.md +25 -0
  61. package/sandbox/4.dag/data/2.job-application/200.json +103 -0
  62. package/sandbox/4.dag/data/2.job-application/201.json +31 -0
  63. package/sandbox/4.dag/data/2.job-application/202.json +31 -0
  64. package/sandbox/4.dag/data/2.job-application/README.md +58 -0
  65. package/sandbox/4.dag/data/3.customer-review/300.json +141 -0
  66. package/sandbox/4.dag/data/3.customer-review/301.json +31 -0
  67. package/sandbox/4.dag/data/3.customer-review/302.json +28 -0
  68. package/sandbox/4.dag/data/3.customer-review/README.md +71 -0
  69. package/sandbox/4.dag/data/4.content-moderation/400.json +161 -0
  70. package/sandbox/4.dag/data/4.content-moderation/401.json +47 -0
  71. package/sandbox/4.dag/data/4.content-moderation/402.json +46 -0
  72. package/sandbox/4.dag/data/4.content-moderation/403.json +31 -0
  73. package/sandbox/4.dag/data/4.content-moderation/README.md +83 -0
  74. package/sandbox/4.dag/package.json +19 -0
  75. package/sandbox/4.dag/src/main.ts +73 -0
  76. package/sandbox/4.dag/src/nodes.ts +134 -0
  77. package/sandbox/4.dag/src/registry.ts +87 -0
  78. package/sandbox/4.dag/src/types.ts +25 -0
  79. package/sandbox/4.dag/src/utils.ts +42 -0
  80. package/sandbox/4.dag/tsconfig.json +3 -0
  81. package/sandbox/5.distributed/.env.example +1 -0
  82. package/sandbox/5.distributed/README.md +88 -0
  83. package/sandbox/5.distributed/data/1.blog-post/100.json +59 -0
  84. package/sandbox/5.distributed/data/1.blog-post/README.md +25 -0
  85. package/sandbox/5.distributed/data/2.job-application/200.json +103 -0
  86. package/sandbox/5.distributed/data/2.job-application/201.json +30 -0
  87. package/sandbox/5.distributed/data/2.job-application/202.json +30 -0
  88. package/sandbox/5.distributed/data/2.job-application/README.md +58 -0
  89. package/sandbox/5.distributed/data/3.customer-review/300.json +141 -0
  90. package/sandbox/5.distributed/data/3.customer-review/301.json +31 -0
  91. package/sandbox/5.distributed/data/3.customer-review/302.json +57 -0
  92. package/sandbox/5.distributed/data/3.customer-review/README.md +71 -0
  93. package/sandbox/5.distributed/data/4.content-moderation/400.json +173 -0
  94. package/sandbox/5.distributed/data/4.content-moderation/401.json +47 -0
  95. package/sandbox/5.distributed/data/4.content-moderation/402.json +46 -0
  96. package/sandbox/5.distributed/data/4.content-moderation/403.json +31 -0
  97. package/sandbox/5.distributed/data/4.content-moderation/README.md +83 -0
  98. package/sandbox/5.distributed/package.json +20 -0
  99. package/sandbox/5.distributed/src/client.ts +124 -0
  100. package/sandbox/5.distributed/src/executor.ts +69 -0
  101. package/sandbox/5.distributed/src/nodes.ts +136 -0
  102. package/sandbox/5.distributed/src/registry.ts +101 -0
  103. package/sandbox/5.distributed/src/types.ts +45 -0
  104. package/sandbox/5.distributed/src/utils.ts +69 -0
  105. package/sandbox/5.distributed/src/worker.ts +217 -0
  106. package/sandbox/5.distributed/tsconfig.json +3 -0
  107. package/sandbox/6.rag/.env.example +1 -0
  108. package/sandbox/6.rag/README.md +60 -0
  109. package/sandbox/6.rag/data/README.md +31 -0
  110. package/sandbox/6.rag/data/rag.json +58 -0
  111. package/sandbox/6.rag/documents/sample-cascade.txt +11 -0
  112. package/sandbox/6.rag/package.json +18 -0
  113. package/sandbox/6.rag/src/main.ts +52 -0
  114. package/sandbox/6.rag/src/nodes/GenerateEmbeddingsNode.ts +54 -0
  115. package/sandbox/6.rag/src/nodes/LLMProcessNode.ts +48 -0
  116. package/sandbox/6.rag/src/nodes/LoadAndChunkNode.ts +40 -0
  117. package/sandbox/6.rag/src/nodes/StoreInVectorDBNode.ts +36 -0
  118. package/sandbox/6.rag/src/nodes/VectorSearchNode.ts +53 -0
  119. package/sandbox/6.rag/src/nodes/index.ts +28 -0
  120. package/sandbox/6.rag/src/registry.ts +23 -0
  121. package/sandbox/6.rag/src/types.ts +44 -0
  122. package/sandbox/6.rag/src/utils.ts +77 -0
  123. package/sandbox/6.rag/tsconfig.json +3 -0
  124. package/sandbox/tsconfig.json +13 -0
  125. package/src/builder/collection.test.ts +287 -0
  126. package/src/builder/collection.ts +269 -0
  127. package/src/builder/graph.test.ts +406 -0
  128. package/src/builder/graph.ts +336 -0
  129. package/src/builder/graph.types.ts +104 -0
  130. package/src/builder/index.ts +3 -0
  131. package/src/context.ts +111 -0
  132. package/src/errors.ts +34 -0
  133. package/src/executor.ts +29 -0
  134. package/src/executors/in-memory.test.ts +93 -0
  135. package/src/executors/in-memory.ts +140 -0
  136. package/src/functions.test.ts +191 -0
  137. package/src/functions.ts +117 -0
  138. package/src/index.ts +5 -0
  139. package/src/logger.ts +41 -0
  140. package/src/types.ts +75 -0
  141. package/src/utils/graph.test.ts +144 -0
  142. package/src/utils/graph.ts +182 -0
  143. package/src/utils/index.ts +3 -0
  144. package/src/utils/mermaid.test.ts +239 -0
  145. package/src/utils/mermaid.ts +133 -0
  146. package/src/utils/sleep.ts +20 -0
  147. package/src/workflow.test.ts +622 -0
  148. package/src/workflow.ts +561 -0
@@ -0,0 +1,65 @@
1
+ # Parallel Batch Translation
2
+
3
+ This project demonstrates using `flowcraft`'s `ParallelBatchFlow` to translate a document into multiple languages concurrently, showcasing significant performance improvements for I/O-bound tasks.
4
+
5
+ ## Goal
6
+
7
+ Translate a source `README.md` file into multiple languages (Chinese, Spanish, etc.) in parallel and save each translation to the `translations/` directory.
8
+
9
+ ## How to Run
10
+
11
+ 1. **Install dependencies**:
12
+
13
+ ```bash
14
+ npm install
15
+ ```
16
+
17
+ 2. **Set your OpenAI API key**:
18
+ Create a `.env` file in this directory:
19
+
20
+ ```
21
+ OPENAI_API_KEY="your-api-key-here"
22
+ ```
23
+
24
+ 3. **Run the translation process**:
25
+
26
+ ```bash
27
+ npm start
28
+ ```
29
+
30
+ ## How It Works
31
+
32
+ The implementation uses an `ParallelBatchFlow` to process translation requests for all languages at the same time.
33
+
34
+ ```mermaid
35
+ graph TD
36
+ A[Load README.md] --> B["ParallelBatchFlow"]
37
+ subgraph "Concurrent Translation"
38
+ B -- "Chinese" --> T1[TranslateNode]
39
+ B -- "Spanish" --> T2[TranslateNode]
40
+ B -- "Japanese" --> T3[TranslateNode]
41
+ B -- "German" --> T4[TranslateNode]
42
+ end
43
+ T1 --> S1[Save Chinese.md]
44
+ T2 --> S2[Save Spanish.md]
45
+ T3 --> S3[Save Japanese.md]
46
+ T4 --> S4[Save German.md]
47
+ ```
48
+
49
+ 1. **`TranslateFlow` (extends `ParallelBatchFlow`)**: The `prep` method prepares a list of parameters, one for each language.
50
+
51
+ ```typescript
52
+ // prep returns: [{ language: 'Chinese' }, { language: 'Spanish' }, ...]
53
+ ```
54
+
55
+ 2. **`TranslateNode` (extends `Node`)**: This node is executed in parallel for each set of parameters. Its `exec` method calls the LLM for a single translation.
56
+ 3. **File I/O**: The results are written to disk asynchronously.
57
+
58
+ ## Performance Comparison
59
+
60
+ Running translations in parallel dramatically reduces the total execution time compared to a one-by-one sequential approach.
61
+
62
+ - **Sequential**: ~60 seconds
63
+ - **Parallel (this example)**: ~10 seconds
64
+
65
+ *(Actual times will vary based on API response speed and system.)*
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "parallel-workflow",
3
+ "type": "module",
4
+ "scripts": {
5
+ "start": "npx tsx src/main.ts"
6
+ },
7
+ "dependencies": {
8
+ "dotenv": "^16.4.5",
9
+ "flowcraft": "workspace:*",
10
+ "openai": "^4.52.7",
11
+ "yaml": "^2.5.0"
12
+ },
13
+ "devDependencies": {
14
+ "typescript": "^5.5.4"
15
+ }
16
+ }
@@ -0,0 +1,45 @@
1
+ import type { Context } from 'flowcraft'
2
+ import * as fs from 'node:fs/promises'
3
+ import * as path from 'node:path'
4
+ import process from 'node:process'
5
+ import dotenv from 'dotenv'
6
+ import { ConsoleLogger, TypedContext } from 'flowcraft'
7
+ import { TranslateFlow } from './nodes'
8
+
9
+ dotenv.config()
10
+
11
+ async function main() {
12
+ const sourceReadmePath = path.resolve(process.cwd(), '../../README.md')
13
+ const outputDir = path.resolve(process.cwd(), 'translations')
14
+ await fs.mkdir(outputDir, { recursive: true })
15
+
16
+ const text = await fs.readFile(sourceReadmePath, 'utf-8')
17
+ const languages = [
18
+ 'Chinese',
19
+ 'Spanish',
20
+ 'Japanese',
21
+ 'German',
22
+ // 'Russian',
23
+ // 'Portuguese',
24
+ // 'French',
25
+ // 'Korean',
26
+ ]
27
+
28
+ const context: Context = new TypedContext([
29
+ ['text', text],
30
+ ['languages', languages],
31
+ ['output_dir', outputDir],
32
+ ])
33
+
34
+ const flow = new TranslateFlow()
35
+ console.log(`Starting parallel translation into ${languages.length} languages...`)
36
+ const startTime = Date.now()
37
+
38
+ await flow.run(context, { logger: new ConsoleLogger() })
39
+
40
+ const duration = (Date.now() - startTime) / 1000
41
+ console.log(`\nTotal parallel translation time: ${duration.toFixed(2)} seconds`)
42
+ console.log('\n=== Translation Complete ===')
43
+ }
44
+
45
+ main().catch(console.error)
@@ -0,0 +1,43 @@
1
+ import type { AbstractNode, NodeArgs } from 'flowcraft'
2
+ import * as fs from 'node:fs/promises'
3
+ import * as path from 'node:path'
4
+ import { Node, ParallelBatchFlow } from 'flowcraft'
5
+ import { callLLM } from './utils'
6
+
7
+ export class TranslateNode extends Node<void, { language: string, translation: string }> {
8
+ async exec({ params }: NodeArgs) {
9
+ const { text, language } = params
10
+ const prompt = `
11
+ Translate the following markdown text into ${language}.
12
+ Preserve markdown formatting, links, and code blocks.
13
+ Return only the translated text.
14
+
15
+ Original Text:
16
+ ${text}`
17
+ console.log(`Translating to ${language}...`)
18
+ const translation = await callLLM(prompt)
19
+ console.log(`✓ Finished ${language}`)
20
+ return { language, translation }
21
+ }
22
+
23
+ async post({ ctx, execRes }: NodeArgs<void, { language: string, translation: string }>) {
24
+ const { language, translation } = execRes
25
+ const outputDir = ctx.get('output_dir')!
26
+ const filename = path.join(outputDir, `README_${language.toUpperCase()}.md`)
27
+ await fs.writeFile(filename, translation, 'utf-8')
28
+ console.log(`Saved translation to ${filename}`)
29
+ }
30
+ }
31
+
32
+ export class TranslateFlow extends ParallelBatchFlow {
33
+ protected nodeToRun: AbstractNode = new TranslateNode()
34
+
35
+ async prep({ ctx }: NodeArgs): Promise<any[]> {
36
+ const languages = ctx.get('languages')!
37
+ const text = ctx.get('text')
38
+ return languages.map((language: string) => ({
39
+ language,
40
+ text,
41
+ }))
42
+ }
43
+ }
@@ -0,0 +1,25 @@
1
+ import process from 'node:process'
2
+ import OpenAI from 'openai'
3
+
4
+ const client = new OpenAI({
5
+ apiKey: process.env.OPENAI_API_KEY,
6
+ })
7
+
8
+ /**
9
+ * Asynchronously calls the OpenAI Chat Completions API.
10
+ * @param prompt The user prompt to send to the LLM.
11
+ * @returns The content of the LLM's response as a string.
12
+ */
13
+ export async function callLLM(prompt: string): Promise<string> {
14
+ try {
15
+ const response = await client.chat.completions.create({
16
+ model: 'gpt-4o-mini',
17
+ messages: [{ role: 'user', content: prompt }],
18
+ })
19
+ return response.choices[0].message.content || ''
20
+ }
21
+ catch (error: any) {
22
+ console.error('Error calling OpenAI API:', error)
23
+ return `Error: Could not get a response from the LLM. Details: ${error.message}`
24
+ }
25
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../tsconfig.json"
3
+ }
@@ -0,0 +1,179 @@
1
+ # Dynamic AI Agent from Visual Graphs
2
+
3
+ This example demonstrates a runtime engine that can execute complex, graph-based AI workflows defined as simple JSON files. It showcases how to build a powerful AI agent that can reason, branch, and call other workflows recursively using the `workflow` framework.
4
+
5
+ The system is designed to be highly modular, allowing you to define different use-cases (e.g., customer support, content creation, HR) by simply adding new workflow graphs, without changing the core application code.
6
+
7
+ ## Features
8
+
9
+ - **File-Based Workflow Definitions**: Define complex workflows as simple JSON files. This decouples the logic from the code, allowing for easy creation, modification, and management of flows.
10
+ - **Enhanced DAG Support**: The engine automatically detects both parallel start points and **parallel branching from any node in the graph (fan-out)**. This allows for complex, non-linear workflows where a single condition can trigger multiple independent paths to run concurrently.
11
+ - **LLM-Powered Logic**: Utilizes generic, reusable nodes that call a Large Language Model (LLM) for sophisticated tasks:
12
+ - `llm-process`: For content generation, summarization, and data extraction.
13
+ - `llm-condition`: For natural language-based conditional branching (`true`/`false`).
14
+ - `llm-router`: For dynamic, multi-way branching based on an LLM's decision.
15
+ - **Composable & Nested Workflows**: A `sub-workflow` node allows you to embed and reuse entire workflows within others, promoting modularity and DRY principles.
16
+ - **Robust Data Handling**: The framework seamlessly passes data between nodes and even across sub-workflow boundaries. It includes a pattern for safely aggregating outputs from different conditional branches.
17
+ - **Dynamic & Scalable**: A `WorkflowRegistry` dynamically loads all workflow definitions from a specified use-case directory on startup, making it easy to add or switch between different applications.
18
+
19
+ ## How to Run
20
+
21
+ 1. **Install dependencies**:
22
+
23
+ ```bash
24
+ npm install
25
+ ```
26
+
27
+ 2. **Set your OpenAI API key**:
28
+ Create a `.env` file in this project's root directory:
29
+
30
+ ```
31
+ OPENAI_API_KEY="your-api-key-here"
32
+ ```
33
+
34
+ 3. **Choose a Use-Case**:
35
+ Open `src/main.ts` and configure the `ACTIVE_USE_CASE` constant at the top of the file to select which example to run.
36
+
37
+ ```typescript
38
+ // src/main.ts
39
+ // --- CONFIGURATION ---
40
+ // Change this value to run different use-cases.
41
+ const ACTIVE_USE_CASE: UseCase = '1.blog-post'
42
+ // const ACTIVE_USE_CASE: UseCase = '2.job-application'
43
+ // const ACTIVE_USE_CASE: UseCase = '3.customer-review'
44
+ // const ACTIVE_USE_CASE: UseCase = '4.content-moderation'
45
+ ```
46
+
47
+ 4. **Run the application**:
48
+
49
+ ```bash
50
+ npm start
51
+ ```
52
+
53
+ ## How It Works
54
+
55
+ This example uses a `Registry -> Builder -> Executor` pattern to run workflows defined in the `data/` directory.
56
+
57
+ 1. **Workflow Definitions**: Workflows are defined as JSON files within a use-case-specific subdirectory (e.g., `data/customer-review/`). Each file is named with its ID (e.g., `300.json`) and contains a list of nodes and edges. Each subdirectory also contains a `README.md` with detailed information.
58
+
59
+ 2. **`WorkflowRegistry`**: On startup, the registry is initialized for a specific use-case directory. It reads all `.json` files, parses them into `WorkflowGraph` objects, and stores them in memory.
60
+
61
+ 3. **`FlowBuilder`**: When a flow is requested, the builder translates the declarative graph data (nodes and edges) into a connected graph of executable `Node` instances. It intelligently handles parallelism in two ways:
62
+ 1. It wraps multiple start nodes in a `ParallelNode` for an initial fan-out.
63
+ 2. More powerfully, if it detects that a single node's action (e.g., `'true'`) connects to multiple downstream nodes, it **dynamically inserts another `ParallelNode` to execute that branch concurrently**.
64
+
65
+ 4. **Generic Executor Nodes**: A small set of powerful, reusable nodes in `src/nodes.ts` provide the runtime logic. These nodes are configured entirely by the `data` field in the JSON definition, making them highly flexible.
66
+
67
+ ## Example Use-Cases
68
+
69
+ The `data` directory contains several examples, each demonstrating different capabilities of the framework.
70
+
71
+ ### Use-Case 1: Blog Post Generation (Sequential Flow)
72
+
73
+ A classic linear workflow for content creation, where the output of one step becomes the input for the next.
74
+
75
+ ```mermaid
76
+ graph TD
77
+ subgraph "Blog Post Generation (ID: 100)"
78
+ A[generate_outline] --> B[draft_post]
79
+ B --> C[suggest_titles]
80
+ C --> D[final_output]
81
+ end
82
+ ```
83
+
84
+ **Demonstrates**:
85
+
86
+ - **Sequential Processing**: A simple, powerful chain of nodes.
87
+ - **Content Generation Pipeline**: A common pattern for AI-powered content creation.
88
+ - **For more details, see [`data/1.blog-post/README.md`](./data/1.blog-post/README.md).**
89
+
90
+ ### Use-Case 2: Job Applicant Screener (DAG with Conditional Branching)
91
+
92
+ A workflow that analyzes a resume and cover letter in parallel, then makes a decision to call one of two sub-workflows.
93
+
94
+ ```mermaid
95
+ graph TD
96
+ subgraph "Job Application Screener (ID: 200)"
97
+ A(Resume) --> B[extract_skills]
98
+ C(Cover Letter) --> D[analyze_tone]
99
+ B --> E{check_qualifications}
100
+ D --> E
101
+ E -- true --> F["Sub-Workflow: Send Interview (201)"]
102
+ E -- false --> G["Sub-Workflow: Send Rejection (202)"]
103
+ F --> H[final_output]
104
+ G --> H
105
+ end
106
+ ```
107
+
108
+ **Demonstrates**:
109
+
110
+ - **Parallel Inputs**: Processing multiple independent data sources concurrently.
111
+ - **Conditional Branching**: Using an `llm-condition` node to direct the flow.
112
+ - **Sub-Workflows**: Composing flows by calling other, reusable workflows.
113
+ - **For more details, see [`data/2.job-application/README.md`](./data/2.job-application/README.md).**
114
+
115
+ ### Use-Case 3: Customer Review Analysis (DAG with Fan-Out)
116
+
117
+ An advanced workflow that runs initial processing in parallel, makes a conditional decision, and then demonstrates a **mid-flow fan-out**, where one node triggers multiple parallel downstream actions.
118
+
119
+ ```mermaid
120
+ graph TD
121
+ subgraph "Customer Review Analysis (ID: 300)"
122
+ A(Initial Review) --> B[summarize]
123
+ A --> C[categorize]
124
+ B --> D{check_sentiment}
125
+ C --> D
126
+ D -- true --> E["Sub-Workflow: Positive Reply (301)"]
127
+ D -- false --> F["Sub-Workflow: Create Ticket & Reply (302)"]
128
+
129
+ subgraph "Negative Path (Parallel Fan-Out)"
130
+ F --> G[send_to_ticketing_system]
131
+ F --> H[send_email_to_customer]
132
+ end
133
+
134
+ E --> Z[final_step]
135
+ G --> Z
136
+ H --> Z
137
+ end
138
+ ```
139
+
140
+ **Demonstrates**:
141
+
142
+ - **Mid-Flow Fan-Out**: A single node (`negative_branch`) triggers multiple concurrent downstream tasks.
143
+ - **Complex Sub-Workflows**: The sub-workflows themselves can have parallel steps.
144
+ - **Data Aggregation**: A final node gathers results from multiple, mutually exclusive branches.
145
+ - **For more details, see [`data/3.customer-review/README.md`](./data/3.customer-review/README.md).**
146
+
147
+ ### Use-Case 4: Content Moderation (Router Pattern)
148
+
149
+ A sophisticated workflow that performs multiple parallel checks on content and then uses a central **router** node to decide on one of several possible actions.
150
+
151
+ ```mermaid
152
+ graph TD
153
+ subgraph "Content Moderation (ID: 400)"
154
+ A(User Post) --> B[check_for_pii]
155
+ A --> C[check_for_hate_speech]
156
+ A --> D[check_for_spam]
157
+
158
+ B --> E{triage_post}
159
+ C --> E
160
+ D --> E
161
+
162
+ E -- action_ban --> F["Sub-Workflow: Ban User (401)"]
163
+ E -- action_redact --> G["Sub-Workflow: Redact Post (402)"]
164
+ E -- action_delete_spam --> H["Sub-Workflow: Delete Spam (403)"]
165
+ E -- action_approve --> I[approve_post_branch]
166
+
167
+ F --> Z[final_log]
168
+ G --> Z
169
+ H --> Z
170
+ I --> Z
171
+ end
172
+ ```
173
+
174
+ **Demonstrates**:
175
+
176
+ - **LLM-Powered Routing**: Using the `llm-router` node to implement a dynamic `switch` statement based on multiple inputs.
177
+ - **Multi-Way Branching**: Directing the flow to one of many distinct paths.
178
+ - **Scalable Moderation Logic**: New moderation rules or actions can be added by modifying the graph and prompts, not the core code.
179
+ - **For more details, see [`data/4.content-moderation/README.md`](./data/4.content-moderation/README.md).**
@@ -0,0 +1,60 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "id": "generate_outline",
5
+ "type": "llm-process",
6
+ "data": {
7
+ "promptTemplate": "Generate a detailed, multi-point blog post outline for the topic: \"{{topic}}\".",
8
+ "inputs": {
9
+ "topic": "topic"
10
+ }
11
+ }
12
+ },
13
+ {
14
+ "id": "draft_post",
15
+ "type": "llm-process",
16
+ "data": {
17
+ "promptTemplate": "Write a full-length, engaging blog post based on the following outline:\n\n{{outline}}",
18
+ "inputs": {
19
+ "outline": "generate_outline"
20
+ }
21
+ }
22
+ },
23
+ {
24
+ "id": "suggest_titles",
25
+ "type": "llm-process",
26
+ "data": {
27
+ "promptTemplate": "Suggest 5 catchy, SEO-friendly titles for the following blog post. Respond with a simple numbered list.\n\nPost:\n{{draft}}",
28
+ "inputs": {
29
+ "draft": "draft_post"
30
+ }
31
+ }
32
+ },
33
+ {
34
+ "id": "final_output",
35
+ "type": "output",
36
+ "data": {
37
+ "outputKey": "final_output",
38
+ "promptTemplate": "--- TITLES ---\n{{titles}}\n\n--- DRAFT ---\n{{draft}}",
39
+ "inputs": {
40
+ "titles": "suggest_titles",
41
+ "draft": "draft_post"
42
+ }
43
+ }
44
+ }
45
+ ],
46
+ "edges": [
47
+ {
48
+ "source": "generate_outline",
49
+ "target": "draft_post"
50
+ },
51
+ {
52
+ "source": "draft_post",
53
+ "target": "suggest_titles"
54
+ },
55
+ {
56
+ "source": "suggest_titles",
57
+ "target": "final_output"
58
+ }
59
+ ]
60
+ }
@@ -0,0 +1,25 @@
1
+ # Use Case 1: Blog Post Generation
2
+
3
+ This workflow demonstrates a simple, linear pipeline for content creation. It takes an initial topic and sequentially processes it through a series of steps to produce a finished blog post with title suggestions.
4
+
5
+ This is a classic example of how to chain nodes together to form a multi-step, sequential process where the output of one node becomes the input for the next.
6
+
7
+ ## Workflow ID: 100
8
+
9
+ ### Description
10
+
11
+ 1. **`generate_outline`**: Takes the initial `topic` and uses an LLM to generate a blog post outline.
12
+ 2. **`draft_post`**: Uses the generated outline to write a full-length blog post.
13
+ 3. **`suggest_titles`**: Analyzes the drafted post to suggest several catchy, SEO-friendly titles.
14
+ 4. **`final_output`**: Combines the suggested titles and the final draft into a single output string for review.
15
+
16
+ ### Visual Graph
17
+
18
+ ```mermaid
19
+ graph TD
20
+ subgraph "Blog Post Generation (ID: 100)"
21
+ A[generate_outline] --> B[draft_post]
22
+ B --> C[suggest_titles]
23
+ C --> D[final_output]
24
+ end
25
+ ```
@@ -0,0 +1,103 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "id": "extract_skills",
5
+ "type": "llm-process",
6
+ "data": {
7
+ "promptTemplate": "Extract a list of all technical skills from this resume. Format as a simple comma-separated string:\n\n{{resume}}",
8
+ "inputs": {
9
+ "resume": "resume"
10
+ }
11
+ }
12
+ },
13
+ {
14
+ "id": "analyze_tone",
15
+ "type": "llm-process",
16
+ "data": {
17
+ "promptTemplate": "Analyze the tone of this cover letter. Respond with a single word: \"professional\", \"casual\", or \"unprofessional\".\n\n{{coverLetter}}",
18
+ "inputs": {
19
+ "coverLetter": "coverLetter"
20
+ }
21
+ }
22
+ },
23
+ {
24
+ "id": "check_qualifications",
25
+ "type": "llm-condition",
26
+ "data": {
27
+ "promptTemplate": "Does this list of skills `{{skills}}` contain all of the following required skills: `TypeScript, Node.js, SQL`? Also, is the cover letter tone `{{tone}}` professional? Respond only 'true' if both conditions are met, otherwise 'false'.",
28
+ "inputs": {
29
+ "skills": "extract_skills",
30
+ "tone": "analyze_tone"
31
+ }
32
+ }
33
+ },
34
+ {
35
+ "id": "send_interview_email",
36
+ "type": "sub-workflow",
37
+ "data": {
38
+ "workflowId": 201,
39
+ "outputs": {
40
+ "send_interview_email": "final_output"
41
+ },
42
+ "inputs": {
43
+ "applicantName": "applicantName"
44
+ }
45
+ }
46
+ },
47
+ {
48
+ "id": "send_rejection_email",
49
+ "type": "sub-workflow",
50
+ "data": {
51
+ "workflowId": 202,
52
+ "outputs": {
53
+ "send_rejection_email": "final_output"
54
+ },
55
+ "inputs": {
56
+ "applicantName": "applicantName"
57
+ }
58
+ }
59
+ },
60
+ {
61
+ "id": "final_output",
62
+ "type": "output",
63
+ "data": {
64
+ "promptTemplate": "Final email sent to {{applicantName}}:\n\n{{email_body}}",
65
+ "inputs": {
66
+ "applicantName": "applicantName",
67
+ "email_body": [
68
+ "send_interview_email",
69
+ "send_rejection_email"
70
+ ]
71
+ }
72
+ }
73
+ }
74
+ ],
75
+ "edges": [
76
+ {
77
+ "source": "extract_skills",
78
+ "target": "check_qualifications"
79
+ },
80
+ {
81
+ "source": "analyze_tone",
82
+ "target": "check_qualifications"
83
+ },
84
+ {
85
+ "source": "check_qualifications",
86
+ "target": "send_interview_email",
87
+ "action": "true"
88
+ },
89
+ {
90
+ "source": "check_qualifications",
91
+ "target": "send_rejection_email",
92
+ "action": "false"
93
+ },
94
+ {
95
+ "source": "send_interview_email",
96
+ "target": "final_output"
97
+ },
98
+ {
99
+ "source": "send_rejection_email",
100
+ "target": "final_output"
101
+ }
102
+ ]
103
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "id": "gen_interview_email",
5
+ "type": "llm-process",
6
+ "data": {
7
+ "promptTemplate": "Write a polite and professional email inviting {{applicantName}} to an interview. Mention you were impressed with their resume.",
8
+ "inputs": {
9
+ "applicantName": "applicantName"
10
+ }
11
+ }
12
+ },
13
+ {
14
+ "id": "output_email",
15
+ "type": "output",
16
+ "data": {
17
+ "outputKey": "final_output",
18
+ "promptTemplate": "{{result}}",
19
+ "inputs": {
20
+ "result": "gen_interview_email"
21
+ }
22
+ }
23
+ }
24
+ ],
25
+ "edges": [
26
+ {
27
+ "source": "gen_interview_email",
28
+ "target": "output_email"
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "id": "gen_rejection_email",
5
+ "type": "llm-process",
6
+ "data": {
7
+ "promptTemplate": "Write a polite and professional email to {{applicantName}} informing them that you will not be moving forward with their application at this time. Thank them for their interest.",
8
+ "inputs": {
9
+ "applicantName": "applicantName"
10
+ }
11
+ }
12
+ },
13
+ {
14
+ "id": "output_email",
15
+ "type": "output",
16
+ "data": {
17
+ "outputKey": "final_output",
18
+ "promptTemplate": "{{result}}",
19
+ "inputs": {
20
+ "result": "gen_rejection_email"
21
+ }
22
+ }
23
+ }
24
+ ],
25
+ "edges": [
26
+ {
27
+ "source": "gen_rejection_email",
28
+ "target": "output_email"
29
+ }
30
+ ]
31
+ }