@aj-archipelago/cortex 1.3.35 → 1.3.36

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.
Files changed (47) hide show
  1. package/README.md +9 -9
  2. package/config/default.example.json +0 -20
  3. package/config.js +160 -6
  4. package/lib/pathwayTools.js +79 -1
  5. package/lib/requestExecutor.js +3 -1
  6. package/lib/util.js +7 -0
  7. package/package.json +1 -1
  8. package/pathways/basePathway.js +2 -0
  9. package/pathways/call_tools.js +379 -0
  10. package/pathways/system/entity/memory/shared/sys_memory_helpers.js +1 -1
  11. package/pathways/system/entity/memory/sys_search_memory.js +2 -2
  12. package/pathways/system/entity/sys_entity_agent.js +289 -0
  13. package/pathways/system/entity/sys_generator_memory.js +1 -1
  14. package/pathways/system/entity/sys_generator_results.js +1 -1
  15. package/pathways/system/entity/sys_get_entities.js +19 -0
  16. package/pathways/system/entity/tools/shared/sys_entity_tools.js +150 -0
  17. package/pathways/system/entity/tools/sys_tool_bing_search.js +147 -0
  18. package/pathways/system/entity/tools/sys_tool_callmodel.js +62 -0
  19. package/pathways/system/entity/tools/sys_tool_coding.js +53 -0
  20. package/pathways/system/entity/tools/sys_tool_codingagent.js +100 -0
  21. package/pathways/system/entity/tools/sys_tool_cognitive_search.js +231 -0
  22. package/pathways/system/entity/tools/sys_tool_image.js +57 -0
  23. package/pathways/system/entity/tools/sys_tool_readfile.js +119 -0
  24. package/pathways/system/entity/tools/sys_tool_reasoning.js +75 -0
  25. package/pathways/system/entity/tools/sys_tool_remember.js +59 -0
  26. package/pathways/vision.js +1 -1
  27. package/server/modelExecutor.js +4 -12
  28. package/server/pathwayResolver.js +53 -40
  29. package/server/plugins/azureBingPlugin.js +42 -4
  30. package/server/plugins/azureCognitivePlugin.js +40 -12
  31. package/server/plugins/claude3VertexPlugin.js +67 -18
  32. package/server/plugins/modelPlugin.js +3 -2
  33. package/server/plugins/openAiReasoningPlugin.js +3 -3
  34. package/server/plugins/openAiReasoningVisionPlugin.js +48 -0
  35. package/server/plugins/openAiVisionPlugin.js +192 -7
  36. package/tests/agentic.test.js +256 -0
  37. package/tests/call_tools.test.js +216 -0
  38. package/tests/claude3VertexToolConversion.test.js +78 -0
  39. package/tests/mocks.js +11 -3
  40. package/tests/multimodal_conversion.test.js +1 -1
  41. package/tests/openAiToolPlugin.test.js +242 -0
  42. package/pathways/test_palm_chat.js +0 -31
  43. package/server/plugins/palmChatPlugin.js +0 -233
  44. package/server/plugins/palmCodeCompletionPlugin.js +0 -45
  45. package/server/plugins/palmCompletionPlugin.js +0 -135
  46. package/tests/palmChatPlugin.test.js +0 -219
  47. package/tests/palmCompletionPlugin.test.js +0 -58
