symposium 2.4.3 → 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 +65 -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
|
@@ -77,16 +77,16 @@ export default class OpenAIModel extends Model {
|
|
|
77
77
|
return this.openai;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
async generate(model, thread,
|
|
81
|
-
const parsed = this.parseOptions(options,
|
|
80
|
+
async *generate(model, thread, tools = [], options = {}) {
|
|
81
|
+
const parsed = this.parseOptions(options, tools);
|
|
82
82
|
options = parsed.options;
|
|
83
|
-
|
|
83
|
+
tools = parsed.tools;
|
|
84
84
|
|
|
85
85
|
let messages = thread.messages;
|
|
86
86
|
|
|
87
|
-
if (
|
|
88
|
-
// Se il modello non supporta nativamente
|
|
89
|
-
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);
|
|
90
90
|
let system_messages = [], other_messages = [], first_found = false;
|
|
91
91
|
for (let message of messages) {
|
|
92
92
|
if (!first_found && message.role !== 'system')
|
|
@@ -98,39 +98,39 @@ export default class OpenAIModel extends Model {
|
|
|
98
98
|
other_messages.push(message);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
system_messages.push(new Message('system',
|
|
101
|
+
system_messages.push(new Message('system', tools_prompt));
|
|
102
102
|
|
|
103
103
|
messages = [...system_messages, ...other_messages];
|
|
104
|
-
|
|
104
|
+
tools = [];
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
const convertedMessages = [];
|
|
108
108
|
for (let m of messages)
|
|
109
109
|
convertedMessages.push(...(await this.convertMessage(m, model)));
|
|
110
110
|
|
|
111
|
-
const
|
|
111
|
+
const apiTools = tools.map(t => ({
|
|
112
112
|
type: 'function',
|
|
113
|
-
...
|
|
113
|
+
...t,
|
|
114
114
|
}));
|
|
115
115
|
|
|
116
116
|
if (model.tools && model.image_generation && options.image_generation)
|
|
117
|
-
|
|
117
|
+
apiTools.push({type: 'image_generation'});
|
|
118
118
|
|
|
119
119
|
const completion_payload = {
|
|
120
120
|
model: model.name,
|
|
121
121
|
input: convertedMessages,
|
|
122
122
|
store: false,
|
|
123
123
|
include: ['reasoning.encrypted_content'],
|
|
124
|
-
tools,
|
|
124
|
+
tools: apiTools,
|
|
125
125
|
reasoning: {
|
|
126
126
|
summary: 'auto',
|
|
127
127
|
},
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
-
if (options.
|
|
130
|
+
if (options.force_tool) {
|
|
131
131
|
completion_payload.tool_choice = {
|
|
132
132
|
type: 'function',
|
|
133
|
-
name: options.
|
|
133
|
+
name: options.force_tool,
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -140,7 +140,52 @@ export default class OpenAIModel extends Model {
|
|
|
140
140
|
if (!completion_payload.tools.length)
|
|
141
141
|
delete completion_payload.tools;
|
|
142
142
|
|
|
143
|
-
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();
|
|
144
189
|
|
|
145
190
|
const message_content = [];
|
|
146
191
|
for (let output of completion.output) {
|
|
@@ -170,7 +215,7 @@ export default class OpenAIModel extends Model {
|
|
|
170
215
|
|
|
171
216
|
case 'function_call':
|
|
172
217
|
message_content.push({
|
|
173
|
-
type: '
|
|
218
|
+
type: 'tool_call',
|
|
174
219
|
content: [
|
|
175
220
|
{
|
|
176
221
|
id: output.call_id,
|
|
@@ -284,7 +329,7 @@ export default class OpenAIModel extends Model {
|
|
|
284
329
|
}
|
|
285
330
|
break;
|
|
286
331
|
|
|
287
|
-
case '
|
|
332
|
+
case 'tool_call':
|
|
288
333
|
if (model.tools) {
|
|
289
334
|
messages.push({
|
|
290
335
|
type: 'function_call',
|
|
@@ -295,12 +340,12 @@ export default class OpenAIModel extends Model {
|
|
|
295
340
|
} else {
|
|
296
341
|
messages.push({
|
|
297
342
|
role,
|
|
298
|
-
content: c.content.map(
|
|
343
|
+
content: c.content.map(t => '```CALL \n' + t.name + '\n' + JSON.stringify(t.arguments || {}) + '\n```').join("\n\n"),
|
|
299
344
|
});
|
|
300
345
|
}
|
|
301
346
|
break;
|
|
302
347
|
|
|
303
|
-
case '
|
|
348
|
+
case 'tool_result':
|
|
304
349
|
if (model.tools) {
|
|
305
350
|
messages.push({
|
|
306
351
|
type: 'function_call_output',
|
|
@@ -310,7 +355,7 @@ export default class OpenAIModel extends Model {
|
|
|
310
355
|
} else {
|
|
311
356
|
messages.push({
|
|
312
357
|
role: 'user',
|
|
313
|
-
content: '
|
|
358
|
+
content: 'TOOL RESPONSE:\n' + JSON.stringify(c.content.response),
|
|
314
359
|
});
|
|
315
360
|
}
|
|
316
361
|
break;
|