@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,689 @@
|
|
|
1
|
+
# Tools
|
|
2
|
+
|
|
3
|
+
Tools are how agents interact with your data. This guide covers creating, configuring, and best practices for building agent tools.
|
|
4
|
+
|
|
5
|
+
## What is a Tool?
|
|
6
|
+
|
|
7
|
+
A tool is a function that an agent can call to perform actions or retrieve data. Tools have:
|
|
8
|
+
|
|
9
|
+
1. **Name**: Unique identifier for the tool
|
|
10
|
+
2. **Description**: Explains when to use the tool (read by the LLM)
|
|
11
|
+
3. **Schema**: Zod schema defining input parameters
|
|
12
|
+
4. **Function**: Async function that executes the action
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
interface ToolDefinition<T extends z.ZodObject<any>> {
|
|
16
|
+
name: string
|
|
17
|
+
description: string
|
|
18
|
+
schema: T
|
|
19
|
+
func: (input: z.infer<T>) => Promise<string>
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Creating Tools
|
|
24
|
+
|
|
25
|
+
### Basic Tool
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { z } from 'zod'
|
|
29
|
+
import type { ToolDefinition } from '@/contents/plugins/langchain/lib/tools-builder'
|
|
30
|
+
|
|
31
|
+
const listItemsTool: ToolDefinition<typeof schema> = {
|
|
32
|
+
name: 'list_items',
|
|
33
|
+
description: 'List all items for the current user',
|
|
34
|
+
schema: z.object({
|
|
35
|
+
limit: z.number().optional().default(20).describe('Max items to return'),
|
|
36
|
+
}),
|
|
37
|
+
func: async ({ limit }) => {
|
|
38
|
+
const items = await ItemService.list(userId, { limit })
|
|
39
|
+
return JSON.stringify(items)
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Tool Factory Pattern
|
|
45
|
+
|
|
46
|
+
The recommended pattern is to create a factory function that receives context:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { z } from 'zod'
|
|
50
|
+
import type { ToolDefinition } from '@/contents/plugins/langchain/lib/tools-builder'
|
|
51
|
+
|
|
52
|
+
interface ToolContext {
|
|
53
|
+
userId: string
|
|
54
|
+
teamId: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createItemTools(context: ToolContext): ToolDefinition<any>[] {
|
|
58
|
+
const { userId, teamId } = context
|
|
59
|
+
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
name: 'list_items',
|
|
63
|
+
description: 'List all items for the current user',
|
|
64
|
+
schema: z.object({
|
|
65
|
+
limit: z.number().optional().default(20),
|
|
66
|
+
}),
|
|
67
|
+
func: async ({ limit }) => {
|
|
68
|
+
const items = await ItemService.list(userId, { limit })
|
|
69
|
+
return JSON.stringify(items)
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'create_item',
|
|
74
|
+
description: 'Create a new item',
|
|
75
|
+
schema: z.object({
|
|
76
|
+
name: z.string().describe('Item name'),
|
|
77
|
+
description: z.string().optional().describe('Item description'),
|
|
78
|
+
}),
|
|
79
|
+
func: async (data) => {
|
|
80
|
+
const item = await ItemService.create(userId, { ...data, teamId })
|
|
81
|
+
return JSON.stringify(item)
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Zod Schema Best Practices
|
|
89
|
+
|
|
90
|
+
### Use Descriptive Fields
|
|
91
|
+
|
|
92
|
+
The `.describe()` method helps the LLM understand parameters:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
schema: z.object({
|
|
96
|
+
// Good - describes the field purpose
|
|
97
|
+
query: z.string().describe('Search term to match against item names'),
|
|
98
|
+
|
|
99
|
+
// Good - explains valid values
|
|
100
|
+
status: z.enum(['active', 'completed', 'archived'])
|
|
101
|
+
.describe('Filter by item status'),
|
|
102
|
+
|
|
103
|
+
// Good - explains defaults
|
|
104
|
+
limit: z.number().optional().default(20)
|
|
105
|
+
.describe('Maximum items to return (default: 20)'),
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Required vs Optional Fields
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
schema: z.object({
|
|
113
|
+
// Required - must be provided
|
|
114
|
+
name: z.string().min(1).describe('Item name (required)'),
|
|
115
|
+
|
|
116
|
+
// Optional with default
|
|
117
|
+
priority: z.enum(['low', 'medium', 'high'])
|
|
118
|
+
.optional()
|
|
119
|
+
.default('medium')
|
|
120
|
+
.describe('Priority level'),
|
|
121
|
+
|
|
122
|
+
// Optional, truly optional
|
|
123
|
+
notes: z.string().optional().describe('Additional notes'),
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Common Schema Patterns
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Pagination
|
|
131
|
+
z.object({
|
|
132
|
+
limit: z.number().optional().default(20),
|
|
133
|
+
offset: z.number().optional().default(0),
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// Search
|
|
137
|
+
z.object({
|
|
138
|
+
query: z.string().min(1).describe('Search query'),
|
|
139
|
+
limit: z.number().optional().default(10),
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Entity ID
|
|
143
|
+
z.object({
|
|
144
|
+
entityId: z.string().describe('The unique identifier of the entity'),
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// Date handling
|
|
148
|
+
z.object({
|
|
149
|
+
dueDate: z.string().optional().describe('Due date in ISO format (YYYY-MM-DD)'),
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// Arrays
|
|
153
|
+
z.object({
|
|
154
|
+
tags: z.array(z.string()).optional().describe('List of tags'),
|
|
155
|
+
days: z.array(z.enum(['mon', 'tue', 'wed', 'thu', 'fri']))
|
|
156
|
+
.optional()
|
|
157
|
+
.describe('Days of the week'),
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Real-World Examples
|
|
162
|
+
|
|
163
|
+
### Task Tools
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// tools/tasks.ts
|
|
167
|
+
import { z } from 'zod'
|
|
168
|
+
import { TasksService } from '@/themes/default/entities/tasks/tasks.service'
|
|
169
|
+
import type { ToolDefinition } from '@/contents/plugins/langchain/lib/tools-builder'
|
|
170
|
+
|
|
171
|
+
interface TaskToolContext {
|
|
172
|
+
userId: string
|
|
173
|
+
teamId: string
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function createTaskTools(context: TaskToolContext): ToolDefinition<any>[] {
|
|
177
|
+
const { userId, teamId } = context
|
|
178
|
+
|
|
179
|
+
return [
|
|
180
|
+
{
|
|
181
|
+
name: 'list_tasks',
|
|
182
|
+
description: 'List tasks with optional filtering by status or priority.',
|
|
183
|
+
schema: z.object({
|
|
184
|
+
status: z.enum(['todo', 'in-progress', 'review', 'done', 'blocked'])
|
|
185
|
+
.optional()
|
|
186
|
+
.describe('Filter by task status'),
|
|
187
|
+
priority: z.enum(['low', 'medium', 'high', 'urgent'])
|
|
188
|
+
.optional()
|
|
189
|
+
.describe('Filter by priority level'),
|
|
190
|
+
}),
|
|
191
|
+
func: async (params) => {
|
|
192
|
+
try {
|
|
193
|
+
const result = await TasksService.list(userId, params)
|
|
194
|
+
return JSON.stringify(result.tasks.map(t => ({
|
|
195
|
+
id: t.id,
|
|
196
|
+
title: t.title,
|
|
197
|
+
status: t.status,
|
|
198
|
+
priority: t.priority,
|
|
199
|
+
dueDate: t.dueDate,
|
|
200
|
+
})), null, 2)
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return `Error listing tasks: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'search_tasks',
|
|
208
|
+
description: 'Search tasks by keyword in title or description.',
|
|
209
|
+
schema: z.object({
|
|
210
|
+
query: z.string().describe('Search term'),
|
|
211
|
+
}),
|
|
212
|
+
func: async ({ query }) => {
|
|
213
|
+
try {
|
|
214
|
+
const result = await TasksService.list(userId, {})
|
|
215
|
+
const filtered = result.tasks.filter(t =>
|
|
216
|
+
t.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
217
|
+
t.description?.toLowerCase().includes(query.toLowerCase())
|
|
218
|
+
)
|
|
219
|
+
return JSON.stringify(filtered.map(t => ({
|
|
220
|
+
id: t.id,
|
|
221
|
+
title: t.title,
|
|
222
|
+
status: t.status,
|
|
223
|
+
})), null, 2)
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return `Error searching tasks: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'get_task_details',
|
|
231
|
+
description: 'Get full details of a specific task by ID.',
|
|
232
|
+
schema: z.object({
|
|
233
|
+
taskId: z.string().describe('The task ID to retrieve'),
|
|
234
|
+
}),
|
|
235
|
+
func: async ({ taskId }) => {
|
|
236
|
+
try {
|
|
237
|
+
const task = await TasksService.getById(taskId, userId)
|
|
238
|
+
if (!task) {
|
|
239
|
+
return JSON.stringify({ error: 'Task not found' })
|
|
240
|
+
}
|
|
241
|
+
return JSON.stringify(task, null, 2)
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return `Error getting task: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'create_task',
|
|
249
|
+
description: 'Create a new task with title and optional details.',
|
|
250
|
+
schema: z.object({
|
|
251
|
+
title: z.string().min(1).describe('Task title (required)'),
|
|
252
|
+
description: z.string().optional().describe('Task description'),
|
|
253
|
+
priority: z.enum(['low', 'medium', 'high'])
|
|
254
|
+
.optional()
|
|
255
|
+
.default('medium')
|
|
256
|
+
.describe('Priority level'),
|
|
257
|
+
dueDate: z.string().optional().describe('Due date (ISO format)'),
|
|
258
|
+
}),
|
|
259
|
+
func: async (data) => {
|
|
260
|
+
try {
|
|
261
|
+
const task = await TasksService.create(userId, {
|
|
262
|
+
...data,
|
|
263
|
+
teamId,
|
|
264
|
+
status: 'todo',
|
|
265
|
+
})
|
|
266
|
+
return JSON.stringify({
|
|
267
|
+
success: true,
|
|
268
|
+
task,
|
|
269
|
+
message: `Task created: ${task.title}`,
|
|
270
|
+
link: `/dashboard/tasks/${task.id}`,
|
|
271
|
+
}, null, 2)
|
|
272
|
+
} catch (error) {
|
|
273
|
+
return `Error creating task: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: 'update_task',
|
|
279
|
+
description: 'Update an existing task. Only specify fields you want to change.',
|
|
280
|
+
schema: z.object({
|
|
281
|
+
taskId: z.string().describe('The task ID to update'),
|
|
282
|
+
title: z.string().optional().describe('New title'),
|
|
283
|
+
description: z.string().optional().describe('New description'),
|
|
284
|
+
status: z.enum(['todo', 'in-progress', 'review', 'done', 'blocked']).optional(),
|
|
285
|
+
priority: z.enum(['low', 'medium', 'high']).optional(),
|
|
286
|
+
dueDate: z.string().optional(),
|
|
287
|
+
}),
|
|
288
|
+
func: async ({ taskId, ...updates }) => {
|
|
289
|
+
try {
|
|
290
|
+
const task = await TasksService.update(userId, taskId, updates)
|
|
291
|
+
return JSON.stringify({
|
|
292
|
+
success: true,
|
|
293
|
+
task,
|
|
294
|
+
message: `Task updated: ${task.title}`,
|
|
295
|
+
link: `/dashboard/tasks/${task.id}`,
|
|
296
|
+
}, null, 2)
|
|
297
|
+
} catch (error) {
|
|
298
|
+
return `Error updating task: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
]
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Customer Tools
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// tools/customers.ts
|
|
310
|
+
import { z } from 'zod'
|
|
311
|
+
import { CustomersService } from '@/themes/default/entities/customers/customers.service'
|
|
312
|
+
import type { ToolDefinition } from '@/contents/plugins/langchain/lib/tools-builder'
|
|
313
|
+
|
|
314
|
+
export function createCustomerTools(context: { userId: string; teamId: string }): ToolDefinition<any>[] {
|
|
315
|
+
const { userId, teamId } = context
|
|
316
|
+
|
|
317
|
+
return [
|
|
318
|
+
{
|
|
319
|
+
name: 'list_customers',
|
|
320
|
+
description: 'List all customers with optional pagination and sorting.',
|
|
321
|
+
schema: z.object({
|
|
322
|
+
limit: z.number().optional().default(20).describe('Max customers'),
|
|
323
|
+
offset: z.number().optional().default(0).describe('Pagination offset'),
|
|
324
|
+
orderBy: z.enum(['name', 'account', 'office', 'salesRep', 'createdAt'])
|
|
325
|
+
.optional()
|
|
326
|
+
.describe('Sort field'),
|
|
327
|
+
orderDir: z.enum(['asc', 'desc']).optional().describe('Sort direction'),
|
|
328
|
+
}),
|
|
329
|
+
func: async (params) => {
|
|
330
|
+
try {
|
|
331
|
+
const result = await CustomersService.list(userId, params)
|
|
332
|
+
return JSON.stringify({
|
|
333
|
+
customers: result.customers.map(c => ({
|
|
334
|
+
id: c.id,
|
|
335
|
+
name: c.name,
|
|
336
|
+
account: c.account,
|
|
337
|
+
office: c.office,
|
|
338
|
+
phone: c.phone,
|
|
339
|
+
})),
|
|
340
|
+
total: result.total,
|
|
341
|
+
}, null, 2)
|
|
342
|
+
} catch (error) {
|
|
343
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: 'search_customers',
|
|
349
|
+
description: 'Search customers by name, account, office, or sales rep.',
|
|
350
|
+
schema: z.object({
|
|
351
|
+
query: z.string().describe('Search term'),
|
|
352
|
+
limit: z.number().optional().default(10),
|
|
353
|
+
}),
|
|
354
|
+
func: async (params) => {
|
|
355
|
+
try {
|
|
356
|
+
const results = await CustomersService.search(userId, params)
|
|
357
|
+
return JSON.stringify(results.map(c => ({
|
|
358
|
+
id: c.id,
|
|
359
|
+
name: c.name,
|
|
360
|
+
account: c.account,
|
|
361
|
+
office: c.office,
|
|
362
|
+
phone: c.phone,
|
|
363
|
+
})), null, 2)
|
|
364
|
+
} catch (error) {
|
|
365
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: 'get_customer',
|
|
371
|
+
description: 'Get full details of a specific customer by ID.',
|
|
372
|
+
schema: z.object({
|
|
373
|
+
customerId: z.string().describe('The customer ID to retrieve'),
|
|
374
|
+
}),
|
|
375
|
+
func: async ({ customerId }) => {
|
|
376
|
+
try {
|
|
377
|
+
const customer = await CustomersService.getById(customerId, userId)
|
|
378
|
+
if (!customer) {
|
|
379
|
+
return JSON.stringify({ error: 'Customer not found' })
|
|
380
|
+
}
|
|
381
|
+
return JSON.stringify(customer, null, 2)
|
|
382
|
+
} catch (error) {
|
|
383
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'create_customer',
|
|
389
|
+
description: 'Create a new customer. Name, account, and office are required.',
|
|
390
|
+
schema: z.object({
|
|
391
|
+
name: z.string().describe('Customer or company name'),
|
|
392
|
+
account: z.number().describe('Unique account number'),
|
|
393
|
+
office: z.string().describe('Office location or branch'),
|
|
394
|
+
phone: z.string().optional().describe('Contact phone number'),
|
|
395
|
+
salesRep: z.string().optional().describe('Assigned sales representative'),
|
|
396
|
+
visitDays: z.array(z.enum(['lun', 'mar', 'mie', 'jue', 'vie']))
|
|
397
|
+
.optional()
|
|
398
|
+
.describe('Days for in-person visits'),
|
|
399
|
+
contactDays: z.array(z.enum(['lun', 'mar', 'mie', 'jue', 'vie']))
|
|
400
|
+
.optional()
|
|
401
|
+
.describe('Days for phone/email contact'),
|
|
402
|
+
}),
|
|
403
|
+
func: async (data) => {
|
|
404
|
+
try {
|
|
405
|
+
const customer = await CustomersService.create(userId, {
|
|
406
|
+
...data,
|
|
407
|
+
teamId,
|
|
408
|
+
})
|
|
409
|
+
return JSON.stringify({
|
|
410
|
+
success: true,
|
|
411
|
+
customer,
|
|
412
|
+
link: `/dashboard/customers/${customer.id}`,
|
|
413
|
+
}, null, 2)
|
|
414
|
+
} catch (error) {
|
|
415
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
name: 'update_customer',
|
|
421
|
+
description: 'Update a customer. Only specify fields to change.',
|
|
422
|
+
schema: z.object({
|
|
423
|
+
customerId: z.string().describe('The customer ID to update'),
|
|
424
|
+
name: z.string().optional(),
|
|
425
|
+
phone: z.string().optional(),
|
|
426
|
+
office: z.string().optional(),
|
|
427
|
+
salesRep: z.string().optional(),
|
|
428
|
+
visitDays: z.array(z.enum(['lun', 'mar', 'mie', 'jue', 'vie']))
|
|
429
|
+
.optional()
|
|
430
|
+
.describe('New visit days'),
|
|
431
|
+
contactDays: z.array(z.enum(['lun', 'mar', 'mie', 'jue', 'vie']))
|
|
432
|
+
.optional()
|
|
433
|
+
.describe('New contact days'),
|
|
434
|
+
}),
|
|
435
|
+
func: async ({ customerId, ...updates }) => {
|
|
436
|
+
try {
|
|
437
|
+
const customer = await CustomersService.update(userId, customerId, updates)
|
|
438
|
+
return JSON.stringify({
|
|
439
|
+
success: true,
|
|
440
|
+
customer,
|
|
441
|
+
link: `/dashboard/customers/${customer.id}`,
|
|
442
|
+
}, null, 2)
|
|
443
|
+
} catch (error) {
|
|
444
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: 'delete_customer',
|
|
450
|
+
description: 'Delete a customer permanently. This action cannot be undone.',
|
|
451
|
+
schema: z.object({
|
|
452
|
+
customerId: z.string().describe('The customer ID to delete'),
|
|
453
|
+
}),
|
|
454
|
+
func: async ({ customerId }) => {
|
|
455
|
+
try {
|
|
456
|
+
const success = await CustomersService.delete(userId, customerId)
|
|
457
|
+
return JSON.stringify({
|
|
458
|
+
success,
|
|
459
|
+
message: success ? 'Customer deleted successfully' : 'Failed to delete customer',
|
|
460
|
+
})
|
|
461
|
+
} catch (error) {
|
|
462
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown'}`
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Orchestrator Tools
|
|
471
|
+
|
|
472
|
+
Orchestrator tools are special - they route requests to other agents:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
// tools/orchestrator.ts
|
|
476
|
+
import { z } from 'zod'
|
|
477
|
+
import type { ToolDefinition } from '@/contents/plugins/langchain/lib/tools-builder'
|
|
478
|
+
|
|
479
|
+
export interface RoutingResult {
|
|
480
|
+
agent: 'task' | 'customer' | 'page'
|
|
481
|
+
message: string
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export interface ClarificationResult {
|
|
485
|
+
action: 'clarify'
|
|
486
|
+
question: string
|
|
487
|
+
options: Array<{ label: string; description: string }>
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export function createOrchestratorTools(): ToolDefinition<any>[] {
|
|
491
|
+
return [
|
|
492
|
+
{
|
|
493
|
+
name: 'route_to_task',
|
|
494
|
+
description: 'Route to task agent for task-related requests.',
|
|
495
|
+
schema: z.object({
|
|
496
|
+
message: z.string().describe('The user message to forward'),
|
|
497
|
+
}),
|
|
498
|
+
func: async ({ message }) => {
|
|
499
|
+
return JSON.stringify({ agent: 'task', message } as RoutingResult)
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'route_to_customer',
|
|
504
|
+
description: 'Route to customer agent for customer-related requests.',
|
|
505
|
+
schema: z.object({
|
|
506
|
+
message: z.string().describe('The user message to forward'),
|
|
507
|
+
}),
|
|
508
|
+
func: async ({ message }) => {
|
|
509
|
+
return JSON.stringify({ agent: 'customer', message } as RoutingResult)
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: 'route_to_page',
|
|
514
|
+
description: 'Route to page agent for content/page requests.',
|
|
515
|
+
schema: z.object({
|
|
516
|
+
message: z.string().describe('The user message to forward'),
|
|
517
|
+
}),
|
|
518
|
+
func: async ({ message }) => {
|
|
519
|
+
return JSON.stringify({ agent: 'page', message } as RoutingResult)
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: 'ask_clarification',
|
|
524
|
+
description: 'Ask user for clarification when intent is unclear.',
|
|
525
|
+
schema: z.object({
|
|
526
|
+
question: z.string().describe('The clarifying question'),
|
|
527
|
+
options: z.array(z.object({
|
|
528
|
+
label: z.string(),
|
|
529
|
+
description: z.string(),
|
|
530
|
+
})).describe('Options for the user'),
|
|
531
|
+
}),
|
|
532
|
+
func: async ({ question, options }) => {
|
|
533
|
+
return JSON.stringify({
|
|
534
|
+
action: 'clarify',
|
|
535
|
+
question,
|
|
536
|
+
options,
|
|
537
|
+
} as ClarificationResult)
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
]
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Building Tools with buildTools
|
|
545
|
+
|
|
546
|
+
The plugin provides a `buildTools` helper:
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
import { buildTools, type ToolDefinition } from '@/contents/plugins/langchain/lib/tools-builder'
|
|
550
|
+
|
|
551
|
+
const toolDefinitions: ToolDefinition<any>[] = [
|
|
552
|
+
{ name: 'tool1', schema: z.object({}), ... },
|
|
553
|
+
{ name: 'tool2', schema: z.object({}), ... },
|
|
554
|
+
]
|
|
555
|
+
|
|
556
|
+
// Convert to LangChain DynamicStructuredTool instances
|
|
557
|
+
const langChainTools = buildTools(toolDefinitions)
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## Tool Response Format
|
|
561
|
+
|
|
562
|
+
Tools should return JSON strings:
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
// Good - structured response
|
|
566
|
+
func: async () => {
|
|
567
|
+
const data = await Service.getData()
|
|
568
|
+
return JSON.stringify(data, null, 2)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Good - with metadata
|
|
572
|
+
func: async () => {
|
|
573
|
+
const item = await Service.create(...)
|
|
574
|
+
return JSON.stringify({
|
|
575
|
+
success: true,
|
|
576
|
+
item,
|
|
577
|
+
message: 'Created successfully',
|
|
578
|
+
link: `/items/${item.id}`,
|
|
579
|
+
}, null, 2)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Good - error handling
|
|
583
|
+
func: async () => {
|
|
584
|
+
try {
|
|
585
|
+
const data = await Service.getData()
|
|
586
|
+
return JSON.stringify(data)
|
|
587
|
+
} catch (error) {
|
|
588
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
## Error Handling
|
|
594
|
+
|
|
595
|
+
Always handle errors gracefully:
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
{
|
|
599
|
+
name: 'risky_operation',
|
|
600
|
+
schema: z.object({ id: z.string() }),
|
|
601
|
+
func: async ({ id }) => {
|
|
602
|
+
try {
|
|
603
|
+
const result = await Service.riskyOperation(id)
|
|
604
|
+
return JSON.stringify({ success: true, result })
|
|
605
|
+
} catch (error) {
|
|
606
|
+
// Return error as string - agent can inform user
|
|
607
|
+
if (error instanceof NotFoundError) {
|
|
608
|
+
return JSON.stringify({ error: 'Item not found', id })
|
|
609
|
+
}
|
|
610
|
+
if (error instanceof PermissionError) {
|
|
611
|
+
return JSON.stringify({ error: 'Access denied' })
|
|
612
|
+
}
|
|
613
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## Security Considerations
|
|
620
|
+
|
|
621
|
+
### Always Use Context
|
|
622
|
+
|
|
623
|
+
Tools receive context with user/team IDs - use them:
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
createTools: (context) => [
|
|
627
|
+
{
|
|
628
|
+
func: async (params) => {
|
|
629
|
+
// Use context.userId for RLS
|
|
630
|
+
const data = await Service.list(context.userId, params)
|
|
631
|
+
return JSON.stringify(data)
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
]
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Never Expose Internal IDs
|
|
638
|
+
|
|
639
|
+
Return only necessary fields:
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
// Bad - exposes internal data
|
|
643
|
+
return JSON.stringify(fullDatabaseRecord)
|
|
644
|
+
|
|
645
|
+
// Good - curated response
|
|
646
|
+
return JSON.stringify({
|
|
647
|
+
id: record.id,
|
|
648
|
+
name: record.name,
|
|
649
|
+
// Exclude: internalId, secrets, etc.
|
|
650
|
+
})
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Validate Before Destructive Actions
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
{
|
|
657
|
+
name: 'delete_item',
|
|
658
|
+
description: 'Delete an item. This action cannot be undone.',
|
|
659
|
+
schema: z.object({
|
|
660
|
+
itemId: z.string(),
|
|
661
|
+
confirm: z.boolean().describe('Must be true to confirm deletion'),
|
|
662
|
+
}),
|
|
663
|
+
func: async ({ itemId, confirm }) => {
|
|
664
|
+
if (!confirm) {
|
|
665
|
+
return JSON.stringify({
|
|
666
|
+
error: 'Deletion not confirmed',
|
|
667
|
+
message: 'Set confirm=true to proceed',
|
|
668
|
+
})
|
|
669
|
+
}
|
|
670
|
+
await Service.delete(itemId)
|
|
671
|
+
return JSON.stringify({ success: true, deleted: itemId })
|
|
672
|
+
},
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
## Using Presets
|
|
677
|
+
|
|
678
|
+
The plugin includes a tools preset:
|
|
679
|
+
|
|
680
|
+
```bash
|
|
681
|
+
cp contents/plugins/langchain/presets/lib/tools/entity-tools.ts.preset \
|
|
682
|
+
contents/themes/your-theme/lib/langchain/tools/my-entity.ts
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
## Next Steps
|
|
686
|
+
|
|
687
|
+
- [Set up graph orchestration](../03-orchestration/01-graph-orchestrator.md) (recommended)
|
|
688
|
+
- [Advanced customization](../05-reference/02-customization.md)
|
|
689
|
+
- [Configure observability](../04-advanced/01-observability.md)
|