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 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,60 @@ 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
+ if (data.type === 'response')
187
+ resolve(data.content);
188
+ });
189
+
190
+ emitter.on('error', error => reject(error));
191
+ });
163
192
 
164
- case 'void':
165
- return;
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
- default:
168
- throw new Error('Unknown response type');
169
- }
170
- } catch (e) {
171
- console.error(e);
201
+ return emitter;
172
202
 
173
- if (counter < this.max_retries)
174
- await this.execute(thread, counter + 1);
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
- required.push(key);
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
- await this.error(thread, error.response.status + ': ' + JSON.stringify(error.response.data));
275
+ throw new Error(error.response.status + ': ' + JSON.stringify(error.response.data));
236
276
  } else if (error.message) {
237
- console.error(error.message);
238
- await this.error(thread, error.message);
277
+ throw new Error(error.message);
239
278
  } else {
240
- console.error(error);
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
- return this.afterHandle(thread, completion, 'return', m.content);
325
+ response = await this.afterHandle(thread, completion, 'return', m.content);
311
326
  if (this.utility.type === 'json' && model.supports_structured_output)
312
- return this.afterHandle(thread, completion, 'return', JSON.parse(m.content));
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
- await this.output(thread, m.content);
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
- return this.afterHandle(thread, completion, 'return', f.arguments);
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 response = await this.callFunction(thread, f);
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, response, id: f.id || undefined},
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', response);
364
+ await this.log('function_response', function_response);
340
365
  }
341
366
 
342
- return this.afterHandle(thread, completion, 'continue');
367
+ if (!response)
368
+ response = this.afterHandle(thread, completion, 'continue');
343
369
  } else {
344
370
  await thread.storeState();
345
- return this.afterHandle(thread, completion, 'void');
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
- this.partial(thread, partialOutput);
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
- this.partial(thread, 'Risposta ricevuta da ' + func.tool.name);
429
+ emitter.emit('data', {type: 'partial', content: 'Risposta ricevuta da ' + func.tool.name});
393
430
  return response;
394
431
  } catch (error) {
395
- this.partial(thread, 'Ricevuto errore da ' + func.tool.name);
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, interface_name = 'default', options = {}) {
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(), interface_name);
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 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.21",
4
+ "version": "0.15.1",
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
- }