@aj-archipelago/cortex 1.3.23 → 1.3.24
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/package.json +2 -2
- package/pathways/system/entity/sys_generator_memory.js +3 -3
- package/pathways/system/rest_streaming/sys_openai_chat.js +2 -2
- package/server/pathwayResolver.js +9 -5
- package/server/plugins/claude3VertexPlugin.js +10 -1
- package/server/plugins/gemini15ChatPlugin.js +4 -0
- package/tests/openai_api.test.js +43 -23
- package/tests/streaming.test.js +197 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aj-archipelago/cortex",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.24",
|
|
4
4
|
"description": "Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"type": "module",
|
|
34
34
|
"homepage": "https://github.com/aj-archipelago/cortex#readme",
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@aj-archipelago/subvibe": "^1.0.
|
|
36
|
+
"@aj-archipelago/subvibe": "^1.0.5",
|
|
37
37
|
"@apollo/server": "^4.7.3",
|
|
38
38
|
"@apollo/server-plugin-response-cache": "^4.1.2",
|
|
39
39
|
"@apollo/utils.keyvadapter": "^3.0.0",
|
|
@@ -18,7 +18,7 @@ export default {
|
|
|
18
18
|
const { aiStyle, AI_STYLE_ANTHROPIC, AI_STYLE_OPENAI } = args;
|
|
19
19
|
const styleModel = aiStyle === "Anthropic" ? AI_STYLE_ANTHROPIC : AI_STYLE_OPENAI;
|
|
20
20
|
|
|
21
|
-
const memoryContext = await callPathway('sys_search_memory', { ...args, section: 'memoryAll', updateContext: true });
|
|
21
|
+
const memoryContext = await callPathway('sys_search_memory', { ...args, stream: false, section: 'memoryAll', updateContext: true });
|
|
22
22
|
if (memoryContext) {
|
|
23
23
|
const {toolCallId} = addToolCalls(args.chatHistory, "search memory for relevant information", "memory_lookup");
|
|
24
24
|
addToolResults(args.chatHistory, memoryContext, toolCallId);
|
|
@@ -26,9 +26,9 @@ export default {
|
|
|
26
26
|
|
|
27
27
|
let result;
|
|
28
28
|
if (args.voiceResponse) {
|
|
29
|
-
result = await callPathway('sys_generator_quick', { ...args, model: styleModel, stream: false });
|
|
29
|
+
result = await callPathway('sys_generator_quick', { ...args, model: styleModel, stream: false }, resolver);
|
|
30
30
|
} else {
|
|
31
|
-
result = await callPathway('sys_generator_quick', { ...args, model: styleModel });
|
|
31
|
+
result = await callPathway('sys_generator_quick', { ...args, model: styleModel }, resolver);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
resolver.tool = JSON.stringify({ toolUsed: "memory" });
|
|
@@ -79,6 +79,13 @@ class PathwayResolver {
|
|
|
79
79
|
let streamErrorOccurred = false;
|
|
80
80
|
let responseData = null;
|
|
81
81
|
|
|
82
|
+
const publishNestedRequestProgress = (requestProgress) => {
|
|
83
|
+
if (requestProgress.progress === 1 && this.rootRequestId) {
|
|
84
|
+
delete requestProgress.progress;
|
|
85
|
+
}
|
|
86
|
+
publishRequestProgress(requestProgress);
|
|
87
|
+
}
|
|
88
|
+
|
|
82
89
|
try {
|
|
83
90
|
responseData = await this.executePathway(args);
|
|
84
91
|
}
|
|
@@ -105,7 +112,7 @@ class PathwayResolver {
|
|
|
105
112
|
|
|
106
113
|
// some models don't support progress updates
|
|
107
114
|
if (!modelTypesExcludedFromProgressUpdates.includes(this.model.type)) {
|
|
108
|
-
await
|
|
115
|
+
await publishNestedRequestProgress({
|
|
109
116
|
requestId: this.rootRequestId || this.requestId,
|
|
110
117
|
progress: Math.min(completedCount,totalCount) / totalCount,
|
|
111
118
|
data: JSON.stringify(responseData),
|
|
@@ -144,10 +151,7 @@ class PathwayResolver {
|
|
|
144
151
|
|
|
145
152
|
try {
|
|
146
153
|
if (!streamEnded && requestProgress.data) {
|
|
147
|
-
|
|
148
|
-
logger.debug(`Publishing stream message to requestId ${this.requestId}: ${requestProgress.data}`);
|
|
149
|
-
publishRequestProgress(requestProgress);
|
|
150
|
-
}
|
|
154
|
+
publishNestedRequestProgress(requestProgress);
|
|
151
155
|
streamEnded = requestProgress.progress === 1;
|
|
152
156
|
}
|
|
153
157
|
} catch (error) {
|
|
@@ -136,7 +136,16 @@ class Claude3VertexPlugin extends OpenAIVisionPlugin {
|
|
|
136
136
|
// Extract system messages
|
|
137
137
|
const systemMessages = messagesCopy.filter(message => message.role === "system");
|
|
138
138
|
if (systemMessages.length > 0) {
|
|
139
|
-
system = systemMessages.map(message =>
|
|
139
|
+
system = systemMessages.map(message => {
|
|
140
|
+
if (Array.isArray(message.content)) {
|
|
141
|
+
// For content arrays, extract text content and join
|
|
142
|
+
return message.content
|
|
143
|
+
.filter(item => item.type === 'text')
|
|
144
|
+
.map(item => item.text)
|
|
145
|
+
.join("\n");
|
|
146
|
+
}
|
|
147
|
+
return message.content;
|
|
148
|
+
}).join("\n");
|
|
140
149
|
}
|
|
141
150
|
|
|
142
151
|
// Filter out system messages and empty messages
|
|
@@ -213,6 +213,10 @@ class Gemini15ChatPlugin extends ModelPlugin {
|
|
|
213
213
|
|
|
214
214
|
// If this message also has STOP, mark it for completion but don't overwrite the content
|
|
215
215
|
if (eventData.candidates[0].finishReason === "STOP") {
|
|
216
|
+
// Send the content first
|
|
217
|
+
requestProgress.data = JSON.stringify(createChunk({
|
|
218
|
+
content: eventData.candidates[0].content.parts[0].text
|
|
219
|
+
}));
|
|
216
220
|
requestProgress.progress = 1;
|
|
217
221
|
}
|
|
218
222
|
} else if (eventData.candidates?.[0]?.finishReason === "STOP") {
|
package/tests/openai_api.test.js
CHANGED
|
@@ -48,7 +48,7 @@ test('POST /completions', async (t) => {
|
|
|
48
48
|
test('POST /chat/completions', async (t) => {
|
|
49
49
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
50
50
|
json: {
|
|
51
|
-
model: 'gpt-
|
|
51
|
+
model: 'gpt-4o',
|
|
52
52
|
messages: [{ role: 'user', content: 'Hello!' }],
|
|
53
53
|
stream: false,
|
|
54
54
|
},
|
|
@@ -63,7 +63,7 @@ test('POST /chat/completions', async (t) => {
|
|
|
63
63
|
test('POST /chat/completions with multimodal content', async (t) => {
|
|
64
64
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
65
65
|
json: {
|
|
66
|
-
model: '
|
|
66
|
+
model: 'gpt-4o',
|
|
67
67
|
messages: [{
|
|
68
68
|
role: 'user',
|
|
69
69
|
content: [
|
|
@@ -153,7 +153,7 @@ test('POST SSE: /v1/completions should send a series of events and a [DONE] even
|
|
|
153
153
|
|
|
154
154
|
test('POST SSE: /v1/chat/completions should send a series of events and a [DONE] event', async (t) => {
|
|
155
155
|
const payload = {
|
|
156
|
-
model: 'gpt-
|
|
156
|
+
model: 'gpt-4o',
|
|
157
157
|
messages: [
|
|
158
158
|
{
|
|
159
159
|
role: 'user',
|
|
@@ -177,7 +177,7 @@ test('POST SSE: /v1/chat/completions should send a series of events and a [DONE]
|
|
|
177
177
|
|
|
178
178
|
test('POST SSE: /v1/chat/completions with multimodal content should send a series of events and a [DONE] event', async (t) => {
|
|
179
179
|
const payload = {
|
|
180
|
-
model: '
|
|
180
|
+
model: 'gpt-4o',
|
|
181
181
|
messages: [{
|
|
182
182
|
role: 'user',
|
|
183
183
|
content: [
|
|
@@ -213,7 +213,7 @@ test('POST SSE: /v1/chat/completions with multimodal content should send a serie
|
|
|
213
213
|
test('POST /chat/completions should handle multimodal content for non-multimodal model', async (t) => {
|
|
214
214
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
215
215
|
json: {
|
|
216
|
-
model: 'gpt-
|
|
216
|
+
model: 'gpt-4o',
|
|
217
217
|
messages: [{
|
|
218
218
|
role: 'user',
|
|
219
219
|
content: [
|
|
@@ -242,7 +242,7 @@ test('POST /chat/completions should handle multimodal content for non-multimodal
|
|
|
242
242
|
|
|
243
243
|
test('POST SSE: /v1/chat/completions should handle streaming multimodal content for non-multimodal model', async (t) => {
|
|
244
244
|
const payload = {
|
|
245
|
-
model: 'gpt-
|
|
245
|
+
model: 'gpt-4o',
|
|
246
246
|
messages: [{
|
|
247
247
|
role: 'user',
|
|
248
248
|
content: [
|
|
@@ -282,7 +282,7 @@ test('POST SSE: /v1/chat/completions should handle streaming multimodal content
|
|
|
282
282
|
test('POST /chat/completions should handle malformed multimodal content', async (t) => {
|
|
283
283
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
284
284
|
json: {
|
|
285
|
-
model: '
|
|
285
|
+
model: 'gpt-4o',
|
|
286
286
|
messages: [{
|
|
287
287
|
role: 'user',
|
|
288
288
|
content: [
|
|
@@ -310,7 +310,7 @@ test('POST /chat/completions should handle malformed multimodal content', async
|
|
|
310
310
|
test('POST /chat/completions should handle invalid image data', async (t) => {
|
|
311
311
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
312
312
|
json: {
|
|
313
|
-
model: '
|
|
313
|
+
model: 'gpt-4o',
|
|
314
314
|
messages: [{
|
|
315
315
|
role: 'user',
|
|
316
316
|
content: [
|
|
@@ -361,7 +361,7 @@ test('POST /completions should handle model parameters', async (t) => {
|
|
|
361
361
|
test('POST /chat/completions should handle function calling', async (t) => {
|
|
362
362
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
363
363
|
json: {
|
|
364
|
-
model: 'gpt-
|
|
364
|
+
model: 'gpt-4o',
|
|
365
365
|
messages: [{ role: 'user', content: 'What is the weather in Boston?' }],
|
|
366
366
|
functions: [{
|
|
367
367
|
name: 'get_weather',
|
|
@@ -401,7 +401,7 @@ test('POST /chat/completions should handle function calling', async (t) => {
|
|
|
401
401
|
test('POST /chat/completions should validate response format', async (t) => {
|
|
402
402
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
403
403
|
json: {
|
|
404
|
-
model: 'gpt-
|
|
404
|
+
model: 'gpt-4o',
|
|
405
405
|
messages: [{ role: 'user', content: 'Hello!' }],
|
|
406
406
|
stream: false,
|
|
407
407
|
},
|
|
@@ -426,7 +426,7 @@ test('POST /chat/completions should validate response format', async (t) => {
|
|
|
426
426
|
test('POST /chat/completions should handle system messages', async (t) => {
|
|
427
427
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
428
428
|
json: {
|
|
429
|
-
model: 'gpt-
|
|
429
|
+
model: 'gpt-4o',
|
|
430
430
|
messages: [
|
|
431
431
|
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
432
432
|
{ role: 'user', content: 'Hello!' }
|
|
@@ -443,10 +443,29 @@ test('POST /chat/completions should handle system messages', async (t) => {
|
|
|
443
443
|
});
|
|
444
444
|
|
|
445
445
|
test('POST /chat/completions should handle errors gracefully', async (t) => {
|
|
446
|
+
const error = await t.throwsAsync(
|
|
447
|
+
() => got.post(`${API_BASE}/chat/completions`, {
|
|
448
|
+
json: {
|
|
449
|
+
// Missing required model field
|
|
450
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
451
|
+
},
|
|
452
|
+
responseType: 'json',
|
|
453
|
+
})
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
t.is(error.response.statusCode, 404);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test('POST /chat/completions should handle token limits', async (t) => {
|
|
446
460
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
447
461
|
json: {
|
|
448
|
-
|
|
449
|
-
messages: [{
|
|
462
|
+
model: 'gpt-4o',
|
|
463
|
+
messages: [{
|
|
464
|
+
role: 'user',
|
|
465
|
+
content: 'Hello!'.repeat(5000) // Very long message
|
|
466
|
+
}],
|
|
467
|
+
max_tokens: 100,
|
|
468
|
+
stream: false,
|
|
450
469
|
},
|
|
451
470
|
responseType: 'json',
|
|
452
471
|
});
|
|
@@ -455,17 +474,16 @@ test('POST /chat/completions should handle errors gracefully', async (t) => {
|
|
|
455
474
|
t.is(response.body.object, 'chat.completion');
|
|
456
475
|
t.true(Array.isArray(response.body.choices));
|
|
457
476
|
t.truthy(response.body.choices[0].message.content);
|
|
458
|
-
});
|
|
477
|
+
});
|
|
459
478
|
|
|
460
|
-
test('POST /chat/completions should
|
|
479
|
+
test('POST /chat/completions should return complete responses from gpt-4o', async (t) => {
|
|
461
480
|
const response = await got.post(`${API_BASE}/chat/completions`, {
|
|
462
481
|
json: {
|
|
463
|
-
model: 'gpt-
|
|
464
|
-
messages: [
|
|
465
|
-
role: '
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
max_tokens: 100,
|
|
482
|
+
model: 'gpt-4o',
|
|
483
|
+
messages: [
|
|
484
|
+
{ role: 'system', content: 'You are a helpful assistant. Always end your response with the exact string "END_MARKER_XYZ".' },
|
|
485
|
+
{ role: 'user', content: 'Say hello and explain why complete responses matter.' }
|
|
486
|
+
],
|
|
469
487
|
stream: false,
|
|
470
488
|
},
|
|
471
489
|
responseType: 'json',
|
|
@@ -474,6 +492,8 @@ test('POST /chat/completions should handle token limits', async (t) => {
|
|
|
474
492
|
t.is(response.statusCode, 200);
|
|
475
493
|
t.is(response.body.object, 'chat.completion');
|
|
476
494
|
t.true(Array.isArray(response.body.choices));
|
|
477
|
-
|
|
478
|
-
|
|
495
|
+
console.log('GPT-4o Response:', JSON.stringify(response.body.choices[0].message.content));
|
|
496
|
+
const content = response.body.choices[0].message.content;
|
|
497
|
+
t.regex(content, /END_MARKER_XYZ$/);
|
|
498
|
+
});
|
|
479
499
|
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import serverFactory from '../index.js';
|
|
3
|
+
import { PathwayResolver } from '../server/pathwayResolver.js';
|
|
4
|
+
import OpenAIChatPlugin from '../server/plugins/openAiChatPlugin.js';
|
|
5
|
+
import GeminiChatPlugin from '../server/plugins/geminiChatPlugin.js';
|
|
6
|
+
import Gemini15ChatPlugin from '../server/plugins/gemini15ChatPlugin.js';
|
|
7
|
+
import Claude3VertexPlugin from '../server/plugins/claude3VertexPlugin.js';
|
|
8
|
+
import { config } from '../config.js';
|
|
9
|
+
|
|
10
|
+
let testServer;
|
|
11
|
+
|
|
12
|
+
test.before(async () => {
|
|
13
|
+
process.env.CORTEX_ENABLE_REST = 'true';
|
|
14
|
+
const { server, startServer } = await serverFactory();
|
|
15
|
+
startServer && await startServer();
|
|
16
|
+
testServer = server;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test.after.always('cleanup', async () => {
|
|
20
|
+
if (testServer) {
|
|
21
|
+
await testServer.stop();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Helper function to create a PathwayResolver with a specific plugin
|
|
26
|
+
function createResolverWithPlugin(pluginClass, modelName = 'test-model') {
|
|
27
|
+
// Map plugin classes to their corresponding model types
|
|
28
|
+
const pluginToModelType = {
|
|
29
|
+
OpenAIChatPlugin: 'OPENAI-VISION',
|
|
30
|
+
GeminiChatPlugin: 'GEMINI-VISION',
|
|
31
|
+
Gemini15ChatPlugin: 'GEMINI-1.5-VISION',
|
|
32
|
+
Claude3VertexPlugin: 'CLAUDE-3-VERTEX'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const modelType = pluginToModelType[pluginClass.name];
|
|
36
|
+
if (!modelType) {
|
|
37
|
+
throw new Error(`Unknown plugin class: ${pluginClass.name}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const pathway = {
|
|
41
|
+
name: 'test-pathway',
|
|
42
|
+
model: modelName,
|
|
43
|
+
prompt: 'test prompt'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const model = {
|
|
47
|
+
name: modelName,
|
|
48
|
+
type: modelType
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const resolver = new PathwayResolver({
|
|
52
|
+
config,
|
|
53
|
+
pathway,
|
|
54
|
+
args: {},
|
|
55
|
+
endpoints: { [modelName]: model }
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
resolver.modelExecutor.plugin = new pluginClass(pathway, model);
|
|
59
|
+
return resolver;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Test OpenAI Chat Plugin Streaming
|
|
63
|
+
test('OpenAI Chat Plugin - processStreamEvent handles content chunks correctly', async t => {
|
|
64
|
+
const resolver = createResolverWithPlugin(OpenAIChatPlugin);
|
|
65
|
+
const plugin = resolver.modelExecutor.plugin;
|
|
66
|
+
|
|
67
|
+
// Test regular content chunk
|
|
68
|
+
const contentEvent = {
|
|
69
|
+
data: JSON.stringify({
|
|
70
|
+
id: 'test-id',
|
|
71
|
+
choices: [{
|
|
72
|
+
delta: { content: 'test content' },
|
|
73
|
+
finish_reason: null
|
|
74
|
+
}]
|
|
75
|
+
})
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
let progress = plugin.processStreamEvent(contentEvent, {});
|
|
79
|
+
t.is(progress.data, contentEvent.data);
|
|
80
|
+
t.falsy(progress.progress);
|
|
81
|
+
|
|
82
|
+
// Test stream end
|
|
83
|
+
const endEvent = {
|
|
84
|
+
data: JSON.stringify({
|
|
85
|
+
id: 'test-id',
|
|
86
|
+
choices: [{
|
|
87
|
+
delta: {},
|
|
88
|
+
finish_reason: 'stop'
|
|
89
|
+
}]
|
|
90
|
+
})
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
progress = plugin.processStreamEvent(endEvent, {});
|
|
94
|
+
t.is(progress.progress, 1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Test Gemini Chat Plugin Streaming
|
|
98
|
+
test('Gemini Chat Plugin - processStreamEvent handles content chunks correctly', async t => {
|
|
99
|
+
const resolver = createResolverWithPlugin(GeminiChatPlugin);
|
|
100
|
+
const plugin = resolver.modelExecutor.plugin;
|
|
101
|
+
|
|
102
|
+
// Test regular content chunk
|
|
103
|
+
const contentEvent = {
|
|
104
|
+
data: JSON.stringify({
|
|
105
|
+
candidates: [{
|
|
106
|
+
content: {
|
|
107
|
+
parts: [{ text: 'test content' }]
|
|
108
|
+
},
|
|
109
|
+
finishReason: null
|
|
110
|
+
}]
|
|
111
|
+
})
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
let progress = plugin.processStreamEvent(contentEvent, {});
|
|
115
|
+
t.truthy(progress.data, 'Should have data');
|
|
116
|
+
const parsedData = JSON.parse(progress.data);
|
|
117
|
+
t.truthy(parsedData.candidates, 'Should have candidates array');
|
|
118
|
+
t.truthy(parsedData.candidates[0].content, 'Should have content object');
|
|
119
|
+
t.truthy(parsedData.candidates[0].content.parts, 'Should have parts array');
|
|
120
|
+
t.is(parsedData.candidates[0].content.parts[0].text, 'test content', 'Content should match');
|
|
121
|
+
t.falsy(progress.progress);
|
|
122
|
+
|
|
123
|
+
// Test stream end with STOP
|
|
124
|
+
const endEvent = {
|
|
125
|
+
data: JSON.stringify({
|
|
126
|
+
candidates: [{
|
|
127
|
+
content: {
|
|
128
|
+
parts: [{ text: '' }]
|
|
129
|
+
},
|
|
130
|
+
finishReason: 'STOP'
|
|
131
|
+
}]
|
|
132
|
+
})
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
progress = plugin.processStreamEvent(endEvent, {});
|
|
136
|
+
t.is(progress.progress, 1);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Test Gemini 15 Chat Plugin Streaming
|
|
140
|
+
test('Gemini 15 Chat Plugin - processStreamEvent handles safety blocks', async t => {
|
|
141
|
+
const resolver = createResolverWithPlugin(Gemini15ChatPlugin);
|
|
142
|
+
const plugin = resolver.modelExecutor.plugin;
|
|
143
|
+
|
|
144
|
+
// Test safety block
|
|
145
|
+
const safetyEvent = {
|
|
146
|
+
data: JSON.stringify({
|
|
147
|
+
candidates: [{
|
|
148
|
+
safetyRatings: [{ blocked: true }]
|
|
149
|
+
}]
|
|
150
|
+
})
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const progress = plugin.processStreamEvent(safetyEvent, {});
|
|
154
|
+
t.true(progress.data.includes('Response blocked'));
|
|
155
|
+
t.is(progress.progress, 1);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Test Claude 3 Vertex Plugin Streaming
|
|
159
|
+
test('Claude 3 Vertex Plugin - processStreamEvent handles message types', async t => {
|
|
160
|
+
const resolver = createResolverWithPlugin(Claude3VertexPlugin);
|
|
161
|
+
const plugin = resolver.modelExecutor.plugin;
|
|
162
|
+
|
|
163
|
+
// Test message start
|
|
164
|
+
const startEvent = {
|
|
165
|
+
data: JSON.stringify({
|
|
166
|
+
type: 'message_start',
|
|
167
|
+
message: { id: 'test-id' }
|
|
168
|
+
})
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
let progress = plugin.processStreamEvent(startEvent, {});
|
|
172
|
+
t.true(JSON.parse(progress.data).choices[0].delta.role === 'assistant');
|
|
173
|
+
|
|
174
|
+
// Test content block
|
|
175
|
+
const contentEvent = {
|
|
176
|
+
data: JSON.stringify({
|
|
177
|
+
type: 'content_block_delta',
|
|
178
|
+
delta: {
|
|
179
|
+
type: 'text_delta',
|
|
180
|
+
text: 'test content'
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
progress = plugin.processStreamEvent(contentEvent, {});
|
|
186
|
+
t.true(JSON.parse(progress.data).choices[0].delta.content === 'test content');
|
|
187
|
+
|
|
188
|
+
// Test message stop
|
|
189
|
+
const stopEvent = {
|
|
190
|
+
data: JSON.stringify({
|
|
191
|
+
type: 'message_stop'
|
|
192
|
+
})
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
progress = plugin.processStreamEvent(stopEvent, {});
|
|
196
|
+
t.is(progress.progress, 1);
|
|
197
|
+
});
|