@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,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Node (GENERIC - Theme-Agnostic)
|
|
3
|
+
*
|
|
4
|
+
* Classifies user intents using structured output.
|
|
5
|
+
* Single LLM call that parses intent type, action, and parameters.
|
|
6
|
+
* Includes retry logic with Zod validation for local model compatibility.
|
|
7
|
+
*
|
|
8
|
+
* GENERIC ARCHITECTURE:
|
|
9
|
+
* - Schema and prompt are generated dynamically from OrchestratorConfig
|
|
10
|
+
* - No hardcoded knowledge of specific entities (task, customer, page)
|
|
11
|
+
* - Theme configures available tools via config
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from 'zod'
|
|
15
|
+
import { HumanMessage, SystemMessage } from '@langchain/core/messages'
|
|
16
|
+
import type { BaseMessage } from '@langchain/core/messages'
|
|
17
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models'
|
|
18
|
+
import { getModel, getStructuredOutputMethod } from '../../providers'
|
|
19
|
+
import { tracer } from '../../tracer'
|
|
20
|
+
import { config as pluginConfig } from '../../../plugin.config'
|
|
21
|
+
import type { OrchestratorState, Intent, IntentType, IntentAction, OrchestratorConfig } from '../types'
|
|
22
|
+
import { DEFAULT_GRAPH_CONFIG } from '../types'
|
|
23
|
+
|
|
24
|
+
// ============================================
|
|
25
|
+
// CONFIGURATION
|
|
26
|
+
// ============================================
|
|
27
|
+
|
|
28
|
+
const ROUTER_CONFIG = {
|
|
29
|
+
maxRetries: 3,
|
|
30
|
+
retryDelayMs: 500,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================
|
|
34
|
+
// DYNAMIC SCHEMA GENERATION
|
|
35
|
+
// ============================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create Zod schema dynamically from orchestrator config
|
|
39
|
+
*/
|
|
40
|
+
function createIntentSchema(config: OrchestratorConfig) {
|
|
41
|
+
const toolNames = config.tools.map(t => t.name)
|
|
42
|
+
const systemIntents = config.systemIntents || ['greeting', 'clarification']
|
|
43
|
+
const allIntentTypes = [...toolNames, ...systemIntents]
|
|
44
|
+
|
|
45
|
+
// Create description for intent types
|
|
46
|
+
const toolDescriptions = config.tools
|
|
47
|
+
.map(t => `${t.name} for ${t.description}`)
|
|
48
|
+
.join(', ')
|
|
49
|
+
const systemDescriptions = systemIntents
|
|
50
|
+
.map(s => s === 'greeting' ? 'greeting for hello/hi' : 'clarification if unclear')
|
|
51
|
+
.join(', ')
|
|
52
|
+
|
|
53
|
+
const IntentSchema = z.object({
|
|
54
|
+
type: z.enum(allIntentTypes as [string, ...string[]]).describe(
|
|
55
|
+
`The type of intent: ${toolDescriptions}, ${systemDescriptions}`
|
|
56
|
+
),
|
|
57
|
+
action: z.enum(['list', 'create', 'update', 'delete', 'search', 'get', 'unknown']).describe(
|
|
58
|
+
'The action to perform on the entity'
|
|
59
|
+
),
|
|
60
|
+
parameters: z.record(z.string(), z.unknown()).describe(
|
|
61
|
+
'Extracted parameters like title, priority, query, name, etc.'
|
|
62
|
+
),
|
|
63
|
+
originalText: z.string().describe(
|
|
64
|
+
'The portion of the user message that maps to this intent'
|
|
65
|
+
),
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const RouterOutputSchema = z.object({
|
|
69
|
+
intents: z.array(IntentSchema).describe(
|
|
70
|
+
'All intents extracted from the user message. Include multiple if user asks for multiple things.'
|
|
71
|
+
),
|
|
72
|
+
needsClarification: z.boolean().describe(
|
|
73
|
+
'True if the request is too vague to understand'
|
|
74
|
+
),
|
|
75
|
+
clarificationQuestion: z.string().nullable().describe(
|
|
76
|
+
'Question to ask user if clarification is needed, in their language. Null if not needed.'
|
|
77
|
+
),
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return RouterOutputSchema
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type RouterOutput = {
|
|
84
|
+
intents: Array<{
|
|
85
|
+
type: string
|
|
86
|
+
action: IntentAction
|
|
87
|
+
parameters: Record<string, unknown>
|
|
88
|
+
originalText: string
|
|
89
|
+
}>
|
|
90
|
+
needsClarification: boolean
|
|
91
|
+
clarificationQuestion: string | null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================
|
|
95
|
+
// RETRY HELPERS
|
|
96
|
+
// ============================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Extract JSON from a string that might contain markdown or extra text
|
|
100
|
+
*/
|
|
101
|
+
function extractJsonFromResponse(text: string): string {
|
|
102
|
+
// Try to find JSON in markdown code block
|
|
103
|
+
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/)
|
|
104
|
+
if (codeBlockMatch) {
|
|
105
|
+
return codeBlockMatch[1].trim()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Try to find raw JSON object
|
|
109
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/)
|
|
110
|
+
if (jsonMatch) {
|
|
111
|
+
return jsonMatch[0]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return text
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Validate and parse router output with Zod
|
|
119
|
+
* Returns null if validation fails
|
|
120
|
+
*/
|
|
121
|
+
function validateRouterOutput(data: unknown, schema: z.ZodSchema): RouterOutput | null {
|
|
122
|
+
try {
|
|
123
|
+
return schema.parse(data) as RouterOutput
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (pluginConfig.debug) {
|
|
126
|
+
console.log('[Router] Zod validation failed:', error)
|
|
127
|
+
}
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Attempt to invoke the model with structured output
|
|
134
|
+
* Returns the validated output or null on failure
|
|
135
|
+
*/
|
|
136
|
+
async function tryStructuredOutput(
|
|
137
|
+
model: BaseChatModel,
|
|
138
|
+
messages: BaseMessage[],
|
|
139
|
+
method: 'functionCalling' | 'jsonMode' | 'jsonSchema',
|
|
140
|
+
schema: z.ZodSchema
|
|
141
|
+
): Promise<RouterOutput | null> {
|
|
142
|
+
try {
|
|
143
|
+
const structuredModel = model.withStructuredOutput(schema, {
|
|
144
|
+
name: 'extract_intents',
|
|
145
|
+
method,
|
|
146
|
+
})
|
|
147
|
+
const result = await structuredModel.invoke(messages)
|
|
148
|
+
return validateRouterOutput(result, schema)
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (pluginConfig.debug) {
|
|
151
|
+
console.log('[Router] Structured output failed:', error)
|
|
152
|
+
}
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Fallback: invoke model without structured output and parse JSON manually
|
|
159
|
+
*/
|
|
160
|
+
async function tryManualJsonParsing(
|
|
161
|
+
model: BaseChatModel,
|
|
162
|
+
messages: BaseMessage[],
|
|
163
|
+
schema: z.ZodSchema
|
|
164
|
+
): Promise<RouterOutput | null> {
|
|
165
|
+
try {
|
|
166
|
+
const result = await model.invoke(messages)
|
|
167
|
+
const content = typeof result.content === 'string'
|
|
168
|
+
? result.content
|
|
169
|
+
: JSON.stringify(result.content)
|
|
170
|
+
|
|
171
|
+
const jsonStr = extractJsonFromResponse(content)
|
|
172
|
+
const parsed = JSON.parse(jsonStr)
|
|
173
|
+
return validateRouterOutput(parsed, schema)
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (pluginConfig.debug) {
|
|
176
|
+
console.log('[Router] Manual JSON parsing failed:', error)
|
|
177
|
+
}
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Invoke router with retry logic
|
|
184
|
+
* Tries structured output first, then falls back to manual parsing
|
|
185
|
+
*/
|
|
186
|
+
async function invokeRouterWithRetry(
|
|
187
|
+
model: BaseChatModel,
|
|
188
|
+
messages: BaseMessage[],
|
|
189
|
+
structuredOutputMethod: 'functionCalling' | 'jsonMode' | 'jsonSchema',
|
|
190
|
+
schema: z.ZodSchema
|
|
191
|
+
): Promise<RouterOutput> {
|
|
192
|
+
let lastError: Error | null = null
|
|
193
|
+
|
|
194
|
+
for (let attempt = 1; attempt <= ROUTER_CONFIG.maxRetries; attempt++) {
|
|
195
|
+
if (pluginConfig.debug && attempt > 1) {
|
|
196
|
+
console.log(`[Router] Retry attempt ${attempt}/${ROUTER_CONFIG.maxRetries}`)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Try structured output first
|
|
200
|
+
const structuredResult = await tryStructuredOutput(model, messages, structuredOutputMethod, schema)
|
|
201
|
+
if (structuredResult) {
|
|
202
|
+
if (pluginConfig.debug && attempt > 1) {
|
|
203
|
+
console.log('[Router] Succeeded on retry with structured output')
|
|
204
|
+
}
|
|
205
|
+
return structuredResult
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Fallback to manual JSON parsing
|
|
209
|
+
if (pluginConfig.debug) {
|
|
210
|
+
console.log('[Router] Falling back to manual JSON parsing')
|
|
211
|
+
}
|
|
212
|
+
const manualResult = await tryManualJsonParsing(model, messages, schema)
|
|
213
|
+
if (manualResult) {
|
|
214
|
+
if (pluginConfig.debug) {
|
|
215
|
+
console.log('[Router] Succeeded with manual JSON parsing')
|
|
216
|
+
}
|
|
217
|
+
return manualResult
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Wait before retry (exponential backoff)
|
|
221
|
+
if (attempt < ROUTER_CONFIG.maxRetries) {
|
|
222
|
+
const delay = ROUTER_CONFIG.retryDelayMs * Math.pow(2, attempt - 1)
|
|
223
|
+
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// All retries failed - throw error
|
|
228
|
+
throw lastError || new Error('Router failed after all retry attempts')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ============================================
|
|
232
|
+
// DYNAMIC PROMPT GENERATION
|
|
233
|
+
// ============================================
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Generate router prompt dynamically from orchestrator config
|
|
237
|
+
*/
|
|
238
|
+
function createRouterPrompt(config: OrchestratorConfig): string {
|
|
239
|
+
const toolNames = config.tools.map(t => t.name)
|
|
240
|
+
const systemIntents = config.systemIntents || ['greeting', 'clarification']
|
|
241
|
+
|
|
242
|
+
// Build intent types section
|
|
243
|
+
const intentTypesSection = [
|
|
244
|
+
...config.tools.map(tool => `- ${tool.name}: ${tool.description}`),
|
|
245
|
+
...systemIntents.map(intent =>
|
|
246
|
+
intent === 'greeting' ? '- greeting: Greeting or small talk' : '- clarification: Request is too vague to understand'
|
|
247
|
+
)
|
|
248
|
+
].join('\n')
|
|
249
|
+
|
|
250
|
+
// Build parameter examples section
|
|
251
|
+
const parameterExamplesSection = config.tools
|
|
252
|
+
.filter(t => t.exampleParameters)
|
|
253
|
+
.map(t => `- ${t.name}: ${t.exampleParameters}`)
|
|
254
|
+
.join('\n')
|
|
255
|
+
|
|
256
|
+
// Build type union for JSON format
|
|
257
|
+
const typeUnion = [...toolNames, ...systemIntents].map(t => `"${t}"`).join(' | ')
|
|
258
|
+
|
|
259
|
+
// Build examples dynamically from first tool (if available)
|
|
260
|
+
const exampleTool = config.tools[0]
|
|
261
|
+
const examplePrompts = exampleTool ? `
|
|
262
|
+
User: "Show me my ${exampleTool.name}s"
|
|
263
|
+
Response: {"intents": [{"type": "${exampleTool.name}", "action": "list", "parameters": {}, "originalText": "Show me my ${exampleTool.name}s"}], "needsClarification": false}
|
|
264
|
+
|
|
265
|
+
User: "Create ${exampleTool.name} 'Example' high priority"
|
|
266
|
+
Response: {"intents": [{"type": "${exampleTool.name}", "action": "create", "parameters": {"title": "Example", "priority": "high"}, "originalText": "Create ${exampleTool.name} 'Example' high priority"}], "needsClarification": false}
|
|
267
|
+
` : ''
|
|
268
|
+
|
|
269
|
+
const multiToolExample = config.tools.length >= 2 ? `
|
|
270
|
+
User: "Show my ${config.tools[0].name}s and find ${config.tools[1].name} data"
|
|
271
|
+
Response: {"intents": [{"type": "${config.tools[0].name}", "action": "list", "parameters": {}, "originalText": "Show my ${config.tools[0].name}s"}, {"type": "${config.tools[1].name}", "action": "search", "parameters": {"query": "data"}, "originalText": "find ${config.tools[1].name} data"}], "needsClarification": false}
|
|
272
|
+
` : ''
|
|
273
|
+
|
|
274
|
+
return `You are an intent classifier for a multi-agent system. Your job is to analyze user messages and extract ALL intents.
|
|
275
|
+
|
|
276
|
+
IMPORTANT: You MUST respond with valid JSON only. No additional text or explanation.
|
|
277
|
+
|
|
278
|
+
## Intent Types
|
|
279
|
+
${intentTypesSection}
|
|
280
|
+
|
|
281
|
+
## Rules
|
|
282
|
+
1. Extract ALL intents if user asks for multiple things
|
|
283
|
+
2. Be specific with parameters (title, priority, query, etc.)
|
|
284
|
+
3. Preserve user's language for clarification questions
|
|
285
|
+
4. Use clarification only when truly unclear
|
|
286
|
+
5. Map originalText to the relevant portion of the message
|
|
287
|
+
|
|
288
|
+
## Parameter Examples
|
|
289
|
+
${parameterExamplesSection}
|
|
290
|
+
|
|
291
|
+
## JSON Output Format
|
|
292
|
+
{
|
|
293
|
+
"intents": [
|
|
294
|
+
{
|
|
295
|
+
"type": ${typeUnion},
|
|
296
|
+
"action": "list" | "create" | "update" | "delete" | "search" | "get" | "unknown",
|
|
297
|
+
"parameters": {},
|
|
298
|
+
"originalText": "portion of user message"
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
"needsClarification": false,
|
|
302
|
+
"clarificationQuestion": null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
## Examples
|
|
306
|
+
${examplePrompts}${multiToolExample}
|
|
307
|
+
User: "Hola"
|
|
308
|
+
Response: {"intents": [{"type": "greeting", "action": "unknown", "parameters": {}, "originalText": "Hola"}], "needsClarification": false}
|
|
309
|
+
|
|
310
|
+
${config.routerPromptExtras || ''}`
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ============================================
|
|
314
|
+
// ROUTER NODE FACTORY (GENERIC)
|
|
315
|
+
// ============================================
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Create router node with configuration
|
|
319
|
+
*
|
|
320
|
+
* Uses structured output for reliable JSON extraction.
|
|
321
|
+
* Single LLM call replaces multiple ReAct iterations.
|
|
322
|
+
*
|
|
323
|
+
* @param config - Orchestrator configuration with tools
|
|
324
|
+
* @returns Router node function
|
|
325
|
+
*/
|
|
326
|
+
export function createRouterNode(config: OrchestratorConfig) {
|
|
327
|
+
// Generate schema and prompt from config (done once at graph creation)
|
|
328
|
+
const RouterOutputSchema = createIntentSchema(config)
|
|
329
|
+
const routerPrompt = createRouterPrompt(config)
|
|
330
|
+
|
|
331
|
+
// Return the router node function
|
|
332
|
+
return async function routerNode(
|
|
333
|
+
state: OrchestratorState
|
|
334
|
+
): Promise<Partial<OrchestratorState>> {
|
|
335
|
+
const { context, traceId, modelConfig } = state
|
|
336
|
+
|
|
337
|
+
// Use model config from state (injected by theme)
|
|
338
|
+
// Fallback to defaults if not provided
|
|
339
|
+
const modelCfg = modelConfig || {
|
|
340
|
+
provider: 'openai',
|
|
341
|
+
model: undefined,
|
|
342
|
+
temperature: DEFAULT_GRAPH_CONFIG.routerTemperature,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Start span for router with provider/model info
|
|
346
|
+
const spanContext = traceId
|
|
347
|
+
? await tracer.startSpan(
|
|
348
|
+
{ userId: context.userId, teamId: context.teamId },
|
|
349
|
+
traceId,
|
|
350
|
+
{
|
|
351
|
+
name: 'router',
|
|
352
|
+
type: 'llm',
|
|
353
|
+
provider: modelCfg.provider,
|
|
354
|
+
model: modelCfg.model,
|
|
355
|
+
input: { message: state.input },
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
: null
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
// Get model with orchestrator's provider and low temperature for consistent classification
|
|
362
|
+
const model = getModel(modelCfg)
|
|
363
|
+
|
|
364
|
+
// Automatically detect the best structured output method for the provider
|
|
365
|
+
// (jsonSchema for LM Studio, functionCalling for OpenAI/Anthropic/Ollama)
|
|
366
|
+
const structuredOutputMethod = getStructuredOutputMethod(modelCfg)
|
|
367
|
+
|
|
368
|
+
// Build messages with recent conversation context
|
|
369
|
+
const recentHistory = state.conversationHistory.slice(-DEFAULT_GRAPH_CONFIG.maxHistoryMessages)
|
|
370
|
+
|
|
371
|
+
const messages = [
|
|
372
|
+
new SystemMessage(routerPrompt),
|
|
373
|
+
...recentHistory,
|
|
374
|
+
new HumanMessage(state.input),
|
|
375
|
+
]
|
|
376
|
+
|
|
377
|
+
// Log in debug mode
|
|
378
|
+
if (pluginConfig.debug) {
|
|
379
|
+
console.log('[Router] Classifying intent for:', state.input)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Invoke with retry logic and Zod validation
|
|
383
|
+
const result = await invokeRouterWithRetry(model, messages, structuredOutputMethod, RouterOutputSchema)
|
|
384
|
+
|
|
385
|
+
if (pluginConfig.debug) {
|
|
386
|
+
console.log('[Router] Classified intents:', JSON.stringify(result.intents, null, 2))
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Transform to our Intent type
|
|
390
|
+
const intents: Intent[] = result.intents.map((intent) => ({
|
|
391
|
+
type: intent.type as IntentType,
|
|
392
|
+
action: intent.action as IntentAction,
|
|
393
|
+
parameters: intent.parameters as Record<string, unknown>,
|
|
394
|
+
originalText: intent.originalText,
|
|
395
|
+
}))
|
|
396
|
+
|
|
397
|
+
// End span with success
|
|
398
|
+
if (spanContext && traceId) {
|
|
399
|
+
await tracer.endSpan(
|
|
400
|
+
{ userId: context.userId, teamId: context.teamId },
|
|
401
|
+
traceId,
|
|
402
|
+
spanContext.spanId,
|
|
403
|
+
{
|
|
404
|
+
output: {
|
|
405
|
+
intentsCount: intents.length,
|
|
406
|
+
intents: intents.map((i) => ({ type: i.type, action: i.action })),
|
|
407
|
+
needsClarification: result.needsClarification,
|
|
408
|
+
},
|
|
409
|
+
}
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
intents,
|
|
415
|
+
needsClarification: result.needsClarification,
|
|
416
|
+
clarificationQuestion: result.clarificationQuestion ?? undefined,
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error('[Router] Error classifying intent:', error)
|
|
420
|
+
|
|
421
|
+
// End span with error
|
|
422
|
+
if (spanContext && traceId) {
|
|
423
|
+
await tracer.endSpan(
|
|
424
|
+
{ userId: context.userId, teamId: context.teamId },
|
|
425
|
+
traceId,
|
|
426
|
+
spanContext.spanId,
|
|
427
|
+
{ error: error instanceof Error ? error : new Error(String(error)) }
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
intents: [],
|
|
433
|
+
needsClarification: true,
|
|
434
|
+
clarificationQuestion:
|
|
435
|
+
'I encountered an error understanding your request. Could you please rephrase it?',
|
|
436
|
+
error: error instanceof Error ? error.message : 'Router classification failed',
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|