symposium 0.14.21 → 0.15.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/Agent.js +132 -95
- package/BufferedEventEmitter.js +28 -0
- package/Symposium.js +21 -42
- package/Thread.js +4 -7
- package/index.js +0 -2
- package/package.json +1 -1
- package/Interface.js +0 -15
- package/README.md +0 -152
- package/examples/ChatAgent.js +0 -21
- package/examples/MultiAgent.js +0 -115
- package/examples/TitlerAgent.js +0 -25
- package/examples/Tools/GenericTool.js +0 -42
- package/examples/Tools/MultiAgentTool.js +0 -77
package/Agent.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import {v7 as uuid} from 'uuid';
|
|
2
|
+
|
|
3
|
+
import BufferedEventEmitter from "./BufferedEventEmitter.js";
|
|
4
|
+
|
|
1
5
|
import Symposium from "./Symposium.js";
|
|
2
6
|
import Thread from "./Thread.js";
|
|
3
|
-
import {v7 as uuid} from 'uuid';
|
|
4
7
|
|
|
5
8
|
export default class Agent {
|
|
6
9
|
name = 'Agent';
|
|
@@ -11,13 +14,13 @@ export default class Agent {
|
|
|
11
14
|
tools = new Map();
|
|
12
15
|
default_model = 'gpt-4o';
|
|
13
16
|
max_retries = 5;
|
|
14
|
-
|
|
17
|
+
type = 'chat'; // chat, utility
|
|
15
18
|
utility = null;
|
|
19
|
+
initialized = false;
|
|
16
20
|
|
|
17
21
|
constructor(options) {
|
|
18
22
|
this.options = {
|
|
19
23
|
memory_handler: null,
|
|
20
|
-
interfaces: [],
|
|
21
24
|
...options,
|
|
22
25
|
};
|
|
23
26
|
|
|
@@ -25,11 +28,20 @@ export default class Agent {
|
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
async init() {
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
if (this.initialized)
|
|
32
|
+
return;
|
|
30
33
|
|
|
31
34
|
if (this.options.memory_handler)
|
|
32
35
|
this.options.memory_handler.setAgent(this);
|
|
36
|
+
|
|
37
|
+
if (this.type === 'utility') {
|
|
38
|
+
if (!this.utility || !this.utility.type)
|
|
39
|
+
throw new Error('Utility function not defined');
|
|
40
|
+
if (!['text', 'function', 'json'].includes(this.utility.type))
|
|
41
|
+
throw new Error('Bad utility definition');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.initialized = true;
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
async reset(thread) {
|
|
@@ -60,13 +72,10 @@ export default class Agent {
|
|
|
60
72
|
async doInitThread(thread) {
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
async getThread(id
|
|
75
|
+
async getThread(id) {
|
|
64
76
|
let thread = this.threads.get(id);
|
|
65
|
-
if (thread) {
|
|
66
|
-
|
|
67
|
-
throw new Error('Required thread is not from the same interface');
|
|
68
|
-
} else {
|
|
69
|
-
thread = new Thread(id, i, this);
|
|
77
|
+
if (!thread) {
|
|
78
|
+
thread = new Thread(id, this);
|
|
70
79
|
|
|
71
80
|
if (!(await thread.loadState())) {
|
|
72
81
|
await this.resetState(thread);
|
|
@@ -79,15 +88,14 @@ export default class Agent {
|
|
|
79
88
|
return thread;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
async message(
|
|
83
|
-
if (
|
|
84
|
-
|
|
91
|
+
async message(content, thread = null) {
|
|
92
|
+
if (!this.initialized)
|
|
93
|
+
throw new Error('Agent not initialized');
|
|
85
94
|
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
95
|
+
if (thread === null)
|
|
96
|
+
thread = uuid();
|
|
97
|
+
if (typeof thread !== 'object')
|
|
98
|
+
thread = await this.getThread(thread);
|
|
91
99
|
|
|
92
100
|
const model = Symposium.getModelByName(thread.state.model);
|
|
93
101
|
if (!model.supports_audio && typeof content !== 'string') {
|
|
@@ -112,17 +120,14 @@ export default class Agent {
|
|
|
112
120
|
return thread;
|
|
113
121
|
}
|
|
114
122
|
|
|
115
|
-
async execute(thread, counter = 0) {
|
|
123
|
+
async execute(thread, counter = 0, existing_emitter = null) {
|
|
116
124
|
if (counter === 0)
|
|
117
125
|
thread = await this.beforeExecute(thread);
|
|
118
126
|
|
|
119
127
|
const model = Symposium.getModelByName(thread.state.model);
|
|
120
128
|
|
|
121
129
|
const completion_options = {};
|
|
122
|
-
if (this.utility) {
|
|
123
|
-
if (!['text', 'function', 'json'].includes(this.utility.type))
|
|
124
|
-
throw new Error('Bad utility definition');
|
|
125
|
-
|
|
130
|
+
if (this.type === 'utility') {
|
|
126
131
|
if (['function', 'json'].includes(this.utility.type)) {
|
|
127
132
|
if (!this.utility.function || !this.utility.function.name || !this.utility.function.parameters)
|
|
128
133
|
throw new Error('Bad function definition');
|
|
@@ -131,7 +136,7 @@ export default class Agent {
|
|
|
131
136
|
if (this.utility.type === 'json' && model.supports_structured_output)
|
|
132
137
|
response_format = this.convertFunctionToResponseFormat(this.utility.function.parameters);
|
|
133
138
|
|
|
134
|
-
if (response_format && response_format.count <= 100) { //
|
|
139
|
+
if (response_format && response_format.count <= 100) { // OpenAI does not support structured output if there are more than 100 parameters
|
|
135
140
|
completion_options.response_format = {
|
|
136
141
|
type: 'json_schema',
|
|
137
142
|
json_schema: {
|
|
@@ -149,30 +154,60 @@ export default class Agent {
|
|
|
149
154
|
}
|
|
150
155
|
}
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
let completion;
|
|
158
|
+
try {
|
|
159
|
+
completion = await this.generateCompletion(thread, completion_options);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error(e.message);
|
|
162
|
+
switch (this.type) {
|
|
163
|
+
case 'chat':
|
|
164
|
+
const emitter = existing_emitter || new BufferedEventEmitter();
|
|
165
|
+
emitter.emit('error', e.message);
|
|
166
|
+
if (!existing_emitter)
|
|
167
|
+
emitter.emit('end');
|
|
168
|
+
return emitter;
|
|
169
|
+
|
|
170
|
+
case 'utility':
|
|
171
|
+
throw e;
|
|
172
|
+
|
|
173
|
+
default:
|
|
174
|
+
throw new Error('Bad agent type');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
160
177
|
|
|
161
|
-
|
|
162
|
-
|
|
178
|
+
try {
|
|
179
|
+
thread = await this.afterExecute(thread, completion);
|
|
180
|
+
const emitter = this.handleCompletion(thread, completion, existing_emitter);
|
|
181
|
+
|
|
182
|
+
switch (this.type) {
|
|
183
|
+
case 'utility':
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
emitter.on('data', data => {
|
|
186
|
+
if (data.type === 'response')
|
|
187
|
+
resolve(data.content);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
emitter.on('error', error => reject(error));
|
|
191
|
+
});
|
|
163
192
|
|
|
164
|
-
|
|
165
|
-
|
|
193
|
+
case 'chat':
|
|
194
|
+
if (!existing_emitter) {
|
|
195
|
+
emitter.on('data', data => {
|
|
196
|
+
if (data.type === 'response' && data.content.type === 'continue')
|
|
197
|
+
this.execute(thread, 0, emitter);
|
|
198
|
+
}, false);
|
|
199
|
+
}
|
|
166
200
|
|
|
167
|
-
|
|
168
|
-
throw new Error('Unknown response type');
|
|
169
|
-
}
|
|
170
|
-
} catch (e) {
|
|
171
|
-
console.error(e);
|
|
201
|
+
return emitter;
|
|
172
202
|
|
|
173
|
-
|
|
174
|
-
|
|
203
|
+
default:
|
|
204
|
+
throw new Error('Bad agent type');
|
|
175
205
|
}
|
|
206
|
+
} catch (e) {
|
|
207
|
+
console.error(e);
|
|
208
|
+
|
|
209
|
+
if (counter < this.max_retries)
|
|
210
|
+
await this.execute(thread, counter + 1, existing_emitter);
|
|
176
211
|
}
|
|
177
212
|
}
|
|
178
213
|
|
|
@@ -180,11 +215,16 @@ export default class Agent {
|
|
|
180
215
|
if (obj.type !== 'object')
|
|
181
216
|
return {obj, count: 0};
|
|
182
217
|
|
|
183
|
-
let properties_count = 0, required = [];
|
|
218
|
+
let properties_count = 0, all_required = false, required = [];
|
|
219
|
+
if (obj.required)
|
|
220
|
+
required = obj.required;
|
|
221
|
+
else
|
|
222
|
+
all_required = true;
|
|
184
223
|
|
|
185
224
|
for (let [key, property] of Object.entries(obj.properties || {})) {
|
|
186
225
|
properties_count++;
|
|
187
|
-
|
|
226
|
+
if (all_required)
|
|
227
|
+
required.push(key);
|
|
188
228
|
|
|
189
229
|
if (property.type === 'object') {
|
|
190
230
|
const {obj: subobj, count} = this.convertFunctionToResponseFormat(property);
|
|
@@ -232,43 +272,15 @@ export default class Agent {
|
|
|
232
272
|
return this.generateCompletion(thread, options, retry_counter + 1);
|
|
233
273
|
}
|
|
234
274
|
|
|
235
|
-
|
|
275
|
+
throw new Error(error.response.status + ': ' + JSON.stringify(error.response.data));
|
|
236
276
|
} else if (error.message) {
|
|
237
|
-
|
|
238
|
-
await this.error(thread, error.message);
|
|
277
|
+
throw new Error(error.message);
|
|
239
278
|
} else {
|
|
240
|
-
|
|
241
|
-
await this.error(thread, 'Errore interno');
|
|
279
|
+
throw new Error('Errore interno');
|
|
242
280
|
}
|
|
243
281
|
}
|
|
244
282
|
}
|
|
245
283
|
|
|
246
|
-
async error(thread, error) {
|
|
247
|
-
const i = this.options.interfaces.find(i => i.name === thread.interface);
|
|
248
|
-
if (i)
|
|
249
|
-
return i.error(thread, error);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async output(thread, msg) {
|
|
253
|
-
if (this.callbacks.hasOwnProperty(thread.interface + '-' + thread.id) && this.callbacks[thread.interface + '-' + thread.id].length) {
|
|
254
|
-
const callback = this.callbacks[thread.interface + '-' + thread.id].shift();
|
|
255
|
-
await callback(msg);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const i = this.options.interfaces.find(i => i.name === thread.interface);
|
|
259
|
-
if (i)
|
|
260
|
-
return i.output(thread, msg);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async partial(thread, msg) {
|
|
264
|
-
if ((typeof msg) === 'text')
|
|
265
|
-
msg = {summary: msg};
|
|
266
|
-
|
|
267
|
-
const i = this.options.interfaces.find(i => i.name === thread.interface);
|
|
268
|
-
if (i)
|
|
269
|
-
return i.partial(thread, msg);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
284
|
parseFunctions(message) {
|
|
273
285
|
const newContent = [];
|
|
274
286
|
for (let m of message.content) {
|
|
@@ -294,9 +306,11 @@ export default class Agent {
|
|
|
294
306
|
return message;
|
|
295
307
|
}
|
|
296
308
|
|
|
297
|
-
async handleCompletion(thread, completion) {
|
|
309
|
+
async handleCompletion(thread, completion, existing_emitter = null) {
|
|
298
310
|
const model = Symposium.getModelByName(thread.state.model);
|
|
299
311
|
|
|
312
|
+
const emitter = existing_emitter || new BufferedEventEmitter();
|
|
313
|
+
|
|
300
314
|
const functions = [];
|
|
301
315
|
for (let message of completion) {
|
|
302
316
|
thread.addDirectMessage(message);
|
|
@@ -305,13 +319,21 @@ export default class Agent {
|
|
|
305
319
|
for (let m of message.content) {
|
|
306
320
|
switch (m.type) {
|
|
307
321
|
case 'text':
|
|
308
|
-
if (this.utility) {
|
|
322
|
+
if (this.type === 'utility') {
|
|
323
|
+
let response = null;
|
|
309
324
|
if (this.utility.type === 'text')
|
|
310
|
-
|
|
325
|
+
response = await this.afterHandle(thread, completion, 'return', m.content);
|
|
311
326
|
if (this.utility.type === 'json' && model.supports_structured_output)
|
|
312
|
-
|
|
327
|
+
response = await this.afterHandle(thread, completion, 'return', JSON.parse(m.content));
|
|
328
|
+
|
|
329
|
+
if (response) {
|
|
330
|
+
emitter.emit('data', {type: 'response', content: response});
|
|
331
|
+
emitter.emit('end');
|
|
332
|
+
return emitter;
|
|
333
|
+
}
|
|
313
334
|
}
|
|
314
|
-
|
|
335
|
+
|
|
336
|
+
emitter.emit('data', {type: 'output', content: m.content});
|
|
315
337
|
break;
|
|
316
338
|
|
|
317
339
|
case 'function':
|
|
@@ -322,28 +344,43 @@ export default class Agent {
|
|
|
322
344
|
}
|
|
323
345
|
}
|
|
324
346
|
|
|
347
|
+
let response;
|
|
325
348
|
if (functions.length) {
|
|
326
349
|
for (let f of functions) {
|
|
327
|
-
if (this.utility && ['function', 'json'].includes(this.utility.type))
|
|
328
|
-
|
|
350
|
+
if (this.utility && ['function', 'json'].includes(this.utility.type)) {
|
|
351
|
+
response = this.afterHandle(thread, completion, 'return', f.arguments);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
329
354
|
|
|
330
|
-
const
|
|
355
|
+
const function_response = await this.callFunction(thread, f, emitter);
|
|
331
356
|
|
|
332
357
|
thread.addMessage('tool', [
|
|
333
358
|
{
|
|
334
359
|
type: 'function_response',
|
|
335
|
-
content: {name: f.name,
|
|
360
|
+
content: {name: f.name, function_response, id: f.id || undefined},
|
|
336
361
|
},
|
|
337
362
|
], f.name);
|
|
338
363
|
|
|
339
|
-
await this.log('function_response',
|
|
364
|
+
await this.log('function_response', function_response);
|
|
340
365
|
}
|
|
341
366
|
|
|
342
|
-
|
|
367
|
+
if (!response)
|
|
368
|
+
response = this.afterHandle(thread, completion, 'continue');
|
|
343
369
|
} else {
|
|
344
370
|
await thread.storeState();
|
|
345
|
-
|
|
371
|
+
response = this.afterHandle(thread, completion, 'void');
|
|
346
372
|
}
|
|
373
|
+
|
|
374
|
+
response
|
|
375
|
+
.then(content => {
|
|
376
|
+
emitter.emit('data', {type: 'response', content});
|
|
377
|
+
emitter.emit('end');
|
|
378
|
+
})
|
|
379
|
+
.catch(error => {
|
|
380
|
+
emitter.emit('error', error);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return emitter;
|
|
347
384
|
}
|
|
348
385
|
|
|
349
386
|
async afterHandle(thread, completion, type, value = null) {
|
|
@@ -376,23 +413,23 @@ export default class Agent {
|
|
|
376
413
|
return this.functions;
|
|
377
414
|
}
|
|
378
415
|
|
|
379
|
-
async callFunction(thread, function_call) {
|
|
416
|
+
async callFunction(thread, function_call, emitter) {
|
|
380
417
|
const functions = await this.getFunctions(false);
|
|
381
418
|
if (!functions.has(function_call.name))
|
|
382
419
|
throw new Error('Unrecognized function ' + function_call.name);
|
|
383
420
|
|
|
384
421
|
const func = functions.get(function_call.name);
|
|
385
422
|
const partialOutput = func.partialOutput ? ((typeof func.partialOutput) === 'text' ? func.partialOutput : func.partialOutput.call(this, function_call.arguments)) : 'Uso lo strumento ' + function_call.name + '...';
|
|
386
|
-
|
|
423
|
+
emitter.emit('data', {type: 'partial', content: partialOutput});
|
|
387
424
|
|
|
388
425
|
await this.log('function_call', function_call);
|
|
389
426
|
|
|
390
427
|
try {
|
|
391
428
|
const response = await func.tool.callFunction(thread, function_call.name, function_call.arguments);
|
|
392
|
-
|
|
429
|
+
emitter.emit('data', {type: 'partial', content: 'Risposta ricevuta da ' + func.tool.name});
|
|
393
430
|
return response;
|
|
394
431
|
} catch (error) {
|
|
395
|
-
|
|
432
|
+
emitter.emit('data', {type: 'partial', content: 'Ricevuto errore da ' + func.tool.name});
|
|
396
433
|
return {error};
|
|
397
434
|
}
|
|
398
435
|
}
|
|
@@ -414,7 +451,7 @@ export default class Agent {
|
|
|
414
451
|
return [this.name];
|
|
415
452
|
}
|
|
416
453
|
|
|
417
|
-
async createRealtimeSession(thread_id = null,
|
|
454
|
+
async createRealtimeSession(thread_id = null, options = {}) {
|
|
418
455
|
options = {
|
|
419
456
|
include_thread: true,
|
|
420
457
|
language: 'it',
|
|
@@ -422,7 +459,7 @@ export default class Agent {
|
|
|
422
459
|
};
|
|
423
460
|
|
|
424
461
|
// Se viene passato un thread esistente, lo si usa, altrimenti si crea un nuovo thread temporaneo
|
|
425
|
-
const thread = await this.getThread(thread_id || uuid()
|
|
462
|
+
const thread = await this.getThread(thread_id || uuid());
|
|
426
463
|
|
|
427
464
|
const system_message = [], conversation = [];
|
|
428
465
|
for (let message of thread.messages) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import EventEmitter from 'events';
|
|
2
|
+
|
|
3
|
+
export default class BufferedEventEmitter extends EventEmitter {
|
|
4
|
+
#buffer = [];
|
|
5
|
+
|
|
6
|
+
emit(eventName, ...args) {
|
|
7
|
+
if (this.listenerCount(eventName) > 0)
|
|
8
|
+
return super.emit(eventName, ...args);
|
|
9
|
+
|
|
10
|
+
this.#buffer.push({eventName, args});
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#flush() {
|
|
15
|
+
for (const {eventName, args} of this.#buffer)
|
|
16
|
+
if (this.listenerCount(eventName) > 0)
|
|
17
|
+
super.emit(eventName, ...args);
|
|
18
|
+
|
|
19
|
+
this.#buffer = this.#buffer.filter(({eventName}) => this.listenerCount(eventName) === 0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
on(eventName, listener, flush = true) {
|
|
23
|
+
super.on(eventName, listener);
|
|
24
|
+
if (flush)
|
|
25
|
+
this.#flush();
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/Symposium.js
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import Gpt4 from "./models/Gpt4.js";
|
|
5
|
-
import Gpt4Turbo from "./models/Gpt4Turbo.js";
|
|
6
|
-
import Gpt4O from "./models/Gpt4O.js";
|
|
7
|
-
import GptO1 from "./models/GptO1.js";
|
|
8
|
-
import GptO1Mini from "./models/GptO1Mini.js";
|
|
9
|
-
import Gpt5 from "./models/Gpt5.js";
|
|
10
|
-
import Gpt5Mini from "./models/Gpt5Mini.js";
|
|
11
|
-
import Whisper from "./models/Whisper.js";
|
|
12
|
-
import Claude35Sonnet from "./models/Claude35Sonnet.js";
|
|
13
|
-
import Claude37Sonnet from "./models/Claude37Sonnet.js";
|
|
14
|
-
import Claude4Sonnet from "./models/Claude4Sonnet.js";
|
|
15
|
-
import Claude4Opus from "./models/Claude4Opus.js";
|
|
16
|
-
import Llama3 from "./models/Llama3.js";
|
|
17
|
-
import Mixtral8 from "./models/Mixtral8.js";
|
|
18
|
-
import DeepSeekChat from "./models/DeepSeekChat.js";
|
|
19
|
-
import DeepSeekReasoner from "./models/DeepSeekReasoner.js";
|
|
20
|
-
import Grok4 from "./models/Grok4.js";
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import {fileURLToPath} from 'url';
|
|
21
4
|
|
|
22
5
|
export default class Symposium {
|
|
23
6
|
static models = new Map();
|
|
@@ -31,28 +14,24 @@ export default class Symposium {
|
|
|
31
14
|
* - async set(key, value)
|
|
32
15
|
*/
|
|
33
16
|
static async init(storage = null) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.loadModel(new DeepSeekChat());
|
|
53
|
-
this.loadModel(new DeepSeekReasoner());
|
|
54
|
-
|
|
55
|
-
this.loadModel(new Grok4());
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
const modelsPath = path.join(__dirname, 'models');
|
|
20
|
+
|
|
21
|
+
const modelFiles = await fs.promises.readdir(modelsPath);
|
|
22
|
+
for (const file of modelFiles) {
|
|
23
|
+
if (!file.endsWith('.js'))
|
|
24
|
+
continue;
|
|
25
|
+
|
|
26
|
+
const module = await import(`./models/${file}`);
|
|
27
|
+
const ModelClass = module.default;
|
|
28
|
+
if (ModelClass) {
|
|
29
|
+
const model = new ModelClass();
|
|
30
|
+
if (!model.name)
|
|
31
|
+
continue;
|
|
32
|
+
this.loadModel(model);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
56
35
|
|
|
57
36
|
if (storage) {
|
|
58
37
|
this.storage = storage;
|
|
@@ -97,7 +76,7 @@ export default class Symposium {
|
|
|
97
76
|
if (!audio.data)
|
|
98
77
|
throw new Error('Audio URL is required');
|
|
99
78
|
|
|
100
|
-
if (audio.data
|
|
79
|
+
if (path.isAbsolute(audio.data)) { // Local path
|
|
101
80
|
// Get with fs
|
|
102
81
|
if (!fs.existsSync(audio.data))
|
|
103
82
|
throw new Error('Audio file does not exist at the specified path: ' + audio.data);
|
package/Thread.js
CHANGED
|
@@ -7,18 +7,15 @@ export default class Thread {
|
|
|
7
7
|
agent;
|
|
8
8
|
messages = [];
|
|
9
9
|
state = {};
|
|
10
|
-
interface = null;
|
|
11
10
|
|
|
12
|
-
constructor(id,
|
|
11
|
+
constructor(id, agent) {
|
|
13
12
|
this.id = id;
|
|
14
|
-
this.unique = agent.name + '-' +
|
|
13
|
+
this.unique = agent.name + '-' + id;
|
|
15
14
|
this.agent = agent;
|
|
16
|
-
this.interface = i;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
clone(keepMessages = true) {
|
|
20
|
-
let newThread = new Thread(this.id, this.
|
|
21
|
-
newThread.interface = this.interface;
|
|
18
|
+
let newThread = new Thread(this.id, this.agent);
|
|
22
19
|
newThread.state = this.state;
|
|
23
20
|
if (keepMessages)
|
|
24
21
|
newThread.messages = [...this.messages];
|
|
@@ -27,7 +24,7 @@ export default class Thread {
|
|
|
27
24
|
|
|
28
25
|
changeId(id) {
|
|
29
26
|
this.id = id;
|
|
30
|
-
this.unique = this.agent.name + '-' +
|
|
27
|
+
this.unique = this.agent.name + '-' + id;
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
async flush() {
|
package/index.js
CHANGED
|
@@ -7,7 +7,6 @@ import Tool from "./Tool.js";
|
|
|
7
7
|
import Logger from "./Logger.js";
|
|
8
8
|
import MemoryHandler from "./MemoryHandler.js";
|
|
9
9
|
import Summarizer from "./Summarizer.js";
|
|
10
|
-
import Interface from "./Interface.js";
|
|
11
10
|
|
|
12
11
|
export {
|
|
13
12
|
Symposium,
|
|
@@ -17,5 +16,4 @@ export {
|
|
|
17
16
|
Logger,
|
|
18
17
|
MemoryHandler,
|
|
19
18
|
Summarizer,
|
|
20
|
-
Interface,
|
|
21
19
|
};
|
package/package.json
CHANGED
package/Interface.js
DELETED
package/README.md
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
# Symposium
|
|
2
|
-
|
|
3
|
-
Symposium is an npm library designed to simplify the deployment and management of AI agents. Its modular and flexible architecture makes it easy to create agents that can interact with users, execute functions through integrated tools, and manage complex conversation threads—all while supporting multiple language models from various providers.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Note: I am as lazy as all devs are, hence this README is written by an AI as well :)) I will improve it with time if needed
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Table of Contents
|
|
12
|
-
|
|
13
|
-
- [Overview](#overview)
|
|
14
|
-
- [Features](#features)
|
|
15
|
-
- [Installation](#installation)
|
|
16
|
-
- [Usage](#usage)
|
|
17
|
-
- [Architecture](#architecture)
|
|
18
|
-
- [Supported Models](#supported-models)
|
|
19
|
-
- [Extending Symposium](#extending-symposium)
|
|
20
|
-
- [Examples](#examples)
|
|
21
|
-
- [Contributing](#contributing)
|
|
22
|
-
- [License](#license)
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Overview
|
|
27
|
-
|
|
28
|
-
Symposium provides a robust framework for deploying AI agents with ease. It handles message threading, state management, logging, and even conversation summarization to keep interactions within token limits. With built-in support for multiple AI models and a plugin-like system for tools, Symposium enables developers to quickly build sophisticated agent-based applications.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Features
|
|
33
|
-
|
|
34
|
-
- **Agent Management**: Create and manage agents that process user messages and execute actions.
|
|
35
|
-
- **Tool Integration**: Extend your agents’ capabilities by integrating custom tools with defined functions.
|
|
36
|
-
- **Thread & Memory Handling**: Manage conversation state and threads seamlessly, with automatic summarization support.
|
|
37
|
-
- **Multi-Agent Coordination**: Support for multi-agent systems to allow agents to collaborate and share tasks.
|
|
38
|
-
- **Flexible Model Support**: Out-of-the-box compatibility with various models including OpenAI, Anthropic, Groq, and DeepSeek.
|
|
39
|
-
- **Structured Function Calls**: Enable agents to call functions using structured output, ensuring clarity and consistency.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Installation
|
|
44
|
-
|
|
45
|
-
Install Symposium via npm:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
npm install symposium
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## Usage
|
|
54
|
-
|
|
55
|
-
Below is a basic example of how to create a custom agent using Symposium:
|
|
56
|
-
|
|
57
|
-
```js
|
|
58
|
-
import { Symposium, Agent } from 'symposium';
|
|
59
|
-
|
|
60
|
-
class MyAgent extends Agent {
|
|
61
|
-
constructor(options = {}) {
|
|
62
|
-
super(options);
|
|
63
|
-
this.name = 'MyAgent';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async doInitThread(thread) {
|
|
67
|
-
await thread.addMessage('system', 'Welcome! How can I assist you today?');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
(async () => {
|
|
72
|
-
const myAgent = new MyAgent();
|
|
73
|
-
await myAgent.init();
|
|
74
|
-
|
|
75
|
-
const thread = await myAgent.getThread('example-thread');
|
|
76
|
-
await myAgent.message(thread, 'default', 'Hello, agent!');
|
|
77
|
-
})();
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Architecture
|
|
83
|
-
|
|
84
|
-
Symposium is built around several core components:
|
|
85
|
-
|
|
86
|
-
- **Agent**: The base class for creating agents. Agents process messages, manage threads, and interact with tools.
|
|
87
|
-
- **Tool**: Extend functionality by integrating custom functions. Tools define their own functions and provide implementations through the `callFunction` method.
|
|
88
|
-
- **Thread**: Represents a conversation, handling message storage, state management, and persistence.
|
|
89
|
-
- **Logger & MemoryHandler**: Utilities for logging events and managing conversation memory, respectively.
|
|
90
|
-
- **Summarizer**: A specialized memory handler that summarizes conversation threads to maintain context within token limits.
|
|
91
|
-
- **Interface**: Abstracts output and error handling, enabling custom integrations with external systems.
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## Supported Models
|
|
96
|
-
|
|
97
|
-
Symposium supports a variety of AI models, allowing you to switch between them based on your needs:
|
|
98
|
-
|
|
99
|
-
- **OpenAI Models**:
|
|
100
|
-
- GPT-3.5-turbo
|
|
101
|
-
- GPT-4, GPT-4Turbo, GPT-4o
|
|
102
|
-
- GPT-o1, GPT-o1 mini
|
|
103
|
-
- **Anthropic Models**:
|
|
104
|
-
- Claude variants (3.5 Sonnet, 3.7 Sonnet, 4 Sonnet, 4 Opus)
|
|
105
|
-
- **Groq Models**:
|
|
106
|
-
- Llama3, Mixtral8
|
|
107
|
-
- **DeepSeek Models**:
|
|
108
|
-
- DeepSeekChat, DeepSeekReasoner
|
|
109
|
-
- **Other Models**:
|
|
110
|
-
- Whisper (for speech-to-text)
|
|
111
|
-
|
|
112
|
-
These models are encapsulated in their own modules, and you can easily switch the active model by updating the conversation thread state.
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## Extending Symposium
|
|
117
|
-
|
|
118
|
-
Symposium is designed to be extended and customized:
|
|
119
|
-
|
|
120
|
-
- **Creating Custom Agents**: Inherit from the `Agent` class and override methods like `doInitThread` and `message` to implement custom behavior.
|
|
121
|
-
- **Implementing Tools**: Build your own tools by extending the `Tool` class. Define the functions your tool exposes and provide implementations via `callFunction`.
|
|
122
|
-
- **Custom Memory Handlers & Loggers**: Implement your own memory or logging strategies by creating classes that adhere to the respective interfaces.
|
|
123
|
-
- **Defining Interfaces**: Customize output and error handling by implementing your own `Interface` classes.
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Examples
|
|
128
|
-
|
|
129
|
-
The repository includes an `examples` folder with sample implementations demonstrating:
|
|
130
|
-
|
|
131
|
-
- **ChatAgent**: A basic conversational agent.
|
|
132
|
-
- **MultiAgent**: An agent that coordinates with other agents via internal interfaces.
|
|
133
|
-
- **TitlerAgent**: An agent that generates concise titles for conversations.
|
|
134
|
-
- **Tools**: Examples like `GenericTool` and `MultiAgentTool` show how to integrate custom functionality.
|
|
135
|
-
|
|
136
|
-
> **Important:** The content in the `examples` folder is provided solely for demo purposes and will not be part of the exported npm package. You can use these examples as a guide for building your own implementations.
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## Contributing
|
|
141
|
-
|
|
142
|
-
Contributions to Symposium are welcome! If you have ideas, improvements, or bug fixes, please fork the repository and submit a pull request.
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## License
|
|
147
|
-
|
|
148
|
-
Symposium is released under the ISC License.
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
For more details or to report issues, please refer to the repository's issue tracker. Enjoy building your AI-driven applications with Symposium!
|
package/examples/ChatAgent.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import {Agent, Symposium} from "symposium";
|
|
2
|
-
import GenericTool from "./Tools/GenericTool.js";
|
|
3
|
-
|
|
4
|
-
export default class ChatAgent extends Agent {
|
|
5
|
-
default_model = 'gpt-4o';
|
|
6
|
-
|
|
7
|
-
constructor(options = {}) {
|
|
8
|
-
super(options);
|
|
9
|
-
|
|
10
|
-
this.name = 'ChatAgent';
|
|
11
|
-
|
|
12
|
-
this.addTool(new GenericTool());
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async doInitThread(thread) {
|
|
16
|
-
await thread.addMessage('system',
|
|
17
|
-
`You are a helpful assistant, assist the user using the tools you are provided with.
|
|
18
|
-
Current time is: ${(new Date()).toLocaleDateString()}`
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
}
|
package/examples/MultiAgent.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import {Agent, Symposium} from "symposium";
|
|
2
|
-
import MultiAgentTool from "./Tools/MultiAgentTool.js";
|
|
3
|
-
|
|
4
|
-
export default class MultiAgent extends Agent {
|
|
5
|
-
default_model = 'gpt-4o';
|
|
6
|
-
|
|
7
|
-
constructor(options = {}) {
|
|
8
|
-
options = {
|
|
9
|
-
agents: [],
|
|
10
|
-
...options,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
super(options);
|
|
14
|
-
this.name = 'Multi Agent';
|
|
15
|
-
|
|
16
|
-
this.internalInterface = {
|
|
17
|
-
name: 'agent-vs-agent',
|
|
18
|
-
promises: [],
|
|
19
|
-
init: async () => {
|
|
20
|
-
},
|
|
21
|
-
message: async (thread, msg) => {
|
|
22
|
-
},
|
|
23
|
-
output: async (thread, msg) => {
|
|
24
|
-
},
|
|
25
|
-
error: async (thread, error) => {
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
this.addTool(new MultiAgentTool(this.options.agents));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async init() {
|
|
33
|
-
await super.init();
|
|
34
|
-
|
|
35
|
-
for (let agent of this.options.agents) {
|
|
36
|
-
agent.options.interfaces = [this.internalInterface];
|
|
37
|
-
await agent.init();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async reset(thread) {
|
|
42
|
-
await super.reset(thread);
|
|
43
|
-
|
|
44
|
-
for (let agent of this.options.agents) {
|
|
45
|
-
const sub_thread = await agent.getThread(thread.id, 'agent-vs-agent');
|
|
46
|
-
await agent.reset(sub_thread);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async doInitThread(thread) {
|
|
51
|
-
const memory = await this.tools.get('Memory').get();
|
|
52
|
-
|
|
53
|
-
await thread.addMessage('system',
|
|
54
|
-
`You are a helpful assistant, assist the user using the tools you are provided with.
|
|
55
|
-
In order to do so, you have a series of agents at your service, with which you can communicate behind the scenes to provide the user with what they asked for. You can communicate with them through their respective tools that you have.
|
|
56
|
-
You can talk to the agents in natural language, and also respond to them multiple times if necessary. Try to use clear and direct language with precise instructions when talking to an agent.
|
|
57
|
-
|
|
58
|
-
At the end of each task, when the agent has completed its function, remember to reset the conversation with the agent to save memory. You can do this using the "reset" function.
|
|
59
|
-
|
|
60
|
-
Interrogate them only if the user asks to perform tasks that concern them, otherwise you can rely on your knowledge to respond directly.
|
|
61
|
-
|
|
62
|
-
Current time is: ${(new Date()).toLocaleDateString()}`,
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
for (let agent of this.options.agents) {
|
|
66
|
-
const sub_thread = await agent.getThread(thread.id, 'agent-vs-agent');
|
|
67
|
-
if (sub_thread) {
|
|
68
|
-
await sub_thread.flush();
|
|
69
|
-
await agent.initThread(sub_thread);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async beforeExecute(thread) {
|
|
75
|
-
if (this.options.reasoning) {
|
|
76
|
-
let reasoning = await this.generateCompletion(thread, {
|
|
77
|
-
functions: [
|
|
78
|
-
{
|
|
79
|
-
name: 'ragionamento',
|
|
80
|
-
description: 'Before responding to the user, ask yourself what needs to be done and if you need to ask for more info.',
|
|
81
|
-
parameters: {
|
|
82
|
-
type: 'object',
|
|
83
|
-
properties: {
|
|
84
|
-
ragionamento: {
|
|
85
|
-
type: 'string',
|
|
86
|
-
description: 'Reflect on the request just made. What do you need to do to fulfill the user\'s request? These are your thoughts and the user cannot see them.',
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
required: ['ragionamento'],
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
...(await this.getFunctions()),
|
|
93
|
-
],
|
|
94
|
-
force_function: 'ragionamento',
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
if (reasoning) {
|
|
98
|
-
reasoning = Symposium.extractFunctionsFromResponse(reasoning)[0];
|
|
99
|
-
await this.log('reasoning', reasoning);
|
|
100
|
-
thread.addMessage('assistant', "[Thinking] " + reasoning.ragionamento + "\n");
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return thread;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async getPromptWordsForTranscription(thread) {
|
|
108
|
-
let words = [this.name];
|
|
109
|
-
for (let agent of this.options.agents) {
|
|
110
|
-
const sub_thread = await agent.getThread(thread.id, 'agent-vs-agent');
|
|
111
|
-
words = [...words, ...(await agent.getPromptWordsForTranscription(sub_thread))];
|
|
112
|
-
}
|
|
113
|
-
return words;
|
|
114
|
-
}
|
|
115
|
-
}
|
package/examples/TitlerAgent.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import {Agent} from "symposium";
|
|
2
|
-
|
|
3
|
-
export default class TitlerAgent extends Agent {
|
|
4
|
-
default_model = 'gpt-4o';
|
|
5
|
-
|
|
6
|
-
constructor(options) {
|
|
7
|
-
super(options);
|
|
8
|
-
this.name = 'Titler';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async init() {
|
|
12
|
-
await super.init();
|
|
13
|
-
|
|
14
|
-
this.utility = {
|
|
15
|
-
type: 'text',
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async doInitThread(thread) {
|
|
20
|
-
await thread.addMessage('system',
|
|
21
|
-
`Your task is to observe the following message, which represents the beginning of a conversation from the user, and give a title of a few words (maximum 30 characters) that describes it as accurately as possible. Even a single word is fine, if necessary.
|
|
22
|
-
Do not write anything else in your message, but only and exclusively the title.`
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import {Tool} from "symposium";
|
|
2
|
-
|
|
3
|
-
export default class GenericTool extends Tool {
|
|
4
|
-
name = 'GenericTool';
|
|
5
|
-
|
|
6
|
-
async getFunctions() {
|
|
7
|
-
return [
|
|
8
|
-
{
|
|
9
|
-
name: `create_todo`,
|
|
10
|
-
description: `Create a new TODO`,
|
|
11
|
-
parameters: {
|
|
12
|
-
type: 'object',
|
|
13
|
-
properties: {
|
|
14
|
-
title: {
|
|
15
|
-
type: 'string',
|
|
16
|
-
description: 'Title of the TODO',
|
|
17
|
-
},
|
|
18
|
-
description: {
|
|
19
|
-
type: 'string',
|
|
20
|
-
description: 'Description of the TODO',
|
|
21
|
-
},
|
|
22
|
-
due_date: {
|
|
23
|
-
type: 'string',
|
|
24
|
-
description: 'Due date of the TODO',
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
required: ['title', 'description', 'due_date'],
|
|
28
|
-
},
|
|
29
|
-
}
|
|
30
|
-
];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async callFunction(thread, name, payload) {
|
|
34
|
-
try {
|
|
35
|
-
// Implement your function here
|
|
36
|
-
|
|
37
|
-
throw new Error(`Unknown function: ${name}`);
|
|
38
|
-
} catch (error) {
|
|
39
|
-
return {error: error.response?.body || error};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import {Tool} from "symposium";
|
|
2
|
-
|
|
3
|
-
export default class MultiAgentTool extends Tool {
|
|
4
|
-
name = 'MultiAgentTool';
|
|
5
|
-
agents;
|
|
6
|
-
|
|
7
|
-
constructor(agents) {
|
|
8
|
-
super();
|
|
9
|
-
this.agents = agents;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async getFunctions() {
|
|
13
|
-
return [
|
|
14
|
-
...this.agents.map(a => ({
|
|
15
|
-
name: a.name,
|
|
16
|
-
description: a.description,
|
|
17
|
-
parameters: {
|
|
18
|
-
type: 'object',
|
|
19
|
-
properties: {
|
|
20
|
-
message: {
|
|
21
|
-
type: 'string',
|
|
22
|
-
description: 'The message to send to the agent'
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
})),
|
|
27
|
-
{
|
|
28
|
-
name: 'reset',
|
|
29
|
-
description: 'Reset an agent\'s memory to start the conversation again',
|
|
30
|
-
parameters: {
|
|
31
|
-
type: 'object',
|
|
32
|
-
properties: {
|
|
33
|
-
agent: {
|
|
34
|
-
type: 'string',
|
|
35
|
-
description: 'The name of the agent to reset'
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async callFunction(thread, name, payload) {
|
|
44
|
-
try {
|
|
45
|
-
if (name === 'reset') {
|
|
46
|
-
const agent = this.agents.find(a => a.name === payload.agent);
|
|
47
|
-
if (!agent)
|
|
48
|
-
throw new Error('No agent named "' + payload.agent + '"');
|
|
49
|
-
|
|
50
|
-
const sub_thread = await agent.getThread(thread.id, 'agent-vs-agent');
|
|
51
|
-
|
|
52
|
-
await agent.reset(sub_thread);
|
|
53
|
-
|
|
54
|
-
return {success: true};
|
|
55
|
-
} else {
|
|
56
|
-
const agent = this.agents.find(a => a.name === name);
|
|
57
|
-
if (!agent)
|
|
58
|
-
throw new Error('No agent named "' + name + '"');
|
|
59
|
-
|
|
60
|
-
return await (new Promise(async (resolve, reject) => {
|
|
61
|
-
try {
|
|
62
|
-
const sub_thread = await agent.getThread(thread.id, 'agent-vs-agent');
|
|
63
|
-
|
|
64
|
-
await agent.message(sub_thread, 'agent-vs-agent', payload.message, msg => {
|
|
65
|
-
resolve({reply: msg});
|
|
66
|
-
});
|
|
67
|
-
} catch (e) {
|
|
68
|
-
console.error(JSON.stringify(e));
|
|
69
|
-
reject(e);
|
|
70
|
-
}
|
|
71
|
-
}));
|
|
72
|
-
}
|
|
73
|
-
} catch (error) {
|
|
74
|
-
return {error};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|