ai-functions 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 +55 -1
- package/README.md +38 -0
- package/dist/ai-promise.d.ts +3 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +135 -64
- package/dist/ai-promise.js.map +1 -1
- package/dist/ai-schemas.d.ts +56 -0
- package/dist/ai-schemas.d.ts.map +1 -0
- package/dist/ai-schemas.js +53 -0
- package/dist/ai-schemas.js.map +1 -0
- package/dist/ai.d.ts +16 -242
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +51 -858
- package/dist/ai.js.map +1 -1
- package/dist/batch/anthropic.d.ts +6 -4
- package/dist/batch/anthropic.d.ts.map +1 -1
- package/dist/batch/anthropic.js +83 -145
- package/dist/batch/anthropic.js.map +1 -1
- package/dist/batch/bedrock.d.ts +8 -30
- package/dist/batch/bedrock.d.ts.map +1 -1
- package/dist/batch/bedrock.js +155 -338
- package/dist/batch/bedrock.js.map +1 -1
- package/dist/batch/cloudflare.d.ts +8 -20
- package/dist/batch/cloudflare.d.ts.map +1 -1
- package/dist/batch/cloudflare.js +68 -189
- package/dist/batch/cloudflare.js.map +1 -1
- package/dist/batch/google.d.ts +6 -20
- package/dist/batch/google.d.ts.map +1 -1
- package/dist/batch/google.js +70 -238
- package/dist/batch/google.js.map +1 -1
- package/dist/batch/index.d.ts +4 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +4 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/memory.d.ts +1 -1
- package/dist/batch/memory.d.ts.map +1 -1
- package/dist/batch/memory.js +14 -10
- package/dist/batch/memory.js.map +1 -1
- package/dist/batch/openai.d.ts +11 -14
- package/dist/batch/openai.d.ts.map +1 -1
- package/dist/batch/openai.js +52 -156
- package/dist/batch/openai.js.map +1 -1
- package/dist/batch/provider.d.ts +111 -0
- package/dist/batch/provider.d.ts.map +1 -0
- package/dist/batch/provider.js +233 -0
- package/dist/batch/provider.js.map +1 -0
- package/dist/batch-map.d.ts.map +1 -1
- package/dist/batch-map.js +23 -17
- package/dist/batch-map.js.map +1 -1
- package/dist/batch-queue.d.ts +65 -0
- package/dist/batch-queue.d.ts.map +1 -1
- package/dist/batch-queue.js +169 -14
- package/dist/batch-queue.js.map +1 -1
- package/dist/budget.d.ts.map +1 -1
- package/dist/budget.js +27 -14
- package/dist/budget.js.map +1 -1
- package/dist/cache.d.ts +23 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +36 -15
- package/dist/cache.js.map +1 -1
- package/dist/context.d.ts +26 -8
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +64 -62
- package/dist/context.js.map +1 -1
- package/dist/digital-objects-registry.d.ts +229 -0
- package/dist/digital-objects-registry.d.ts.map +1 -0
- package/dist/digital-objects-registry.js +617 -0
- package/dist/digital-objects-registry.js.map +1 -0
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval/runner.d.ts +8 -0
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +41 -35
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval-log/in-memory.d.ts +34 -0
- package/dist/eval-log/in-memory.d.ts.map +1 -0
- package/dist/eval-log/in-memory.js +84 -0
- package/dist/eval-log/in-memory.js.map +1 -0
- package/dist/eval-log/index.d.ts +29 -0
- package/dist/eval-log/index.d.ts.map +1 -0
- package/dist/eval-log/index.js +39 -0
- package/dist/eval-log/index.js.map +1 -0
- package/dist/eval-log/types.d.ts +101 -0
- package/dist/eval-log/types.d.ts.map +1 -0
- package/dist/eval-log/types.js +16 -0
- package/dist/eval-log/types.js.map +1 -0
- package/dist/function-registry.d.ts +116 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +546 -0
- package/dist/function-registry.js.map +1 -0
- package/dist/generate.d.ts +9 -3
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +18 -18
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -18
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +118 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +187 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/budget.d.ts +84 -0
- package/dist/middleware/budget.d.ts.map +1 -0
- package/dist/middleware/budget.js +110 -0
- package/dist/middleware/budget.js.map +1 -0
- package/dist/middleware/cache.d.ts +103 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +228 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/embed-cache.d.ts +99 -0
- package/dist/middleware/embed-cache.d.ts.map +1 -0
- package/dist/middleware/embed-cache.js +128 -0
- package/dist/middleware/embed-cache.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/trace.d.ts +103 -0
- package/dist/middleware/trace.d.ts.map +1 -0
- package/dist/middleware/trace.js +176 -0
- package/dist/middleware/trace.js.map +1 -0
- package/dist/primitives.d.ts +120 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +398 -26
- package/dist/primitives.js.map +1 -1
- package/dist/retry.d.ts +66 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +115 -8
- package/dist/retry.js.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/telemetry.d.ts +128 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +285 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +6 -1
- package/dist/template.js.map +1 -1
- package/dist/tool-orchestration.d.ts +66 -4
- package/dist/tool-orchestration.d.ts.map +1 -1
- package/dist/tool-orchestration.js +123 -23
- package/dist/tool-orchestration.js.map +1 -1
- package/dist/type-guards.d.ts +28 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +29 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +135 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +36 -1
- package/dist/types.js.map +1 -1
- package/dist/wrap-for-v3.d.ts +80 -0
- package/dist/wrap-for-v3.d.ts.map +1 -0
- package/dist/wrap-for-v3.js +89 -0
- package/dist/wrap-for-v3.js.map +1 -0
- package/examples/00-quickstart.ts +232 -0
- package/examples/01-rag-chatbot.ts +212 -0
- package/examples/02-multi-agent-research.ts +290 -0
- package/examples/03-email-classification.ts +379 -0
- package/examples/04-content-moderation.ts +400 -0
- package/examples/05-document-extraction.ts +455 -0
- package/examples/06-streaming-chat-nextjs.ts +437 -0
- package/examples/07-cloudflare-worker.ts +483 -0
- package/examples/08-batch-processing.ts +491 -0
- package/examples/09-budget-constrained.ts +527 -0
- package/examples/10-tool-orchestration.ts +565 -0
- package/examples/11-retry-resilience.ts +403 -0
- package/examples/12-caching-strategies.ts +422 -0
- package/examples/README.md +145 -0
- package/package.json +28 -25
- package/src/ai-promise.ts +226 -140
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +69 -1176
- package/src/batch/anthropic.ts +96 -161
- package/src/batch/bedrock.ts +203 -454
- package/src/batch/cloudflare.ts +99 -282
- package/src/batch/google.ts +91 -297
- package/src/batch/index.ts +4 -1
- package/src/batch/memory.ts +15 -10
- package/src/batch/openai.ts +65 -193
- package/src/batch/provider.ts +336 -0
- package/src/batch-map.ts +29 -24
- package/src/batch-queue.ts +200 -11
- package/src/budget.ts +31 -18
- package/src/cache.ts +45 -17
- package/src/context.ts +106 -77
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +60 -36
- package/src/eval-log/in-memory.ts +90 -0
- package/src/eval-log/index.ts +46 -0
- package/src/eval-log/types.ts +110 -0
- package/src/function-registry.ts +671 -0
- package/src/generate.ts +33 -28
- package/src/index.ts +119 -21
- package/src/logger.ts +232 -0
- package/src/middleware/budget.ts +171 -0
- package/src/middleware/cache.ts +299 -0
- package/src/middleware/embed-cache.ts +195 -0
- package/src/middleware/index.ts +23 -0
- package/src/middleware/trace.ts +248 -0
- package/src/primitives.ts +589 -62
- package/src/retry.ts +144 -18
- package/src/schema.ts +8 -8
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +213 -48
- package/src/type-guards.ts +31 -0
- package/src/types.ts +164 -25
- package/src/wrap-for-v3.ts +105 -0
- package/test/ai-promise.test.ts +1080 -0
- package/test/ai-proxy.test.ts +1 -1
- package/test/batch-autosubmit-errors.test.ts +49 -37
- package/test/batch-blog-posts.test.ts +87 -129
- package/test/core-functions.test.ts +183 -579
- package/test/decide.test.ts +154 -322
- package/test/define.test.ts +211 -8
- package/test/digital-objects-registry.test.ts +760 -0
- package/test/embedding-cache-middleware.test.ts +140 -0
- package/test/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/schema.test.ts +55 -19
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +270 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/LICENSE +0 -21
- package/dist/rpc/auth.d.ts +0 -69
- package/dist/rpc/auth.d.ts.map +0 -1
- package/dist/rpc/auth.js +0 -136
- package/dist/rpc/auth.js.map +0 -1
- package/dist/rpc/client.d.ts +0 -62
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -103
- package/dist/rpc/client.js.map +0 -1
- package/dist/rpc/deferred.d.ts +0 -60
- package/dist/rpc/deferred.d.ts.map +0 -1
- package/dist/rpc/deferred.js +0 -96
- package/dist/rpc/deferred.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -22
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -38
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/local.d.ts +0 -42
- package/dist/rpc/local.d.ts.map +0 -1
- package/dist/rpc/local.js +0 -50
- package/dist/rpc/local.js.map +0 -1
- package/dist/rpc/server.d.ts +0 -165
- package/dist/rpc/server.d.ts.map +0 -1
- package/dist/rpc/server.js +0 -405
- package/dist/rpc/server.js.map +0 -1
- package/dist/rpc/session.d.ts +0 -32
- package/dist/rpc/session.d.ts.map +0 -1
- package/dist/rpc/session.js +0 -43
- package/dist/rpc/session.js.map +0 -1
- package/dist/rpc/transport.d.ts +0 -306
- package/dist/rpc/transport.d.ts.map +0 -1
- package/dist/rpc/transport.js +0 -731
- package/dist/rpc/transport.js.map +0 -1
- package/src/batch/anthropic.js +0 -256
- package/src/batch/bedrock.js +0 -584
- package/src/batch/cloudflare.js +0 -287
- package/src/batch/google.js +0 -359
- package/src/batch/index.js +0 -30
- package/src/batch/memory.js +0 -187
- package/src/batch/openai.js +0 -402
- package/src/eval/index.js +0 -7
- package/src/eval/models.js +0 -119
- package/src/eval/runner.js +0 -147
- package/test/schema.test.js +0 -96
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Moderation Pipeline Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates building a content moderation pipeline using ai-functions.
|
|
5
|
+
* It shows how to:
|
|
6
|
+
* - Check content for policy violations
|
|
7
|
+
* - Classify content severity
|
|
8
|
+
* - Handle edge cases with human review
|
|
9
|
+
* - Build a multi-stage moderation pipeline
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```bash
|
|
13
|
+
* ANTHROPIC_API_KEY=sk-... npx tsx examples/04-content-moderation.ts
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { ai, is, list, configure, withRetry, FallbackChain } from '../src/index.js'
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
interface ModerationResult {
|
|
24
|
+
contentId: string
|
|
25
|
+
decision: 'approved' | 'flagged' | 'rejected' | 'needs_review'
|
|
26
|
+
categories: ViolationCategory[]
|
|
27
|
+
severity: 'none' | 'low' | 'medium' | 'high' | 'severe'
|
|
28
|
+
confidence: number
|
|
29
|
+
reasoning: string
|
|
30
|
+
suggestedAction: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ViolationCategory {
|
|
34
|
+
category: string
|
|
35
|
+
detected: boolean
|
|
36
|
+
confidence: number
|
|
37
|
+
examples?: string[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ContentItem {
|
|
41
|
+
id: string
|
|
42
|
+
type: 'text' | 'comment' | 'review' | 'bio'
|
|
43
|
+
content: string
|
|
44
|
+
author?: string
|
|
45
|
+
context?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Sample Content
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
const sampleContent: ContentItem[] = [
|
|
53
|
+
{
|
|
54
|
+
id: 'content-1',
|
|
55
|
+
type: 'review',
|
|
56
|
+
content: 'Great product! Really helped with my workflow. Highly recommend to anyone.',
|
|
57
|
+
author: 'happy_user',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'content-2',
|
|
61
|
+
type: 'comment',
|
|
62
|
+
content: 'This is terrible! The developers are incompetent idiots who should be fired!',
|
|
63
|
+
author: 'angry_person',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'content-3',
|
|
67
|
+
type: 'bio',
|
|
68
|
+
content: 'Follow me on MyWebsite.com for free stuff! DM for exclusive deals!!!',
|
|
69
|
+
author: 'spammer_account',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'content-4',
|
|
73
|
+
type: 'review',
|
|
74
|
+
content: "I didn't like this product. It wasn't what I expected and the quality was poor.",
|
|
75
|
+
author: 'honest_reviewer',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'content-5',
|
|
79
|
+
type: 'comment',
|
|
80
|
+
content:
|
|
81
|
+
'Let me share some really helpful information: To solve this, try restarting the app and clearing cache.',
|
|
82
|
+
author: 'helpful_user',
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// Moderation Checks
|
|
88
|
+
// ============================================================================
|
|
89
|
+
|
|
90
|
+
const POLICY_CATEGORIES = [
|
|
91
|
+
'hate_speech',
|
|
92
|
+
'harassment',
|
|
93
|
+
'spam',
|
|
94
|
+
'personal_attacks',
|
|
95
|
+
'misinformation',
|
|
96
|
+
'self_promotion',
|
|
97
|
+
'profanity',
|
|
98
|
+
'threats',
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Quick initial check - fast binary decision
|
|
103
|
+
*/
|
|
104
|
+
async function quickCheck(content: ContentItem): Promise<boolean> {
|
|
105
|
+
return is`This content is clearly safe and requires no further moderation review:
|
|
106
|
+
"${content.content}"
|
|
107
|
+
|
|
108
|
+
Content is safe if it:
|
|
109
|
+
- Is constructive or helpful
|
|
110
|
+
- Contains no insults, threats, or harassment
|
|
111
|
+
- Is not spam or self-promotion
|
|
112
|
+
- Is appropriate for a general audience`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Detailed category analysis
|
|
117
|
+
*/
|
|
118
|
+
async function analyzeCategories(content: ContentItem): Promise<ViolationCategory[]> {
|
|
119
|
+
const results: ViolationCategory[] = []
|
|
120
|
+
|
|
121
|
+
// Check each category
|
|
122
|
+
for (const category of POLICY_CATEGORIES) {
|
|
123
|
+
const detected = await is`This content violates the "${category}" policy:
|
|
124
|
+
"${content.content}"
|
|
125
|
+
|
|
126
|
+
${getCategoryDescription(category)}`
|
|
127
|
+
|
|
128
|
+
results.push({
|
|
129
|
+
category,
|
|
130
|
+
detected,
|
|
131
|
+
confidence: detected ? 0.8 : 0.9, // Simplified confidence
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return results
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getCategoryDescription(category: string): string {
|
|
139
|
+
const descriptions: Record<string, string> = {
|
|
140
|
+
hate_speech: 'Content that attacks or demeans people based on protected characteristics',
|
|
141
|
+
harassment: 'Content that targets individuals with abuse, intimidation, or threats',
|
|
142
|
+
spam: 'Unsolicited promotional content, repetitive messages, or commercial solicitation',
|
|
143
|
+
personal_attacks: 'Direct insults or attacks on specific individuals',
|
|
144
|
+
misinformation: 'Demonstrably false information presented as fact',
|
|
145
|
+
self_promotion: 'Excessive self-promotion or advertising',
|
|
146
|
+
profanity: 'Explicit language or vulgar content',
|
|
147
|
+
threats: 'Threats of violence or harm to individuals or groups',
|
|
148
|
+
}
|
|
149
|
+
return descriptions[category] || 'Content that violates community guidelines'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Determine severity based on violations
|
|
154
|
+
*/
|
|
155
|
+
async function assessSeverity(
|
|
156
|
+
content: ContentItem,
|
|
157
|
+
violations: ViolationCategory[]
|
|
158
|
+
): Promise<{
|
|
159
|
+
severity: ModerationResult['severity']
|
|
160
|
+
reasoning: string
|
|
161
|
+
}> {
|
|
162
|
+
const detectedViolations = violations.filter((v) => v.detected)
|
|
163
|
+
|
|
164
|
+
if (detectedViolations.length === 0) {
|
|
165
|
+
return { severity: 'none', reasoning: 'No policy violations detected' }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const { severity, reasoning } = await ai`Assess the severity of these policy violations:
|
|
169
|
+
|
|
170
|
+
Content: "${content.content}"
|
|
171
|
+
|
|
172
|
+
Detected violations: ${detectedViolations.map((v) => v.category).join(', ')}
|
|
173
|
+
|
|
174
|
+
Provide:
|
|
175
|
+
- severity: one of [low, medium, high, severe] based on potential harm
|
|
176
|
+
- reasoning: brief explanation of the severity assessment`
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
severity: severity as ModerationResult['severity'],
|
|
180
|
+
reasoning: reasoning as string,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Determine final action
|
|
186
|
+
*/
|
|
187
|
+
async function determineAction(
|
|
188
|
+
severity: ModerationResult['severity'],
|
|
189
|
+
violations: ViolationCategory[]
|
|
190
|
+
): Promise<{
|
|
191
|
+
decision: ModerationResult['decision']
|
|
192
|
+
suggestedAction: string
|
|
193
|
+
}> {
|
|
194
|
+
const detectedViolations = violations.filter((v) => v.detected)
|
|
195
|
+
|
|
196
|
+
// Clear approval
|
|
197
|
+
if (severity === 'none') {
|
|
198
|
+
return {
|
|
199
|
+
decision: 'approved',
|
|
200
|
+
suggestedAction: 'No action needed - content is appropriate',
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Auto-reject severe violations
|
|
205
|
+
if (severity === 'severe') {
|
|
206
|
+
return {
|
|
207
|
+
decision: 'rejected',
|
|
208
|
+
suggestedAction: 'Remove content and warn user account',
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// High severity needs review
|
|
213
|
+
if (severity === 'high') {
|
|
214
|
+
return {
|
|
215
|
+
decision: 'needs_review',
|
|
216
|
+
suggestedAction: 'Escalate to human moderator for final decision',
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Medium/low - flag for monitoring
|
|
221
|
+
const { action } = await ai`Recommend an action for content with ${severity} severity violations:
|
|
222
|
+
Violations: ${detectedViolations.map((v) => v.category).join(', ')}
|
|
223
|
+
|
|
224
|
+
Provide a brief action recommendation.`
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
decision: 'flagged',
|
|
228
|
+
suggestedAction: action as string,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ============================================================================
|
|
233
|
+
// Moderation Pipeline
|
|
234
|
+
// ============================================================================
|
|
235
|
+
|
|
236
|
+
async function moderateContent(content: ContentItem): Promise<ModerationResult> {
|
|
237
|
+
console.log(`\nModerating [${content.id}]: "${content.content.substring(0, 40)}..."`)
|
|
238
|
+
|
|
239
|
+
// Stage 1: Quick check
|
|
240
|
+
const isClearlySafe = await quickCheck(content)
|
|
241
|
+
console.log(` Quick check: ${isClearlySafe ? 'Safe' : 'Needs analysis'}`)
|
|
242
|
+
|
|
243
|
+
if (isClearlySafe) {
|
|
244
|
+
return {
|
|
245
|
+
contentId: content.id,
|
|
246
|
+
decision: 'approved',
|
|
247
|
+
categories: POLICY_CATEGORIES.map((c) => ({
|
|
248
|
+
category: c,
|
|
249
|
+
detected: false,
|
|
250
|
+
confidence: 0.95,
|
|
251
|
+
})),
|
|
252
|
+
severity: 'none',
|
|
253
|
+
confidence: 0.95,
|
|
254
|
+
reasoning: 'Content passed quick safety check',
|
|
255
|
+
suggestedAction: 'No action needed',
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Stage 2: Detailed category analysis
|
|
260
|
+
const violations = await analyzeCategories(content)
|
|
261
|
+
const detectedCount = violations.filter((v) => v.detected).length
|
|
262
|
+
console.log(` Category analysis: ${detectedCount} violations detected`)
|
|
263
|
+
|
|
264
|
+
// Stage 3: Severity assessment
|
|
265
|
+
const { severity, reasoning } = await assessSeverity(content, violations)
|
|
266
|
+
console.log(` Severity: ${severity}`)
|
|
267
|
+
|
|
268
|
+
// Stage 4: Action determination
|
|
269
|
+
const { decision, suggestedAction } = await determineAction(severity, violations)
|
|
270
|
+
console.log(` Decision: ${decision}`)
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
contentId: content.id,
|
|
274
|
+
decision,
|
|
275
|
+
categories: violations,
|
|
276
|
+
severity,
|
|
277
|
+
confidence: 0.85, // Simplified
|
|
278
|
+
reasoning,
|
|
279
|
+
suggestedAction,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ============================================================================
|
|
284
|
+
// Batch Processing with Resilience
|
|
285
|
+
// ============================================================================
|
|
286
|
+
|
|
287
|
+
async function batchModerate(contents: ContentItem[]): Promise<ModerationResult[]> {
|
|
288
|
+
console.log(`\n${'='.repeat(60)}`)
|
|
289
|
+
console.log('Content Moderation Pipeline')
|
|
290
|
+
console.log('='.repeat(60))
|
|
291
|
+
|
|
292
|
+
const results: ModerationResult[] = []
|
|
293
|
+
|
|
294
|
+
for (const content of contents) {
|
|
295
|
+
try {
|
|
296
|
+
// Use retry logic for resilience
|
|
297
|
+
const result = await withRetry(async () => moderateContent(content), {
|
|
298
|
+
maxRetries: 2,
|
|
299
|
+
baseDelay: 1000,
|
|
300
|
+
})
|
|
301
|
+
results.push(result)
|
|
302
|
+
} catch (error) {
|
|
303
|
+
// If moderation fails, flag for human review
|
|
304
|
+
results.push({
|
|
305
|
+
contentId: content.id,
|
|
306
|
+
decision: 'needs_review',
|
|
307
|
+
categories: [],
|
|
308
|
+
severity: 'medium',
|
|
309
|
+
confidence: 0,
|
|
310
|
+
reasoning: `Moderation failed: ${(error as Error).message}`,
|
|
311
|
+
suggestedAction: 'Manual review required due to processing error',
|
|
312
|
+
})
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return results
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// Report Generation
|
|
321
|
+
// ============================================================================
|
|
322
|
+
|
|
323
|
+
function generateReport(results: ModerationResult[]): void {
|
|
324
|
+
console.log(`\n${'='.repeat(60)}`)
|
|
325
|
+
console.log('Moderation Report')
|
|
326
|
+
console.log('='.repeat(60))
|
|
327
|
+
|
|
328
|
+
const stats = {
|
|
329
|
+
approved: results.filter((r) => r.decision === 'approved').length,
|
|
330
|
+
flagged: results.filter((r) => r.decision === 'flagged').length,
|
|
331
|
+
rejected: results.filter((r) => r.decision === 'rejected').length,
|
|
332
|
+
needs_review: results.filter((r) => r.decision === 'needs_review').length,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log('\nSummary:')
|
|
336
|
+
console.log(` Approved: ${stats.approved}`)
|
|
337
|
+
console.log(` Flagged: ${stats.flagged}`)
|
|
338
|
+
console.log(` Rejected: ${stats.rejected}`)
|
|
339
|
+
console.log(` Needs Review: ${stats.needs_review}`)
|
|
340
|
+
|
|
341
|
+
console.log('\nDetailed Results:')
|
|
342
|
+
for (const result of results) {
|
|
343
|
+
const detectedCategories = result.categories.filter((c) => c.detected).map((c) => c.category)
|
|
344
|
+
|
|
345
|
+
console.log(`
|
|
346
|
+
[${result.contentId}]
|
|
347
|
+
Decision: ${result.decision.toUpperCase()}
|
|
348
|
+
Severity: ${result.severity}
|
|
349
|
+
Confidence: ${(result.confidence * 100).toFixed(0)}%
|
|
350
|
+
${
|
|
351
|
+
detectedCategories.length > 0
|
|
352
|
+
? `Violations: ${detectedCategories.join(', ')}`
|
|
353
|
+
: 'No violations'
|
|
354
|
+
}
|
|
355
|
+
Action: ${result.suggestedAction}`)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Violation breakdown
|
|
359
|
+
console.log('\nViolation Breakdown:')
|
|
360
|
+
const violationCounts: Record<string, number> = {}
|
|
361
|
+
for (const result of results) {
|
|
362
|
+
for (const cat of result.categories.filter((c) => c.detected)) {
|
|
363
|
+
violationCounts[cat.category] = (violationCounts[cat.category] || 0) + 1
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (const [category, count] of Object.entries(violationCounts)) {
|
|
368
|
+
console.log(` ${category}: ${count}`)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ============================================================================
|
|
373
|
+
// Main Example
|
|
374
|
+
// ============================================================================
|
|
375
|
+
|
|
376
|
+
async function main() {
|
|
377
|
+
console.log('\n=== Content Moderation Pipeline Example ===\n')
|
|
378
|
+
|
|
379
|
+
// Configure the AI provider
|
|
380
|
+
configure({
|
|
381
|
+
model: 'sonnet',
|
|
382
|
+
provider: 'anthropic',
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
// Process all sample content
|
|
386
|
+
const results = await batchModerate(sampleContent)
|
|
387
|
+
|
|
388
|
+
// Generate report
|
|
389
|
+
generateReport(results)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
main()
|
|
393
|
+
.then(() => {
|
|
394
|
+
console.log('\n=== Example Complete ===\n')
|
|
395
|
+
process.exit(0)
|
|
396
|
+
})
|
|
397
|
+
.catch((error) => {
|
|
398
|
+
console.error('\nError:', error.message)
|
|
399
|
+
process.exit(1)
|
|
400
|
+
})
|