symposium 0.14.20 → 0.15.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/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
- callbacks = {};
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
- for (let i of this.options.interfaces)
29
- await i.init(this);
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, i = 'default') {
75
+ async getThread(id) {
64
76
  let thread = this.threads.get(id);
65
- if (thread) {
66
- if (thread.interface !== i)
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(thread, i, content, callback = null) {
83
- if (typeof thread !== 'object')
84
- thread = await this.getThread(thread, i);
91
+ async message(content, thread = null) {
92
+ if (!this.initialized)
93
+ throw new Error('Agent not initialized');
85
94
 
86
- if (callback) {
87
- if (!this.callbacks.hasOwnProperty(i + '-' + thread.id))
88
- this.callbacks[i + '-' + thread.id] = [];
89
- this.callbacks[i + '-' + thread.id].push(callback);
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) { // Se ci sono più di 100 parametri, OpenAI non supporta gli structured output
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,62 @@ export default class Agent {
149
154
  }
150
155
  }
151
156
 
152
- const completion = await this.generateCompletion(thread, completion_options);
153
- if (completion) {
154
- try {
155
- thread = await this.afterExecute(thread, completion);
156
- const response = await this.handleCompletion(thread, completion);
157
- switch (response.type) {
158
- case 'return':
159
- return response.value;
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
- case 'continue':
162
- return await this.execute(thread);
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
+ const parsed = JSON.parse(data);
187
+ if (parsed.type === 'response')
188
+ resolve(parsed.content);
189
+ });
190
+
191
+ emitter.on('error', error => reject(error));
192
+ });
163
193
 
164
- case 'void':
165
- return;
194
+ case 'chat':
195
+ if (!existing_emitter) {
196
+ emitter.on('data', data => {
197
+ const parsed = JSON.parse(data);
198
+ if (parsed.type === 'response' && parsed.content.type === 'continue')
199
+ this.execute(thread, 0, emitter);
200
+ }, false);
201
+ }
166
202
 
167
- default:
168
- throw new Error('Unknown response type');
169
- }
170
- } catch (e) {
171
- console.error(e);
203
+ return emitter;
172
204
 
173
- if (counter < this.max_retries)
174
- await this.execute(thread, counter + 1);
205
+ default:
206
+ throw new Error('Bad agent type');
175
207
  }
208
+ } catch (e) {
209
+ console.error(e);
210
+
211
+ if (counter < this.max_retries)
212
+ await this.execute(thread, counter + 1, existing_emitter);
176
213
  }
177
214
  }
178
215
 
@@ -180,11 +217,16 @@ export default class Agent {
180
217
  if (obj.type !== 'object')
181
218
  return {obj, count: 0};
182
219
 
183
- let properties_count = 0, required = [];
220
+ let properties_count = 0, all_required = false, required = [];
221
+ if (obj.required)
222
+ required = obj.required;
223
+ else
224
+ all_required = true;
184
225
 
185
226
  for (let [key, property] of Object.entries(obj.properties || {})) {
186
227
  properties_count++;
187
- required.push(key);
228
+ if (all_required)
229
+ required.push(key);
188
230
 
189
231
  if (property.type === 'object') {
190
232
  const {obj: subobj, count} = this.convertFunctionToResponseFormat(property);
@@ -232,43 +274,15 @@ export default class Agent {
232
274
  return this.generateCompletion(thread, options, retry_counter + 1);
233
275
  }
234
276
 
235
- await this.error(thread, error.response.status + ': ' + JSON.stringify(error.response.data));
277
+ throw new Error(error.response.status + ': ' + JSON.stringify(error.response.data));
236
278
  } else if (error.message) {
237
- console.error(error.message);
238
- await this.error(thread, error.message);
279
+ throw new Error(error.message);
239
280
  } else {
240
- console.error(error);
241
- await this.error(thread, 'Errore interno');
281
+ throw new Error('Errore interno');
242
282
  }
243
283
  }
244
284
  }
