symposium 0.15.0 → 0.15.2

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.
Files changed (3) hide show
  1. package/Agent.js +9 -11
  2. package/README.md +282 -0
  3. package/package.json +1 -1
package/Agent.js CHANGED
@@ -183,9 +183,8 @@ export default class Agent {
183
183
  case 'utility':
184
184
  return new Promise((resolve, reject) => {
185
185
  emitter.on('data', data => {
186
- const parsed = JSON.parse(data);
187
- if (parsed.type === 'response')
188
- resolve(parsed.content);
186
+ if (data.type === 'response')
187
+ resolve(data.content);
189
188
  });
190
189
 
191
190
  emitter.on('error', error => reject(error));
@@ -194,8 +193,7 @@ export default class Agent {
194
193
  case 'chat':
195
194
  if (!existing_emitter) {
196
195
  emitter.on('data', data => {
197
- const parsed = JSON.parse(data);
198
- if (parsed.type === 'response' && parsed.content.type === 'continue')
196
+ if (data.type === 'response' && data.content.type === 'continue')
199
197
  this.execute(thread, 0, emitter);
200
198
  }, false);
201
199
  }
@@ -329,13 +327,13 @@ export default class Agent {
329
327
  response = await this.afterHandle(thread, completion, 'return', JSON.parse(m.content));
330
328
 
331
329
  if (response) {
332
- emitter.emit('data', JSON.stringify({type: 'response', content: response}));
330
+ emitter.emit('data', {type: 'response', content: response});
333
331
  emitter.emit('end');
334
332
  return emitter;
335
333
  }
336
334
  }
337
335
 
338
- emitter.emit('data', JSON.stringify({type: 'output', content: m.content}));
336
+ emitter.emit('data', {type: 'output', content: m.content});
339
337
  break;
340
338
 
341
339
  case 'function':
@@ -375,7 +373,7 @@ export default class Agent {
375
373
 
376
374
  response
377
375
  .then(content => {
378
- emitter.emit('data', JSON.stringify({type: 'response', content}));
376
+ emitter.emit('data', {type: 'response', content});
379
377
  emitter.emit('end');
380
378
  })
381
379
  .catch(error => {
@@ -422,16 +420,16 @@ export default class Agent {
422
420
 
423
421
  const func = functions.get(function_call.name);
424
422
  const partialOutput = func.partialOutput ? ((typeof func.partialOutput) === 'text' ? func.partialOutput : func.partialOutput.call(this, function_call.arguments)) : 'Uso lo strumento ' + function_call.name + '...';
425
- emitter.emit('data', JSON.stringify({type: 'partial', content: partialOutput}));
423
+ emitter.emit('data', {type: 'partial', content: partialOutput});
426
424
 
427
425
  await this.log('function_call', function_call);
428
426
 
429
427
  try {
430
428
  const response = await func.tool.callFunction(thread, function_call.name, function_call.arguments);
431
- emitter.emit('data', JSON.stringify({type: 'partial', content: 'Risposta ricevuta da ' + func.tool.name}));
429
+ emitter.emit('data', {type: 'partial', content: 'Risposta ricevuta da ' + func.tool.name});
432
430
  return response;
433
431
  } catch (error) {
434
- emitter.emit('data', JSON.stringify({type: 'partial', content: 'Ricevuto errore da ' + func.tool.name}));
432
+ emitter.emit('data', {type: 'partial', content: 'Ricevuto errore da ' + func.tool.name});
435
433
  return {error};
436
434
  }
437
435
  }
package/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # Symposium
2
+
3
+ Symposium is a powerful and flexible Node.js framework for building Large Language Model (LLM)-powered agents. It provides a structured, extensible architecture for creating complex AI systems with distinct behaviors, tools, and memory.
4
+
5
+ ## Features
6
+
7
+ - **Agent-Based Architecture**: Create multiple, specialized agents that can be extended with unique behaviors.
8
+ - **Model Agnostic**: Easily integrate with various LLM providers (OpenAI, Anthropic, Groq, etc.). A list of supported models is available in the `models` folder.
9
+ - **Tool Integration**: Extend agents' capabilities by giving them tools to interact with external systems.
10
+ - **Stateful Conversations**: Manages conversational state and history through Threads.
11
+ - **Persistent Memory**: Pluggable storage adapters allow for long-term memory.
12
+ - **Real-time Sessions**: Built-in support for real-time voice conversations.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install symposium
18
+ ```
19
+
20
+ ## Core Concepts
21
+
22
+ The framework is built around a few core components:
23
+
24
+ - **`Symposium`**: A static class that acts as the central hub. It's responsible for loading models and initializing the storage adapter.
25
+ - **`Agent`**: The heart of the framework. An `Agent` is an autonomous entity with a specific goal. You extend this class to define your agent's unique prompt, behavior, and tools.
26
+ - **`Thread`**: Represents a single conversation with an agent. It maintains the message history and the agent's state for that conversation. Each thread has a unique ID.
27
+ - **`Tool`**: A base class for creating tools that an `Agent` can use. Tools expose functions that the LLM can call to interact with external APIs or data.
28
+ - **`Message`**: A wrapper for messages within a `Thread`, containing the role (`user`, `assistant`, `system`, `tool`), content, and other metadata.
29
+
30
+ ## Getting Started
31
+
32
+ Here's a simple example of how to create a basic chat agent.
33
+
34
+ ### 1. Initialize Symposium
35
+
36
+ First, you need to initialize `Symposium`. This will load all the available models. You can also provide a storage adapter for persistence.
37
+
38
+ ```javascript
39
+ // index.js
40
+ import { Symposium } from 'symposium';
41
+
42
+ async function main() {
43
+ await Symposium.init(); // You can pass a storage adapter here
44
+ // ... your agent code
45
+ }
46
+
47
+ main();
48
+ ```
49
+
50
+ ### 2. Create your Agent
51
+
52
+ Create a new class that extends `Agent`. At a minimum, you'll want to define a name and a system prompt.
53
+
54
+ ```javascript
55
+ // MyChatAgent.js
56
+ import { Agent } from 'symposium';
57
+
58
+ export default class MyChatAgent extends Agent {
59
+ name = 'MyChatAgent';
60
+ description = 'A simple chat agent.';
61
+
62
+ async doInitThread(thread) {
63
+ await thread.addMessage('system', 'You are a helpful assistant.');
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### 3. Start a Conversation
69
+
70
+ Now you can instantiate your agent and start a conversation.
71
+
72
+ ```javascript
73
+ // index.js
74
+ import { Symposium } from 'symposium';
75
+ import MyChatAgent from './MyChatAgent.js';
76
+
77
+ async function main() {
78
+ await Symposium.init();
79
+
80
+ const agent = new MyChatAgent();
81
+ await agent.init();
82
+
83
+ const emitter = await agent.message('Hello, who are you?');
84
+
85
+ emitter.on('data', (data) => {
86
+ if (data.type === 'output') {
87
+ process.stdout.write(data.content);
88
+ }
89
+ });
90
+
91
+ emitter.on('end', () => {
92
+ console.log('\nConversation ended.');
93
+ });
94
+ }
95
+
96
+ main();
97
+ ```
98
+
99
+ When you run this, the agent will respond to your message, and the response will be streamed to the console. The `message` method returns an `EventEmitter` that emits `data` events for text chunks, partial tool usage, and the final response object.
100
+
101
+ ## Advanced Usage
102
+
103
+ ### Using Tools
104
+
105
+ Tools allow your agent to interact with the outside world. To create a tool, extend the `Tool` class and define one or more functions.
106
+
107
+ #### 1. Create a Tool
108
+
109
+ Here's an example of a tool that can get the current weather.
110
+
111
+ ```javascript
112
+ // WeatherTool.js
113
+ import { Tool } from 'symposium';
114
+
115
+ export default class WeatherTool extends Tool {
116
+ name = 'WeatherTool';
117
+
118
+ async getFunctions() {
119
+ return [
120
+ {
121
+ name: 'get_weather',
122
+ description: 'Get the current weather for a specific city',
123
+ parameters: {
124
+ type: 'object',
125
+ properties: {
126
+ city: {
127
+ type: 'string',
128
+ description: 'The city name',
129
+ },
130
+ },
131
+ required: ['city'],
132
+ },
133
+ },
134
+ ];
135
+ }
136
+
137
+ async callFunction(thread, name, payload) {
138
+ if (name === 'get_weather') {
139
+ const city = payload.city;
140
+ // In a real app, you would call a weather API here
141
+ return { temperature: '25°C', condition: 'sunny' };
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ #### 2. Add the Tool to your Agent
148
+
149
+ Now, add the tool to your agent instance.
150
+
151
+ ```javascript
152
+ // index.js
153
+ import MyChatAgent from './MyChatAgent.js';
154
+ import WeatherTool from './WeatherTool.js';
155
+
156
+ // ... inside main()
157
+ const agent = new MyChatAgent();
158
+ agent.addTool(new WeatherTool());
159
+ await agent.init();
160
+
161
+ const emitter = await agent.message("What's the weather like in Paris?");
162
+ // ...
163
+ ```
164
+
165
+ The agent's underlying LLM will now be able to see the `get_weather` function and will call it when appropriate, passing the result back into the conversation.
166
+
167
+ ### Switching Models
168
+
169
+ You can set a default model for an agent or change it on a per-thread basis.
170
+
171
+ ```javascript
172
+ // Setting a default model for the agent
173
+ class MyAgent extends Agent {
174
+ default_model = 'claude-3-5-sonnet';
175
+ //...
176
+ }
177
+
178
+ // Changing the model for a specific thread
179
+ const thread = await agent.getThread('thread-id');
180
+ await agent.setModel(thread, 'gpt-3.5-turbo');
181
+ ```
182
+
183
+ The model label must be one of the models available in the `models` directory.
184
+
185
+ ### Persistence
186
+
187
+ Symposium can persist thread state and messages if you provide a storage adapter. The adapter must implement three methods: `init()`, `get(key)`, and `set(key, value)`.
188
+
189
+ ```javascript
190
+ // MySimpleFileStorage.js
191
+ import fs from 'fs/promises';
192
+
193
+ class MySimpleFileStorage {
194
+ async init() {
195
+ await fs.mkdir('./storage', { recursive: true });
196
+ }
197
+ async get(key) {
198
+ try {
199
+ const data = await fs.readFile(`./storage/${key}.json`, 'utf-8');
200
+ return JSON.parse(data);
201
+ } catch (e) {
202
+ return null;
203
+ }
204
+ }
205
+ async set(key, value) {
206
+ await fs.writeFile(`./storage/${key}.json`, JSON.stringify(value, null, 2));
207
+ }
208
+ }
209
+
210
+ // index.js
211
+ await Symposium.init(new MySimpleFileStorage());
212
+ ```
213
+
214
+ With a storage adapter in place, conversations will be saved and loaded automatically based on the thread ID.
215
+
216
+ ### Utility Agents
217
+
218
+ Besides `chat` agents, you can create `utility` agents. These are designed for specific, one-shot tasks like data extraction or classification, rather than open-ended conversation. They typically return structured JSON.
219
+
220
+ ```javascript
221
+ // TextExtractorAgent.js
222
+ import { Agent } from 'symposium';
223
+
224
+ export default class TextExtractorAgent extends Agent {
225
+ name = 'TextExtractorAgent';
226
+ type = 'utility';
227
+ utility = {
228
+ type: 'json',
229
+ function: {
230
+ name: 'extract_data',
231
+ parameters: {
232
+ type: 'object',
233
+ properties: {
234
+ name: { type: 'string' },
235
+ email: { type: 'string' },
236
+ },
237
+ required: ['name', 'email'],
238
+ },
239
+ },
240
+ };
241
+
242
+ async doInitThread(thread) {
243
+ await thread.addMessage('system', 'Extract the name and email from the text.');
244
+ }
245
+ }
246
+
247
+ // Usage
248
+ const extractor = new TextExtractorAgent();
249
+ await extractor.init();
250
+ const result = await extractor.message('My name is John Doe and my email is john.doe@example.com');
251
+ console.log(result); // { name: 'John Doe', email: 'john.doe@example.com' }
252
+ ```
253
+
254
+ ## API Reference
255
+
256
+ This is a high-level overview. For details, please refer to the source code.
257
+
258
+ ### `Agent`
259
+
260
+ - `constructor(options)`: Creates a new agent. Options can include a `memory_handler` or `logger`.
261
+ - `init()`: Initializes the agent. Must be called before use.
262
+ - `addTool(tool)`: Adds a `Tool` instance to the agent.
263
+ - `message(content, thread)`: Sends a message to the agent. Returns an EventEmitter.
264
+ - `getThread(id)`: Retrieves a `Thread` instance by its ID.
265
+ - `setModel(thread, modelLabel)`: Changes the LLM for a specific thread.
266
+ - `createRealtimeSession(thread_id, options)`: Creates a real-time session for voice interaction.
267
+
268
+ ### `Thread`
269
+
270
+ - `constructor(id, agent)`: Creates a new thread.
271
+ - `addMessage(role, content, name, tags)`: Adds a message to the thread.
272
+ - `setState(state, save)`: Updates the thread's state object.
273
+ - `loadState()` / `storeState()`: Manages persistence (used internally).
274
+
275
+ ### `Tool`
276
+
277
+ - `getFunctions()`: **Abstract**. Must return an array of function definitions that the LLM can call.
278
+ - `callFunction(thread, name, payload)`: **Abstract**. Called when the LLM decides to use one of the tool's functions.
279
+
280
+ ## License
281
+
282
+ ISC
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "symposium",
4
- "version": "0.15.0",
4
+ "version": "0.15.2",
5
5
  "description": "Agents",
6
6
  "main": "index.js",
7
7
  "author": "Domenico Giambra",