@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
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
|
+
}
|