symposium 2.1.10 → 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 CHANGED
@@ -219,11 +219,9 @@ export default class Agent {
219
219
  if (response_format && response_format.count <= 100) { // OpenAI does not support structured output if there are more than 100 parameters
220
220
  completion_options.response_format = {
221
221
  type: 'json_schema',
222
- json_schema: {
223
- name: this.utility.function.name,
224
- schema: response_format.obj,
225
- strict: true,
226
- },
222
+ name: this.utility.function.name,
223
+ schema: response_format.obj,
224
+ strict: true,
227
225
  };
228
226
  } else {
229
227
  completion_options.functions = [
@@ -1,7 +1,7 @@
1
- import OpenAIModel from "./OpenAIModel.js";
1
+ import LegacyOpenAIModel from "./LegacyOpenAIModel.js";
2
2
  import OpenAI from "openai";
3
3
 
4
- export default class DeepSeekModel extends OpenAIModel {
4
+ export default class DeepSeekModel extends LegacyOpenAIModel {
5
5
  async getModels() {
6
6
  return new Map([
7
7
  ['deepseek-chat', {
@@ -1,7 +1,7 @@
1
- import OpenAIModel from "./OpenAIModel.js";
1
+ import LegacyOpenAIModel from "./LegacyOpenAIModel.js";
2
2
  import OpenAI from "openai";
3
3
 
4
- export default class GrokModel extends OpenAIModel {
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
+ }
@@ -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',
@@ -73,7 +74,7 @@ export default class OpenAIModel extends Model {
73
74
 
74
75
  const tools = functions.map(f => ({
75
76
  type: 'function',
76
- function: f,
77
+ ...f,
77
78
  }));
78
79
 
79
80
  if (model.tools && model.image_generation && options.image_generation)
@@ -81,44 +82,59 @@ export default class OpenAIModel extends Model {
81
82
 
82
83
  const completion_payload = {
83
84
  model: model.name,
84
- messages: convertedMessages,
85
+ input: convertedMessages,
86
+ store: false,
87
+ include: ['reasoning.encrypted_content'],
85
88
  tools,
89
+ reasoning: {
90
+ summary: 'auto',
91
+ },
86
92
  };
87
93
 
88
94
  if (options.force_function) {
89
95
  completion_payload.tool_choice = {
90
96
  type: 'function',
91
- function: {name: options.force_function},
97
+ name: options.force_function,
92
98
  };
93
99
  }
94
100
 
95
101
  if (options.response_format)
96
- completion_payload.response_format = options.response_format;
102
+ completion_payload.text = {format: options.response_format};
97
103
 
98
104
  if (!completion_payload.tools.length)
99
105
  delete completion_payload.tools;
100
106
 
101
- const chatCompletion = await this.getOpenAi().chat.completions.create(completion_payload);
102
- const completion = chatCompletion.choices[0].message;
107
+ const completion = await this.getOpenAi().responses.create(completion_payload);
103
108
 
104
109
  const message_content = [];
105
- if (completion.content)
106
- message_content.push({type: 'text', content: completion.content});
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;
107
116
 
108
- if (completion.tool_calls?.length) {
109
- message_content.push({
110
- type: 'function',
111
- content: completion.tool_calls.map(tool_call => {
112
- if (tool_call.type !== 'function')
113
- throw new Error('Unsupported tool type ' + tool_call.type);
114
-
115
- return {
116
- id: tool_call.id,
117
- name: tool_call.function.name,
118
- arguments: tool_call.function.arguments ? JSON.parse(tool_call.function.arguments) : {},
119
- };
120
- }),
121
- });
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
+ }
122
138
  }
123
139
 
124
140
  return [
@@ -150,7 +166,6 @@ export default class OpenAIModel extends Model {
150
166
  messages.push({
151
167
  role,
152
168
  content: c.content,
153
- name: message.name,
154
169
  });
155
170
  break;
156
171
 
@@ -166,7 +181,6 @@ export default class OpenAIModel extends Model {
166
181
  },
167
182
  },
168
183
  ],
169
- name: message.name,
170
184
  });
171
185
  break;
172
186
 
@@ -188,13 +202,11 @@ export default class OpenAIModel extends Model {
188
202
  },
189
203
  },
190
204
  ],
191
- name: message.name,
192
205
  });
193
206
  } else if (c.content.transcription) {
194
207
  messages.push({
195
208
  role,
196
209
  content: '[transcribed] ' + c.content.transcription,
197
- name: message.name,
198
210
  });
199
211
  } else {
200
212
  throw new Error('Audio content is not supported by this model');
@@ -204,22 +216,15 @@ export default class OpenAIModel extends Model {
204
216
  case 'function':
205
217
  if (model.tools) {
206
218
  messages.push({
207
- role,
208
- name: message.name,
209
- tool_calls: c.content.map(tool_call => ({
210
- id: tool_call.id,
211
- type: 'function',
212
- function: {
213
- name: tool_call.name,
214
- arguments: tool_call.arguments ? JSON.stringify(tool_call.arguments) : '{}',
215
- },
216
- })),
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) : '{}',
217
223
  });
218
224
  } else {
219
225
  messages.push({
220
226
  role,
221
227
  content: c.content.map(f => '```CALL \n' + f.name + '\n' + JSON.stringify(f.arguments || {}) + '\n```').join("\n\n"),
222
- name: message.name,
223
228
  });
224
229
  }
225
230
  break;
@@ -227,20 +232,22 @@ export default class OpenAIModel extends Model {
227
232
  case 'function_response':
228
233
  if (model.tools) {
229
234
  messages.push({
230
- role,
231
- tool_call_id: c.content.id,
232
- content: JSON.stringify(c.content.response),
233
- name: message.name,
235
+ type: 'function_call_output',
236
+ call_id: c.content.id,
237
+ output: JSON.stringify(c.content.response),
234
238
  });
235
239
  } else {
236
240
  messages.push({
237
241
  role: 'user',
238
242
  content: 'FUNCTION RESPONSE:\n' + JSON.stringify(c.content.response),
239
- name: message.name,
240
243
  });
241
244
  }
242
245
  break;
243
246
 
247
+ case 'reasoning':
248
+ messages.push(c.original);
249
+ break;
250
+
244
251
  default:
245
252
  throw new Error('Message type unsupported by this model');
246
253
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "symposium",
4
- "version": "2.1.10",
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.67.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",