symposium 0.15.1 → 0.15.3
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 +16 -44
- package/README.md +282 -0
- package/package.json +1 -1
package/Agent.js
CHANGED
|
@@ -114,15 +114,17 @@ export default class Agent {
|
|
|
114
114
|
return this.execute(thread);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
async beforeExecute(thread) {
|
|
117
|
+
async beforeExecute(thread, emitter) {
|
|
118
118
|
if (this.options.memory_handler)
|
|
119
119
|
thread = await this.options.memory_handler.handle(thread);
|
|
120
120
|
return thread;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
async execute(thread, counter = 0, existing_emitter = null) {
|
|
124
|
+
const emitter = existing_emitter || new BufferedEventEmitter();
|
|
125
|
+
|
|
124
126
|
if (counter === 0)
|
|
125
|
-
thread = await this.beforeExecute(thread);
|
|
127
|
+
thread = await this.beforeExecute(thread, emitter);
|
|
126
128
|
|
|
127
129
|
const model = Symposium.getModelByName(thread.state.model);
|
|
128
130
|
|
|
@@ -161,7 +163,6 @@ export default class Agent {
|
|
|
161
163
|
console.error(e.message);
|
|
162
164
|
switch (this.type) {
|
|
163
165
|
case 'chat':
|
|
164
|
-
const emitter = existing_emitter || new BufferedEventEmitter();
|
|
165
166
|
emitter.emit('error', e.message);
|
|
166
167
|
if (!existing_emitter)
|
|
167
168
|
emitter.emit('end');
|
|
@@ -177,18 +178,13 @@ export default class Agent {
|
|
|
177
178
|
|
|
178
179
|
try {
|
|
179
180
|
thread = await this.afterExecute(thread, completion);
|
|
180
|
-
const
|
|
181
|
+
const response = await this.handleCompletion(thread, completion, emitter);
|
|
181
182
|
|
|
182
183
|
switch (this.type) {
|
|
183
184
|
case 'utility':
|
|
184
|
-
|
|
185
|
-
emitter.
|
|
186
|
-
|
|
187
|
-
resolve(data.content);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
emitter.on('error', error => reject(error));
|
|
191
|
-
});
|
|
185
|
+
if (!existing_emitter)
|
|
186
|
+
emitter.emit('end');
|
|
187
|
+
return response;
|
|
192
188
|
|
|
193
189
|
case 'chat':
|
|
194
190
|
if (!existing_emitter) {
|
|
@@ -207,7 +203,7 @@ export default class Agent {
|
|
|
207
203
|
console.error(e);
|
|
208
204
|
|
|
209
205
|
if (counter < this.max_retries)
|
|
210
|
-
await this.execute(thread, counter + 1,
|
|
206
|
+
await this.execute(thread, counter + 1, emitter);
|
|
211
207
|
}
|
|
212
208
|
}
|
|
213
209
|
|
|
@@ -306,11 +302,9 @@ export default class Agent {
|
|
|
306
302
|
return message;
|
|
307
303
|
}
|
|
308
304
|
|
|
309
|
-
async handleCompletion(thread, completion,
|
|
305
|
+
async handleCompletion(thread, completion, emitter) {
|
|
310
306
|
const model = Symposium.getModelByName(thread.state.model);
|
|
311
307
|
|
|
312
|
-
const emitter = existing_emitter || new BufferedEventEmitter();
|
|
313
|
-
|
|
314
308
|
const functions = [];
|
|
315
309
|
for (let message of completion) {
|
|
316
310
|
thread.addDirectMessage(message);
|
|
@@ -320,17 +314,10 @@ export default class Agent {
|
|
|
320
314
|
switch (m.type) {
|
|
321
315
|
case 'text':
|
|
322
316
|
if (this.type === 'utility') {
|
|
323
|
-
let response = null;
|
|
324
317
|
if (this.utility.type === 'text')
|
|
325
|
-
|
|
318
|
+
return this.afterHandle(thread, completion, 'return', m.content);
|
|
326
319
|
if (this.utility.type === 'json' && model.supports_structured_output)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (response) {
|
|
330
|
-
emitter.emit('data', {type: 'response', content: response});
|
|
331
|
-
emitter.emit('end');
|
|
332
|
-
return emitter;
|
|
333
|
-
}
|
|
320
|
+
return this.afterHandle(thread, completion, 'return', JSON.parse(m.content));
|
|
334
321
|
}
|
|
335
322
|
|
|
336
323
|
emitter.emit('data', {type: 'output', content: m.content});
|
|
@@ -344,13 +331,10 @@ export default class Agent {
|
|
|
344
331
|
}
|
|
345
332
|
}
|
|
346
333
|
|
|
347
|
-
let response;
|
|
348
334
|
if (functions.length) {
|
|
349
335
|
for (let f of functions) {
|
|
350
|
-
if (this.utility && ['function', 'json'].includes(this.utility.type))
|
|
351
|
-
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
336
|
+
if (this.utility && ['function', 'json'].includes(this.utility.type))
|
|
337
|
+
return this.afterHandle(thread, completion, 'return', f.arguments);
|
|
354
338
|
|
|
355
339
|
const function_response = await this.callFunction(thread, f, emitter);
|
|
356
340
|
|
|
@@ -364,23 +348,11 @@ export default class Agent {
|
|
|
364
348
|
await this.log('function_response', function_response);
|
|
365
349
|
}
|
|
366
350
|
|
|
367
|
-
|
|
368
|
-
response = this.afterHandle(thread, completion, 'continue');
|
|
351
|
+
return this.afterHandle(thread, completion, 'continue');
|
|
369
352
|
} else {
|
|
370
353
|
await thread.storeState();
|
|
371
|
-
|
|
354
|
+
return this.afterHandle(thread, completion, 'void');
|
|
372
355
|
}
|
|
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;
|
|
384
356
|
}
|
|
385
357
|
|
|
386
358
|
async afterHandle(thread, completion, type, value = null) {
|
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
|