@@ -0,0 +1,216 @@
1
+ import test from 'ava';
2
+ import serverFactory from '../index.js';
3
+
4
+ let testServer;
5
+
6
+ // List of models to test - comment out models you don't want to test
7
+ const modelsToTest = [
8
+ 'oai-gpt41-mini',
9
+ 'claude-35-sonnet-vertex',
10
+ ];
11
+
12
+ // Add timing data structure
13
+ const modelTimings = {};
14
+
15
+ // Helper function to track timing
16
+ const trackTiming = (model, startTime) => {
17
+ const duration = Date.now() - startTime;
18
+ if (!modelTimings[model]) {
19
+ modelTimings[model] = [];
20
+ }
21
+ modelTimings[model].push(duration);
22
+ };
23
+
24
+ // Helper function to calculate average timing
25
+ const calculateAverageTiming = (timings) => {
26
+ return timings.reduce((a, b) => a + b, 0) / timings.length;
27
+ };
28
+
29
+ // Helper function to print model rankings
30
+ const printModelRankings = () => {
31
+ const averageTimings = Object.entries(modelTimings).map(([model, timings]) => ({
32
+ model,
33
+ avgTime: calculateAverageTiming(timings)
34
+ }));
35
+
36
+ averageTimings.sort((a, b) => a.avgTime - b.avgTime);
37
+
38
+ console.log('\nModel Performance Rankings:');
39
+ console.log('-------------------------');
40
+ averageTimings.forEach((entry, index) => {
41
+ console.log(`${index + 1}. ${entry.model}: ${Math.round(entry.avgTime)}ms average`);
42
+ });
43
+ };
44
+
45
+ // Modify runTestForModels to run tests sequentially
46
+ const runTestForModels = (testName, testFn) => {
47
+ for (const model of modelsToTest) {
48
+ test.serial(`${testName}-${model} (sequential)`, async t => {
49
+ console.log(`\nRunning ${testName} for ${model}...`);
50
+ const startTime = Date.now();
51
+
52
+ try {
53
+ await testFn(t, model);
54
+ trackTiming(model, startTime);
55
+ console.log(`✓ ${model} completed in ${Date.now() - startTime}ms`);
56
+ } catch (error) {
57
+ console.log(`✗ ${model} failed after ${Date.now() - startTime}ms`);
58
+ console.error(error);
59
+ throw error; // Re-throw to fail the test
60
+ }
61
+ });
62
+ }
63
+ };
64
+
65
+ test.before(async () => {
66
+ const { server, startServer } = await serverFactory();
67
+ startServer && await startServer();
68
+ testServer = server;
69
+ });
70
+
71
+ test.after.always('cleanup', async () => {
72
+ if (testServer) {
73
+ await testServer.stop();
74
+ }
75
+ });
76
+
77
+ // Add after.always hook to print rankings
78
+ test.after.always('print rankings', async () => {
79
+ printModelRankings();
80
+ });
81
+
82
+ // Test basic tool calling with a search request
83
+ runTestForModels('call_tools handles search request correctly', async (t, model) => {
84
+ t.timeout(120000); // 2 minutes timeout for search
85
+ const response = await testServer.executeOperation({
86
+ query: `
87
+ query TestToolCalling($text: String!, $chatHistory: [MultiMessage]!, $model: String) {
88
+ call_tools(
89
+ text: $text,
90
+ chatHistory: $chatHistory,
91
+ model: $model
92
+ ) {
93
+ result
94
+ contextId
95
+ tool
96
+ warnings
97
+ errors
98
+ }
99
+ }
100
+ `,
101
+ variables: {
102
+ text: 'What are the latest developments in renewable energy?',
103
+ chatHistory: [{
104
+ role: 'user',
105
+ content: ['What are the latest developments in renewable energy?']
106
+ }],
107
+ model: model
108
+ }
109
+ });
110
+
111
+ t.is(response.body?.singleResult?.errors, undefined);
112
+ const result = response.body?.singleResult?.data?.call_tools.result;
113
+ t.true(result.length > 0, 'Should have a non-empty result');
114
+ });
115
+
116
+ // Test tool calling with a code execution request
117
+ runTestForModels('call_tools handles code execution request correctly', async (t, model) => {
118
+ t.timeout(120000); // 2 minutes timeout for code execution
119
+ const response = await testServer.executeOperation({
120
+ query: `
121
+ query TestToolCalling($text: String!, $chatHistory: [MultiMessage]!, $model: String) {
122
+ call_tools(
123
+ text: $text,
124
+ chatHistory: $chatHistory,
125
+ model: $model
126
+ ) {
127
+ result
128
+ contextId
129
+ tool
130
+ warnings
131
+ errors
132
+ }
133
+ }
134
+ `,
135
+ variables: {
136
+ text: 'Write a Python function to calculate fibonacci numbers',
137
+ chatHistory: [{
138
+ role: 'user',
139
+ content: ['Write a Python function to calculate fibonacci numbers']
140
+ }],
141
+ model: model
142
+ }
143
+ });
144
+
145
+ t.is(response.body?.singleResult?.errors, undefined);
146
+ const result = response.body?.singleResult?.data?.call_tools.result;
147
+ t.true(result.length > 0, 'Should have a non-empty result');
148
+ });
149
+
150
+ // Test tool calling with a reasoning request
151
+ runTestForModels('call_tools handles reasoning request correctly', async (t, model) => {
152
+ t.timeout(120000); // 2 minutes timeout for reasoning
153
+ const response = await testServer.executeOperation({
154
+ query: `
155
+ query TestToolCalling($text: String!, $chatHistory: [MultiMessage]!, $model: String) {
156
+ call_tools(
157
+ text: $text,
158
+ chatHistory: $chatHistory,
159
+ model: $model
160
+ ) {
161
+ result
162
+ contextId
163
+ tool
164
+ warnings
165
+ errors
166
+ }
167
+ }
168
+ `,
169
+ variables: {
170
+ text: 'Explain the implications of quantum computing on cryptography',
171
+ chatHistory: [{
172
+ role: 'user',
173
+ content: ['Explain the implications of quantum computing on cryptography']
174
+ }],
175
+ model: model
176
+ }
177
+ });
178
+
179
+ t.is(response.body?.singleResult?.errors, undefined);
180
+ const result = response.body?.singleResult?.data?.call_tools.result;
181
+ t.true(result.length > 0, 'Should have a non-empty result');
182
+ });
183
+
184
+ // Test tool calling with a document request
185
+ runTestForModels('call_tools handles document request correctly', async (t, model) => {
186
+ t.timeout(120000); // 2 minutes timeout for document processing
187
+ const response = await testServer.executeOperation({
188
+ query: `
189
+ query TestToolCalling($text: String!, $chatHistory: [MultiMessage]!, $model: String) {
190
+ call_tools(
191
+ text: $text,
192
+ chatHistory: $chatHistory,
193
+ model: $model
194
+ ) {
195
+ result
196
+ contextId
197
+ tool
198
+ warnings
199
+ errors
200
+ }
201
+ }
202
+ `,
203
+ variables: {
204
+ text: 'Summarize the key points from my document about project management',
205
+ chatHistory: [{
206
+ role: 'user',
207
+ content: ['Summarize the key points from my document about project management']
208
+ }],
209
+ model: model
210
+ }
211
+ });
212
+
213
+ t.is(response.body?.singleResult?.errors, undefined);
214
+ const result = response.body?.singleResult?.data?.call_tools.result;
215
+ t.true(result.length > 0, 'Should have a non-empty result');
216
+ });
@@ -408,4 +408,82 @@ test('Combining existing tools block with generated tools', async (t) => {
408
408
  t.truthy(result.tools, 'Tools should be defined');
409
409
  t.is(result.tools.length, 2, 'Should have 2 tools');
410
410
  t.deepEqual(result.tools.map(t => t.name).sort(), ['get_weather', 'memory_lookup']);
411
+ });
412
+
413
+ // Test preventing duplicate tool definitions
414
+ test('Prevent duplicate tool definitions', async (t) => {
415
+ const plugin = createPlugin();
416
+ const prompt = mockPathwayResolverMessages.pathway.prompt;
417
+
418
+ const parameters = {
419
+ tools: [
420
+ {
421
+ type: 'function',
422
+ function: {
423
+ name: 'memory_lookup',
424
+ description: 'Look up information in memory',
425
+ parameters: {
426
+ type: 'object',
427
+ properties: {
428
+ query: {
429
+ type: 'string',
430
+ description: 'The search query'
431
+ }
432
+ },
433
+ required: ['query']
434
+ }
435
+ }
436
+ }
437
+ ]
438
+ };
439
+
440
+ const messages = [
441
+ {
442
+ role: 'system',
443
+ content: 'You are a helpful assistant'
444
+ },
445
+ {
446
+ role: 'user',
447
+ content: 'What\'s in my memory?'
448
+ },
449
+ {
450
+ role: 'assistant',
451
+ content: [
452
+ {
453
+ type: 'tool_use',
454
+ id: 'tool_1',
455
+ name: 'memory_lookup',
456
+ input: { query: 'search memory' }
457
+ }
458
+ ]
459
+ }
460
+ ];
461
+
462
+ // Set up the mock prompt with messages
463
+ prompt.messages = messages;
464
+
465
+ const cortexRequest = { messages };
466
+ const result = await plugin.getRequestParameters('test', parameters, prompt, cortexRequest);
467
+
468
+ // Check that we only have one memory_lookup tool definition
469
+ t.truthy(result.tools, 'Tools should be defined');
470
+ t.is(result.tools.length, 1, 'Should have exactly 1 tool');
471
+ t.is(result.tools[0].name, 'memory_lookup', 'Tool should be memory_lookup');
472
+ t.is(result.tools[0].description, 'Look up information in memory', 'Should preserve original tool description');
473
+
474
+ // Verify the tool_use call is still properly converted
475
+ t.truthy(result.messages, 'Messages should be defined');
476
+ t.is(result.messages.length, 1, 'Should have 1 message after conversion');
477
+
478
+ // Check the converted message
479
+ const message = result.messages[0];
480
+ t.is(message.role, 'assistant', 'Message should be from assistant');
481
+ t.truthy(message.content, 'Message should have content');
482
+ t.is(message.content.length, 1, 'Message should have one content item');
483
+ t.deepEqual(message.content[0], {
484
+ type: 'tool_use',
485
+ id: 'tool_1',
486
+ name: 'memory_lookup',
487
+ input: { query: 'search memory' }
488
+ });
411
489
  });