245
285
 
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
286
  parseFunctions(message) {
273
287
  const newContent = [];
274
288
  for (let m of message.content) {
@@ -294,9 +308,11 @@ export default class Agent {
294
308
  return message;
295
309
  }
296
310
 
297
- async handleCompletion(thread, completion) {
311
+ async handleCompletion(thread, completion, existing_emitter = null) {
298
312
  const model = Symposium.getModelByName(thread.state.model);
299
313
 
314
+ const emitter = existing_emitter || new BufferedEventEmitter();
315
+
300
316
  const functions = [];
301
317
  for (let message of completion) {
302
318
  thread.addDirectMessage(message);
@@ -305,13 +321,21 @@ export default class Agent {
305
321
  for (let m of message.content) {
306
322
  switch (m.type) {
307
323
  case 'text':
308
- if (this.utility) {
324
+ if (this.type === 'utility') {
325
+ let response = null;
309
326
  if (this.utility.type === 'text')
310
- return this.afterHandle(thread, completion, 'return', m.content);
327
+ response = await this.afterHandle(thread, completion, 'return', m.content);
311
328
  if (this.utility.type === 'json' && model.supports_structured_output)
312
- return this.afterHandle(thread, completion, 'return', JSON.parse(m.content));
329
+ response = await this.afterHandle(thread, completion, 'return', JSON.parse(m.content));
330
+
331
+ if (response) {
332
+ emitter.emit('data', JSON.stringify({type: 'response', content: response}));
333
+ emitter.emit('end');
334
+ return emitter;
335
+ }
313
336
  }
314
- await this.output(thread, m.content);
337
+
338
+ emitter.emit('data', JSON.stringify({type: 'output', content: m.content}));
315
339
  break;
316
340
 
317
341
  case 'function':
@@ -322,28 +346,43 @@ export default class Agent {
322
346
  }
323
347
  }
324
348
 
349
+ let response;
325
350
  if (functions.length) {
326
351
  for (let f of functions) {
327
- if (this.utility && ['function', 'json'].includes(this.utility.type))
328
- return this.afterHandle(thread, completion, 'return', f.arguments);
352
+ if (this.utility && ['function', 'json'].includes(this.utility.type)) {
353
+ response = this.afterHandle(thread, completion, 'return', f.arguments);
354
+ break;
355
+ }
329
356
 
330
- const response = await this.callFunction(thread, f);
357
+ const function_response = await this.callFunction(thread, f, emitter);
331
358
 
332
359
  thread.addMessage('tool', [
333
360
  {
334
361
  type: 'function_response',
335
- content: {name: f.name, response, id: f.id || undefined},
362
+ content: {name: f.name, function_response, id: f.id || undefined},
336
363
  },
337
364
  ], f.name);
338
365
 
339
- await this.log('function_response', response);
366
+ await this.log('function_response', function_response);
340
367
  }
341
368
 
342
- return this.afterHandle(thread, completion, 'continue');
369
+ if (!response)
370
+ response = this.afterHandle(thread, completion, 'continue');
343
371
  } else {
344
372
  await thread.storeState();
345
- return this.afterHandle(thread, completion, 'void');
373
+ response = this.afterHandle(thread, completion, 'void');
346
374
  }
375
+
376
+ response
377
+ .then(content => {
378
+ emitter.emit('data', JSON.stringify({type: 'response', content}));
379
+ emitter.emit('end');
380
+ })
381
+ .catch(error => {
382
+ emitter.emit('error', error);
383
+ });
384
+
385
+ return emitter;
347
386
  }
348
387
 
349
388
  async afterHandle(thread, completion, type, value = null) {
@@ -376,23 +415,23 @@ export default class Agent {
376
415
  return this.functions;
377
416
  }
378
417
 
379
- async callFunction(thread, function_call) {
418
+ async callFunction(thread, function_call, emitter) {
380
419
  const functions = await this.getFunctions(false);
381
420
  if (!functions.has(function_call.name))
382
421
  throw new Error('Unrecognized function ' + function_call.name);
383
422
 
384
423
  const func = functions.get(function_call.name);
385
424
  const partialOutput = func.partialOutput ? ((typeof func.partialOutput) === 'text' ? func.partialOutput : func.partialOutput.call(this, function_call.arguments)) : 'Uso lo strumento ' + function_call.name + '...';
386
- this.partial(thread, partialOutput);
425
+ emitter.emit('data', JSON.stringify({type: 'partial', content: partialOutput}));
387
426
 
388
427
  await this.log('function_call', function_call);
389
428
 
390
429
  try {
391
430
  const response = await func.tool.callFunction(thread, function_call.name, function_call.arguments);
392
- this.partial(thread, 'Risposta ricevuta da ' + func.tool.name);
431
+ emitter.emit('data', JSON.stringify({type: 'partial', content: 'Risposta ricevuta da ' + func.tool.name}));
393
432
  return response;
394
433
  } catch (error) {
395
- this.partial(thread, 'Ricevuto errore da ' + func.tool.name);
434
+ emitter.emit('data', JSON.stringify({type: 'partial', content: 'Ricevuto errore da ' + func.tool.name}));
396
435
  return {error};
397
436
  }
398
437
  }
@@ -414,9 +453,15 @@ export default class Agent {
414
453
  return [this.name];
415
454
  }
416
455
 
417
- async createRealtimeSession(thread_id = null, interface_name = 'default') {
456
+ async createRealtimeSession(thread_id = null, options = {}) {
457
+ options = {
458
+ include_thread: true,
459
+ language: 'it',
460
+ ...options,
461
+ };
462
+
418
463
  // Se viene passato un thread esistente, lo si usa, altrimenti si crea un nuovo thread temporaneo
419
- const thread = await this.getThread(thread_id || uuid(), interface_name);
464
+ const thread = await this.getThread(thread_id || uuid());
420
465
 
421
466
  const system_message = [], conversation = [];
422
467
  for (let message of thread.messages) {
@@ -427,7 +472,7 @@ export default class Agent {
427
472
  }
428
473
 
429
474
  let instructions = system_message.join('\n');
430
- if (conversation.length)
475
+ if (conversation.length && options.include_thread)
431
476
  instructions += '\n\n# Ecco la tua conversazione fino ad ora: #\n' + conversation.join('\n');
432
477
 
433
478
  const tools = (await this.getFunctions()).map(t => ({
@@ -447,6 +492,7 @@ export default class Agent {
447
492
  tools,
448
493
  input_audio_transcription: {
449
494
  model: 'gpt-4o-transcribe',
495
+ language: options.language,
450
496
  },
451
497
  }),
452
498
  }).then(response => response.json());
@@ -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 Gpt35 from "./models/Gpt35.js";
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
- this.loadModel(new Gpt35());
35
- this.loadModel(new Gpt4());
36
- this.loadModel(new Gpt4Turbo());
37
- this.loadModel(new Gpt4O());
38
- this.loadModel(new GptO1());
39
- this.loadModel(new GptO1Mini());
40
- this.loadModel(new Whisper());
41
- this.loadModel(new Gpt5());
42
- this.loadModel(new Gpt5Mini());
43
-
44
- this.loadModel(new Claude35Sonnet());
45
- this.loadModel(new Claude37Sonnet());
46
- this.loadModel(new Claude4Sonnet());
47
- this.loadModel(new Claude4Opus());
48
-
49
- this.loadModel(new Llama3());
50
- this.loadModel(new Mixtral8());
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.startsWith('/')) { // Local path
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, i, agent) {
11
+ constructor(id, agent) {
13
12
  this.id = id;
14
- this.unique = agent.name + '-' + i + '-' + id;
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.interface, this.agent);
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 + '-' + this.interface + '-' + id;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "symposium",
4
- "version": "0.14.20",
4
+ "version": "0.15.0",
5
5
  "description": "Agents",
6
6
  "main": "index.js",
7
7
  "author": "Domenico Giambra",
package/Interface.js DELETED
@@ -1,15 +0,0 @@
1
- export default class Interface {
2
- static name;
3
-
4
- static async init(agent) {
5
- }
6
-
7
- static async output(thread, msg) {
8
- }
9
-
10
- static async partial(thread, msg) {
11
- }
12
-
13
- static async error(thread, error) {
14
- }
15
- }
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!
@@ -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
- }
@@ -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
- }
@@ -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
- }