symposium 2.1.9 → 2.2.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 +8 -6
- package/Models/DeepSeekModel.js +2 -2
- package/Models/GrokModel.js +2 -2
- package/Models/LegacyOpenAIModel.js +223 -0
- package/Models/OpenAIModel.js +57 -45
- package/package.json +2 -2
package/Agent.js
CHANGED
|
@@ -22,6 +22,7 @@ export default class Agent {
|
|
|
22
22
|
type = 'chat'; // chat, utility
|
|
23
23
|
utility = null;
|
|
24
24
|
initialized = false;
|
|
25
|
+
enable_image_generation = false;
|
|
25
26
|
|
|
26
27
|
constructor(options) {
|
|
27
28
|
this.options = {
|
|
@@ -218,11 +219,9 @@ export default class Agent {
|
|
|
218
219
|
if (response_format && response_format.count <= 100) { // OpenAI does not support structured output if there are more than 100 parameters
|
|
219
220
|
completion_options.response_format = {
|
|
220
221
|
type: 'json_schema',
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
strict: true,
|
|
225
|
-
},
|
|
222
|
+
name: this.utility.function.name,
|
|
223
|
+
schema: response_format.obj,
|
|
224
|
+
strict: true,
|
|
226
225
|
};
|
|
227
226
|
} else {
|
|
228
227
|
completion_options.functions = [
|
|
@@ -341,7 +340,10 @@ export default class Agent {
|
|
|
341
340
|
async generateCompletion(thread, options = {}, retry_counter = 1) {
|
|
342
341
|
try {
|
|
343
342
|
const model = Symposium.getModel(thread.state.model);
|
|
344
|
-
const messages = await model.class.generate(model, thread, await this.getFunctions(),
|
|
343
|
+
const messages = await model.class.generate(model, thread, await this.getFunctions(), {
|
|
344
|
+
...options,
|
|
345
|
+
image_generation: this.enable_image_generation,
|
|
346
|
+
});
|
|
345
347
|
return model.tools ? messages : messages.map(m => this.parseFunctions(m));
|
|
346
348
|
} catch (error) {
|
|
347
349
|
if (error.response) {
|
package/Models/DeepSeekModel.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import LegacyOpenAIModel from "./LegacyOpenAIModel.js";
|
|
2
2
|
import OpenAI from "openai";
|
|
3
3
|
|
|
4
|
-
export default class DeepSeekModel extends
|
|
4
|
+
export default class DeepSeekModel extends LegacyOpenAIModel {
|
|
5
5
|
async getModels() {
|
|
6
6
|
return new Map([
|
|
7
7
|
['deepseek-chat', {
|
package/Models/GrokModel.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import LegacyOpenAIModel from "./LegacyOpenAIModel.js";
|
|
2
2
|
import OpenAI from "openai";
|
|
3
3
|
|
|
4
|
-
export default class GrokModel extends
|
|
4
|
+
export default class GrokModel extends LegacyOpenAIModel {
|
|
5
5
|
async getModels() {
|
|
6
6
|
return new Map([
|
|
7
7
|
['grok-4', {
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import Model from "../Model.js";
|
|
2
|
+
import OpenAI from "openai";
|
|
3
|
+
import Message from "../Message.js";
|
|
4
|
+
import {encoding_for_model} from "tiktoken";
|
|
5
|
+
|
|
6
|
+
export default class LegacyOpenAIModel extends Model {
|
|
7
|
+
openai;
|
|
8
|
+
|
|
9
|
+
async getModels() {
|
|
10
|
+
return new Map([]);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getOpenAi() {
|
|
14
|
+
if (!this.openai)
|
|
15
|
+
this.openai = new OpenAI({apiKey: process.env.OPENAI_API_KEY});
|
|
16
|
+
|
|
17
|
+
return this.openai;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async generate(model, thread, functions = [], options = {}) {
|
|
21
|
+
const parsed = this.parseOptions(options, functions);
|
|
22
|
+
options = parsed.options;
|
|
23
|
+
functions = parsed.functions;
|
|
24
|
+
|
|
25
|
+
let messages = thread.messages;
|
|
26
|
+
|
|
27
|
+
if (functions.length && !model.tools) {
|
|
28
|
+
// Se il modello non supporta nativamente le funzioni, inserisco il prompt ad hoc come ultimo messaggio di sistema
|
|
29
|
+
const functions_prompt = this.promptFromFunctions(options, functions);
|
|
30
|
+
let system_messages = [], other_messages = [], first_found = false;
|
|
31
|
+
for (let message of messages) {
|
|
32
|
+
if (!first_found && message.role !== 'system')
|
|
33
|
+
first_found = true;
|
|
34
|
+
|
|
35
|
+
if (!first_found)
|
|
36
|
+
system_messages.push(message);
|
|
37
|
+
else
|
|
38
|
+
other_messages.push(message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
system_messages.push(new Message('system', functions_prompt));
|
|
42
|
+
|
|
43
|
+
messages = [...system_messages, ...other_messages];
|
|
44
|
+
functions = [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const convertedMessages = [];
|
|
48
|
+
for (let m of messages)
|
|
49
|
+
convertedMessages.push(...this.convertMessage(m, model));
|
|
50
|
+
|
|
51
|
+
const completion_payload = {
|
|
52
|
+
model: model.name,
|
|
53
|
+
messages: convertedMessages,
|
|
54
|
+
tools: functions.map(f => ({
|
|
55
|
+
type: 'function',
|
|
56
|
+
function: f,
|
|
57
|
+
})),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (options.force_function) {
|
|
61
|
+
completion_payload.tool_choice = {
|
|
62
|
+
type: 'function',
|
|
63
|
+
function: {name: options.force_function},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (options.response_format)
|
|
68
|
+
completion_payload.response_format = options.response_format;
|
|
69
|
+
|
|
70
|
+
if (!completion_payload.tools.length)
|
|
71
|
+
delete completion_payload.tools;
|
|
72
|
+
|
|
73
|
+
const chatCompletion = await this.getOpenAi().chat.completions.create(completion_payload);
|
|
74
|
+
const completion = chatCompletion.choices[0].message;
|
|
75
|
+
|
|
76
|
+
const message_content = [];
|
|
77
|
+
if (completion.content)
|
|
78
|
+
message_content.push({type: 'text', content: completion.content});
|
|
79
|
+
|
|
80
|
+
if (completion.tool_calls?.length) {
|
|
81
|
+
message_content.push({
|
|
82
|
+
type: 'function',
|
|
83
|
+
content: completion.tool_calls.map(tool_call => {
|
|
84
|
+
if (tool_call.type !== 'function')
|
|
85
|
+
throw new Error('Unsupported tool type ' + tool_call.type);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
id: tool_call.id,
|
|
89
|
+
name: tool_call.function.name,
|
|
90
|
+
arguments: tool_call.function.arguments ? JSON.parse(tool_call.function.arguments) : {},
|
|
91
|
+
};
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return [
|
|
97
|
+
new Message('assistant', message_content),
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async countTokens(thread) {
|
|
102
|
+
try {
|
|
103
|
+
const model = (await this.getModels()).get(thread.state.model);
|
|
104
|
+
const encoder = encoding_for_model(model.tiktoken || model.name);
|
|
105
|
+
|
|
106
|
+
const texts = [];
|
|
107
|
+
for (let message of thread.messages)
|
|
108
|
+
texts.push(message.content.map(m => typeof m.content === 'string' ? m.content : JSON.stringify(m.content)).join(''));
|
|
109
|
+
return encoder.encode(texts.join('')).length;
|
|
110
|
+
} catch (e) {
|
|
111
|
+
throw new Error('Error while counting tokens');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
convertMessage(message, model) {
|
|
116
|
+
const messages = [],
|
|
117
|
+
role = message.role === 'system' ? this.system_role_name : message.role;
|
|
118
|
+
|
|
119
|
+
for (let c of message.content) {
|
|
120
|
+
switch (c.type) {
|
|
121
|
+
case 'text':
|
|
122
|
+
messages.push({
|
|
123
|
+
role,
|
|
124
|
+
content: c.content,
|
|
125
|
+
name: message.name,
|
|
126
|
+
});
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
case 'image':
|
|
130
|
+
messages.push({
|
|
131
|
+
role,
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: 'image_url',
|
|
135
|
+
image_url: {
|
|
136
|
+
url: c.content.type === 'base64' ? 'data:' + c.content.mime + ';base64,' + c.content.data : c.content.data,
|
|
137
|
+
detail: c.content.detail || 'auto',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
name: message.name,
|
|
142
|
+
});
|
|
143
|
+
break;
|
|
144
|
+
|
|
145
|
+
case 'audio':
|
|
146
|
+
if (model.audio) {
|
|
147
|
+
if (c.content.type !== 'base64')
|
|
148
|
+
throw new Error('Audio content must be base64 encoded for this model');
|
|
149
|
+
if (!['audio/mpeg', 'audio/wav'].includes(c.content.mime))
|
|
150
|
+
throw new Error('Audio content must have a valid MIME type');
|
|
151
|
+
|
|
152
|
+
messages.push({
|
|
153
|
+
role,
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: 'input_audio',
|
|
157
|
+
input_audio: {
|
|
158
|
+
data: c.content.data,
|
|
159
|
+
format: c.content.mime === 'audio/mpeg' ? 'mp3' : 'wav',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
name: message.name,
|
|
164
|
+
});
|
|
165
|
+
} else if (c.content.transcription) {
|
|
166
|
+
messages.push({
|
|
167
|
+
role,
|
|
168
|
+
content: '[transcribed] ' + c.content.transcription,
|
|
169
|
+
name: message.name,
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
throw new Error('Audio content is not supported by this model');
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
|
|
176
|
+
case 'function':
|
|
177
|
+
if (model.tools) {
|
|
178
|
+
messages.push({
|
|
179
|
+
role,
|
|
180
|
+
name: message.name,
|
|
181
|
+
tool_calls: c.content.map(tool_call => ({
|
|
182
|
+
id: tool_call.id,
|
|
183
|
+
type: 'function',
|
|
184
|
+
function: {
|
|
185
|
+
name: tool_call.name,
|
|
186
|
+
arguments: tool_call.arguments ? JSON.stringify(tool_call.arguments) : '{}',
|
|
187
|
+
},
|
|
188
|
+
})),
|
|
189
|
+
});
|
|
190
|
+
} else {
|
|
191
|
+
messages.push({
|
|
192
|
+
role,
|
|
193
|
+
content: c.content.map(f => '```CALL \n' + f.name + '\n' + JSON.stringify(f.arguments || {}) + '\n```').join("\n\n"),
|
|
194
|
+
name: message.name,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
|
|
199
|
+
case 'function_response':
|
|
200
|
+
if (model.tools) {
|
|
201
|
+
messages.push({
|
|
202
|
+
role,
|
|
203
|
+
tool_call_id: c.content.id,
|
|
204
|
+
content: JSON.stringify(c.content.response),
|
|
205
|
+
name: message.name,
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
messages.push({
|
|
209
|
+
role: 'user',
|
|
210
|
+
content: 'FUNCTION RESPONSE:\n' + JSON.stringify(c.content.response),
|
|
211
|
+
name: message.name,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
|
|
216
|
+
default:
|
|
217
|
+
throw new Error('Message type unsupported by this model');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return messages;
|
|
222
|
+
}
|
|
223
|
+
}
|
package/Models/OpenAIModel.js
CHANGED
|
@@ -22,6 +22,7 @@ export default class OpenAIModel extends Model {
|
|
|
22
22
|
tools: true,
|
|
23
23
|
structured_output: true,
|
|
24
24
|
audio: true,
|
|
25
|
+
image_generation: true,
|
|
25
26
|
}],
|
|
26
27
|
['gpt-5-mini', {
|
|
27
28
|
name: 'gpt-5-mini',
|
|
@@ -71,49 +72,69 @@ export default class OpenAIModel extends Model {
|
|
|
71
72
|
for (let m of messages)
|
|
72
73
|
convertedMessages.push(...this.convertMessage(m, model));
|
|
73
74
|
|
|
75
|
+
const tools = functions.map(f => ({
|
|
76
|
+
type: 'function',
|
|
77
|
+
...f,
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
if (model.tools && model.image_generation && options.image_generation)
|
|
81
|
+
tools.push({type: 'image_generation'});
|
|
82
|
+
|
|
74
83
|
const completion_payload = {
|
|
75
84
|
model: model.name,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
input: convertedMessages,
|
|
86
|
+
store: false,
|
|
87
|
+
include: ['reasoning.encrypted_content'],
|
|
88
|
+
tools,
|
|
89
|
+
reasoning: {
|
|
90
|
+
summary: 'auto',
|
|
91
|
+
},
|
|
81
92
|
};
|
|
82
93
|
|
|
83
94
|
if (options.force_function) {
|
|
84
95
|
completion_payload.tool_choice = {
|
|
85
96
|
type: 'function',
|
|
86
|
-
|
|
97
|
+
name: options.force_function,
|
|
87
98
|
};
|
|
88
99
|
}
|
|
89
100
|
|
|
90
101
|
if (options.response_format)
|
|
91
|
-
completion_payload.
|
|
102
|
+
completion_payload.text = {format: options.response_format};
|
|
92
103
|
|
|
93
104
|
if (!completion_payload.tools.length)
|
|
94
105
|
delete completion_payload.tools;
|
|
95
106
|
|
|
96
|
-
const
|
|
97
|
-
const completion = chatCompletion.choices[0].message;
|
|
107
|
+
const completion = await this.getOpenAi().responses.create(completion_payload);
|
|
98
108
|
|
|
99
109
|
const message_content = [];
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
for (let output of completion.output) {
|
|
111
|
+
switch (output.type) {
|
|
112
|
+
case 'message':
|
|
113
|
+
let text = output.content.map(c => c.text).join('\n');
|
|
114
|
+
message_content.push({type: 'text', content: text});
|
|
115
|
+
break;
|
|
102
116
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
case 'function_call':
|
|
118
|
+
message_content.push({
|
|
119
|
+
type: 'function',
|
|
120
|
+
content: [
|
|
121
|
+
{
|
|
122
|
+
id: output.call_id,
|
|
123
|
+
name: output.name,
|
|
124
|
+
arguments: output.arguments ? JSON.parse(output.arguments) : {},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'reasoning':
|
|
131
|
+
message_content.push({
|
|
132
|
+
type: 'reasoning',
|
|
133
|
+
content: output.summary?.length ? output.summary.map(s => s.text).join('\n') : null,
|
|
134
|
+
original: output,
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
117
138
|
}
|
|
118
139
|
|
|
119
140
|
return [
|
|
@@ -145,7 +166,6 @@ export default class OpenAIModel extends Model {
|
|
|
145
166
|
messages.push({
|
|
146
167
|
role,
|
|
147
168
|
content: c.content,
|
|
148
|
-
name: message.name,
|
|
149
169
|
});
|
|
150
170
|
break;
|
|
151
171
|
|
|
@@ -161,7 +181,6 @@ export default class OpenAIModel extends Model {
|
|
|
161
181
|
},
|
|
162
182
|
},
|
|
163
183
|
],
|
|
164
|
-
name: message.name,
|
|
165
184
|
});
|
|
166
185
|
break;
|
|
167
186
|
|
|
@@ -183,13 +202,11 @@ export default class OpenAIModel extends Model {
|
|
|
183
202
|
},
|
|
184
203
|
},
|
|
185
204
|
],
|
|
186
|
-
name: message.name,
|
|
187
205
|
});
|
|
188
206
|
} else if (c.content.transcription) {
|
|
189
207
|
messages.push({
|
|
190
208
|
role,
|
|
191
209
|
content: '[transcribed] ' + c.content.transcription,
|
|
192
|
-
name: message.name,
|
|
193
210
|
});
|
|
194
211
|
} else {
|
|
195
212
|
throw new Error('Audio content is not supported by this model');
|
|
@@ -199,22 +216,15 @@ export default class OpenAIModel extends Model {
|
|
|
199
216
|
case 'function':
|
|
200
217
|
if (model.tools) {
|
|
201
218
|
messages.push({
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
type: 'function',
|
|
207
|
-
function: {
|
|
208
|
-
name: tool_call.name,
|
|
209
|
-
arguments: tool_call.arguments ? JSON.stringify(tool_call.arguments) : '{}',
|
|
210
|
-
},
|
|
211
|
-
})),
|
|
219
|
+
type: 'function_call',
|
|
220
|
+
call_id: c.content[0].id,
|
|
221
|
+
name: c.content[0].name,
|
|
222
|
+
arguments: c.content[0].arguments ? JSON.stringify(c.content[0].arguments) : '{}',
|
|
212
223
|
});
|
|
213
224
|
} else {
|
|
214
225
|
messages.push({
|
|
215
226
|
role,
|
|
216
227
|
content: c.content.map(f => '```CALL \n' + f.name + '\n' + JSON.stringify(f.arguments || {}) + '\n```').join("\n\n"),
|
|
217
|
-
name: message.name,
|
|
218
228
|
});
|
|
219
229
|
}
|
|
220
230
|
break;
|
|
@@ -222,20 +232,22 @@ export default class OpenAIModel extends Model {
|
|
|
222
232
|
case 'function_response':
|
|
223
233
|
if (model.tools) {
|
|
224
234
|
messages.push({
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
name: message.name,
|
|
235
|
+
type: 'function_call_output',
|
|
236
|
+
call_id: c.content.id,
|
|
237
|
+
output: JSON.stringify(c.content.response),
|
|
229
238
|
});
|
|
230
239
|
} else {
|
|
231
240
|
messages.push({
|
|
232
241
|
role: 'user',
|
|
233
242
|
content: 'FUNCTION RESPONSE:\n' + JSON.stringify(c.content.response),
|
|
234
|
-
name: message.name,
|
|
235
243
|
});
|
|
236
244
|
}
|
|
237
245
|
break;
|
|
238
246
|
|
|
247
|
+
case 'reasoning':
|
|
248
|
+
messages.push(c.original);
|
|
249
|
+
break;
|
|
250
|
+
|
|
239
251
|
default:
|
|
240
252
|
throw new Error('Message type unsupported by this model');
|
|
241
253
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "symposium",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"description": "Agents",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Domenico Giambra",
|
|
8
8
|
"license": "ISC",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@anthropic-ai/sdk": "^0.
|
|
10
|
+
"@anthropic-ai/sdk": "^0.68.0",
|
|
11
11
|
"groq-sdk": "^0.34.0",
|
|
12
12
|
"ollama": "^0.6.0",
|
|
13
13
|
"openai": "^6.0.0",
|