package/tests/mocks.js CHANGED
@@ -68,9 +68,17 @@ export const mockConfig = {
68
68
 
69
69
  export const mockPathwayResolverMessages = {
70
70
  model: {
71
- name: 'testModel',
72
- url: 'https://api.example.com/testModel',
73
- type: 'OPENAI-COMPLETION',
71
+ name: 'testModel',
72
+ type: 'OPENAI-CHAT',
73
+ url: 'https://api.openai.com/v1/chat/completions',
74
+ endpoints: [{
75
+ name: 'Test Endpoint',
76
+ url: 'https://api.openai.com/v1/chat/completions',
77
+ headers: {
78
+ 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
79
+ 'Content-Type': 'application/json'
80
+ }
81
+ }]
74
82
  },
75
83
  modelName: 'testModel',
76
84
  pathway: mockPathwayMessages,
@@ -209,7 +209,7 @@ test('Unsupported mime type conversion', async (t) => {
209
209
 
210
210
  t.is(modifiedMessages[0].content.length, 2);
211
211
  t.is(modifiedMessages[0].content[0].text, 'Can you analyze this PDF?');
212
- t.is(modifiedMessages[0].content[1].text, 'Image skipped: unsupported format');
212
+ t.true(modifiedMessages[0].content[1].text.includes('image_url'));
213
213
  });
214
214
 
215
215
  // Test pathological cases
@@ -0,0 +1,242 @@
1
+ import test from 'ava';
2
+ import OpenAIVisionPlugin from '../server/plugins/openAiVisionPlugin.js';
3
+ import { mockPathwayResolverMessages } from './mocks.js';
4
+ import { config } from '../config.js';
5
+
6
+ const { pathway, modelName, model } = mockPathwayResolverMessages;
7
+
8
+ // Helper function to create a plugin instance
9
+ const createPlugin = () => {
10
+ const plugin = new OpenAIVisionPlugin(pathway, {
11
+ name: 'test-model',
12
+ type: 'OPENAI-VISION'
13
+ });
14
+ return plugin;
15
+ };
16
+
17
+ // Test OpenAI tools block conversion
18
+ test('OpenAI tools block conversion', async (t) => {
19
+ const plugin = createPlugin();
20
+ const prompt = mockPathwayResolverMessages.pathway.prompt;
21
+
22
+ const parameters = {
23
+ tools: [
24
+ {
25
+ type: 'function',
26
+ function: {
27
+ name: 'get_weather',
28
+ description: 'Get current temperature for a given location.',
29
+ parameters: {
30
+ type: 'object',
31
+ properties: {
32
+ location: {
33
+ type: 'string',
34
+ description: 'City and country e.g. Bogotá, Colombia'
35
+ }
36
+ },
37
+ required: ['location'],
38
+ additionalProperties: false
39
+ }
40
+ }
41
+ }
42
+ ]
43
+ };
44
+
45
+ const cortexRequest = { tools: parameters.tools };
46
+ const result = await plugin.getRequestParameters('test', parameters, prompt, cortexRequest);
47
+
48
+ t.deepEqual(result.tools, parameters.tools);
49
+ });
50
+
51
+ // Test tool call response handling
52
+ test('Tool call response handling', async (t) => {
53
+ const plugin = createPlugin();
54
+
55
+ const responseData = {
56
+ choices: [{
57
+ message: {
58
+ role: 'assistant',
59
+ content: 'I will check the weather for you.',
60
+ tool_calls: [{
61
+ id: 'call_123',
62
+ type: 'function',
63
+ function: {
64
+ name: 'get_weather',
65
+ arguments: '{"location": "Bogotá, Colombia"}'
66
+ }
67
+ }]
68
+ }
69
+ }]
70
+ };
71
+
72
+ const result = plugin.parseResponse(responseData);
73
+
74
+ t.deepEqual(result, {
75
+ role: 'assistant',
76
+ content: 'I will check the weather for you.',
77
+ tool_calls: [{
78
+ id: 'call_123',
79
+ type: 'function',
80
+ function: {
81
+ name: 'get_weather',
82
+ arguments: '{"location": "Bogotá, Colombia"}'
83
+ }
84
+ }]
85
+ });
86
+ });
87
+
88
+ // Test tool result message handling
89
+ test('Tool result message handling', async (t) => {
90
+ const plugin = createPlugin();
91
+ const prompt = mockPathwayResolverMessages.pathway.prompt;
92
+
93
+ const messages = [
94
+ {
95
+ role: 'assistant',
96
+ content: 'I will check the weather for you.',
97
+ tool_calls: [{
98
+ id: 'call_123',
99
+ type: 'function',
100
+ function: {
101
+ name: 'get_weather',
102
+ arguments: '{"location": "Bogotá, Colombia"}'
103
+ }
104
+ }]
105
+ },
106
+ {
107
+ role: 'tool',
108
+ content: 'The weather in Bogotá is 18°C and sunny.',
109
+ tool_call_id: 'call_123'
110
+ }
111
+ ];
112
+
113
+ const result = await plugin.tryParseMessages(messages);
114
+
115
+ t.deepEqual(result, messages);
116
+ });
117
+
118
+ // Test mixed content with tools and images
119
+ test('Mixed content with tools and images', async (t) => {
120
+ const plugin = createPlugin();
121
+ const prompt = mockPathwayResolverMessages.pathway.prompt;
122
+
123
+ // Mock the validateImageUrl method to always return true
124
+ plugin.validateImageUrl = async () => true;
125
+
126
+ const messages = [
127
+ {
128
+ role: 'user',
129
+ content: [
130
+ { type: 'text', text: 'What\'s the weather in this image?' },
131
+ { type: 'image_url', image_url: { url: 'https://example.com/image.jpg' } }
132
+ ]
133
+ },
134
+ {
135
+ role: 'assistant',
136
+ content: 'I will analyze the image and check the weather.',
137
+ tool_calls: [{
138
+ id: 'call_123',
139
+ type: 'function',
140
+ function: {
141
+ name: 'get_weather',
142
+ arguments: '{"location": "Bogotá, Colombia"}'
143
+ }
144
+ }]
145
+ }
146
+ ];
147
+
148
+ const result = await plugin.tryParseMessages(messages);
149
+
150
+ t.is(result[0].role, 'user');
151
+ t.is(result[0].content[0].type, 'text');
152
+ t.is(result[0].content[1].type, 'image_url');
153
+ t.is(result[1].role, 'assistant');
154
+ t.truthy(result[1].tool_calls);
155
+ });
156
+
157
+ // Test error handling in tool calls
158
+ test('Error handling in tool calls', async (t) => {
159
+ const plugin = createPlugin();
160
+
161
+ const responseData = {
162
+ choices: [{
163
+ message: {
164
+ role: 'assistant',
165
+ content: 'I will check the weather for you.',
166
+ tool_calls: [{
167
+ id: 'call_123',
168
+ type: 'function',
169
+ function: {
170
+ name: 'get_weather',
171
+ arguments: 'invalid json'
172
+ }
173
+ }]
174
+ }
175
+ }]
176
+ };
177
+
178
+ const result = plugin.parseResponse(responseData);
179
+
180
+ t.deepEqual(result, {
181
+ role: 'assistant',
182
+ content: 'I will check the weather for you.',
183
+ tool_calls: [{
184
+ id: 'call_123',
185
+ type: 'function',
186
+ function: {
187
+ name: 'get_weather',
188
+ arguments: 'invalid json'
189
+ }
190
+ }]
191
+ });
192
+ });
193
+
194
+ // Test multiple tool calls in sequence
195
+ test('Multiple tool calls in sequence', async (t) => {
196
+ const plugin = createPlugin();
197
+ const prompt = mockPathwayResolverMessages.pathway.prompt;
198
+
199
+ const messages = [
200
+ {
201
+ role: 'assistant',
202
+ content: 'I will check multiple things for you.',
203
+ tool_calls: [
204
+ {
205
+ id: 'call_123',
206
+ type: 'function',
207
+ function: {
208
+ name: 'get_weather',
209
+ arguments: '{"location": "Bogotá, Colombia"}'
210
+ }
211
+ },
212
+ {
213
+ id: 'call_124',
214
+ type: 'function',
215
+ function: {
216
+ name: 'get_time',
217
+ arguments: '{"location": "Bogotá, Colombia"}'
218
+ }
219
+ }
220
+ ]
221
+ },
222
+ {
223
+ role: 'tool',
224
+ content: 'The weather in Bogotá is 18°C and sunny.',
225
+ tool_call_id: 'call_123'
226
+ },
227
+ {
228
+ role: 'tool',
229
+ content: 'The current time in Bogotá is 14:30.',
230
+ tool_call_id: 'call_124'
231
+ }
232
+ ];
233
+
234
+ const result = await plugin.tryParseMessages(messages);
235
+
236
+ t.is(result.length, 3);
237
+ t.is(result[0].role, 'assistant');
238
+ t.is(result[0].tool_calls.length, 2);
239
+ t.is(result[1].role, 'tool');
240
+ t.is(result[2].role, 'tool');
241
+ });
242
+
@@ -1,31 +0,0 @@
1
- //test_palm_chat.mjs
2
- // Test for handling of prompts in the PaLM chat format for Cortex
3
-
4
- import { Prompt } from '../server/prompt.js';
5
-
6
- // Description: Have a chat with a bot that uses context to understand the conversation
7
- export default {
8
- prompt:
9
- [
10
- new Prompt({
11
- context: "Instructions:\nYou an AI entity working a global media network. You are truthful, kind, and helpful. Your expertise includes journalism, journalistic ethics, researching and composing documents, and technology. You know the current date and time - it is {{now}}.",
12
- examples: [
13
- {
14
- input: {"content": "What is your expertise?"},
15
- output: {"content": "I am an expert in journalism and journalistic ethics."}
16
- }],
17
- messages: [
18
- {"author": "user", "content": "Hi how are you today?"},
19
- {"author": "assistant", "content": "I am doing well. How are you?"},
20
- {"author": "user", "content": "I am doing well. What is your name?"},
21
- {"author": "assistant", "content": "My name is Hula. What is your name?"},
22
- {"author": "user", "content": "My name is Bob. What is your expertise?"},
23
- ]}),
24
- ],
25
- inputParameters: {
26
- chatHistory: [],
27
- contextId: ``,
28
- },
29
- model: 'palm-chat',
30
- useInputChunking: false,
31
- }