symposium 2.4.2 → 3.0.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 +509 -219
- package/CLAUDE.md +101 -0
- package/Contexts/MCPResource.js +19 -0
- package/{GetContextTool.js → GetContextToolkit.js} +5 -5
- package/InputChannel.js +42 -0
- package/MCPServer.js +160 -0
- package/MIGRATION.md +369 -0
- package/Model.js +32 -25
- package/Models/AnthropicModel.js +66 -20
- package/Models/GrokModel.js +8 -8
- package/Models/GroqModel.js +61 -35
- package/Models/LegacyOpenAIModel.js +61 -35
- package/Models/OllamaModel.js +57 -31
- package/Models/OpenAIModel.js +74 -20
- package/README.md +458 -396
- package/Summarizer.js +5 -5
- package/Symposium.js +12 -12
- package/{Tool.js → Toolkit.js} +4 -4
- package/index.js +10 -2
- package/package.json +7 -3
- package/test/agent.test.js +698 -0
- package/test/helpers/mockSdk.js +52 -0
- package/test/mcp.test.js +216 -0
- package/test/models/anthropic.test.js +135 -0
- package/test/models/groq.test.js +71 -0
- package/test/models/legacyOpenai.test.js +87 -0
- package/test/models/ollama.test.js +90 -0
- package/test/models/openai.test.js +168 -0
- package/BufferedEventEmitter.js +0 -28
package/Models/GroqModel.js
CHANGED
|
@@ -27,16 +27,16 @@ export default class GroqModel extends Model {
|
|
|
27
27
|
return this.groq;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
async generate(model, thread,
|
|
31
|
-
const parsed = this.parseOptions(options,
|
|
30
|
+
async *generate(model, thread, tools = [], options = {}) {
|
|
31
|
+
const parsed = this.parseOptions(options, tools);
|
|
32
32
|
options = parsed.options;
|
|
33
|
-
|
|
33
|
+
tools = parsed.tools;
|
|
34
34
|
|
|
35
35
|
let messages = thread.messages;
|
|
36
36
|
|
|
37
|
-
if (
|
|
38
|
-
// Se il modello non supporta nativamente
|
|
39
|
-
const
|
|
37
|
+
if (tools.length && !model.tools) {
|
|
38
|
+
// Se il modello non supporta nativamente gli strumenti, inserisco il prompt ad hoc come ultimo messaggio di sistema
|
|
39
|
+
const tools_prompt = this.promptFromTools(options, tools);
|
|
40
40
|
let system_messages = [], other_messages = [], first_found = false;
|
|
41
41
|
for (let message of messages) {
|
|
42
42
|
if (!first_found && message.role !== 'system')
|
|
@@ -48,10 +48,10 @@ export default class GroqModel extends Model {
|
|
|
48
48
|
other_messages.push(message);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
system_messages.push(new Message('system',
|
|
51
|
+
system_messages.push(new Message('system', tools_prompt));
|
|
52
52
|
|
|
53
53
|
messages = [...system_messages, ...other_messages];
|
|
54
|
-
|
|
54
|
+
tools = [];
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const convertedMessages = [];
|
|
@@ -61,45 +61,71 @@ export default class GroqModel extends Model {
|
|
|
61
61
|
const completion_payload = {
|
|
62
62
|
model: model.name,
|
|
63
63
|
messages: convertedMessages,
|
|
64
|
-
tools:
|
|
64
|
+
tools: tools.map(t => ({
|
|
65
65
|
type: 'function',
|
|
66
|
-
function:
|
|
66
|
+
function: t,
|
|
67
67
|
})),
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
if (options.
|
|
70
|
+
if (options.force_tool) {
|
|
71
71
|
completion_payload.tool_choice = {
|
|
72
72
|
type: 'function',
|
|
73
|
-
function: {name: options.
|
|
73
|
+
function: {name: options.force_tool},
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
if (!completion_payload.tools.length)
|
|
78
78
|
delete completion_payload.tools;
|
|
79
79
|
|
|
80
|
-
const
|
|
81
|
-
const completion = chatCompletion.choices[0].message;
|
|
80
|
+
const stream = await this.getGroq().chat.completions.create({...completion_payload, stream: true});
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
message_content.push({type: 'text', content: completion.content});
|
|
82
|
+
let fullText = '';
|
|
83
|
+
const toolBuffer = new Map();
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
85
|
+
for await (const chunk of stream) {
|
|
86
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
87
|
+
if (!delta)
|
|
88
|
+
continue;
|
|
89
|
+
|
|
90
|
+
if (delta.content) {
|
|
91
|
+
fullText += delta.content;
|
|
92
|
+
yield {type: 'text_delta', content: delta.content};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (delta.tool_calls) {
|
|
96
|
+
for (const tc of delta.tool_calls) {
|
|
97
|
+
const idx = tc.index;
|
|
98
|
+
if (!toolBuffer.has(idx))
|
|
99
|
+
toolBuffer.set(idx, {id: '', name: '', argumentsRaw: ''});
|
|
100
|
+
const buf = toolBuffer.get(idx);
|
|
101
|
+
if (tc.id)
|
|
102
|
+
buf.id = tc.id;
|
|
103
|
+
if (tc.function?.name)
|
|
104
|
+
buf.name = tc.function.name;
|
|
105
|
+
if (tc.function?.arguments)
|
|
106
|
+
buf.argumentsRaw += tc.function.arguments;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
101
109
|
}
|
|
102
110
|
|
|
111
|
+
const toolCalls = [];
|
|
112
|
+
for (const [, buf] of [...toolBuffer.entries()].sort((a, b) => a[0] - b[0])) {
|
|
113
|
+
const tc = {
|
|
114
|
+
id: buf.id,
|
|
115
|
+
name: buf.name,
|
|
116
|
+
arguments: buf.argumentsRaw ? JSON.parse(buf.argumentsRaw) : {},
|
|
117
|
+
};
|
|
118
|
+
toolCalls.push(tc);
|
|
119
|
+
yield {type: 'tool_call', content: tc};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const message_content = [];
|
|
123
|
+
if (fullText)
|
|
124
|
+
message_content.push({type: 'text', content: fullText});
|
|
125
|
+
|
|
126
|
+
if (toolCalls.length)
|
|
127
|
+
message_content.push({type: 'tool_call', content: toolCalls});
|
|
128
|
+
|
|
103
129
|
return [
|
|
104
130
|
new Message('assistant', message_content),
|
|
105
131
|
];
|
|
@@ -132,7 +158,7 @@ export default class GroqModel extends Model {
|
|
|
132
158
|
});
|
|
133
159
|
break;
|
|
134
160
|
|
|
135
|
-
case '
|
|
161
|
+
case 'tool_call':
|
|
136
162
|
if (model.tools) {
|
|
137
163
|
messages.push({
|
|
138
164
|
role: message.role,
|
|
@@ -149,13 +175,13 @@ export default class GroqModel extends Model {
|
|
|
149
175
|
} else {
|
|
150
176
|
messages.push({
|
|
151
177
|
role: message.role,
|
|
152
|
-
content: c.content.map(
|
|
178
|
+
content: c.content.map(t => '```CALL \n' + t.name + '\n' + JSON.stringify(t.arguments || {}) + '\n```').join("\n\n"),
|
|
153
179
|
name: message.name,
|
|
154
180
|
});
|
|
155
181
|
}
|
|
156
182
|
break;
|
|
157
183
|
|
|
158
|
-
case '
|
|
184
|
+
case 'tool_result':
|
|
159
185
|
if (model.tools) {
|
|
160
186
|
messages.push({
|
|
161
187
|
role: message.role,
|
|
@@ -166,7 +192,7 @@ export default class GroqModel extends Model {
|
|
|
166
192
|
} else {
|
|
167
193
|
messages.push({
|
|
168
194
|
role: 'user',
|
|
169
|
-
content: '
|
|
195
|
+
content: 'TOOL RESPONSE:\n' + JSON.stringify(c.content.response),
|
|
170
196
|
name: message.name,
|
|
171
197
|
});
|
|
172
198
|
}
|
|
@@ -17,16 +17,16 @@ export default class LegacyOpenAIModel extends Model {
|
|
|
17
17
|
return this.openai;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
async generate(model, thread,
|
|
21
|
-
const parsed = this.parseOptions(options,
|
|
20
|
+
async *generate(model, thread, tools = [], options = {}) {
|
|
21
|
+
const parsed = this.parseOptions(options, tools);
|
|
22
22
|
options = parsed.options;
|
|
23
|
-
|
|
23
|
+
tools = parsed.tools;
|
|
24
24
|
|
|
25
25
|
let messages = thread.messages;
|
|
26
26
|
|
|
27
|
-
if (
|
|
28
|
-
// Se il modello non supporta nativamente
|
|
29
|
-
const
|
|
27
|
+
if (tools.length && !model.tools) {
|
|
28
|
+
// Se il modello non supporta nativamente gli strumenti, inserisco il prompt ad hoc come ultimo messaggio di sistema
|
|
29
|
+
const tools_prompt = this.promptFromTools(options, tools);
|
|
30
30
|
let system_messages = [], other_messages = [], first_found = false;
|
|
31
31
|
for (let message of messages) {
|
|
32
32
|
if (!first_found && message.role !== 'system')
|
|
@@ -38,10 +38,10 @@ export default class LegacyOpenAIModel extends Model {
|
|
|
38
38
|
other_messages.push(message);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
system_messages.push(new Message('system',
|
|
41
|
+
system_messages.push(new Message('system', tools_prompt));
|
|
42
42
|
|
|
43
43
|
messages = [...system_messages, ...other_messages];
|
|
44
|
-
|
|
44
|
+
tools = [];
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
const convertedMessages = [];
|
|
@@ -51,16 +51,16 @@ export default class LegacyOpenAIModel extends Model {
|
|
|
51
51
|
const completion_payload = {
|
|
52
52
|
model: model.name,
|
|
53
53
|
messages: convertedMessages,
|
|
54
|
-
tools:
|
|
54
|
+
tools: tools.map(t => ({
|
|
55
55
|
type: 'function',
|
|
56
|
-
function:
|
|
56
|
+
function: t,
|
|
57
57
|
})),
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
if (options.
|
|
60
|
+
if (options.force_tool) {
|
|
61
61
|
completion_payload.tool_choice = {
|
|
62
62
|
type: 'function',
|
|
63
|
-
function: {name: options.
|
|
63
|
+
function: {name: options.force_tool},
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -70,29 +70,55 @@ export default class LegacyOpenAIModel extends Model {
|
|
|
70
70
|
if (!completion_payload.tools.length)
|
|
71
71
|
delete completion_payload.tools;
|
|
72
72
|
|
|
73
|
-
const
|
|
74
|
-
const completion = chatCompletion.choices[0].message;
|
|
73
|
+
const stream = await this.getOpenAi().chat.completions.create({...completion_payload, stream: true});
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
message_content.push({type: 'text', content: completion.content});
|
|
75
|
+
let fullText = '';
|
|
76
|
+
const toolBuffer = new Map();
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
78
|
+
for await (const chunk of stream) {
|
|
79
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
80
|
+
if (!delta)
|
|
81
|
+
continue;
|
|
82
|
+
|
|
83
|
+
if (delta.content) {
|
|
84
|
+
fullText += delta.content;
|
|
85
|
+
yield {type: 'text_delta', content: delta.content};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (delta.tool_calls) {
|
|
89
|
+
for (const tc of delta.tool_calls) {
|
|
90
|
+
const idx = tc.index;
|
|
91
|
+
if (!toolBuffer.has(idx))
|
|
92
|
+
toolBuffer.set(idx, {id: '', name: '', argumentsRaw: ''});
|
|
93
|
+
const buf = toolBuffer.get(idx);
|
|
94
|
+
if (tc.id)
|
|
95
|
+
buf.id = tc.id;
|
|
96
|
+
if (tc.function?.name)
|
|
97
|
+
buf.name = tc.function.name;
|
|
98
|
+
if (tc.function?.arguments)
|
|
99
|
+
buf.argumentsRaw += tc.function.arguments;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
94
102
|
}
|
|
95
103
|
|
|
104
|
+
const toolCalls = [];
|
|
105
|
+
for (const [, buf] of [...toolBuffer.entries()].sort((a, b) => a[0] - b[0])) {
|
|
106
|
+
const tc = {
|
|
107
|
+
id: buf.id,
|
|
108
|
+
name: buf.name,
|
|
109
|
+
arguments: buf.argumentsRaw ? JSON.parse(buf.argumentsRaw) : {},
|
|
110
|
+
};
|
|
111
|
+
toolCalls.push(tc);
|
|
112
|
+
yield {type: 'tool_call', content: tc};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const message_content = [];
|
|
116
|
+
if (fullText)
|
|
117
|
+
message_content.push({type: 'text', content: fullText});
|
|
118
|
+
|
|
119
|
+
if (toolCalls.length)
|
|
120
|
+
message_content.push({type: 'tool_call', content: toolCalls});
|
|
121
|
+
|
|
96
122
|
return [
|
|
97
123
|
new Message('assistant', message_content),
|
|
98
124
|
];
|
|
@@ -173,7 +199,7 @@ export default class LegacyOpenAIModel extends Model {
|
|
|
173
199
|
}
|
|
174
200
|
break;
|
|
175
201
|
|
|
176
|
-
case '
|
|
202
|
+
case 'tool_call':
|
|
177
203
|
if (model.tools) {
|
|
178
204
|
messages.push({
|
|
179
205
|
role,
|
|
@@ -190,13 +216,13 @@ export default class LegacyOpenAIModel extends Model {
|
|
|
190
216
|
} else {
|
|
191
217
|
messages.push({
|
|
192
218
|
role,
|
|
193
|
-
content: c.content.map(
|
|
219
|
+
content: c.content.map(t => '```CALL \n' + t.name + '\n' + JSON.stringify(t.arguments || {}) + '\n```').join("\n\n"),
|
|
194
220
|
name: message.name,
|
|
195
221
|
});
|
|
196
222
|
}
|
|
197
223
|
break;
|
|
198
224
|
|
|
199
|
-
case '
|
|
225
|
+
case 'tool_result':
|
|
200
226
|
if (model.tools) {
|
|
201
227
|
messages.push({
|
|
202
228
|
role,
|
|
@@ -207,7 +233,7 @@ export default class LegacyOpenAIModel extends Model {
|
|
|
207
233
|
} else {
|
|
208
234
|
messages.push({
|
|
209
235
|
role: 'user',
|
|
210
|
-
content: '
|
|
236
|
+
content: 'TOOL RESPONSE:\n' + JSON.stringify(c.content.response),
|
|
211
237
|
name: message.name,
|
|
212
238
|
});
|
|
213
239
|
}
|
package/Models/OllamaModel.js
CHANGED
|
@@ -3,9 +3,13 @@ import Model from "../Model.js";
|
|
|
3
3
|
import Message from "../Message.js";
|
|
4
4
|
|
|
5
5
|
export default class OllamaModel extends Model {
|
|
6
|
+
getOllama() {
|
|
7
|
+
return ollama;
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
async getModels() {
|
|
7
11
|
try {
|
|
8
|
-
const {models} = await
|
|
12
|
+
const {models} = await this.getOllama().list();
|
|
9
13
|
|
|
10
14
|
const map = new Map();
|
|
11
15
|
|
|
@@ -25,16 +29,16 @@ export default class OllamaModel extends Model {
|
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
async generate(model, thread,
|
|
29
|
-
const parsed = this.parseOptions(options,
|
|
32
|
+
async *generate(model, thread, tools = [], options = {}) {
|
|
33
|
+
const parsed = this.parseOptions(options, tools);
|
|
30
34
|
options = parsed.options;
|
|
31
|
-
|
|
35
|
+
tools = parsed.tools;
|
|
32
36
|
|
|
33
37
|
let messages = thread.messages;
|
|
34
38
|
|
|
35
|
-
if (
|
|
36
|
-
// Se il modello non supporta nativamente
|
|
37
|
-
const
|
|
39
|
+
if (tools.length && !model.tools) {
|
|
40
|
+
// Se il modello non supporta nativamente gli strumenti, inserisco il prompt ad hoc come ultimo messaggio di sistema
|
|
41
|
+
const tools_prompt = this.promptFromTools(options, tools);
|
|
38
42
|
let system_messages = [], other_messages = [], first_found = false;
|
|
39
43
|
for (let message of messages) {
|
|
40
44
|
if (!first_found && message.role !== 'system')
|
|
@@ -46,10 +50,10 @@ export default class OllamaModel extends Model {
|
|
|
46
50
|
other_messages.push(message);
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
system_messages.push(new Message('system',
|
|
53
|
+
system_messages.push(new Message('system', tools_prompt));
|
|
50
54
|
|
|
51
55
|
messages = [...system_messages, ...other_messages];
|
|
52
|
-
|
|
56
|
+
tools = [];
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
const convertedMessages = [];
|
|
@@ -59,16 +63,17 @@ export default class OllamaModel extends Model {
|
|
|
59
63
|
const completion_payload = {
|
|
60
64
|
model: model.name,
|
|
61
65
|
messages: convertedMessages,
|
|
62
|
-
tools:
|
|
66
|
+
tools: tools.map(t => ({
|
|
63
67
|
type: 'function',
|
|
64
|
-
function:
|
|
68
|
+
function: t,
|
|
65
69
|
})),
|
|
70
|
+
stream: true,
|
|
66
71
|
};
|
|
67
72
|
|
|
68
|
-
if (options.
|
|
73
|
+
if (options.force_tool) {
|
|
69
74
|
completion_payload.tool_choice = {
|
|
70
75
|
type: 'function',
|
|
71
|
-
function: {name: options.
|
|
76
|
+
function: {name: options.force_tool},
|
|
72
77
|
};
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -81,31 +86,52 @@ export default class OllamaModel extends Model {
|
|
|
81
86
|
if (!completion_payload.tools.length)
|
|
82
87
|
delete completion_payload.tools;
|
|
83
88
|
|
|
84
|
-
const
|
|
85
|
-
const completion = chatCompletion.message;
|
|
89
|
+
const stream = await this.getOllama().chat(completion_payload);
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
let fullText = '';
|
|
92
|
+
let fullThinking = '';
|
|
93
|
+
const toolCalls = [];
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
for await (const chunk of stream) {
|
|
96
|
+
const m = chunk.message;
|
|
97
|
+
if (!m)
|
|
98
|
+
continue;
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
type: '
|
|
97
|
-
|
|
100
|
+
if (m.thinking) {
|
|
101
|
+
fullThinking += m.thinking;
|
|
102
|
+
yield {type: 'reasoning_delta', content: m.thinking};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (m.content) {
|
|
106
|
+
fullText += m.content;
|
|
107
|
+
yield {type: 'text_delta', content: m.content};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (m.tool_calls?.length) {
|
|
111
|
+
for (const tool_call of m.tool_calls) {
|
|
98
112
|
if (!tool_call.function)
|
|
99
113
|
throw new Error('Unsupported tool type');
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
const tc = {
|
|
102
116
|
name: tool_call.function.name,
|
|
103
117
|
arguments: tool_call.function.arguments || {},
|
|
104
118
|
};
|
|
105
|
-
|
|
106
|
-
|
|
119
|
+
toolCalls.push(tc);
|
|
120
|
+
yield {type: 'tool_call', content: tc};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
107
123
|
}
|
|
108
124
|
|
|
125
|
+
const message_content = [];
|
|
126
|
+
if (fullThinking)
|
|
127
|
+
message_content.push({type: 'reasoning', content: fullThinking});
|
|
128
|
+
|
|
129
|
+
if (fullText)
|
|
130
|
+
message_content.push({type: 'text', content: fullText});
|
|
131
|
+
|
|
132
|
+
if (toolCalls.length)
|
|
133
|
+
message_content.push({type: 'tool_call', content: toolCalls});
|
|
134
|
+
|
|
109
135
|
return [
|
|
110
136
|
new Message('assistant', message_content),
|
|
111
137
|
];
|
|
@@ -130,7 +156,7 @@ export default class OllamaModel extends Model {
|
|
|
130
156
|
});
|
|
131
157
|
break;
|
|
132
158
|
|
|
133
|
-
case '
|
|
159
|
+
case 'tool_call':
|
|
134
160
|
if (model.tools) {
|
|
135
161
|
messages.push({
|
|
136
162
|
role,
|
|
@@ -148,12 +174,12 @@ export default class OllamaModel extends Model {
|
|
|
148
174
|
messages.push({
|
|
149
175
|
role,
|
|
150
176
|
thinking: reasoning || undefined,
|
|
151
|
-
content: c.content.map(
|
|
177
|
+
content: c.content.map(t => '```CALL \n' + t.name + '\n' + JSON.stringify(t.arguments || {}) + '\n```').join("\n\n"),
|
|
152
178
|
});
|
|
153
179
|
}
|
|
154
180
|
break;
|
|
155
181
|
|
|
156
|
-
case '
|
|
182
|
+
case 'tool_result':
|
|
157
183
|
if (model.tools) {
|
|
158
184
|
messages.push({
|
|
159
185
|
role: 'tool',
|
|
@@ -163,7 +189,7 @@ export default class OllamaModel extends Model {
|
|
|
163
189
|
} else {
|
|
164
190
|
messages.push({
|
|
165
191
|
role: 'user',
|
|
166
|
-
content: '
|
|
192
|
+
content: 'TOOL RESPONSE:\n' + JSON.stringify(c.content.response),
|
|
167
193
|
});
|
|
168
194
|
}
|
|
169
195
|
break;
|
package/Models/OpenAIModel.js
CHANGED
|
@@ -58,6 +58,15 @@ export default class OpenAIModel extends Model {
|
|
|
58
58
|
audio: false,
|
|
59
59
|
image_generation: false,
|
|
60
60
|
}],
|
|
61
|
+
['gpt-5.4', {
|
|
62
|
+
name: 'gpt-5.4',
|
|
63
|
+
tiktoken: 'gpt-4',
|
|
64
|
+
tokens: 1000000,
|
|
65
|
+
tools: true,
|
|
66
|
+
structured_output: true,
|
|
67
|
+
audio: false,
|
|
68
|
+
image_generation: false,
|
|
69
|
+
}],
|
|
61
70
|
]);
|
|
62
71
|
}
|
|
63
72
|
|
|
@@ -68,16 +77,16 @@ export default class OpenAIModel extends Model {
|
|
|
68
77
|
return this.openai;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
|
-
async generate(model, thread,
|
|
72
|
-
const parsed = this.parseOptions(options,
|
|
80
|
+
async *generate(model, thread, tools = [], options = {}) {
|
|
81
|
+
const parsed = this.parseOptions(options, tools);
|
|
73
82
|
options = parsed.options;
|
|
74
|
-
|
|
83
|
+
tools = parsed.tools;
|
|
75
84
|
|
|
76
85
|
let messages = thread.messages;
|
|
77
86
|
|
|
78
|
-
if (
|
|
79
|
-
// Se il modello non supporta nativamente
|
|
80
|
-
const
|
|
87
|
+
if (tools.length && !model.tools) {
|
|
88
|
+
// Se il modello non supporta nativamente gli strumenti, inserisco il prompt ad hoc come ultimo messaggio di sistema
|
|
89
|
+
const tools_prompt = this.promptFromTools(options, tools);
|
|
81
90
|
let system_messages = [], other_messages = [], first_found = false;
|
|
82
91
|
for (let message of messages) {
|
|
83
92
|
if (!first_found && message.role !== 'system')
|
|
@@ -89,39 +98,39 @@ export default class OpenAIModel extends Model {
|
|
|
89
98
|
other_messages.push(message);
|
|
90
99
|
}
|
|
91
100
|
|
|
92
|
-
system_messages.push(new Message('system',
|
|
101
|
+
system_messages.push(new Message('system', tools_prompt));
|
|
93
102
|
|
|
94
103
|
messages = [...system_messages, ...other_messages];
|
|
95
|
-
|
|
104
|
+
tools = [];
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
const convertedMessages = [];
|
|
99
108
|
for (let m of messages)
|
|
100
109
|
convertedMessages.push(...(await this.convertMessage(m, model)));
|
|
101
110
|
|
|
102
|
-
const
|
|
111
|
+
const apiTools = tools.map(t => ({
|
|
103
112
|
type: 'function',
|
|
104
|
-
...
|
|
113
|
+
...t,
|
|
105
114
|
}));
|
|
106
115
|
|
|
107
116
|
if (model.tools && model.image_generation && options.image_generation)
|
|
108
|
-
|
|
117
|
+
apiTools.push({type: 'image_generation'});
|
|
109
118
|
|
|
110
119
|
const completion_payload = {
|
|
111
120
|
model: model.name,
|
|
112
121
|
input: convertedMessages,
|
|
113
122
|
store: false,
|
|
114
123
|
include: ['reasoning.encrypted_content'],
|
|
115
|
-
tools,
|
|
124
|
+
tools: apiTools,
|
|
116
125
|
reasoning: {
|
|
117
126
|
summary: 'auto',
|
|
118
127
|
},
|
|
119
128
|
};
|
|
120
129
|
|
|
121
|
-
if (options.
|
|
130
|
+
if (options.force_tool) {
|
|
122
131
|
completion_payload.tool_choice = {
|
|
123
132
|
type: 'function',
|
|
124
|
-
name: options.
|
|
133
|
+
name: options.force_tool,
|
|
125
134
|
};
|
|
126
135
|
}
|
|
127
136
|
|
|
@@ -131,7 +140,52 @@ export default class OpenAIModel extends Model {
|
|
|
131
140
|
if (!completion_payload.tools.length)
|
|
132
141
|
delete completion_payload.tools;
|
|
133
142
|
|
|
134
|
-
const
|
|
143
|
+
const stream = this.getOpenAi().responses.stream(completion_payload);
|
|
144
|
+
|
|
145
|
+
for await (const event of stream) {
|
|
146
|
+
switch (event.type) {
|
|
147
|
+
case 'response.output_text.delta':
|
|
148
|
+
if (event.delta)
|
|
149
|
+
yield {type: 'text_delta', content: event.delta};
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
case 'response.reasoning_summary_text.delta':
|
|
153
|
+
if (event.delta)
|
|
154
|
+
yield {type: 'reasoning_delta', content: event.delta};
|
|
155
|
+
break;
|
|
156
|
+
|
|
157
|
+
case 'response.output_item.done':
|
|
158
|
+
if (event.item?.type === 'function_call') {
|
|
159
|
+
yield {
|
|
160
|
+
type: 'tool_call',
|
|
161
|
+
content: {
|
|
162
|
+
id: event.item.call_id,
|
|
163
|
+
name: event.item.name,
|
|
164
|
+
arguments: event.item.arguments ? JSON.parse(event.item.arguments) : {},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
} else if (event.item?.type === 'image_generation_call') {
|
|
168
|
+
const mime = event.item.output_format === 'png' ? 'image/png' : 'image/jpeg';
|
|
169
|
+
yield {
|
|
170
|
+
type: 'image',
|
|
171
|
+
content: {
|
|
172
|
+
type: 'base64',
|
|
173
|
+
mime,
|
|
174
|
+
data: event.item.result,
|
|
175
|
+
},
|
|
176
|
+
meta: {
|
|
177
|
+
id: event.item.id,
|
|
178
|
+
status: event.item.status,
|
|
179
|
+
prompt: event.item.revised_prompt,
|
|
180
|
+
size: event.item.size,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const completion = await stream.finalResponse();
|
|
135
189
|
|
|
136
190
|
const message_content = [];
|
|
137
191
|
for (let output of completion.output) {
|
|
@@ -161,7 +215,7 @@ export default class OpenAIModel extends Model {
|
|
|
161
215
|
|
|
162
216
|
case 'function_call':
|
|
163
217
|
message_content.push({
|
|
164
|
-
type: '
|
|
218
|
+
type: 'tool_call',
|
|
165
219
|
content: [
|
|
166
220
|
{
|
|
167
221
|
id: output.call_id,
|
|
@@ -275,7 +329,7 @@ export default class OpenAIModel extends Model {
|
|
|
275
329
|
}
|
|
276
330
|
break;
|
|
277
331
|
|
|
278
|
-
case '
|
|
332
|
+
case 'tool_call':
|
|
279
333
|
if (model.tools) {
|
|
280
334
|
messages.push({
|
|
281
335
|
type: 'function_call',
|
|
@@ -286,12 +340,12 @@ export default class OpenAIModel extends Model {
|
|
|
286
340
|
} else {
|
|
287
341
|
messages.push({
|
|
288
342
|
role,
|
|
289
|
-
content: c.content.map(
|
|
343
|
+
content: c.content.map(t => '```CALL \n' + t.name + '\n' + JSON.stringify(t.arguments || {}) + '\n```').join("\n\n"),
|
|
290
344
|
});
|
|
291
345
|
}
|
|
292
346
|
break;
|
|
293
347
|
|
|
294
|
-
case '
|
|
348
|
+
case 'tool_result':
|
|
295
349
|
if (model.tools) {
|
|
296
350
|
messages.push({
|
|
297
351
|
type: 'function_call_output',
|
|
@@ -301,7 +355,7 @@ export default class OpenAIModel extends Model {
|
|
|
301
355
|
} else {
|
|
302
356
|
messages.push({
|
|
303
357
|
role: 'user',
|
|
304
|
-
content: '
|
|
358
|
+
content: 'TOOL RESPONSE:\n' + JSON.stringify(c.content.response),
|
|
305
359
|
});
|
|
306
360
|
}
|
|
307
361
|
break;
|