@nextsparkjs/plugin-langchain 0.1.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.
- package/.env.example +41 -0
- package/api/observability/metrics/route.ts +110 -0
- package/api/observability/traces/[traceId]/route.ts +398 -0
- package/api/observability/traces/route.ts +205 -0
- package/api/sessions/route.ts +332 -0
- package/components/observability/CollapsibleJson.tsx +71 -0
- package/components/observability/CompactTimeline.tsx +75 -0
- package/components/observability/ConversationFlow.tsx +271 -0
- package/components/observability/DisabledMessage.tsx +21 -0
- package/components/observability/FiltersPanel.tsx +82 -0
- package/components/observability/ObservabilityDashboard.tsx +230 -0
- package/components/observability/SpansList.tsx +210 -0
- package/components/observability/TraceDetail.tsx +335 -0
- package/components/observability/TraceStatusBadge.tsx +39 -0
- package/components/observability/TracesTable.tsx +97 -0
- package/components/observability/index.ts +7 -0
- package/docs/01-getting-started/01-overview.md +196 -0
- package/docs/01-getting-started/02-installation.md +368 -0
- package/docs/01-getting-started/03-configuration.md +794 -0
- package/docs/02-core-concepts/01-architecture.md +566 -0
- package/docs/02-core-concepts/02-agents.md +597 -0
- package/docs/02-core-concepts/03-tools.md +689 -0
- package/docs/03-orchestration/01-graph-orchestrator.md +809 -0
- package/docs/03-orchestration/02-legacy-react.md +650 -0
- package/docs/04-advanced/01-observability.md +645 -0
- package/docs/04-advanced/02-token-tracking.md +469 -0
- package/docs/04-advanced/03-streaming.md +476 -0
- package/docs/04-advanced/04-guardrails.md +597 -0
- package/docs/05-reference/01-api-reference.md +1403 -0
- package/docs/05-reference/02-customization.md +646 -0
- package/docs/05-reference/03-examples.md +881 -0
- package/docs/index.md +85 -0
- package/hooks/observability/useMetrics.ts +31 -0
- package/hooks/observability/useTraceDetail.ts +48 -0
- package/hooks/observability/useTraces.ts +59 -0
- package/lib/agent-factory.ts +354 -0
- package/lib/agent-helpers.ts +201 -0
- package/lib/db-memory-store.ts +417 -0
- package/lib/graph/index.ts +58 -0
- package/lib/graph/nodes/combiner.ts +399 -0
- package/lib/graph/nodes/router.ts +440 -0
- package/lib/graph/orchestrator-graph.ts +386 -0
- package/lib/graph/prompts/combiner.md +131 -0
- package/lib/graph/prompts/router.md +193 -0
- package/lib/graph/types.ts +365 -0
- package/lib/guardrails.ts +230 -0
- package/lib/index.ts +44 -0
- package/lib/logger.ts +70 -0
- package/lib/memory-store.ts +168 -0
- package/lib/message-serializer.ts +110 -0
- package/lib/prompt-renderer.ts +94 -0
- package/lib/providers.ts +226 -0
- package/lib/streaming.ts +232 -0
- package/lib/token-tracker.ts +298 -0
- package/lib/tools-builder.ts +192 -0
- package/lib/tracer-callbacks.ts +342 -0
- package/lib/tracer.ts +350 -0
- package/migrations/001_langchain_memory.sql +83 -0
- package/migrations/002_token_usage.sql +127 -0
- package/migrations/003_observability.sql +257 -0
- package/package.json +28 -0
- package/plugin.config.ts +170 -0
- package/presets/lib/langchain.config.ts.preset +142 -0
- package/presets/templates/sector7/ai-observability/[traceId]/page.tsx +91 -0
- package/presets/templates/sector7/ai-observability/page.tsx +54 -0
- package/types/langchain.types.ts +274 -0
- package/types/observability.types.ts +270 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangGraph Orchestrator Types
|
|
3
|
+
*
|
|
4
|
+
* State schema and types for the explicit graph-based orchestrator.
|
|
5
|
+
* Replaces ReAct loops with deterministic state machine flow.
|
|
6
|
+
*
|
|
7
|
+
* GENERIC ARCHITECTURE:
|
|
8
|
+
* - Plugin defines generic interfaces (AgentTool, OrchestratorConfig)
|
|
9
|
+
* - Theme provides tool implementations and configuration
|
|
10
|
+
* - No hardcoded entity knowledge in plugin
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { BaseMessage } from '@langchain/core/messages'
|
|
14
|
+
import type { AgentContext, SessionConfig, LLMProvider } from '../../types/langchain.types'
|
|
15
|
+
import type { z } from 'zod'
|
|
16
|
+
|
|
17
|
+
// ============================================
|
|
18
|
+
// AGENT TOOL INTERFACE (NEW - GENERIC)
|
|
19
|
+
// ============================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Handler node function type
|
|
23
|
+
* Theme must implement these and pass to graph factory
|
|
24
|
+
*/
|
|
25
|
+
export type HandlerNodeFn = (state: OrchestratorState) => Promise<Partial<OrchestratorState>>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Agent tool definition - describes a capability the orchestrator can use
|
|
29
|
+
*
|
|
30
|
+
* Example:
|
|
31
|
+
* {
|
|
32
|
+
* name: 'task',
|
|
33
|
+
* description: 'Task/todo management (list, create, update, delete)',
|
|
34
|
+
* handler: taskHandlerNode,
|
|
35
|
+
* exampleParameters: 'title, description, priority (low/medium/high/urgent)',
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
38
|
+
export interface AgentTool {
|
|
39
|
+
/** Unique identifier (e.g., 'task', 'customer', 'product') */
|
|
40
|
+
name: string
|
|
41
|
+
/** Description for router prompt */
|
|
42
|
+
description: string
|
|
43
|
+
/** Optional Zod schema for parameter extraction */
|
|
44
|
+
parameterSchema?: z.ZodSchema
|
|
45
|
+
/** Handler implementation (theme-provided) */
|
|
46
|
+
handler: HandlerNodeFn
|
|
47
|
+
/** Example parameters for router prompt (optional) */
|
|
48
|
+
exampleParameters?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Orchestrator configuration - provided by theme
|
|
53
|
+
*/
|
|
54
|
+
export interface OrchestratorConfig {
|
|
55
|
+
/** Tools registered by theme */
|
|
56
|
+
tools: AgentTool[]
|
|
57
|
+
/** System intents like 'greeting', 'clarification' (default: both) */
|
|
58
|
+
systemIntents?: ('greeting' | 'clarification')[]
|
|
59
|
+
/** Additional router prompt context (optional) */
|
|
60
|
+
routerPromptExtras?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================
|
|
64
|
+
// INTENT TYPES (NOW DYNAMIC)
|
|
65
|
+
// ============================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* System intent types (built into plugin)
|
|
69
|
+
*/
|
|
70
|
+
export type SystemIntentType = 'greeting' | 'clarification'
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Intent types that the router can classify
|
|
74
|
+
* Now dynamic - can be any tool name registered in OrchestratorConfig
|
|
75
|
+
*/
|
|
76
|
+
export type IntentType = string
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Actions that can be performed on entities
|
|
80
|
+
*/
|
|
81
|
+
export type IntentAction = 'list' | 'create' | 'update' | 'delete' | 'search' | 'get' | 'unknown'
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A single parsed intent from user input
|
|
85
|
+
*/
|
|
86
|
+
export interface Intent {
|
|
87
|
+
/** Type of intent (which handler should process it) */
|
|
88
|
+
type: IntentType
|
|
89
|
+
/** Action to perform */
|
|
90
|
+
action: IntentAction
|
|
91
|
+
/** Extracted parameters for the action */
|
|
92
|
+
parameters: Record<string, unknown>
|
|
93
|
+
/** Original text that maps to this intent */
|
|
94
|
+
originalText: string
|
|
95
|
+
/** Confidence score from router (0-1) */
|
|
96
|
+
confidence?: number
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Router output schema (for structured output)
|
|
101
|
+
*/
|
|
102
|
+
export interface RouterOutput {
|
|
103
|
+
intents: Intent[]
|
|
104
|
+
/** If true, needs clarification from user */
|
|
105
|
+
needsClarification: boolean
|
|
106
|
+
/** Clarification question if needed */
|
|
107
|
+
clarificationQuestion?: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================
|
|
111
|
+
// HANDLER RESULT TYPES (NOW GENERIC)
|
|
112
|
+
// ============================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generic handler result interface
|
|
116
|
+
* Replaces entity-specific types (TaskHandlerResult, CustomerHandlerResult, etc.)
|
|
117
|
+
*/
|
|
118
|
+
export interface GenericHandlerResult {
|
|
119
|
+
success: boolean
|
|
120
|
+
operation: IntentAction
|
|
121
|
+
message: string
|
|
122
|
+
data: unknown
|
|
123
|
+
count?: number
|
|
124
|
+
error?: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* All handler results combined - now generic
|
|
129
|
+
* Keys are tool names, values are results
|
|
130
|
+
*/
|
|
131
|
+
export type HandlerResults = Record<string, GenericHandlerResult>
|
|
132
|
+
|
|
133
|
+
// ============================================
|
|
134
|
+
// DEPRECATED TYPES (for backward compatibility during migration)
|
|
135
|
+
// ============================================
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @deprecated Use GenericHandlerResult instead
|
|
139
|
+
*/
|
|
140
|
+
export interface TaskHandlerResult extends GenericHandlerResult {
|
|
141
|
+
data: TaskData[] | TaskData | null
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @deprecated Use generic data structure instead
|
|
146
|
+
*/
|
|
147
|
+
export interface TaskData {
|
|
148
|
+
id: string
|
|
149
|
+
title: string
|
|
150
|
+
status?: string
|
|
151
|
+
priority?: string
|
|
152
|
+
dueDate?: string
|
|
153
|
+
description?: string
|
|
154
|
+
[key: string]: unknown
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @deprecated Use GenericHandlerResult instead
|
|
159
|
+
*/
|
|
160
|
+
export interface CustomerHandlerResult extends GenericHandlerResult {
|
|
161
|
+
data: CustomerData[] | CustomerData | null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @deprecated Use generic data structure instead
|
|
166
|
+
*/
|
|
167
|
+
export interface CustomerData {
|
|
168
|
+
id: string
|
|
169
|
+
name: string
|
|
170
|
+
email?: string
|
|
171
|
+
phone?: string
|
|
172
|
+
accountNumber?: string
|
|
173
|
+
[key: string]: unknown
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @deprecated Use GenericHandlerResult instead
|
|
178
|
+
*/
|
|
179
|
+
export interface PageHandlerResult extends GenericHandlerResult {
|
|
180
|
+
data: PageData[] | PageData | null
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @deprecated Use generic data structure instead
|
|
185
|
+
*/
|
|
186
|
+
export interface PageData {
|
|
187
|
+
id: string
|
|
188
|
+
title: string
|
|
189
|
+
slug?: string
|
|
190
|
+
status?: string
|
|
191
|
+
[key: string]: unknown
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============================================
|
|
195
|
+
// ORCHESTRATOR STATE
|
|
196
|
+
// ============================================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The main state that flows through the orchestrator graph
|
|
200
|
+
*/
|
|
201
|
+
export interface OrchestratorState {
|
|
202
|
+
// ---- Input ----
|
|
203
|
+
/** Original user input */
|
|
204
|
+
input: string
|
|
205
|
+
/** Session identifier for memory */
|
|
206
|
+
sessionId: string
|
|
207
|
+
/** Multi-tenant context (userId, teamId, etc.) */
|
|
208
|
+
context: AgentContext
|
|
209
|
+
/** Recent conversation history (last N messages) */
|
|
210
|
+
conversationHistory: BaseMessage[]
|
|
211
|
+
/** Session configuration for memory */
|
|
212
|
+
sessionConfig?: SessionConfig
|
|
213
|
+
|
|
214
|
+
// ---- Router Configuration ----
|
|
215
|
+
/** Model configuration injected by theme (for router node) */
|
|
216
|
+
modelConfig?: ModelConfig
|
|
217
|
+
|
|
218
|
+
// ---- Router Output ----
|
|
219
|
+
/** Parsed intents from router */
|
|
220
|
+
intents: Intent[]
|
|
221
|
+
/** Whether clarification is needed */
|
|
222
|
+
needsClarification: boolean
|
|
223
|
+
/** Clarification question if needed */
|
|
224
|
+
clarificationQuestion?: string
|
|
225
|
+
|
|
226
|
+
// ---- Handler Outputs ----
|
|
227
|
+
/** Results from each handler (JSON) */
|
|
228
|
+
handlerResults: HandlerResults
|
|
229
|
+
/** Which handlers have been executed */
|
|
230
|
+
completedHandlers: IntentType[]
|
|
231
|
+
|
|
232
|
+
// ---- Final Output ----
|
|
233
|
+
/** Final response to send to user */
|
|
234
|
+
finalResponse: string | null
|
|
235
|
+
/** Error message if something failed */
|
|
236
|
+
error: string | null
|
|
237
|
+
|
|
238
|
+
// ---- Tracing ----
|
|
239
|
+
/** Trace ID for observability */
|
|
240
|
+
traceId?: string
|
|
241
|
+
/** Parent trace ID for nested calls */
|
|
242
|
+
parentTraceId?: string
|
|
243
|
+
|
|
244
|
+
// ---- Logging ----
|
|
245
|
+
/** Timestamp for logger filename (ensures all handlers write to same file) */
|
|
246
|
+
loggerTimestamp?: number
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Initial state factory
|
|
251
|
+
*/
|
|
252
|
+
export function createInitialState(
|
|
253
|
+
input: string,
|
|
254
|
+
sessionId: string,
|
|
255
|
+
context: AgentContext,
|
|
256
|
+
conversationHistory: BaseMessage[] = [],
|
|
257
|
+
sessionConfig?: SessionConfig
|
|
258
|
+
): OrchestratorState {
|
|
259
|
+
return {
|
|
260
|
+
input,
|
|
261
|
+
sessionId,
|
|
262
|
+
context,
|
|
263
|
+
conversationHistory,
|
|
264
|
+
sessionConfig,
|
|
265
|
+
intents: [],
|
|
266
|
+
needsClarification: false,
|
|
267
|
+
handlerResults: {},
|
|
268
|
+
completedHandlers: [],
|
|
269
|
+
finalResponse: null,
|
|
270
|
+
error: null,
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ============================================
|
|
275
|
+
// GRAPH ROUTING TYPES (NOW DYNAMIC)
|
|
276
|
+
// ============================================
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Possible routes from the router node - now dynamic based on tools
|
|
280
|
+
* Examples: 'task_handler', 'customer_handler', 'greeting', 'clarification', 'error'
|
|
281
|
+
*/
|
|
282
|
+
export type RouterRoute = string
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Possible routes after a handler completes - now dynamic
|
|
286
|
+
* Examples: 'customer_handler', 'page_handler', 'combiner'
|
|
287
|
+
*/
|
|
288
|
+
export type PostHandlerRoute = string
|
|
289
|
+
|
|
290
|
+
// ============================================
|
|
291
|
+
// NODE FUNCTION TYPES
|
|
292
|
+
// ============================================
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Type for graph node functions
|
|
296
|
+
*/
|
|
297
|
+
export type GraphNode = (state: OrchestratorState) => Promise<Partial<OrchestratorState>>
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Type for conditional edge functions
|
|
301
|
+
*/
|
|
302
|
+
export type ConditionalEdge = (state: OrchestratorState) => RouterRoute | PostHandlerRoute
|
|
303
|
+
|
|
304
|
+
// ============================================
|
|
305
|
+
// CONFIGURATION
|
|
306
|
+
// ============================================
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Graph configuration options
|
|
310
|
+
*/
|
|
311
|
+
export interface GraphConfig {
|
|
312
|
+
/** Maximum conversation history messages to include */
|
|
313
|
+
maxHistoryMessages: number
|
|
314
|
+
/** Temperature for router LLM */
|
|
315
|
+
routerTemperature: number
|
|
316
|
+
/** Temperature for combiner LLM */
|
|
317
|
+
combinerTemperature: number
|
|
318
|
+
/** Whether to use parallel handler execution */
|
|
319
|
+
parallelExecution: boolean
|
|
320
|
+
/** Timeout for handler execution (ms) */
|
|
321
|
+
handlerTimeout: number
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Default graph configuration
|
|
326
|
+
*/
|
|
327
|
+
export const DEFAULT_GRAPH_CONFIG: GraphConfig = {
|
|
328
|
+
maxHistoryMessages: 5,
|
|
329
|
+
routerTemperature: 0.1,
|
|
330
|
+
combinerTemperature: 0.3,
|
|
331
|
+
parallelExecution: true,
|
|
332
|
+
handlerTimeout: 30000,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ============================================
|
|
336
|
+
// HANDLER FACTORY TYPES (DEPRECATED)
|
|
337
|
+
// ============================================
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @deprecated Use OrchestratorConfig.tools instead
|
|
341
|
+
* Handler factory configuration
|
|
342
|
+
* Theme provides implementations for these handlers
|
|
343
|
+
*/
|
|
344
|
+
export interface HandlerFactories {
|
|
345
|
+
/** Handler for task-related operations */
|
|
346
|
+
taskHandler: HandlerNodeFn
|
|
347
|
+
/** Handler for customer-related operations */
|
|
348
|
+
customerHandler: HandlerNodeFn
|
|
349
|
+
/** Handler for page-related operations */
|
|
350
|
+
pageHandler: HandlerNodeFn
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ============================================
|
|
354
|
+
// MODEL CONFIGURATION TYPES
|
|
355
|
+
// ============================================
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Model configuration to inject into state
|
|
359
|
+
* Avoids router needing to import from theme
|
|
360
|
+
*/
|
|
361
|
+
export interface ModelConfig {
|
|
362
|
+
provider: LLMProvider
|
|
363
|
+
model?: string
|
|
364
|
+
temperature?: number
|
|
365
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardrails Service
|
|
3
|
+
*
|
|
4
|
+
* Security middleware for AI agents:
|
|
5
|
+
* - Prompt injection detection
|
|
6
|
+
* - PII masking
|
|
7
|
+
* - Content filtering
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Injection detection patterns
|
|
11
|
+
const INJECTION_PATTERNS = [
|
|
12
|
+
/ignore\s+(previous|all|above)\s+(instructions?|prompts?)/i,
|
|
13
|
+
/forget\s+(everything|all|previous)/i,
|
|
14
|
+
/you\s+are\s+now\s+/i,
|
|
15
|
+
/disregard\s+(all|previous|your)\s/i,
|
|
16
|
+
/pretend\s+(you\s+are|to\s+be)/i,
|
|
17
|
+
/act\s+as\s+(if|a|an)\s/i,
|
|
18
|
+
/jailbreak/i,
|
|
19
|
+
/bypass\s+(restrictions?|filters?|rules?)/i,
|
|
20
|
+
/system\s*:\s*/i, // Attempting to inject system prompt
|
|
21
|
+
/\[system\]/i,
|
|
22
|
+
/\<system\>/i,
|
|
23
|
+
/\{\{.*system.*\}\}/i, // Template injection
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
// PII patterns
|
|
27
|
+
const PII_PATTERNS = {
|
|
28
|
+
email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
29
|
+
phone: /\b(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g,
|
|
30
|
+
ssn: /\b\d{3}[-.\s]?\d{2}[-.\s]?\d{4}\b/g,
|
|
31
|
+
creditCard: /\b(?:\d{4}[-.\s]?){3}\d{4}\b/g,
|
|
32
|
+
ipAddress: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Content filter patterns (examples - should be customizable)
|
|
36
|
+
const CONTENT_FILTER_PATTERNS: RegExp[] = [
|
|
37
|
+
// Add patterns for harmful content
|
|
38
|
+
// These are placeholders - real implementation should be customizable
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
export interface InjectionCheckResult {
|
|
42
|
+
safe: boolean
|
|
43
|
+
reason?: string
|
|
44
|
+
pattern?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface PIIMaskResult {
|
|
48
|
+
masked: string
|
|
49
|
+
mappings: Array<{ original: string; masked: string; type: string }>
|
|
50
|
+
hasPII: boolean
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ContentFilterResult {
|
|
54
|
+
filtered: string
|
|
55
|
+
blocked: boolean
|
|
56
|
+
reason?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface GuardrailsConfig {
|
|
60
|
+
promptInjection?: {
|
|
61
|
+
enabled: boolean
|
|
62
|
+
action: 'block' | 'warn' | 'log'
|
|
63
|
+
customPatterns?: RegExp[]
|
|
64
|
+
}
|
|
65
|
+
piiMasking?: {
|
|
66
|
+
enabled: boolean
|
|
67
|
+
types: Array<'email' | 'phone' | 'ssn' | 'creditCard' | 'ipAddress'>
|
|
68
|
+
action: 'mask' | 'remove' | 'log'
|
|
69
|
+
}
|
|
70
|
+
contentFilter?: {
|
|
71
|
+
enabled: boolean
|
|
72
|
+
customPatterns?: RegExp[]
|
|
73
|
+
action: 'block' | 'redact'
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const guardrails = {
|
|
78
|
+
/**
|
|
79
|
+
* Check input for prompt injection attempts
|
|
80
|
+
*/
|
|
81
|
+
checkInjection(
|
|
82
|
+
input: string,
|
|
83
|
+
config?: GuardrailsConfig['promptInjection']
|
|
84
|
+
): InjectionCheckResult {
|
|
85
|
+
if (!config?.enabled) {
|
|
86
|
+
return { safe: true }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const patterns = [
|
|
90
|
+
...INJECTION_PATTERNS,
|
|
91
|
+
...(config.customPatterns || []),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
for (const pattern of patterns) {
|
|
95
|
+
if (pattern.test(input)) {
|
|
96
|
+
return {
|
|
97
|
+
safe: false,
|
|
98
|
+
reason: 'Potential prompt injection detected',
|
|
99
|
+
pattern: pattern.toString(),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { safe: true }
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Mask PII in input text
|
|
109
|
+
*/
|
|
110
|
+
maskPII(
|
|
111
|
+
input: string,
|
|
112
|
+
config?: GuardrailsConfig['piiMasking']
|
|
113
|
+
): PIIMaskResult {
|
|
114
|
+
if (!config?.enabled) {
|
|
115
|
+
return { masked: input, mappings: [], hasPII: false }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let masked = input
|
|
119
|
+
const mappings: PIIMaskResult['mappings'] = []
|
|
120
|
+
|
|
121
|
+
for (const type of config.types) {
|
|
122
|
+
const pattern = PII_PATTERNS[type]
|
|
123
|
+
if (!pattern) continue
|
|
124
|
+
|
|
125
|
+
// Reset lastIndex for global patterns
|
|
126
|
+
pattern.lastIndex = 0
|
|
127
|
+
|
|
128
|
+
const matches = input.matchAll(new RegExp(pattern))
|
|
129
|
+
for (const match of matches) {
|
|
130
|
+
const original = match[0]
|
|
131
|
+
const maskChar = '*'
|
|
132
|
+
const maskedValue = config.action === 'remove'
|
|
133
|
+
? '[REDACTED]'
|
|
134
|
+
: original.slice(0, 2) + maskChar.repeat(original.length - 4) + original.slice(-2)
|
|
135
|
+
|
|
136
|
+
mappings.push({ original, masked: maskedValue, type })
|
|
137
|
+
masked = masked.replace(original, maskedValue)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
masked,
|
|
143
|
+
mappings,
|
|
144
|
+
hasPII: mappings.length > 0,
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Filter content from AI output
|
|
150
|
+
*/
|
|
151
|
+
filterContent(
|
|
152
|
+
output: string,
|
|
153
|
+
config?: GuardrailsConfig['contentFilter']
|
|
154
|
+
): ContentFilterResult {
|
|
155
|
+
if (!config?.enabled) {
|
|
156
|
+
return { filtered: output, blocked: false }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const patterns = [
|
|
160
|
+
...CONTENT_FILTER_PATTERNS,
|
|
161
|
+
...(config.customPatterns || []),
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
for (const pattern of patterns) {
|
|
165
|
+
if (pattern.test(output)) {
|
|
166
|
+
if (config.action === 'block') {
|
|
167
|
+
return {
|
|
168
|
+
filtered: '',
|
|
169
|
+
blocked: true,
|
|
170
|
+
reason: 'Content blocked by filter',
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
// Redact matching content
|
|
174
|
+
output = output.replace(pattern, '[FILTERED]')
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { filtered: output, blocked: false }
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Run all guardrails on input
|
|
184
|
+
* Returns processed input or throws if blocked
|
|
185
|
+
*/
|
|
186
|
+
async processInput(
|
|
187
|
+
input: string,
|
|
188
|
+
config?: GuardrailsConfig
|
|
189
|
+
): Promise<{ processed: string; warnings: string[] }> {
|
|
190
|
+
const warnings: string[] = []
|
|
191
|
+
let processed = input
|
|
192
|
+
|
|
193
|
+
// Check injection
|
|
194
|
+
if (config?.promptInjection?.enabled) {
|
|
195
|
+
const injectionResult = this.checkInjection(input, config.promptInjection)
|
|
196
|
+
if (!injectionResult.safe) {
|
|
197
|
+
if (config.promptInjection.action === 'block') {
|
|
198
|
+
throw new Error(`Input blocked: ${injectionResult.reason}`)
|
|
199
|
+
}
|
|
200
|
+
warnings.push(injectionResult.reason || 'Potential injection detected')
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Mask PII
|
|
205
|
+
if (config?.piiMasking?.enabled) {
|
|
206
|
+
const piiResult = this.maskPII(processed, config.piiMasking)
|
|
207
|
+
processed = piiResult.masked
|
|
208
|
+
if (piiResult.hasPII) {
|
|
209
|
+
warnings.push(`PII detected and masked: ${piiResult.mappings.length} items`)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { processed, warnings }
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Run content filter on output
|
|
218
|
+
*/
|
|
219
|
+
async processOutput(
|
|
220
|
+
output: string,
|
|
221
|
+
config?: GuardrailsConfig
|
|
222
|
+
): Promise<{ processed: string; blocked: boolean }> {
|
|
223
|
+
if (!config?.contentFilter?.enabled) {
|
|
224
|
+
return { processed: output, blocked: false }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const result = this.filterContent(output, config.contentFilter)
|
|
228
|
+
return { processed: result.filtered, blocked: result.blocked }
|
|
229
|
+
},
|
|
230
|
+
}
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Agent
|
|
2
|
+
export { createAgent } from './agent-factory'
|
|
3
|
+
|
|
4
|
+
// Tools
|
|
5
|
+
export { createTool, buildTools, type ToolDefinition } from './tools-builder'
|
|
6
|
+
|
|
7
|
+
// Memory
|
|
8
|
+
export {
|
|
9
|
+
memoryStore,
|
|
10
|
+
dbMemoryStore,
|
|
11
|
+
type AgentContext,
|
|
12
|
+
type SessionConfig,
|
|
13
|
+
} from './memory-store'
|
|
14
|
+
|
|
15
|
+
// Prompt Renderer
|
|
16
|
+
export { renderPrompt, hasTemplateVariables, compilePrompt } from './prompt-renderer'
|
|
17
|
+
|
|
18
|
+
// Message Serialization
|
|
19
|
+
export {
|
|
20
|
+
serializeMessages,
|
|
21
|
+
deserializeMessages,
|
|
22
|
+
type SerializedMessage,
|
|
23
|
+
} from './message-serializer'
|
|
24
|
+
|
|
25
|
+
// Providers
|
|
26
|
+
export {
|
|
27
|
+
createOllamaModel,
|
|
28
|
+
createOpenAIModel,
|
|
29
|
+
createAnthropicModel,
|
|
30
|
+
getModel,
|
|
31
|
+
isProviderAvailable,
|
|
32
|
+
getAvailableProviders,
|
|
33
|
+
} from './providers'
|
|
34
|
+
|
|
35
|
+
// Types (re-export for convenience)
|
|
36
|
+
export type {
|
|
37
|
+
ModelConfig,
|
|
38
|
+
LLMProvider,
|
|
39
|
+
ThemeLangChainConfig,
|
|
40
|
+
AgentConfig,
|
|
41
|
+
AgentResponse,
|
|
42
|
+
ChatMessage,
|
|
43
|
+
ProviderConfig,
|
|
44
|
+
} from '../types/langchain.types'
|
package/lib/logger.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { FileLogger } from '@nextsparkjs/core/lib/utils/file-logger'
|
|
2
|
+
|
|
3
|
+
export interface AgentLoggerOptions {
|
|
4
|
+
/** Agent name (e.g., 'graph-orchestrator', 'single-agent') */
|
|
5
|
+
agentName: string
|
|
6
|
+
/** User display name (firstname-lastname), defaults to 'system' */
|
|
7
|
+
userName?: string
|
|
8
|
+
/** Session timestamp, defaults to Date.now() */
|
|
9
|
+
timestamp?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sanitize a string for use in filenames.
|
|
14
|
+
* - Converts to lowercase
|
|
15
|
+
* - Replaces spaces with hyphens
|
|
16
|
+
* - Removes special characters except hyphens and underscores
|
|
17
|
+
* - Collapses multiple hyphens into one
|
|
18
|
+
*/
|
|
19
|
+
function sanitizeForFilename(str: string): string {
|
|
20
|
+
return str
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.trim()
|
|
23
|
+
.replace(/\s+/g, '-') // spaces to hyphens
|
|
24
|
+
.replace(/[^a-z0-9\-_]/g, '') // remove special chars
|
|
25
|
+
.replace(/-+/g, '-') // collapse multiple hyphens
|
|
26
|
+
.replace(/^-|-$/g, '') // trim leading/trailing hyphens
|
|
27
|
+
|| 'unknown' // fallback if empty
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a logger for an AI agent session.
|
|
32
|
+
*
|
|
33
|
+
* Logs are stored in: logger/langchain/{agentName}/{userName}-{timestamp}.log
|
|
34
|
+
*
|
|
35
|
+
* The caller is responsible for managing the logger lifecycle.
|
|
36
|
+
* When the agent/session ends, the logger will be garbage collected.
|
|
37
|
+
*
|
|
38
|
+
* Logging is controlled by the LOG_ENABLED environment variable.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const logger = createAgentLogger({
|
|
43
|
+
* agentName: 'graph-orchestrator',
|
|
44
|
+
* userName: 'Carlos García', // → carlos-garcia
|
|
45
|
+
* })
|
|
46
|
+
*
|
|
47
|
+
* await logger.info('SESSION_INIT', { model: 'gpt-4' })
|
|
48
|
+
* await logger.info('USER_MESSAGE', { message })
|
|
49
|
+
* await logger.error('LLM_FAILED', { error })
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function createAgentLogger(options: AgentLoggerOptions): FileLogger {
|
|
53
|
+
const { agentName, userName = 'system', timestamp = Date.now() } = options
|
|
54
|
+
const sanitizedUserName = sanitizeForFilename(userName)
|
|
55
|
+
|
|
56
|
+
return new FileLogger({
|
|
57
|
+
folder: `langchain/${agentName}`,
|
|
58
|
+
filename: `${sanitizedUserName}-${timestamp}`,
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clear all LangChain agent logs
|
|
64
|
+
* @param agentName - Optional agent name to clear specific agent logs
|
|
65
|
+
* @returns Number of log files deleted
|
|
66
|
+
*/
|
|
67
|
+
export async function clearAgentLogs(agentName?: string): Promise<number> {
|
|
68
|
+
const folder = agentName ? `langchain/${agentName}` : 'langchain'
|
|
69
|
+
return FileLogger.clearAll(folder)
|
|
70
|
+
}
|