@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.
Files changed (67) hide show
  1. package/.env.example +41 -0
  2. package/api/observability/metrics/route.ts +110 -0
  3. package/api/observability/traces/[traceId]/route.ts +398 -0
  4. package/api/observability/traces/route.ts +205 -0
  5. package/api/sessions/route.ts +332 -0
  6. package/components/observability/CollapsibleJson.tsx +71 -0
  7. package/components/observability/CompactTimeline.tsx +75 -0
  8. package/components/observability/ConversationFlow.tsx +271 -0
  9. package/components/observability/DisabledMessage.tsx +21 -0
  10. package/components/observability/FiltersPanel.tsx +82 -0
  11. package/components/observability/ObservabilityDashboard.tsx +230 -0
  12. package/components/observability/SpansList.tsx +210 -0
  13. package/components/observability/TraceDetail.tsx +335 -0
  14. package/components/observability/TraceStatusBadge.tsx +39 -0
  15. package/components/observability/TracesTable.tsx +97 -0
  16. package/components/observability/index.ts +7 -0
  17. package/docs/01-getting-started/01-overview.md +196 -0
  18. package/docs/01-getting-started/02-installation.md +368 -0
  19. package/docs/01-getting-started/03-configuration.md +794 -0
  20. package/docs/02-core-concepts/01-architecture.md +566 -0
  21. package/docs/02-core-concepts/02-agents.md +597 -0
  22. package/docs/02-core-concepts/03-tools.md +689 -0
  23. package/docs/03-orchestration/01-graph-orchestrator.md +809 -0
  24. package/docs/03-orchestration/02-legacy-react.md +650 -0
  25. package/docs/04-advanced/01-observability.md +645 -0
  26. package/docs/04-advanced/02-token-tracking.md +469 -0
  27. package/docs/04-advanced/03-streaming.md +476 -0
  28. package/docs/04-advanced/04-guardrails.md +597 -0
  29. package/docs/05-reference/01-api-reference.md +1403 -0
  30. package/docs/05-reference/02-customization.md +646 -0
  31. package/docs/05-reference/03-examples.md +881 -0
  32. package/docs/index.md +85 -0
  33. package/hooks/observability/useMetrics.ts +31 -0
  34. package/hooks/observability/useTraceDetail.ts +48 -0
  35. package/hooks/observability/useTraces.ts +59 -0
  36. package/lib/agent-factory.ts +354 -0
  37. package/lib/agent-helpers.ts +201 -0
  38. package/lib/db-memory-store.ts +417 -0
  39. package/lib/graph/index.ts +58 -0
  40. package/lib/graph/nodes/combiner.ts +399 -0
  41. package/lib/graph/nodes/router.ts +440 -0
  42. package/lib/graph/orchestrator-graph.ts +386 -0
  43. package/lib/graph/prompts/combiner.md +131 -0
  44. package/lib/graph/prompts/router.md +193 -0
  45. package/lib/graph/types.ts +365 -0
  46. package/lib/guardrails.ts +230 -0
  47. package/lib/index.ts +44 -0
  48. package/lib/logger.ts +70 -0
  49. package/lib/memory-store.ts +168 -0
  50. package/lib/message-serializer.ts +110 -0
  51. package/lib/prompt-renderer.ts +94 -0
  52. package/lib/providers.ts +226 -0
  53. package/lib/streaming.ts +232 -0
  54. package/lib/token-tracker.ts +298 -0
  55. package/lib/tools-builder.ts +192 -0
  56. package/lib/tracer-callbacks.ts +342 -0
  57. package/lib/tracer.ts +350 -0
  58. package/migrations/001_langchain_memory.sql +83 -0
  59. package/migrations/002_token_usage.sql +127 -0
  60. package/migrations/003_observability.sql +257 -0
  61. package/package.json +28 -0
  62. package/plugin.config.ts +170 -0
  63. package/presets/lib/langchain.config.ts.preset +142 -0
  64. package/presets/templates/sector7/ai-observability/[traceId]/page.tsx +91 -0
  65. package/presets/templates/sector7/ai-observability/page.tsx +54 -0
  66. package/types/langchain.types.ts +274 -0
  67. package/types/observability.types.ts +270 -0
