agentic-ai-framework 1.0.0
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/README.md +626 -0
- package/index.js +84 -0
- package/package.json +38 -0
- package/src/agent/Agent.js +278 -0
- package/src/agent/AgentConfig.js +88 -0
- package/src/agent/AgentRunner.js +256 -0
- package/src/llm/BaseLLMProvider.js +78 -0
- package/src/llm/LLMRouter.js +80 -0
- package/src/llm/providers/ClaudeProvider.js +307 -0
- package/src/llm/providers/GrokProvider.js +208 -0
- package/src/llm/providers/OpenAIProvider.js +194 -0
- package/src/memory/FileStore.js +102 -0
- package/src/memory/MemoryManager.js +55 -0
- package/src/memory/SessionMemory.js +124 -0
- package/src/prompt/PromptBuilder.js +95 -0
- package/src/prompt/PromptTemplate.js +58 -0
- package/src/team/AgentTeam.js +308 -0
- package/src/team/TeamResult.js +60 -0
- package/src/tool/Tool.js +138 -0
- package/src/tool/ToolRegistry.js +81 -0
- package/src/utils/errors.js +46 -0
- package/src/utils/logger.js +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
# @insider/agent-framework
|
|
2
|
+
|
|
3
|
+
Reusable agentic framework for Node.js (ES Modules). Provides session memory, LLM-driven tool-calling, Chain-of-Thought, and multi-agent team coordination with pluggable LLM providers.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// In another package inside the monorepo
|
|
11
|
+
import { createAgent, createTeam, Tool } from '@insider/agent-framework';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
import 'dotenv/config';
|
|
20
|
+
import { createAgent, Tool } from '@insider/agent-framework';
|
|
21
|
+
|
|
22
|
+
const agent = createAgent({
|
|
23
|
+
name: 'my-agent',
|
|
24
|
+
provider: 'grok', // 'grok' | 'claude' | 'openai'
|
|
25
|
+
apiKey: process.env.GROK_API_KEY,
|
|
26
|
+
systemPromptTemplate: 'You are a helpful assistant for ${userName}.',
|
|
27
|
+
chainOfThought: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
agent.registerTool(new Tool({
|
|
31
|
+
name: 'get_data',
|
|
32
|
+
description: 'Fetch data by ID',
|
|
33
|
+
parameters: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: { id: { type: 'string' } },
|
|
36
|
+
required: ['id'],
|
|
37
|
+
},
|
|
38
|
+
handler: async ({ id }) => `Data for ${id}`,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
agent.setContext('userName', 'Alice');
|
|
42
|
+
const result = await agent.run('Fetch data for item-42');
|
|
43
|
+
console.log(result.text);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Core Concepts
|
|
49
|
+
|
|
50
|
+
### Agent
|
|
51
|
+
|
|
52
|
+
The main class. Each agent owns an LLM provider, tool registry, session memory, and a prompt builder.
|
|
53
|
+
|
|
54
|
+
**One instance per request** — create a fresh agent per HTTP request. Instances have no shared mutable state between themselves.
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
import { createAgent } from '@insider/agent-framework';
|
|
58
|
+
|
|
59
|
+
const agent = createAgent(options); // shorthand for: new Agent(new AgentConfig(options))
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### AgentConfig Options
|
|
63
|
+
|
|
64
|
+
All options passed to `createAgent()`. Unknown keys throw an error (strict validation).
|
|
65
|
+
|
|
66
|
+
| Option | Type | Default | Description |
|
|
67
|
+
|--------|------|---------|-------------|
|
|
68
|
+
| `name` | `string` | **required** | Unique agent name |
|
|
69
|
+
| `provider` | `'grok'│'claude'│'openai'` | **required** | LLM provider |
|
|
70
|
+
| `apiKey` | `string` | **required** | Provider API key |
|
|
71
|
+
| `systemPromptTemplate` | `string` | one of these required | Inline system prompt with `${var}` placeholders |
|
|
72
|
+
| `systemPromptFile` | `string` | one of these required | Path to `.md`/`.txt` prompt file |
|
|
73
|
+
| `model` | `string` | provider default | Model ID |
|
|
74
|
+
| `temperature` | `number` | `0.1` | Sampling temperature (0–2) |
|
|
75
|
+
| `maxTokens` | `number` | `4000` | Max output tokens |
|
|
76
|
+
| `maxToolIterations` | `number` | `5` | Max tool-calling loop iterations |
|
|
77
|
+
| `loopTimeoutMs` | `number` | `300000` | Tool loop timeout in ms (5 min) |
|
|
78
|
+
| `requestTimeoutMs` | `number` | provider default | Individual LLM request timeout |
|
|
79
|
+
| `maxHistoryMessages` | `number` | `50` | Max session history messages |
|
|
80
|
+
| `persistenceDir` | `string` | `null` | Directory for session JSON files |
|
|
81
|
+
| `chainOfThought` | `boolean` | `true` | Inject CoT reasoning block into prompt |
|
|
82
|
+
| `cotMode` | `'prompt'│'reflect'` | `'prompt'` | CoT strategy |
|
|
83
|
+
| `cotStyle` | `'step-by-step'│'pros-cons'│'custom'` | `'step-by-step'` | CoT block style |
|
|
84
|
+
| `cotCustomInstructions` | `string` | `null` | Custom CoT text (when `cotStyle='custom'`) |
|
|
85
|
+
| `description` | `string` | `''` | Agent description (used by AgentTeam) |
|
|
86
|
+
| `outputSchema` | `object` | `null` | JSON Schema for structured output |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Tools
|
|
91
|
+
|
|
92
|
+
### Defining a Tool
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
import { Tool } from '@insider/agent-framework';
|
|
96
|
+
|
|
97
|
+
const myTool = new Tool({
|
|
98
|
+
name: 'search_docs', // snake_case, unique
|
|
99
|
+
description: 'Search documentation', // shown to the LLM — be descriptive
|
|
100
|
+
parameters: { // JSON Schema object
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
query: { type: 'string', description: 'Search query' },
|
|
104
|
+
limit: { type: 'number', description: 'Max results' },
|
|
105
|
+
strict: { type: 'boolean', description: 'Exact match only' },
|
|
106
|
+
},
|
|
107
|
+
required: ['query'],
|
|
108
|
+
},
|
|
109
|
+
handler: async ({ query, limit = 10, strict }) => {
|
|
110
|
+
// Do real work here
|
|
111
|
+
return `Results for "${query}"`; // return string or JSON-serializable object
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Zod schema** is also accepted for `parameters`:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
import { z } from 'zod';
|
|
120
|
+
|
|
121
|
+
const myTool = new Tool({
|
|
122
|
+
name: 'create_ticket',
|
|
123
|
+
description: 'Create a support ticket',
|
|
124
|
+
parameters: z.object({
|
|
125
|
+
title: z.string(),
|
|
126
|
+
priority: z.enum(['low', 'medium', 'high']),
|
|
127
|
+
assignee: z.string().optional(),
|
|
128
|
+
}),
|
|
129
|
+
handler: async ({ title, priority, assignee }) => {
|
|
130
|
+
// ...
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Supported Zod types: `z.string()`, `z.number()`, `z.boolean()`, `z.array()`, `z.enum()`, `z.object()`, `z.optional()`, `z.nullable()`, `z.default()`. All others throw — use a plain JSON Schema object for complex types.
|
|
136
|
+
|
|
137
|
+
### Registering Tools
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
agent.registerTool(myTool); // single tool
|
|
141
|
+
agent.registerTools([tool1, tool2]); // multiple tools — both return agent (chainable)
|
|
142
|
+
|
|
143
|
+
// Chainable
|
|
144
|
+
agent
|
|
145
|
+
.registerTool(searchTool)
|
|
146
|
+
.registerTool(createTool);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Handler Contract
|
|
150
|
+
|
|
151
|
+
- Receives parsed argument object from the LLM
|
|
152
|
+
- Returns `string` or any JSON-serializable value (objects are auto-stringified)
|
|
153
|
+
- Throwing an error causes the runner to report the error to the LLM as the tool result — the LLM can then decide how to recover
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Session Memory
|
|
158
|
+
|
|
159
|
+
### Stateful (Session) Pattern
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
// Start or resume a named session
|
|
163
|
+
const sessionId = await agent.startSession('user-123');
|
|
164
|
+
|
|
165
|
+
// Inject variables into system prompt as ${key}
|
|
166
|
+
agent.setContext('userName', 'Alice');
|
|
167
|
+
agent.setContext('role', 'admin');
|
|
168
|
+
|
|
169
|
+
const r1 = await agent.run('What tickets are assigned to me?');
|
|
170
|
+
const r2 = await agent.run('Show only the high-priority ones.'); // remembers previous turn
|
|
171
|
+
|
|
172
|
+
await agent.saveSession(); // persist to disk (requires persistenceDir in config)
|
|
173
|
+
await agent.endSession(); // clear in-memory state
|
|
174
|
+
// await agent.endSession(true); // also delete the file on disk
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Resuming a session restores history and context from disk:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
// Next request — session restored automatically
|
|
181
|
+
await agent.startSession('user-123'); // loads history from {persistenceDir}/user-123.json
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Stateless (Per-Request) Pattern
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
// No startSession() needed — pass appendToHistory: false
|
|
188
|
+
const result = await agent.run(userInput, { appendToHistory: false });
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Context Values
|
|
192
|
+
|
|
193
|
+
`setContext(key, value)` injects values into system prompt `${key}` placeholders. Values must be JSON-serializable (strings, numbers, booleans, arrays, plain objects). Functions and Symbols are rejected.
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
agent.setContext('companyName', 'Insider');
|
|
197
|
+
agent.setContext('userTier', 'enterprise');
|
|
198
|
+
// System prompt: "You are an assistant for ${companyName} users on the ${userTier} plan."
|
|
199
|
+
// Rendered as: "You are an assistant for Insider users on the enterprise plan."
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## AgentResult
|
|
205
|
+
|
|
206
|
+
Every `agent.run()` returns an `AgentResult`:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
{
|
|
210
|
+
success: true, // false if LLM failed or max iterations reached
|
|
211
|
+
text: 'Final answer...', // convenience: best text representation of the answer
|
|
212
|
+
content: 'Raw LLM output', // raw text from the last LLM response
|
|
213
|
+
parsed: null, // parsed JSON when outputSchema is set
|
|
214
|
+
toolCallHistory: [ // all tool calls that happened in this run
|
|
215
|
+
{
|
|
216
|
+
id: 'call_abc',
|
|
217
|
+
name: 'get_data',
|
|
218
|
+
arguments: { id: 'item-42' },
|
|
219
|
+
result: 'Data for item-42',
|
|
220
|
+
iteration: 1,
|
|
221
|
+
timestamp: '2026-03-13T10:00:00.000Z',
|
|
222
|
+
}
|
|
223
|
+
],
|
|
224
|
+
usage: {
|
|
225
|
+
promptTokens: 120,
|
|
226
|
+
completionTokens: 45,
|
|
227
|
+
totalTokens: 165,
|
|
228
|
+
},
|
|
229
|
+
iterations: 1, // number of tool-calling iterations
|
|
230
|
+
error: undefined, // error message when success=false
|
|
231
|
+
cotTrace: undefined, // present when cotMode='reflect'
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Chain of Thought (CoT)
|
|
238
|
+
|
|
239
|
+
### Prompt Mode (default)
|
|
240
|
+
|
|
241
|
+
A reasoning block is appended to the system prompt. Zero extra LLM calls.
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
createAgent({
|
|
245
|
+
// ...
|
|
246
|
+
chainOfThought: true,
|
|
247
|
+
cotMode: 'prompt', // default
|
|
248
|
+
cotStyle: 'step-by-step', // default
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Available styles:
|
|
253
|
+
- `'step-by-step'` — numbered reasoning steps before answering
|
|
254
|
+
- `'pros-cons'` — trade-off analysis before deciding
|
|
255
|
+
- `'custom'` — provide your own instructions via `cotCustomInstructions`
|
|
256
|
+
|
|
257
|
+
### Reflect Mode
|
|
258
|
+
|
|
259
|
+
After the tool-calling loop produces a final answer, a second LLM call verifies it. Use for high-stakes agents where accuracy matters more than cost.
|
|
260
|
+
|
|
261
|
+
```js
|
|
262
|
+
createAgent({
|
|
263
|
+
// ...
|
|
264
|
+
chainOfThought: true,
|
|
265
|
+
cotMode: 'reflect',
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// result.cotTrace = { original, reflected, changed: true/false }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Multi-Agent Teams
|
|
274
|
+
|
|
275
|
+
### Router Mode
|
|
276
|
+
|
|
277
|
+
The coordinator LLM decides which specialist(s) to call via tool-calling. Maps to the existing MasterAgent pattern.
|
|
278
|
+
|
|
279
|
+
```js
|
|
280
|
+
import { createAgent, createTeam } from '@insider/agent-framework';
|
|
281
|
+
|
|
282
|
+
const sqlAgent = createAgent({
|
|
283
|
+
name: 'sql-agent',
|
|
284
|
+
provider: 'grok',
|
|
285
|
+
apiKey: process.env.GROK_API_KEY,
|
|
286
|
+
description: 'Answers questions by generating SQL queries',
|
|
287
|
+
systemPromptTemplate: 'You are a SQL specialist. Table: tickets(id, status, priority).',
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const ragAgent = createAgent({
|
|
291
|
+
name: 'rag-agent',
|
|
292
|
+
provider: 'grok',
|
|
293
|
+
apiKey: process.env.GROK_API_KEY,
|
|
294
|
+
description: 'Searches documentation for policy questions',
|
|
295
|
+
systemPromptTemplate: 'You are a knowledge base specialist.',
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const coordinator = createAgent({
|
|
299
|
+
name: 'coordinator',
|
|
300
|
+
provider: 'grok',
|
|
301
|
+
apiKey: process.env.GROK_API_KEY,
|
|
302
|
+
systemPromptTemplate: `You orchestrate specialist agents.
|
|
303
|
+
Available specialists:
|
|
304
|
+
\${teamMembers}
|
|
305
|
+
Always delegate to the most appropriate specialist.`,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const team = createTeam({
|
|
309
|
+
coordinator,
|
|
310
|
+
members: [sqlAgent, ragAgent],
|
|
311
|
+
mode: 'router', // default
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const result = await team.run('How many open tickets are there?');
|
|
315
|
+
console.log(result.final); // final synthesized answer
|
|
316
|
+
console.log(result.memberResults); // per-member results keyed by agent name
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The `${teamMembers}` placeholder in the coordinator's system prompt is auto-populated with the member list.
|
|
320
|
+
|
|
321
|
+
### Parallel Mode
|
|
322
|
+
|
|
323
|
+
All members run simultaneously. The coordinator synthesizes all responses.
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
const team = createTeam({
|
|
327
|
+
coordinator: synthesizerAgent,
|
|
328
|
+
members: [sqlAgent, ragAgent],
|
|
329
|
+
mode: 'parallel',
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const result = await team.run('How many critical tickets are open and what is the SLA?');
|
|
333
|
+
// sqlAgent and ragAgent run in parallel, coordinator combines both answers
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Use parallel mode when a question genuinely needs multiple specialists. Use router when only one specialist is needed per question.
|
|
337
|
+
|
|
338
|
+
### TeamResult
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
{
|
|
342
|
+
success: true,
|
|
343
|
+
final: 'Synthesized answer...',
|
|
344
|
+
memberResults: {
|
|
345
|
+
'sql-agent': { success: true, text: '...', content: '...', toolCallHistory: [] },
|
|
346
|
+
'rag-agent': { success: true, text: '...', content: '...', toolCallHistory: [] },
|
|
347
|
+
},
|
|
348
|
+
coordinatorResult: { /* AgentResult */ },
|
|
349
|
+
mode: 'router',
|
|
350
|
+
error: null,
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Helper methods
|
|
354
|
+
result.getMemberResult('sql-agent'); // get one member's result
|
|
355
|
+
result.getSuccessfulMembers(); // ['sql-agent', 'rag-agent']
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Dynamic Member Management
|
|
359
|
+
|
|
360
|
+
```js
|
|
361
|
+
team.addMember(analyticsAgent); // add at runtime
|
|
362
|
+
team.removeMember('sql-agent'); // remove by name
|
|
363
|
+
team.getMembers(); // ['rag-agent', 'analytics-agent']
|
|
364
|
+
team.getInfo(); // coordinator + members info + mode
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## LLM Providers
|
|
370
|
+
|
|
371
|
+
### Built-in Providers
|
|
372
|
+
|
|
373
|
+
| Name | API | Default Model |
|
|
374
|
+
|------|-----|---------------|
|
|
375
|
+
| `'grok'` | xAI / OpenAI-compatible | `grok-code-fast-1` |
|
|
376
|
+
| `'claude'` | Anthropic Messages API | `claude-sonnet-4-20250514` |
|
|
377
|
+
| `'openai'` | OpenAI Chat Completions | `gpt-4o` |
|
|
378
|
+
|
|
379
|
+
```js
|
|
380
|
+
// Grok
|
|
381
|
+
createAgent({ provider: 'grok', apiKey: process.env.GROK_API_KEY, model: 'grok-2', ... });
|
|
382
|
+
|
|
383
|
+
// Claude
|
|
384
|
+
createAgent({ provider: 'claude', apiKey: process.env.ANTHROPIC_API_KEY, model: 'claude-opus-4-20250514', ... });
|
|
385
|
+
|
|
386
|
+
// OpenAI
|
|
387
|
+
createAgent({ provider: 'openai', apiKey: process.env.OPENAI_API_KEY, model: 'gpt-4o-mini', ... });
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Provider instances are cached by `(provider, model, apiKey)` — the same combination always returns the same instance.
|
|
391
|
+
|
|
392
|
+
### Custom Provider
|
|
393
|
+
|
|
394
|
+
```js
|
|
395
|
+
import { BaseLLMProvider, LLMRouter } from '@insider/agent-framework';
|
|
396
|
+
|
|
397
|
+
class MyProvider extends BaseLLMProvider {
|
|
398
|
+
constructor(apiKey, model = 'my-model') {
|
|
399
|
+
super();
|
|
400
|
+
this.apiKey = apiKey;
|
|
401
|
+
this.model = model;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async complete(prompt, options = {}) {
|
|
405
|
+
const messages = options.messages ?? [{ role: 'user', content: prompt }];
|
|
406
|
+
// call your API...
|
|
407
|
+
return {
|
|
408
|
+
content: 'response text',
|
|
409
|
+
parsed: null,
|
|
410
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
411
|
+
model: this.model,
|
|
412
|
+
finishReason: 'stop',
|
|
413
|
+
toolCalls: undefined,
|
|
414
|
+
messages: [...messages, { role: 'assistant', content: 'response text' }],
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async completeWithSchema(prompt, schema, options = {}) { /* ... */ }
|
|
419
|
+
async testConnection() { return true; }
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
LLMRouter.register('my-provider', MyProvider);
|
|
423
|
+
|
|
424
|
+
// Now usable in createAgent
|
|
425
|
+
createAgent({ provider: 'my-provider', apiKey: '...', ... });
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Structured Output
|
|
431
|
+
|
|
432
|
+
Force the LLM to respond with a specific JSON shape:
|
|
433
|
+
|
|
434
|
+
```js
|
|
435
|
+
const agent = createAgent({
|
|
436
|
+
// ...
|
|
437
|
+
outputSchema: {
|
|
438
|
+
type: 'object',
|
|
439
|
+
properties: {
|
|
440
|
+
answer: { type: 'string' },
|
|
441
|
+
confidence: { type: 'number' },
|
|
442
|
+
sources: { type: 'array', items: { type: 'string' } },
|
|
443
|
+
},
|
|
444
|
+
required: ['answer', 'confidence'],
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const result = await agent.run('What is our refund policy?');
|
|
449
|
+
console.log(result.parsed.answer); // string
|
|
450
|
+
console.log(result.parsed.confidence); // number
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Error Handling
|
|
456
|
+
|
|
457
|
+
Framework throws typed errors — catch them specifically:
|
|
458
|
+
|
|
459
|
+
```js
|
|
460
|
+
import {
|
|
461
|
+
AgentError, ToolError, LLMError, ConfigError, MemoryError
|
|
462
|
+
} from '@insider/agent-framework';
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
const result = await agent.run(userInput);
|
|
466
|
+
if (!result.success) {
|
|
467
|
+
console.error('Agent failed:', result.error);
|
|
468
|
+
}
|
|
469
|
+
} catch (err) {
|
|
470
|
+
if (err instanceof LLMError) { /* API key bad, network down, etc. */ }
|
|
471
|
+
if (err instanceof ToolError) { /* tool registration or execution issue */ }
|
|
472
|
+
if (err instanceof MemoryError) { /* session load/save failed */ }
|
|
473
|
+
if (err instanceof ConfigError) { /* bad AgentConfig options */ }
|
|
474
|
+
if (err instanceof AgentError) { /* other agent-level issue */ }
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
`agent.run()` itself does **not** throw on LLM failures — it returns `{ success: false, error: '...' }`. It only throws for programming errors (wrong arguments, missing session, etc.).
|
|
479
|
+
|
|
480
|
+
Transient LLM errors (rate limits, 5xx, timeouts) are **automatically retried** up to 2 times with exponential backoff before failing.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Logging
|
|
485
|
+
|
|
486
|
+
Structured JSON logging via pino. Set log level with the `AGENT_LOG_LEVEL` environment variable.
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
AGENT_LOG_LEVEL=debug node app.js # debug | info | warn | error | silent
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
In development, if `pino-pretty` is installed, logs are pretty-printed. In production (`NODE_ENV=production`) plain JSON is used regardless.
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Complete Backend API Handler Example
|
|
497
|
+
|
|
498
|
+
```js
|
|
499
|
+
import { createAgent, Tool, AgentError } from '@insider/agent-framework';
|
|
500
|
+
import { queryDatabase } from './db.js';
|
|
501
|
+
|
|
502
|
+
// Agent definition is typically created once at module load
|
|
503
|
+
function buildSQLAgent() {
|
|
504
|
+
const agent = createAgent({
|
|
505
|
+
name: 'sql-agent',
|
|
506
|
+
provider: 'grok',
|
|
507
|
+
apiKey: process.env.GROK_API_KEY,
|
|
508
|
+
systemPromptTemplate: `You are a SQL specialist for ${`\${companyName}`}.
|
|
509
|
+
Table: zendesk_tickets(id, status, assignee, created_at, priority, subject)`,
|
|
510
|
+
chainOfThought: true,
|
|
511
|
+
maxToolIterations: 3,
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
agent.registerTool(new Tool({
|
|
515
|
+
name: 'run_sql',
|
|
516
|
+
description: 'Execute a SQL query and return results',
|
|
517
|
+
parameters: {
|
|
518
|
+
type: 'object',
|
|
519
|
+
properties: { sql: { type: 'string', description: 'SQL query to execute' } },
|
|
520
|
+
required: ['sql'],
|
|
521
|
+
},
|
|
522
|
+
handler: async ({ sql }) => {
|
|
523
|
+
const rows = await queryDatabase(sql);
|
|
524
|
+
return JSON.stringify(rows);
|
|
525
|
+
},
|
|
526
|
+
}));
|
|
527
|
+
|
|
528
|
+
return agent;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Express / Fastify handler
|
|
532
|
+
export async function handleQuestion(req, res) {
|
|
533
|
+
const { question, sessionId, companyName } = req.body;
|
|
534
|
+
|
|
535
|
+
// Fresh agent per request — no shared state
|
|
536
|
+
const agent = buildSQLAgent();
|
|
537
|
+
agent.setContext('companyName', companyName);
|
|
538
|
+
|
|
539
|
+
let activeSessionId;
|
|
540
|
+
try {
|
|
541
|
+
activeSessionId = await agent.startSession(sessionId); // restores history if exists
|
|
542
|
+
const result = await agent.run(question);
|
|
543
|
+
|
|
544
|
+
if (!result.success) {
|
|
545
|
+
return res.status(500).json({ error: result.error });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
await agent.saveSession();
|
|
549
|
+
|
|
550
|
+
return res.json({
|
|
551
|
+
answer: result.text,
|
|
552
|
+
sessionId: activeSessionId,
|
|
553
|
+
toolsUsed: result.toolCallHistory.map(t => t.name),
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
} catch (err) {
|
|
557
|
+
return res.status(500).json({ error: err.message });
|
|
558
|
+
} finally {
|
|
559
|
+
await agent.endSession(); // always clear in-memory state
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## Public API Reference
|
|
567
|
+
|
|
568
|
+
```
|
|
569
|
+
createAgent(options) → Agent
|
|
570
|
+
createTeam(options) → AgentTeam
|
|
571
|
+
|
|
572
|
+
Agent
|
|
573
|
+
.registerTool(tool) → Agent (chainable)
|
|
574
|
+
.registerTools(tools[]) → Agent (chainable)
|
|
575
|
+
.getToolRegistry() → ToolRegistry
|
|
576
|
+
.startSession(sessionId?) → Promise<string>
|
|
577
|
+
.saveSession() → Promise<void>
|
|
578
|
+
.endSession(deletePersisted?) → Promise<void>
|
|
579
|
+
.setContext(key, value) → Agent (chainable)
|
|
580
|
+
.getContext(key) → any
|
|
581
|
+
.run(input, opts?) → Promise<AgentResult>
|
|
582
|
+
.getInfo() → Object
|
|
583
|
+
.testConnection() → Promise<boolean>
|
|
584
|
+
|
|
585
|
+
AgentTeam
|
|
586
|
+
.addMember(agent) → void
|
|
587
|
+
.removeMember(name) → void
|
|
588
|
+
.getMembers() → string[]
|
|
589
|
+
.run(input, opts?) → Promise<TeamResult>
|
|
590
|
+
.getInfo() → Object
|
|
591
|
+
|
|
592
|
+
Tool
|
|
593
|
+
new Tool({ name, description, parameters, handler })
|
|
594
|
+
.toDefinition() → { name, description, parameters }
|
|
595
|
+
.execute(args) → Promise<string | object>
|
|
596
|
+
|
|
597
|
+
ToolRegistry
|
|
598
|
+
.register(tool) → void (throws if duplicate)
|
|
599
|
+
.registerOrReplace(tool) → void
|
|
600
|
+
.unregister(name) → void
|
|
601
|
+
.has(name) → boolean
|
|
602
|
+
.listNames() → string[]
|
|
603
|
+
.getDefinitions() → Array
|
|
604
|
+
.execute(name, args) → Promise<string>
|
|
605
|
+
|
|
606
|
+
LLMRouter
|
|
607
|
+
.get(provider, model, apiKey) → BaseLLMProvider
|
|
608
|
+
.register(name, Class) → void
|
|
609
|
+
.clearCache() → void
|
|
610
|
+
.listProviders() → string[]
|
|
611
|
+
|
|
612
|
+
SessionMemory
|
|
613
|
+
.appendExchange(user, assistant) → void
|
|
614
|
+
.getHistory() → Array
|
|
615
|
+
.setContext(key, value) → void
|
|
616
|
+
.getContext(key) → any
|
|
617
|
+
.snapshot() → Object
|
|
618
|
+
.restore(snapshot) → void
|
|
619
|
+
.clear() → void
|
|
620
|
+
|
|
621
|
+
MemoryManager
|
|
622
|
+
.load(sessionId) → Promise<Object | null>
|
|
623
|
+
.save(sessionId, snapshot) → Promise<void>
|
|
624
|
+
.delete(sessionId) → Promise<void>
|
|
625
|
+
.list() → Promise<string[]>
|
|
626
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @insider/agent-framework
|
|
3
|
+
*
|
|
4
|
+
* Public API surface. Import from this file:
|
|
5
|
+
* import { createAgent, Agent, Tool, AgentTeam } from '@insider/agent-framework';
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ── Core ──────────────────────────────────────────────────────────────────────
|
|
9
|
+
export { Agent } from './src/agent/Agent.js';
|
|
10
|
+
export { AgentConfig } from './src/agent/AgentConfig.js';
|
|
11
|
+
export { AgentRunner } from './src/agent/AgentRunner.js';
|
|
12
|
+
|
|
13
|
+
// ── Memory ────────────────────────────────────────────────────────────────────
|
|
14
|
+
export { SessionMemory } from './src/memory/SessionMemory.js';
|
|
15
|
+
export { MemoryManager } from './src/memory/MemoryManager.js';
|
|
16
|
+
export { FileStore } from './src/memory/FileStore.js';
|
|
17
|
+
|
|
18
|
+
// ── Tools ─────────────────────────────────────────────────────────────────────
|
|
19
|
+
export { Tool } from './src/tool/Tool.js';
|
|
20
|
+
export { ToolRegistry } from './src/tool/ToolRegistry.js';
|
|
21
|
+
|
|
22
|
+
// ── LLM ───────────────────────────────────────────────────────────────────────
|
|
23
|
+
export { BaseLLMProvider } from './src/llm/BaseLLMProvider.js';
|
|
24
|
+
export { LLMRouter } from './src/llm/LLMRouter.js';
|
|
25
|
+
export { GrokProvider } from './src/llm/providers/GrokProvider.js';
|
|
26
|
+
export { ClaudeProvider } from './src/llm/providers/ClaudeProvider.js';
|
|
27
|
+
export { OpenAIProvider } from './src/llm/providers/OpenAIProvider.js';
|
|
28
|
+
|
|
29
|
+
// ── Prompt ────────────────────────────────────────────────────────────────────
|
|
30
|
+
export { PromptBuilder } from './src/prompt/PromptBuilder.js';
|
|
31
|
+
export { PromptTemplate } from './src/prompt/PromptTemplate.js';
|
|
32
|
+
|
|
33
|
+
// ── Team ──────────────────────────────────────────────────────────────────────
|
|
34
|
+
export { AgentTeam } from './src/team/AgentTeam.js';
|
|
35
|
+
export { TeamResult } from './src/team/TeamResult.js';
|
|
36
|
+
|
|
37
|
+
// ── Errors ────────────────────────────────────────────────────────────────────
|
|
38
|
+
export { AgentError, ToolError, LLMError, ConfigError, MemoryError } from './src/utils/errors.js';
|
|
39
|
+
|
|
40
|
+
// ── Convenience factories ─────────────────────────────────────────────────────
|
|
41
|
+
// Import classes for use in factories (separate from re-exports above)
|
|
42
|
+
import { Agent as _Agent } from './src/agent/Agent.js';
|
|
43
|
+
import { AgentConfig as _AgentConfig } from './src/agent/AgentConfig.js';
|
|
44
|
+
import { AgentTeam as _AgentTeam } from './src/team/AgentTeam.js';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create an Agent from a plain config object.
|
|
48
|
+
* Shorthand for: new Agent(new AgentConfig(options))
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} options - See AgentConfig for all options
|
|
51
|
+
* @returns {Agent}
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const agent = createAgent({
|
|
55
|
+
* name: 'my-agent',
|
|
56
|
+
* provider: 'grok',
|
|
57
|
+
* apiKey: process.env.GROK_API_KEY,
|
|
58
|
+
* systemPromptTemplate: 'You are a helpful assistant.',
|
|
59
|
+
* });
|
|
60
|
+
*/
|
|
61
|
+
export function createAgent(options) {
|
|
62
|
+
return new _Agent(new _AgentConfig(options));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create an AgentTeam from a coordinator and optional member agents.
|
|
67
|
+
* Shorthand for: new AgentTeam({ coordinator, members, mode })
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} options
|
|
70
|
+
* @param {Agent} options.coordinator
|
|
71
|
+
* @param {Agent[]} [options.members=[]]
|
|
72
|
+
* @param {'router' | 'parallel'} [options.mode='router']
|
|
73
|
+
* @returns {AgentTeam}
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const team = createTeam({
|
|
77
|
+
* coordinator: masterAgent,
|
|
78
|
+
* members: [sqlAgent, ragAgent],
|
|
79
|
+
* mode: 'router',
|
|
80
|
+
* });
|
|
81
|
+
*/
|
|
82
|
+
export function createTeam(options) {
|
|
83
|
+
return new _AgentTeam(options);
|
|
84
|
+
}
|