ai-workflows 2.1.3 → 2.3.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +8 -1
- package/README.md +2 -0
- package/dist/barrier.d.ts +6 -0
- package/dist/barrier.d.ts.map +1 -1
- package/dist/barrier.js +45 -7
- package/dist/barrier.js.map +1 -1
- package/dist/cascade-context.d.ts.map +1 -1
- package/dist/cascade-context.js +25 -25
- package/dist/cascade-context.js.map +1 -1
- package/dist/cascade-executor.d.ts.map +1 -1
- package/dist/cascade-executor.js +1 -1
- package/dist/cascade-executor.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +23 -7
- package/dist/context.js.map +1 -1
- package/dist/cron-parser.d.ts +65 -0
- package/dist/cron-parser.d.ts.map +1 -0
- package/dist/cron-parser.js +294 -0
- package/dist/cron-parser.js.map +1 -0
- package/dist/cron-scheduler.d.ts +117 -0
- package/dist/cron-scheduler.d.ts.map +1 -0
- package/dist/cron-scheduler.js +176 -0
- package/dist/cron-scheduler.js.map +1 -0
- package/dist/database-context.d.ts +184 -0
- package/dist/database-context.d.ts.map +1 -0
- package/dist/database-context.js +428 -0
- package/dist/database-context.js.map +1 -0
- package/dist/digital-objects-adapter.d.ts +159 -0
- package/dist/digital-objects-adapter.d.ts.map +1 -0
- package/dist/digital-objects-adapter.js +229 -0
- package/dist/digital-objects-adapter.js.map +1 -0
- package/dist/durable-execution-cloudflare.d.ts +427 -0
- package/dist/durable-execution-cloudflare.d.ts.map +1 -0
- package/dist/durable-execution-cloudflare.js +510 -0
- package/dist/durable-execution-cloudflare.js.map +1 -0
- package/dist/durable-execution.d.ts +482 -0
- package/dist/durable-execution.d.ts.map +1 -0
- package/dist/durable-execution.js +594 -0
- package/dist/durable-execution.js.map +1 -0
- package/dist/durable-workflow.d.ts +176 -0
- package/dist/durable-workflow.d.ts.map +1 -0
- package/dist/durable-workflow.js +552 -0
- package/dist/durable-workflow.js.map +1 -0
- package/dist/graph/topological-sort.d.ts.map +1 -1
- package/dist/graph/topological-sort.js +5 -5
- package/dist/graph/topological-sort.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +101 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +115 -0
- package/dist/logger.js.map +1 -0
- package/dist/on.d.ts.map +1 -1
- package/dist/on.js +3 -3
- package/dist/on.js.map +1 -1
- package/dist/runtime.d.ts +169 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +275 -0
- package/dist/runtime.js.map +1 -0
- package/dist/send.d.ts.map +1 -1
- package/dist/send.js +4 -3
- package/dist/send.js.map +1 -1
- package/dist/telemetry.d.ts +150 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +388 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/timer-registry.d.ts +25 -0
- package/dist/timer-registry.d.ts.map +1 -1
- package/dist/timer-registry.js +42 -8
- package/dist/timer-registry.js.map +1 -1
- package/dist/types.d.ts +17 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/worker/durable-step.d.ts +481 -0
- package/dist/worker/durable-step.d.ts.map +1 -0
- package/dist/worker/durable-step.js +606 -0
- package/dist/worker/durable-step.js.map +1 -0
- package/dist/worker/index.d.ts +106 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +124 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/state-adapter.d.ts +230 -0
- package/dist/worker/state-adapter.d.ts.map +1 -0
- package/dist/worker/state-adapter.js +409 -0
- package/dist/worker/state-adapter.js.map +1 -0
- package/dist/worker/topological-executor.d.ts +282 -0
- package/dist/worker/topological-executor.d.ts.map +1 -0
- package/dist/worker/topological-executor.js +396 -0
- package/dist/worker/topological-executor.js.map +1 -0
- package/dist/worker/workflow-builder.d.ts +286 -0
- package/dist/worker/workflow-builder.d.ts.map +1 -0
- package/dist/worker/workflow-builder.js +565 -0
- package/dist/worker/workflow-builder.js.map +1 -0
- package/dist/worker.d.ts +800 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +2428 -0
- package/dist/worker.js.map +1 -0
- package/dist/workflow-builder.d.ts +287 -0
- package/dist/workflow-builder.d.ts.map +1 -0
- package/dist/workflow-builder.js +762 -0
- package/dist/workflow-builder.js.map +1 -0
- package/dist/workflow.d.ts +14 -30
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +132 -292
- package/dist/workflow.js.map +1 -1
- package/examples/01-ecommerce-order-pipeline.ts +358 -0
- package/examples/02-content-moderation-cascade.ts +454 -0
- package/examples/03-scheduled-reporting-dependencies.ts +479 -0
- package/examples/04-database-persistence.ts +518 -0
- package/examples/README.md +173 -0
- package/package.json +30 -13
- package/src/__tests__/digital-objects-adapter.test.ts +274 -0
- package/src/__tests__/durable-workflow.test.ts +297 -0
- package/src/barrier.ts +48 -7
- package/src/cascade-context.ts +36 -29
- package/src/cascade-executor.ts +3 -2
- package/src/context.ts +41 -12
- package/src/cron-parser.ts +347 -0
- package/src/cron-scheduler.ts +239 -0
- package/src/database-context.ts +658 -0
- package/src/digital-objects-adapter.ts +351 -0
- package/src/durable-execution-cloudflare.ts +855 -0
- package/src/durable-execution.ts +1042 -0
- package/src/durable-workflow.ts +717 -0
- package/src/graph/topological-sort.ts +6 -8
- package/src/index.ts +69 -0
- package/src/logger.ts +148 -0
- package/src/on.ts +8 -9
- package/src/runtime.ts +436 -0
- package/src/send.ts +4 -5
- package/src/telemetry.ts +577 -0
- package/src/timer-registry.ts +44 -10
- package/src/types.ts +32 -17
- package/src/worker/durable-step.ts +976 -0
- package/src/worker/index.ts +216 -0
- package/src/worker/state-adapter.ts +589 -0
- package/src/worker/topological-executor.ts +625 -0
- package/src/worker/workflow-builder.ts +871 -0
- package/src/worker.ts +2906 -0
- package/src/workflow-builder.ts +1068 -0
- package/src/workflow.ts +188 -351
- package/test/barrier-join.test.ts +32 -24
- package/test/cascade-executor.test.ts +9 -16
- package/test/cron-parser.test.ts +314 -0
- package/test/cron-scheduler.test.ts +291 -0
- package/test/database-context.test.ts +770 -0
- package/test/db-provider-adapter.test.ts +862 -0
- package/test/durable-execution-cloudflare.test.ts +606 -0
- package/test/durable-execution-in-process.test.ts +286 -0
- package/test/durable-execution.test.ts +247 -0
- package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
- package/test/integration.test.ts +442 -0
- package/test/rpc-surface.test.ts +946 -0
- package/test/runtime.test.ts +262 -0
- package/test/schedule-timer-cleanup.test.ts +30 -21
- package/test/send-race-conditions.test.ts +30 -40
- package/test/worker/durable-cascade.test.ts +1117 -0
- package/test/worker/durable-step.test.ts +723 -0
- package/test/worker/topological-executor.test.ts +1240 -0
- package/test/worker/workflow-builder.test.ts +1067 -0
- package/test/worker.test.ts +608 -0
- package/test/workflow-builder.test.ts +1670 -0
- package/test/workflow-cron.test.ts +256 -0
- package/test/workflow-state-adapter.test.ts +923 -0
- package/test/workflow.test.ts +25 -22
- package/tsconfig.json +3 -1
- package/vitest.config.ts +38 -1
- package/vitest.workers.config.ts +44 -0
- package/wrangler.jsonc +22 -0
- package/.turbo/turbo-test.log +0 -169
- package/LICENSE +0 -21
- package/src/context.js +0 -83
- package/src/every.js +0 -267
- package/src/index.js +0 -71
- package/src/on.js +0 -79
- package/src/send.js +0 -111
- package/src/types.js +0 -4
- package/src/workflow.js +0 -455
- package/test/context.test.js +0 -116
- package/test/every.test.js +0 -282
- package/test/on.test.js +0 -80
- package/test/send.test.js +0 -89
- package/test/workflow.test.js +0 -224
- package/vitest.config.js +0 -7
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: AI Content Moderation with Human Escalation
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates the CascadeExecutor pattern for content moderation.
|
|
5
|
+
* Content is processed through escalating tiers:
|
|
6
|
+
*
|
|
7
|
+
* 1. Code (rules) - Fast keyword/pattern matching
|
|
8
|
+
* 2. Generative AI - LLM-based analysis for nuanced content
|
|
9
|
+
* 3. Agentic AI - Multi-step reasoning with tools
|
|
10
|
+
* 4. Human - Expert review for edge cases
|
|
11
|
+
*
|
|
12
|
+
* Key concepts demonstrated:
|
|
13
|
+
* - CascadeExecutor for tiered escalation
|
|
14
|
+
* - Per-tier timeouts and retry configuration
|
|
15
|
+
* - Skip conditions for tier bypassing
|
|
16
|
+
* - 5W+H audit trail for compliance
|
|
17
|
+
* - Integration with workflow events
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```bash
|
|
21
|
+
* npx tsx examples/02-content-moderation-cascade.ts
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
Workflow,
|
|
27
|
+
CascadeExecutor,
|
|
28
|
+
type TierContext,
|
|
29
|
+
type WorkflowContext,
|
|
30
|
+
TIER_ORDER,
|
|
31
|
+
} from '../dist/index.js'
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Type Definitions
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
interface ContentItem {
|
|
38
|
+
id: string
|
|
39
|
+
type: 'text' | 'image' | 'video'
|
|
40
|
+
content: string
|
|
41
|
+
metadata: {
|
|
42
|
+
userId: string
|
|
43
|
+
platform: string
|
|
44
|
+
timestamp: number
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface ModerationResult {
|
|
49
|
+
action: 'approve' | 'reject' | 'flag'
|
|
50
|
+
confidence: number
|
|
51
|
+
reason: string
|
|
52
|
+
categories?: string[]
|
|
53
|
+
requiresReview?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface HumanReviewTask {
|
|
57
|
+
contentId: string
|
|
58
|
+
content: string
|
|
59
|
+
aiAnalysis: string
|
|
60
|
+
priority: 'low' | 'medium' | 'high'
|
|
61
|
+
assignedTo?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Mock Services
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
// Blocked keywords for rule-based tier
|
|
69
|
+
const BLOCKED_KEYWORDS = ['spam', 'scam', 'hack', 'illegal', 'violence']
|
|
70
|
+
const SUSPICIOUS_PATTERNS = [
|
|
71
|
+
/\b(buy|sell)\s+followers\b/i,
|
|
72
|
+
/\b(free|win)\s+\$?\d+/i,
|
|
73
|
+
/click\s+here\s+now/i,
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
// Mock AI service
|
|
77
|
+
const aiService = {
|
|
78
|
+
async analyzeContent(content: string): Promise<{
|
|
79
|
+
isSafe: boolean
|
|
80
|
+
confidence: number
|
|
81
|
+
categories: string[]
|
|
82
|
+
explanation: string
|
|
83
|
+
}> {
|
|
84
|
+
// Simulate AI analysis with some latency
|
|
85
|
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
86
|
+
|
|
87
|
+
// Mock logic based on content length and keywords
|
|
88
|
+
const hasIssues = content.toLowerCase().includes('suspicious') || content.length > 500
|
|
89
|
+
const confidence = hasIssues ? 0.65 : 0.95
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
isSafe: !hasIssues,
|
|
93
|
+
confidence,
|
|
94
|
+
categories: hasIssues ? ['potentially_harmful'] : [],
|
|
95
|
+
explanation: hasIssues
|
|
96
|
+
? 'Content requires further analysis due to suspicious patterns'
|
|
97
|
+
: 'Content appears safe based on AI analysis',
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Mock agentic AI with tool access
|
|
103
|
+
const agenticService = {
|
|
104
|
+
async deepAnalysis(
|
|
105
|
+
content: string,
|
|
106
|
+
context: TierContext
|
|
107
|
+
): Promise<{
|
|
108
|
+
isSafe: boolean
|
|
109
|
+
confidence: number
|
|
110
|
+
reasoning: string[]
|
|
111
|
+
}> {
|
|
112
|
+
// Simulate multi-step reasoning
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
114
|
+
|
|
115
|
+
const reasoning = [
|
|
116
|
+
'Step 1: Analyzed semantic meaning of content',
|
|
117
|
+
'Step 2: Checked against known harmful patterns',
|
|
118
|
+
'Step 3: Verified user history and context',
|
|
119
|
+
'Step 4: Applied platform-specific policies',
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
const isSafe = !content.toLowerCase().includes('escalate')
|
|
123
|
+
const confidence = isSafe ? 0.92 : 0.45
|
|
124
|
+
|
|
125
|
+
return { isSafe, confidence, reasoning }
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Mock human review queue
|
|
130
|
+
const humanReviewQueue: HumanReviewTask[] = []
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Cascade Executor Configuration
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
function createModerationCascade() {
|
|
137
|
+
const events: Array<{ timestamp: number; event: string; data: unknown }> = []
|
|
138
|
+
|
|
139
|
+
const cascade = new CascadeExecutor<ModerationResult>({
|
|
140
|
+
cascadeName: 'content-moderation',
|
|
141
|
+
actor: 'moderation-system',
|
|
142
|
+
|
|
143
|
+
tiers: {
|
|
144
|
+
// Tier 1: Rule-based (fastest, deterministic)
|
|
145
|
+
code: {
|
|
146
|
+
name: 'rule-based-filter',
|
|
147
|
+
execute: async (input: unknown): Promise<ModerationResult> => {
|
|
148
|
+
const content = (input as ContentItem).content.toLowerCase()
|
|
149
|
+
|
|
150
|
+
// Check blocked keywords
|
|
151
|
+
for (const keyword of BLOCKED_KEYWORDS) {
|
|
152
|
+
if (content.includes(keyword)) {
|
|
153
|
+
return {
|
|
154
|
+
action: 'reject',
|
|
155
|
+
confidence: 1.0,
|
|
156
|
+
reason: `Contains blocked keyword: ${keyword}`,
|
|
157
|
+
categories: ['blocked_content'],
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check suspicious patterns
|
|
163
|
+
for (const pattern of SUSPICIOUS_PATTERNS) {
|
|
164
|
+
if (pattern.test(content)) {
|
|
165
|
+
return {
|
|
166
|
+
action: 'reject',
|
|
167
|
+
confidence: 0.95,
|
|
168
|
+
reason: 'Matches suspicious pattern',
|
|
169
|
+
categories: ['spam_pattern'],
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Quick approve obviously safe content
|
|
175
|
+
if (content.length < 50 && /^[a-z\s.,!?]+$/.test(content)) {
|
|
176
|
+
return {
|
|
177
|
+
action: 'approve',
|
|
178
|
+
confidence: 0.99,
|
|
179
|
+
reason: 'Simple safe content',
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Escalate to AI for more complex content
|
|
184
|
+
throw new Error('Content requires AI analysis')
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
// Tier 2: Generative AI (nuanced analysis)
|
|
189
|
+
generative: {
|
|
190
|
+
name: 'ai-content-analysis',
|
|
191
|
+
execute: async (input: unknown): Promise<ModerationResult> => {
|
|
192
|
+
const contentItem = input as ContentItem
|
|
193
|
+
const analysis = await aiService.analyzeContent(contentItem.content)
|
|
194
|
+
|
|
195
|
+
// High confidence safe - approve
|
|
196
|
+
if (analysis.isSafe && analysis.confidence > 0.9) {
|
|
197
|
+
return {
|
|
198
|
+
action: 'approve',
|
|
199
|
+
confidence: analysis.confidence,
|
|
200
|
+
reason: analysis.explanation,
|
|
201
|
+
categories: analysis.categories,
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// High confidence unsafe - reject
|
|
206
|
+
if (!analysis.isSafe && analysis.confidence > 0.9) {
|
|
207
|
+
return {
|
|
208
|
+
action: 'reject',
|
|
209
|
+
confidence: analysis.confidence,
|
|
210
|
+
reason: analysis.explanation,
|
|
211
|
+
categories: analysis.categories,
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Low confidence - escalate for deeper analysis
|
|
216
|
+
throw new Error(`Confidence too low (${analysis.confidence}) - needs deeper analysis`)
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
// Tier 3: Agentic AI (multi-step reasoning)
|
|
221
|
+
agentic: {
|
|
222
|
+
name: 'agentic-deep-analysis',
|
|
223
|
+
execute: async (input: unknown, context: TierContext): Promise<ModerationResult> => {
|
|
224
|
+
const contentItem = input as ContentItem
|
|
225
|
+
const analysis = await agenticService.deepAnalysis(contentItem.content, context)
|
|
226
|
+
|
|
227
|
+
console.log(' [Agentic] Reasoning steps:', analysis.reasoning)
|
|
228
|
+
|
|
229
|
+
// High confidence from agent
|
|
230
|
+
if (analysis.confidence > 0.85) {
|
|
231
|
+
return {
|
|
232
|
+
action: analysis.isSafe ? 'approve' : 'reject',
|
|
233
|
+
confidence: analysis.confidence,
|
|
234
|
+
reason: analysis.reasoning.join(' -> '),
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Still uncertain - needs human
|
|
239
|
+
throw new Error('Agent uncertain - requires human review')
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
// Tier 4: Human review (final decision)
|
|
244
|
+
human: {
|
|
245
|
+
name: 'human-review',
|
|
246
|
+
execute: async (input: unknown): Promise<ModerationResult> => {
|
|
247
|
+
const contentItem = input as ContentItem
|
|
248
|
+
|
|
249
|
+
// Create review task
|
|
250
|
+
const task: HumanReviewTask = {
|
|
251
|
+
contentId: contentItem.id,
|
|
252
|
+
content: contentItem.content,
|
|
253
|
+
aiAnalysis: 'AI was unable to reach high confidence decision',
|
|
254
|
+
priority: 'high',
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
humanReviewQueue.push(task)
|
|
258
|
+
console.log(` [Human] Created review task for content ${contentItem.id}`)
|
|
259
|
+
|
|
260
|
+
// For demo, simulate immediate human decision
|
|
261
|
+
return {
|
|
262
|
+
action: 'flag',
|
|
263
|
+
confidence: 1.0,
|
|
264
|
+
reason: 'Flagged for human review - decision pending',
|
|
265
|
+
requiresReview: true,
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
// Timeouts per tier
|
|
272
|
+
timeouts: {
|
|
273
|
+
code: 100, // 100ms for rules
|
|
274
|
+
generative: 5000, // 5s for AI
|
|
275
|
+
agentic: 30000, // 30s for agent
|
|
276
|
+
human: 3600000, // 1 hour for human
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// Retry configuration
|
|
280
|
+
retryConfig: {
|
|
281
|
+
generative: { maxRetries: 2, baseDelay: 100, multiplier: 2 },
|
|
282
|
+
agentic: { maxRetries: 1, baseDelay: 500 },
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
// Skip conditions
|
|
286
|
+
skipConditions: {
|
|
287
|
+
// Skip agentic for very short content
|
|
288
|
+
agentic: (input) => (input as ContentItem).content.length < 20,
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// Event callback for audit trail
|
|
292
|
+
onEvent: (event) => {
|
|
293
|
+
events.push({
|
|
294
|
+
timestamp: event.when,
|
|
295
|
+
event: event.what,
|
|
296
|
+
data: {
|
|
297
|
+
status: event.how.status,
|
|
298
|
+
duration: event.how.duration,
|
|
299
|
+
...(event.why && { reason: event.why }),
|
|
300
|
+
},
|
|
301
|
+
})
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
return { cascade, events, humanReviewQueue }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ============================================================================
|
|
309
|
+
// Workflow Integration
|
|
310
|
+
// ============================================================================
|
|
311
|
+
|
|
312
|
+
async function runModerationWorkflow() {
|
|
313
|
+
const { cascade, events, humanReviewQueue } = createModerationCascade()
|
|
314
|
+
|
|
315
|
+
const workflow = Workflow(($) => {
|
|
316
|
+
$.on.Content.submitted(async (content: ContentItem, $: WorkflowContext) => {
|
|
317
|
+
$.log(`Processing content ${content.id}`)
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const result = await cascade.execute(content)
|
|
321
|
+
|
|
322
|
+
$.log(`Moderation complete: ${result.tier} tier -> ${result.value.action}`)
|
|
323
|
+
|
|
324
|
+
// Emit result event
|
|
325
|
+
$.send('Content.moderated', {
|
|
326
|
+
contentId: content.id,
|
|
327
|
+
result: result.value,
|
|
328
|
+
resolvedBy: result.tier,
|
|
329
|
+
metrics: result.metrics,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
// Handle based on result
|
|
333
|
+
if (result.value.action === 'approve') {
|
|
334
|
+
$.send('Content.published', { contentId: content.id })
|
|
335
|
+
} else if (result.value.action === 'reject') {
|
|
336
|
+
$.send('Content.rejected', {
|
|
337
|
+
contentId: content.id,
|
|
338
|
+
reason: result.value.reason,
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
} catch (error) {
|
|
342
|
+
$.log(`Moderation failed: ${error}`)
|
|
343
|
+
$.send('Content.moderationFailed', {
|
|
344
|
+
contentId: content.id,
|
|
345
|
+
error: error instanceof Error ? error.message : String(error),
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
$.on.Content.published(async (data: { contentId: string }, $: WorkflowContext) => {
|
|
351
|
+
$.log(`Content ${data.contentId} published successfully`)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
$.on.Content.rejected(
|
|
355
|
+
async (data: { contentId: string; reason: string }, $: WorkflowContext) => {
|
|
356
|
+
$.log(`Content ${data.contentId} rejected: ${data.reason}`)
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
await workflow.start()
|
|
362
|
+
return { workflow, cascade, events, humanReviewQueue }
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ============================================================================
|
|
366
|
+
// Demo Execution
|
|
367
|
+
// ============================================================================
|
|
368
|
+
|
|
369
|
+
async function runDemo() {
|
|
370
|
+
console.log('='.repeat(60))
|
|
371
|
+
console.log('AI Content Moderation with Human Escalation Demo')
|
|
372
|
+
console.log('='.repeat(60))
|
|
373
|
+
console.log()
|
|
374
|
+
|
|
375
|
+
const { workflow, events } = await runModerationWorkflow()
|
|
376
|
+
|
|
377
|
+
// Test cases demonstrating different tiers
|
|
378
|
+
const testContent: ContentItem[] = [
|
|
379
|
+
// Case 1: Blocked keyword - handled by code tier
|
|
380
|
+
{
|
|
381
|
+
id: 'content_001',
|
|
382
|
+
type: 'text',
|
|
383
|
+
content: 'Check out this amazing spam offer!',
|
|
384
|
+
metadata: { userId: 'user_1', platform: 'web', timestamp: Date.now() },
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
// Case 2: Simple safe content - handled by code tier
|
|
388
|
+
{
|
|
389
|
+
id: 'content_002',
|
|
390
|
+
type: 'text',
|
|
391
|
+
content: 'Hello, how are you today?',
|
|
392
|
+
metadata: { userId: 'user_2', platform: 'mobile', timestamp: Date.now() },
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
// Case 3: Complex content - needs AI analysis
|
|
396
|
+
{
|
|
397
|
+
id: 'content_003',
|
|
398
|
+
type: 'text',
|
|
399
|
+
content:
|
|
400
|
+
'This is a longer piece of content that discusses various topics in a nuanced way. ' +
|
|
401
|
+
'It requires deeper analysis to determine if it meets community guidelines.',
|
|
402
|
+
metadata: { userId: 'user_3', platform: 'web', timestamp: Date.now() },
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
// Case 4: Suspicious content - may need agent
|
|
406
|
+
{
|
|
407
|
+
id: 'content_004',
|
|
408
|
+
type: 'text',
|
|
409
|
+
content:
|
|
410
|
+
'This content has suspicious elements that the AI flagged but cannot ' +
|
|
411
|
+
'determine with high confidence. It requires multi-step reasoning.',
|
|
412
|
+
metadata: { userId: 'user_4', platform: 'web', timestamp: Date.now() },
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
// Case 5: Needs escalation to human
|
|
416
|
+
{
|
|
417
|
+
id: 'content_005',
|
|
418
|
+
type: 'text',
|
|
419
|
+
content:
|
|
420
|
+
'This complex content contains patterns that need to escalate ' +
|
|
421
|
+
'all the way to human review for a final decision.',
|
|
422
|
+
metadata: { userId: 'user_5', platform: 'web', timestamp: Date.now() },
|
|
423
|
+
},
|
|
424
|
+
]
|
|
425
|
+
|
|
426
|
+
// Process each content item
|
|
427
|
+
for (const content of testContent) {
|
|
428
|
+
console.log('-'.repeat(60))
|
|
429
|
+
console.log(`Processing: ${content.id}`)
|
|
430
|
+
console.log(`Content: "${content.content.substring(0, 50)}..."`)
|
|
431
|
+
console.log()
|
|
432
|
+
|
|
433
|
+
await workflow.send('Content.submitted', content)
|
|
434
|
+
|
|
435
|
+
// Small delay between items
|
|
436
|
+
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Display audit trail
|
|
440
|
+
console.log()
|
|
441
|
+
console.log('='.repeat(60))
|
|
442
|
+
console.log('Audit Trail (5W+H Events):')
|
|
443
|
+
console.log('='.repeat(60))
|
|
444
|
+
for (const event of events.slice(-10)) {
|
|
445
|
+
console.log(` ${new Date(event.timestamp).toISOString()} | ${event.event}`)
|
|
446
|
+
console.log(` Data: ${JSON.stringify(event.data)}`)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Clean up
|
|
450
|
+
await workflow.stop()
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Run if executed directly
|
|
454
|
+
runDemo().catch(console.error)
|