package/.env.example ADDED
@@ -0,0 +1,41 @@
1
+ # ===========================================
2
+ # LangChain Plugin Configuration
3
+ # ===========================================
4
+
5
+ # Plugin Settings
6
+ LANGCHAIN_PLUGIN_ENABLED=true
7
+ LANGCHAIN_PLUGIN_DEBUG=false
8
+
9
+ # File Logging (core environment variable)
10
+ # Logs to logger/ai/ when enabled
11
+ LOG_ENABLED=false
12
+
13
+ # ===========================================
14
+ # Ollama Configuration (Local)
15
+ # ===========================================
16
+ # Used by: single-agent
17
+ # No API key required - runs locally
18
+
19
+ # Ollama server URL
20
+ LANGCHAIN_OLLAMA_BASE_URL=http://localhost:11434
21
+
22
+ # Model to use (run `ollama list` to see available models)
23
+ LANGCHAIN_OLLAMA_MODEL=llama3.2:3b
24
+
25
+ # ===========================================
26
+ # OpenAI-compatible Configuration (LM Studio)
27
+ # ===========================================
28
+ # Used by: orchestrator, task-assistant, customer-assistant, page-assistant
29
+ #
30
+ # LM Studio provides an OpenAI-compatible API on localhost.
31
+ # 1. Start LM Studio and load a model
32
+ # 2. Start the local server (default: http://localhost:1234/v1)
33
+
34
+ # LM Studio server URL
35
+ LANGCHAIN_OPENAI_BASE_URL=http://localhost:1234/v1
36
+
37
+ # Model name as loaded in LM Studio
38
+ LANGCHAIN_OPENAI_MODEL=your-loaded-model-name
39
+
40
+ # API Key (LM Studio doesn't require a real key, use any string)
41
+ OPENAI_API_KEY=lm-studio
@@ -0,0 +1,110 @@
1
+ /**
2
+ * GET /api/langchain/observability/metrics
3
+ *
4
+ * Get basic metrics for a time period (MVP simple aggregation).
5
+ * Admin access required (superadmin or developer).
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from 'next/server'
9
+ import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
10
+ import { queryWithRLS } from '@nextsparkjs/core/lib/db'
11
+
12
+ interface MetricsRow {
13
+ totalTraces: string
14
+ successTraces: string
15
+ errorTraces: string
16
+ avgLatency: string
17
+ totalTokens: string
18
+ }
19
+
20
+ const PERIOD_HOURS: Record<string, number> = {
21
+ '1h': 1,
22
+ '24h': 24,
23
+ '7d': 24 * 7,
24
+ '30d': 24 * 30,
25
+ }
26
+
27
+ export async function GET(req: NextRequest) {
28
+ // 1. Authenticate (superadmin only)
29
+ const authResult = await authenticateRequest(req)
30
+ if (!authResult.success || !authResult.user) {
31
+ return NextResponse.json(
32
+ { success: false, error: 'Unauthorized' },
33
+ { status: 401 }
34
+ )
35
+ }
36
+
37
+ // Check if user has admin-level access (superadmin or developer)
38
+ const adminRoles = ['superadmin', 'developer']
39
+ if (!adminRoles.includes(authResult.user.role)) {
40
+ return NextResponse.json(
41
+ { success: false, error: 'Forbidden: Admin access required' },
42
+ { status: 403 }
43
+ )
44
+ }
45
+
46
+ try {
47
+ // 2. Parse query parameters
48
+ const { searchParams } = new URL(req.url)
49
+ const period = searchParams.get('period') || '24h'
50
+
51
+ // Validate period
52
+ if (!PERIOD_HOURS[period]) {
53
+ return NextResponse.json(
54
+ { success: false, error: 'Invalid period. Must be one of: 1h, 24h, 7d, 30d' },
55
+ { status: 400 }
56
+ )
57
+ }
58
+
59
+ const hours = PERIOD_HOURS[period]
60
+
61
+ // 3. Query metrics (parameterized to prevent SQL injection)
62
+ const query = `
63
+ SELECT
64
+ COUNT(*)::text AS "totalTraces",
65
+ COUNT(*) FILTER (WHERE status = 'success')::text AS "successTraces",
66
+ COUNT(*) FILTER (WHERE status = 'error')::text AS "errorTraces",
67
+ COALESCE(AVG("durationMs"), 0)::text AS "avgLatency",
68
+ COALESCE(SUM("totalTokens"), 0)::text AS "totalTokens"
69
+ FROM public."langchain_traces"
70
+ WHERE "startedAt" >= NOW() - INTERVAL '1 hour' * $1
71
+ `
72
+
73
+ const rows = await queryWithRLS<MetricsRow>(query, [hours], authResult.user.id)
74
+
75
+ if (rows.length === 0) {
76
+ return NextResponse.json({
77
+ success: true,
78
+ data: {
79
+ period,
80
+ totalTraces: 0,
81
+ successTraces: 0,
82
+ errorTraces: 0,
83
+ avgLatency: 0,
84
+ totalTokens: 0,
85
+ },
86
+ })
87
+ }
88
+
89
+ const row = rows[0]
90
+
91
+ // 4. Format response
92
+ return NextResponse.json({
93
+ success: true,
94
+ data: {
95
+ period,
96
+ totalTraces: parseInt(row.totalTraces, 10),
97
+ successTraces: parseInt(row.successTraces, 10),
98
+ errorTraces: parseInt(row.errorTraces, 10),
99
+ avgLatency: Math.round(parseFloat(row.avgLatency)),
100
+ totalTokens: parseInt(row.totalTokens, 10),
101
+ },
102
+ })
103
+ } catch (error) {
104
+ console.error('[Observability API] Get metrics error:', error)
105
+ return NextResponse.json(
106
+ { success: false, error: 'Failed to get metrics' },
107
+ { status: 500 }
108
+ )
109
+ }
110
+ }
@@ -0,0 +1,398 @@
1
+ /**
2
+ * GET /api/langchain/observability/traces/[traceId]
3
+ *
4
+ * Get trace detail with spans and child traces.
5
+ * Admin access required (superadmin or developer).
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from 'next/server'
9
+ import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
10
+ import { queryWithRLS } from '@nextsparkjs/core/lib/db'
11
+ import type { Trace, Span } from '../../../../types/observability.types'
12
+
13
+ interface TraceRow {
14
+ traceId: string
15
+ userId: string
16
+ teamId: string
17
+ sessionId: string | null
18
+ agentName: string
19
+ agentType: string | null
20
+ parentId: string | null
21
+ input: string
22
+ output: string | null
23
+ status: 'running' | 'success' | 'error'
24
+ error: string | null
25
+ errorType: string | null
26
+ errorStack: string | null
27
+ startedAt: Date
28
+ endedAt: Date | null
29
+ durationMs: number | null
30
+ inputTokens: number
31
+ outputTokens: number
32
+ totalTokens: number
33
+ totalCost: number
34
+ llmCalls: number
35
+ toolCalls: number
36
+ metadata: Record<string, unknown>
37
+ tags: string[] | null
38
+ createdAt: Date
39
+ }
40
+
41
+ interface SpanRow {
42
+ spanId: string
43
+ traceId: string
44
+ parentSpanId: string | null
45
+ name: string
46
+ type: 'llm' | 'tool' | 'chain' | 'retriever'
47
+ provider: string | null
48
+ model: string | null
49
+ inputTokens: number | null
50
+ outputTokens: number | null
51
+ toolName: string | null
52
+ toolInput: unknown | null
53
+ toolOutput: unknown | null
54
+ input: unknown | null
55
+ output: unknown | null
56
+ status: 'running' | 'success' | 'error'
57
+ error: string | null
58
+ startedAt: Date
59
+ endedAt: Date | null
60
+ durationMs: number | null
61
+ depth: number
62
+ createdAt: Date
63
+ }
64
+
65
+ export async function GET(
66
+ req: NextRequest,
67
+ { params }: { params: Promise<{ traceId: string }> }
68
+ ) {
69
+ // 1. Authenticate (superadmin only)
70
+ const authResult = await authenticateRequest(req)
71
+ if (!authResult.success || !authResult.user) {
72
+ return NextResponse.json(
73
+ { success: false, error: 'Unauthorized' },
74
+ { status: 401 }
75
+ )
76
+ }
77
+
78
+ // Check if user has admin-level access (superadmin or developer)
79
+ const adminRoles = ['superadmin', 'developer']
80
+ if (!adminRoles.includes(authResult.user.role)) {
81
+ return NextResponse.json(
82
+ { success: false, error: 'Forbidden: Admin access required' },
83
+ { status: 403 }
84
+ )
85
+ }
86
+
87
+ try {
88
+ const { traceId } = await params
89
+
90
+ // 2. Get trace
91
+ const traceQuery = `
92
+ SELECT
93
+ "traceId",
94
+ "userId",
95
+ "teamId",
96
+ "sessionId",
97
+ "agentName",
98
+ "agentType",
99
+ "parentId",
100
+ input,
101
+ output,
102
+ status,
103
+ error,
104
+ "errorType",
105
+ "errorStack",
106
+ "startedAt",
107
+ "endedAt",
108
+ "durationMs",
109
+ "inputTokens",
110
+ "outputTokens",
111
+ "totalTokens",
112
+ "totalCost",
113
+ "llmCalls",
114
+ "toolCalls",
115
+ metadata,
116
+ tags,
117
+ "createdAt"
118
+ FROM public."langchain_traces"
119
+ WHERE "traceId" = $1
120
+ `
121
+
122
+ const traceRows = await queryWithRLS<TraceRow>(
123
+ traceQuery,
124
+ [traceId],
125
+ authResult.user.id
126
+ )
127
+
128
+ if (traceRows.length === 0) {
129
+ return NextResponse.json(
130
+ { success: false, error: 'Trace not found' },
131
+ { status: 404 }
132
+ )
133
+ }
134
+
135
+ const traceRow = traceRows[0]
136
+
137
+ // 3. Get spans for this trace
138
+ const spansQuery = `
139
+ SELECT
140
+ "spanId",
141
+ "traceId",
142
+ "parentSpanId",
143
+ name,
144
+ type,
145
+ provider,
146
+ model,
147
+ "inputTokens",
148
+ "outputTokens",
149
+ "toolName",
150
+ "toolInput",
151
+ "toolOutput",
152
+ input,
153
+ output,
154
+ status,
155
+ error,
156
+ "startedAt",
157
+ "endedAt",
158
+ "durationMs",
159
+ depth,
160
+ "createdAt"
161
+ FROM public."langchain_spans"
162
+ WHERE "traceId" = $1
163
+ ORDER BY "startedAt" ASC
164
+ `
165
+
166
+ const spanRows = await queryWithRLS<SpanRow>(
167
+ spansQuery,
168
+ [traceId],
169
+ authResult.user.id
170
+ )
171
+
172
+ // 4. Get child traces
173
+ const childTracesQuery = `
174
+ SELECT
175
+ "traceId",
176
+ "userId",
177
+ "teamId",
178
+ "sessionId",
179
+ "agentName",
180
+ "agentType",
181
+ "parentId",
182
+ input,
183
+ output,
184
+ status,
185
+ error,
186
+ "errorType",
187
+ "errorStack",
188
+ "startedAt",
189
+ "endedAt",
190
+ "durationMs",
191
+ "inputTokens",
192
+ "outputTokens",
193
+ "totalTokens",
194
+ "totalCost",
195
+ "llmCalls",
196
+ "toolCalls",
197
+ metadata,
198
+ tags,
199
+ "createdAt"
200
+ FROM public."langchain_traces"
201
+ WHERE "parentId" = $1
202
+ ORDER BY "startedAt" ASC
203
+ `
204
+
205
+ const childTraceRows = await queryWithRLS<TraceRow>(
206
+ childTracesQuery,
207
+ [traceId],
208
+ authResult.user.id
209
+ )
210
+
211
+ // 4.5. Get spans for each child trace (for timeline display)
212
+ const childSpansMap: Record<string, Span[]> = {}
213
+ if (childTraceRows.length > 0) {
214
+ const childTraceIds = childTraceRows.map((row) => row.traceId)
215
+ const childSpansQuery = `
216
+ SELECT
217
+ "spanId",
218
+ "traceId",
219
+ "parentSpanId",
220
+ name,
221
+ type,
222
+ provider,
223
+ model,
224
+ "inputTokens",
225
+ "outputTokens",
226
+ "toolName",
227
+ "toolInput",
228
+ "toolOutput",
229
+ input,
230
+ output,
231
+ status,
232
+ error,
233
+ "startedAt",
234
+ "endedAt",
235
+ "durationMs",
236
+ depth,
237
+ "createdAt"
238
+ FROM public."langchain_spans"
239
+ WHERE "traceId" = ANY($1)
240
+ ORDER BY "traceId", "startedAt" ASC
241
+ `
242
+ const childSpanRows = await queryWithRLS<SpanRow>(
243
+ childSpansQuery,
244
+ [childTraceIds],
245
+ authResult.user.id
246
+ )
247
+
248
+ // Group spans by traceId
249
+ for (const row of childSpanRows) {
250
+ if (!childSpansMap[row.traceId]) {
251
+ childSpansMap[row.traceId] = []
252
+ }
253
+ childSpansMap[row.traceId].push({
254
+ spanId: row.spanId,
255
+ traceId: row.traceId,
256
+ parentSpanId: row.parentSpanId || undefined,
257
+ name: row.name,
258
+ type: row.type,
259
+ provider: row.provider || undefined,
260
+ model: row.model || undefined,
261
+ inputTokens: row.inputTokens || undefined,
262
+ outputTokens: row.outputTokens || undefined,
263
+ toolName: row.toolName || undefined,
264
+ toolInput: row.toolInput,
265
+ toolOutput: row.toolOutput,
266
+ input: row.input,
267
+ output: row.output,
268
+ status: row.status,
269
+ error: row.error || undefined,
270
+ startedAt: row.startedAt.toISOString(),
271
+ endedAt: row.endedAt?.toISOString(),
272
+ durationMs: row.durationMs || undefined,
273
+ depth: row.depth,
274
+ createdAt: row.createdAt.toISOString(),
275
+ })
276
+ }
277
+ }
278
+
279
+ // 5. Get parent trace if this is a child trace
280
+ let parentTrace: { traceId: string; agentName: string } | undefined
281
+ if (traceRow.parentId) {
282
+ const parentQuery = `
283
+ SELECT "traceId", "agentName"
284
+ FROM public."langchain_traces"
285
+ WHERE "traceId" = $1
286
+ `
287
+ const parentRows = await queryWithRLS<{ traceId: string; agentName: string }>(
288
+ parentQuery,
289
+ [traceRow.parentId],
290
+ authResult.user.id
291
+ )
292
+ if (parentRows.length > 0) {
293
+ parentTrace = {
294
+ traceId: parentRows[0].traceId,
295
+ agentName: parentRows[0].agentName,
296
+ }
297
+ }
298
+ }
299
+
300
+ // 6. Format response
301
+ const trace: Trace = {
302
+ traceId: traceRow.traceId,
303
+ userId: traceRow.userId,
304
+ teamId: traceRow.teamId,
305
+ sessionId: traceRow.sessionId || undefined,
306
+ agentName: traceRow.agentName,
307
+ agentType: traceRow.agentType || undefined,
308
+ parentId: traceRow.parentId || undefined,
309
+ input: traceRow.input,
310
+ output: traceRow.output || undefined,
311
+ status: traceRow.status,
312
+ error: traceRow.error || undefined,
313
+ errorType: traceRow.errorType || undefined,
314
+ errorStack: traceRow.errorStack || undefined,
315
+ startedAt: traceRow.startedAt.toISOString(),
316
+ endedAt: traceRow.endedAt?.toISOString(),
317
+ durationMs: traceRow.durationMs || undefined,
318
+ inputTokens: traceRow.inputTokens,
319
+ outputTokens: traceRow.outputTokens,
320
+ totalTokens: traceRow.totalTokens,
321
+ totalCost: traceRow.totalCost,
322
+ llmCalls: traceRow.llmCalls,
323
+ toolCalls: traceRow.toolCalls,
324
+ metadata: traceRow.metadata,
325
+ tags: traceRow.tags || undefined,
326
+ createdAt: traceRow.createdAt.toISOString(),
327
+ }
328
+
329
+ const spans: Span[] = spanRows.map((row) => ({
330
+ spanId: row.spanId,
331
+ traceId: row.traceId,
332
+ parentSpanId: row.parentSpanId || undefined,
333
+ name: row.name,
334
+ type: row.type,
335
+ provider: row.provider || undefined,
336
+ model: row.model || undefined,
337
+ inputTokens: row.inputTokens || undefined,
338
+ outputTokens: row.outputTokens || undefined,
339
+ toolName: row.toolName || undefined,
340
+ toolInput: row.toolInput,
341
+ toolOutput: row.toolOutput,
342
+ input: row.input,
343
+ output: row.output,
344
+ status: row.status,
345
+ error: row.error || undefined,
346
+ startedAt: row.startedAt.toISOString(),
347
+ endedAt: row.endedAt?.toISOString(),
348
+ durationMs: row.durationMs || undefined,
349
+ depth: row.depth,
350
+ createdAt: row.createdAt.toISOString(),
351
+ }))
352
+
353
+ const childTraces: Trace[] = childTraceRows.map((row) => ({
354
+ traceId: row.traceId,
355
+ userId: row.userId,
356
+ teamId: row.teamId,
357
+ sessionId: row.sessionId || undefined,
358
+ agentName: row.agentName,
359
+ agentType: row.agentType || undefined,
360
+ parentId: row.parentId || undefined,
361
+ input: row.input,
362
+ output: row.output || undefined,
363
+ status: row.status,
364
+ error: row.error || undefined,
365
+ errorType: row.errorType || undefined,
366
+ errorStack: row.errorStack || undefined,
367
+ startedAt: row.startedAt.toISOString(),
368
+ endedAt: row.endedAt?.toISOString(),
369
+ durationMs: row.durationMs || undefined,
370
+ inputTokens: row.inputTokens,
371
+ outputTokens: row.outputTokens,
372
+ totalTokens: row.totalTokens,
373
+ totalCost: row.totalCost,
374
+ llmCalls: row.llmCalls,
375
+ toolCalls: row.toolCalls,
376
+ metadata: row.metadata,
377
+ tags: row.tags || undefined,
378
+ createdAt: row.createdAt.toISOString(),
379
+ }))
380
+
381
+ return NextResponse.json({
382
+ success: true,
383
+ data: {
384
+ trace,
385
+ spans,
386
+ childTraces,
387
+ childSpansMap,
388
+ parentTrace,
389
+ },
390
+ })
391
+ } catch (error) {
392
+ console.error('[Observability API] Get trace detail error:', error)
393
+ return NextResponse.json(
394
+ { success: false, error: 'Failed to get trace detail' },
395
+ { status: 500 }
396
+ )
397
+ }
398
+ }