symposium 0.1.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 +371 -0
- package/Conversation.js +87 -0
- package/Logger.js +17 -0
- package/MemoryHandler.js +12 -0
- package/Message.js +17 -0
- package/Middleware.js +22 -0
- package/Summarizer.js +95 -0
- package/Tool.js +13 -0
- package/index.js +21 -0
- package/package.json +17 -0
package/Agent.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import {Conversation} from "./Conversation.js";
|
|
3
|
+
|
|
4
|
+
class Agent {
|
|
5
|
+
name = 'Agent';
|
|
6
|
+
descriptionForFront = '';
|
|
7
|
+
options = {};
|
|
8
|
+
openai;
|
|
9
|
+
conversations;
|
|
10
|
+
functions = null;
|
|
11
|
+
middlewares = new Map();
|
|
12
|
+
tools = new Map();
|
|
13
|
+
commands;
|
|
14
|
+
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = {
|
|
17
|
+
memory_handler: null,
|
|
18
|
+
...options,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (this.options.memory_handler)
|
|
22
|
+
this.options.memory_handler.setAgent(this);
|
|
23
|
+
|
|
24
|
+
this.models = [
|
|
25
|
+
{
|
|
26
|
+
label: 'gpt-3.5',
|
|
27
|
+
name: 'gpt-3.5-turbo-16k',
|
|
28
|
+
tokens: 16384,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: 'gpt-4',
|
|
32
|
+
name: 'gpt-4',
|
|
33
|
+
tokens: 8192
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
this.commands = new Map();
|
|
38
|
+
|
|
39
|
+
this.commands.set('start', {
|
|
40
|
+
description: '',
|
|
41
|
+
show_in_help: false,
|
|
42
|
+
exec: async conversation => {
|
|
43
|
+
await conversation.reply('Benvenuto! Digita /help per un aiuto sui comandi, oppure procedi pure se già sai come usare.');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.commands.set('model', {
|
|
48
|
+
description: 'Per impostare l\'utilizzo di GPT 3 o 4 (o vedere il modello che si sta usando)',
|
|
49
|
+
show_in_help: true,
|
|
50
|
+
exec: async (conversation, args) => {
|
|
51
|
+
if (args) {
|
|
52
|
+
const model_to_switch = this.getModelFromLabel(args);
|
|
53
|
+
if (model_to_switch) {
|
|
54
|
+
await conversation.setState({model: model_to_switch.name});
|
|
55
|
+
await conversation.reply('# Da ora in poi uso ' + model_to_switch.label + '!');
|
|
56
|
+
} else {
|
|
57
|
+
await conversation.reply("# Versione modello non riconosciuta!\nModelli disponibili:\n" + this.models.map(m => m.label).join("\n"));
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
const currentModel = this.getModelFromName(conversation.state.model);
|
|
61
|
+
await conversation.reply('# Il modello attualmente in uso è ' + currentModel.label);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
this.commands.set('reset', {
|
|
67
|
+
description: 'Reimposta la conversazione, facendo dimenticare al bot tutto ciò che viene prima',
|
|
68
|
+
show_in_help: true,
|
|
69
|
+
exec: async conversation => {
|
|
70
|
+
await this.reset(conversation);
|
|
71
|
+
await conversation.reply('# Conversazione resettata');
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.commands.set('logout', {
|
|
76
|
+
description: 'Reimposta i token di autenticazione',
|
|
77
|
+
show_in_help: true,
|
|
78
|
+
exec: async conversation => {
|
|
79
|
+
await this.logout(conversation);
|
|
80
|
+
await conversation.reply('# Logout effettuato');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.commands.set('help', {
|
|
85
|
+
description: 'Aiuto sui comandi',
|
|
86
|
+
show_in_help: true,
|
|
87
|
+
exec: async conversation => {
|
|
88
|
+
let help = "Comandi disponibili:\n";
|
|
89
|
+
for (let command of this.commands.entries()) {
|
|
90
|
+
if (!command[1].show_in_help)
|
|
91
|
+
continue;
|
|
92
|
+
help += '/' + command[0];
|
|
93
|
+
if (command[1].description)
|
|
94
|
+
help += ' -> ' + command[1].description;
|
|
95
|
+
help += "\n";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await conversation.reply(help);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.openai = new OpenAI({
|
|
103
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
104
|
+
});
|
|
105
|
+
this.conversations = new Map();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async logout(conversation) {
|
|
109
|
+
conversation.auth = new Map();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async reset(conversation) {
|
|
113
|
+
await conversation.flush();
|
|
114
|
+
await this.resetState(conversation);
|
|
115
|
+
await this.initConversation(conversation);
|
|
116
|
+
await conversation.storeState();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getModelFromLabel(label) {
|
|
120
|
+
return this.models.find(model => model.label === label);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getModelFromName(name) {
|
|
124
|
+
return this.models.find(model => model.name === name);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async resetState(conversation) {
|
|
128
|
+
conversation.state = await this.getDefaultState();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async getDefaultState() {
|
|
132
|
+
return {
|
|
133
|
+
model: 'gpt-3.5-turbo-16k',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
addTool(tool) {
|
|
138
|
+
this.tools.set(tool.name, tool);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
addMiddleware(middleware) {
|
|
142
|
+
this.middlewares.set(middleware.name, middleware);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async initConversation(conversation) {
|
|
146
|
+
await this.doInitConversation(conversation);
|
|
147
|
+
await conversation.storeState();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async doInitConversation(conversation) {
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getConversation(id, reply = null) {
|
|
154
|
+
let conversation = this.conversations.get(id);
|
|
155
|
+
if (!conversation) {
|
|
156
|
+
conversation = new Conversation(this.name + '-' + id);
|
|
157
|
+
|
|
158
|
+
if (!(await conversation.loadState())) {
|
|
159
|
+
await this.resetState(conversation);
|
|
160
|
+
await this.initConversation(conversation);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.conversations.set(id, conversation);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (reply)
|
|
167
|
+
conversation.reply = reply;
|
|
168
|
+
|
|
169
|
+
return conversation;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getConversationIfExists(id) {
|
|
173
|
+
if (this.conversations.has(id))
|
|
174
|
+
return this.conversations.get(id);
|
|
175
|
+
|
|
176
|
+
const conversation = new Conversation(this.name + '-' + id);
|
|
177
|
+
|
|
178
|
+
if (await conversation.loadState())
|
|
179
|
+
return conversation;
|
|
180
|
+
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async message(conversation, text) {
|
|
185
|
+
if (text.startsWith('/')) {
|
|
186
|
+
const fullCommand = text.trim().split(' ');
|
|
187
|
+
const command = fullCommand.shift().substring(1);
|
|
188
|
+
const command_args = fullCommand.length ? fullCommand.join(' ').trim() : null;
|
|
189
|
+
try {
|
|
190
|
+
await this.executeCommand(conversation, command, command_args);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
await conversation.reply(e.message || e.error || JSON.stringify(e));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await this.execute(conversation, text);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async executeCommand(conversation, name, args) {
|
|
202
|
+
const command = this.commands.get(name);
|
|
203
|
+
if (!command)
|
|
204
|
+
throw new Error('Comando non riconosciuto');
|
|
205
|
+
|
|
206
|
+
await command.exec(conversation, args);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async execute(conversation, user_message) {
|
|
210
|
+
for (let middleware of this.middlewares.values()) {
|
|
211
|
+
let proceed = await middleware.before_add(conversation, user_message);
|
|
212
|
+
if (!proceed)
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (user_message) {
|
|
217
|
+
await this.log('user_message', user_message);
|
|
218
|
+
conversation.addUserMessage(user_message);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (this.options.memory_handler)
|
|
222
|
+
conversation = await this.options.memory_handler.handle(conversation);
|
|
223
|
+
|
|
224
|
+
for (let middleware of this.middlewares.values()) {
|
|
225
|
+
let proceed = await middleware.before_exec(conversation, user_message);
|
|
226
|
+
if (!proceed) {
|
|
227
|
+
await conversation.storeState();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const completion = await this.generateCompletion(conversation);
|
|
233
|
+
|
|
234
|
+
await this.handleCompletion(conversation, completion);
|
|
235
|
+
|
|
236
|
+
const reversedMiddlewares = [...this.middlewares.values()];
|
|
237
|
+
reversedMiddlewares.reverse();
|
|
238
|
+
|
|
239
|
+
for (let middleware of reversedMiddlewares) {
|
|
240
|
+
let proceed = await middleware.after_exec(conversation, user_message);
|
|
241
|
+
if (!proceed)
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async generateCompletion(conversation, payload = {}, retry_counter = 1) {
|
|
247
|
+
try {
|
|
248
|
+
const completion_payload = {
|
|
249
|
+
model: conversation.state.model,
|
|
250
|
+
messages: conversation.getMessagesJson(),
|
|
251
|
+
functions: await this.getFunctions(),
|
|
252
|
+
...payload,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
if (!completion_payload.functions?.length) {
|
|
256
|
+
delete completion_payload.functions;
|
|
257
|
+
if (completion_payload.hasOwnProperty('function_call'))
|
|
258
|
+
delete completion_payload.function_call;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const chatCompletion = await this.openai.chat.completions.create(completion_payload);
|
|
262
|
+
|
|
263
|
+
let completion = chatCompletion.choices[0].message;
|
|
264
|
+
if (completion.function_call && completion.function_call.arguments)
|
|
265
|
+
completion.function_call.arguments = JSON.parse(completion.function_call.arguments);
|
|
266
|
+
|
|
267
|
+
return completion;
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (error.response) {
|
|
270
|
+
console.error(error.response.status);
|
|
271
|
+
console.error(error.response.data);
|
|
272
|
+
|
|
273
|
+
if (error.response.status >= 500 && retry_counter <= 5) {
|
|
274
|
+
await new Promise(resolve => {
|
|
275
|
+
setTimeout(resolve, 1000);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return this.generateCompletion(conversation, payload, retry_counter + 1);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await conversation.reply('# Errore ' + error.response.status + ': ' + JSON.stringify(error.response.data));
|
|
282
|
+
} else if (error.message) {
|
|
283
|
+
console.error(error.message);
|
|
284
|
+
await conversation.reply('# Errore ' + error.message);
|
|
285
|
+
} else {
|
|
286
|
+
console.error(error);
|
|
287
|
+
await conversation.reply('# Errore interno');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async handleCompletion(conversation, completion) {
|
|
293
|
+
conversation.addAssistantMessage(completion.content, completion.function_call ? {
|
|
294
|
+
...completion.function_call,
|
|
295
|
+
arguments: JSON.stringify(completion.function_call.arguments),
|
|
296
|
+
} : null);
|
|
297
|
+
if (completion.content) {
|
|
298
|
+
await this.log('ai_message', completion.content);
|
|
299
|
+
await conversation.reply(completion.content);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (completion?.function_call)
|
|
303
|
+
return this.callFunction(conversation, completion.function_call);
|
|
304
|
+
else
|
|
305
|
+
return conversation.storeState();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async getFunctions(parsed = true) {
|
|
309
|
+
if (this.functions === null) {
|
|
310
|
+
this.functions = new Map();
|
|
311
|
+
for (let tool of this.tools.values()) {
|
|
312
|
+
let functions = await tool.getFunctions();
|
|
313
|
+
for (let func of functions) {
|
|
314
|
+
if (this.functions.has(func.name))
|
|
315
|
+
throw new Error('Duplicate function ' + func.name + ' in agent');
|
|
316
|
+
|
|
317
|
+
this.functions.set(func.name, {
|
|
318
|
+
tool,
|
|
319
|
+
function: func,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (parsed)
|
|
326
|
+
return Array.from(this.functions.values()).map(f => f.function)
|
|
327
|
+
else
|
|
328
|
+
return this.functions;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async callFunction(conversation, function_call) {
|
|
332
|
+
let functions = await this.getFunctions(false);
|
|
333
|
+
if (!functions.has(function_call.name))
|
|
334
|
+
throw new Error('Unrecognized function ' + function_call.name);
|
|
335
|
+
|
|
336
|
+
await this.log('function_call', function_call);
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const response = await functions.get(function_call.name).tool.callFunction(conversation, function_call.name, function_call.arguments);
|
|
340
|
+
conversation.addFunctionMessage(response, function_call.name);
|
|
341
|
+
await this.log('function_response', response);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
conversation.addFunctionMessage({error}, function_call.name);
|
|
344
|
+
await this.log('function_response', {error});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await this.execute(conversation);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async log(type, payload) {
|
|
351
|
+
if (this.options.logger)
|
|
352
|
+
return this.options.logger.log(this.name, type, payload);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async getPromptWordsForTranscription(conversation) {
|
|
356
|
+
return [this.name];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async transcribe(file, conversation) {
|
|
360
|
+
let words = await this.getPromptWordsForTranscription(conversation);
|
|
361
|
+
|
|
362
|
+
let response = await this.openai.audio.transcriptions.create({
|
|
363
|
+
file,
|
|
364
|
+
model: 'whisper-1',
|
|
365
|
+
prompt: words.join(', '),
|
|
366
|
+
});
|
|
367
|
+
return response.text;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export {Agent};
|
package/Conversation.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {Message} from "./Message.js";
|
|
2
|
+
import Redis from "@travio/redis";
|
|
3
|
+
|
|
4
|
+
class Conversation {
|
|
5
|
+
id;
|
|
6
|
+
reply;
|
|
7
|
+
messages = [];
|
|
8
|
+
state = {};
|
|
9
|
+
auth = new Map();
|
|
10
|
+
|
|
11
|
+
constructor(id) {
|
|
12
|
+
this.id = id;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
clone(keepMessages = true) {
|
|
16
|
+
let newConversation = new Conversation(this.id);
|
|
17
|
+
newConversation.reply = this.reply;
|
|
18
|
+
newConversation.state = this.state;
|
|
19
|
+
if (keepMessages)
|
|
20
|
+
newConversation.messages = [...this.messages];
|
|
21
|
+
return newConversation;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async flush() {
|
|
25
|
+
this.messages = [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async loadState() {
|
|
29
|
+
await this.flush();
|
|
30
|
+
this.state = {};
|
|
31
|
+
|
|
32
|
+
const conv = await Redis.get('conversation-' + this.id);
|
|
33
|
+
if (conv) {
|
|
34
|
+
this.auth = new Map(conv.auth || []);
|
|
35
|
+
this.state = conv.state || {};
|
|
36
|
+
this.messages = conv.messages.map(m => (new Message(m.role, m.text, m.name, m.function_call, m.tags || [])));
|
|
37
|
+
return true;
|
|
38
|
+
} else {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async setState(state, save = true) {
|
|
44
|
+
this.state = {...this.state, ...state};
|
|
45
|
+
if (save)
|
|
46
|
+
await this.storeState();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async storeState() {
|
|
50
|
+
await Redis.set('conversation-' + this.id, {
|
|
51
|
+
auth: [...this.auth.entries()],
|
|
52
|
+
state: this.state,
|
|
53
|
+
messages: this.messages,
|
|
54
|
+
}, 0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getMessagesJson() {
|
|
58
|
+
return this.messages.map(m => ({
|
|
59
|
+
role: m.role,
|
|
60
|
+
content: m.text,
|
|
61
|
+
name: m.name || undefined,
|
|
62
|
+
function_call: m.function_call || undefined,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
addSystemMessage(text, tags = []) {
|
|
67
|
+
this.messages.push(new Message('system', text, null, null, tags));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
addUserMessage(text, name = null, tags = []) {
|
|
71
|
+
this.messages.push(new Message('user', text, name, null, tags));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
addAssistantMessage(text, function_call = null, tags = []) {
|
|
75
|
+
this.messages.push(new Message('assistant', text, null, function_call, tags));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
addFunctionMessage(response, name = null, tags = []) {
|
|
79
|
+
this.messages.push(new Message('function', JSON.stringify(response), name, null, tags));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
removeMessagesWithTag(tag) {
|
|
83
|
+
this.messages = this.messages.filter(m => !m.tags.includes(tag));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {Conversation};
|
package/Logger.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class Logger {
|
|
2
|
+
listeners = [];
|
|
3
|
+
logs = [];
|
|
4
|
+
|
|
5
|
+
subscribe(callback) {
|
|
6
|
+
this.listeners.push(callback);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async log(agent, type, payload) {
|
|
10
|
+
this.logs.push({agent, type, payload});
|
|
11
|
+
|
|
12
|
+
for (let listener of this.listeners)
|
|
13
|
+
await listener.call(null, agent, type, payload);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {Logger};
|
package/MemoryHandler.js
ADDED
package/Message.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class Message {
|
|
2
|
+
role;
|
|
3
|
+
text;
|
|
4
|
+
name;
|
|
5
|
+
function_call;
|
|
6
|
+
tags = [];
|
|
7
|
+
|
|
8
|
+
constructor(role, text, name = null, function_call = null, tags = []) {
|
|
9
|
+
this.role = role;
|
|
10
|
+
this.text = text;
|
|
11
|
+
this.name = name;
|
|
12
|
+
this.function_call = function_call;
|
|
13
|
+
this.tags = tags;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {Message};
|
package/Middleware.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Middleware {
|
|
2
|
+
name;
|
|
3
|
+
agent;
|
|
4
|
+
|
|
5
|
+
constructor(agent) {
|
|
6
|
+
this.agent = agent;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async before_add(conversation, user_message) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async before_exec(conversation, user_message) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async after_exec(conversation, user_message) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {Middleware};
|
package/Summarizer.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {MemoryHandler} from "./MemoryHandler.js";
|
|
2
|
+
import {encoding_for_model} from "tiktoken";
|
|
3
|
+
|
|
4
|
+
class Summarizer extends MemoryHandler {
|
|
5
|
+
constructor(threshold = 0.7, summary_length = 0.4) {
|
|
6
|
+
super();
|
|
7
|
+
this.threshold = 0.7;
|
|
8
|
+
this.summary_length = 0.7;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async handle(conversation) {
|
|
12
|
+
const model = this.agent.models.find(model => model.key === conversation.state.model);
|
|
13
|
+
if (!model)
|
|
14
|
+
return conversation;
|
|
15
|
+
|
|
16
|
+
const encoder = encoding_for_model(model.base_model);
|
|
17
|
+
const tokens = this.countTokens(encoder, conversation);
|
|
18
|
+
if (tokens >= model.tokens * this.threshold)
|
|
19
|
+
return await this.summarize(encoder, conversation, model.tokens * this.summary_length);
|
|
20
|
+
else
|
|
21
|
+
return conversation;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
countTokens(encoder, conversation) {
|
|
25
|
+
return encoder.encode(conversation.messages.map(m => m.text).join('')).length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async summarize(encoder, conversation, maxLength) {
|
|
29
|
+
let summaryConversation = conversation.clone(false);
|
|
30
|
+
|
|
31
|
+
let currentStep = 'system';
|
|
32
|
+
for (let message of conversation.messages) {
|
|
33
|
+
switch (currentStep) {
|
|
34
|
+
case 'system':
|
|
35
|
+
if (message.role !== 'system')
|
|
36
|
+
currentStep = 'summary';
|
|
37
|
+
break;
|
|
38
|
+
case 'summary':
|
|
39
|
+
if (message.role === 'user' && this.hasPassedLimit(encoder, summaryConversation, maxLength)) {
|
|
40
|
+
summaryConversation = await this.doSummarize(summaryConversation, maxLength);
|
|
41
|
+
currentStep = 'retain';
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
summaryConversation.messages.push(message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return summaryConversation;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
hasPassedLimit(encoder, conversation, maxLength) {
|
|
53
|
+
let length = this.countTokens(encoder, conversation);
|
|
54
|
+
return length > maxLength;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async doSummarize(conversation, maxLength) {
|
|
58
|
+
conversation.addSystemMessage('Summarize the conversation up to this moment.');
|
|
59
|
+
const summary = await this.agent.generateCompletion(conversation, {
|
|
60
|
+
functions: [
|
|
61
|
+
{
|
|
62
|
+
name: 'summarize',
|
|
63
|
+
description: 'Generate a summary of the conversation in ' + Math.round(maxLength / 2) + ' words at most, in a way that it is easy for you to keep track of the important info. Do not omit relevant information you need to remember in order to continue the conversation.',
|
|
64
|
+
parameters: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
summary: {
|
|
68
|
+
type: 'string'
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
required: ['summary'],
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
function_call: {name: 'summarize'},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!summary)
|
|
79
|
+
return false;
|
|
80
|
+
|
|
81
|
+
let summarizedConversation = conversation.clone(false);
|
|
82
|
+
for (let message of conversation.messages) {
|
|
83
|
+
if (message.role === 'system' && !message.tags.includes('summary')) {
|
|
84
|
+
summarizedConversation.messages.push(message);
|
|
85
|
+
} else {
|
|
86
|
+
summarizedConversation.addSystemMessage("This is what happened until now:\n" + summary.function_call.arguments.summary, ['summary']);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return summarizedConversation;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {Summarizer};
|
package/Tool.js
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import {Agent} from "./Agent.js";
|
|
4
|
+
import {Conversation} from "./Conversation.js";
|
|
5
|
+
import {Message} from "./Message.js";
|
|
6
|
+
import {Tool} from "./Tool.js";
|
|
7
|
+
import {Logger} from "./Logger.js";
|
|
8
|
+
import {MemoryHandler} from "./MemoryHandler.js";
|
|
9
|
+
import {Middleware} from "./Middleware.js";
|
|
10
|
+
import {Summarizer} from "./Summarizer.js";
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
Agent,
|
|
14
|
+
Conversation,
|
|
15
|
+
Message,
|
|
16
|
+
Tool,
|
|
17
|
+
Logger,
|
|
18
|
+
MemoryHandler,
|
|
19
|
+
Middleware,
|
|
20
|
+
Summarizer,
|
|
21
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "symposium",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Agents",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"author": "Domenico Giambra",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@travio/redis": "^1.3.2",
|
|
14
|
+
"openai": "^4.12.1",
|
|
15
|
+
"tiktoken": "^1.0.10"
|
|
16
|
+
}
|
|
17
|
+
}
|