@aj-archipelago/cortex 1.3.57 → 1.3.58
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/README.md +6 -0
- package/config.js +22 -0
- package/package.json +1 -1
- package/pathways/translate_apptek.js +33 -0
- package/server/plugins/apptekTranslatePlugin.js +46 -91
- package/tests/apptekTranslatePlugin.test.js +0 -2
- package/tests/integration/apptekTranslatePlugin.integration.test.js +159 -93
- package/tests/translate_apptek.test.js +16 -0
package/README.md
CHANGED
|
@@ -561,6 +561,12 @@ Each model configuration can include:
|
|
|
561
561
|
}
|
|
562
562
|
```
|
|
563
563
|
|
|
564
|
+
**Rate Limiting**: The `requestsPerSecond` parameter controls the rate limiting for each model endpoint. If not specified, Cortex defaults to **100 requests per second** per endpoint. This rate limiting is implemented using the Bottleneck library with a token bucket algorithm that includes:
|
|
565
|
+
- Minimum time between requests (`minTime`)
|
|
566
|
+
- Maximum concurrent requests (`maxConcurrent`)
|
|
567
|
+
- Token reservoir that refreshes every second
|
|
568
|
+
- Optional Redis clustering support when `storageConnectionString` is configured
|
|
569
|
+
|
|
564
570
|
### API Compatibility
|
|
565
571
|
|
|
566
572
|
Cortex provides OpenAI-compatible REST endpoints that allow you to use various models through a standardized interface. When `enableRestEndpoints` is set to `true`, Cortex exposes the following endpoints:
|
package/config.js
CHANGED
|
@@ -435,6 +435,17 @@ var config = convict({
|
|
|
435
435
|
"maxReturnTokens": 4096,
|
|
436
436
|
"supportsStreaming": true
|
|
437
437
|
},
|
|
438
|
+
"apptek-translate": {
|
|
439
|
+
"type": "APPTEK-TRANSLATE",
|
|
440
|
+
"url": "{{APPTEK_API_ENDPOINT}}",
|
|
441
|
+
"headers": {
|
|
442
|
+
"x-token": "{{APPTEK_API_KEY}}",
|
|
443
|
+
"Accept": "application/json",
|
|
444
|
+
"Content-Type": "text/plain"
|
|
445
|
+
},
|
|
446
|
+
"requestsPerSecond": 10,
|
|
447
|
+
"maxTokenLength": 128000
|
|
448
|
+
},
|
|
438
449
|
},
|
|
439
450
|
env: 'CORTEX_MODELS'
|
|
440
451
|
},
|
|
@@ -533,6 +544,17 @@ var config = convict({
|
|
|
533
544
|
format: String,
|
|
534
545
|
default: null,
|
|
535
546
|
env: 'JINA_API_KEY'
|
|
547
|
+
},
|
|
548
|
+
apptekApiKey: {
|
|
549
|
+
format: String,
|
|
550
|
+
default: null,
|
|
551
|
+
env: 'APPTEK_API_KEY',
|
|
552
|
+
sensitive: true
|
|
553
|
+
},
|
|
554
|
+
apptekApiEndpoint: {
|
|
555
|
+
format: String,
|
|
556
|
+
default: null,
|
|
557
|
+
env: 'APPTEK_API_ENDPOINT'
|
|
536
558
|
}
|
|
537
559
|
});
|
|
538
560
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aj-archipelago/cortex",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.58",
|
|
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": {
|
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
// Description: Translate text using AppTek's translation service
|
|
2
2
|
|
|
3
|
+
import logger from '../lib/logger.js';
|
|
4
|
+
const { callPathway } = await import('../lib/pathwayTools.js');
|
|
5
|
+
|
|
3
6
|
export default {
|
|
4
7
|
inputParameters: {
|
|
5
8
|
from: 'auto', // Source language, 'auto' for automatic detection
|
|
6
9
|
to: 'en', // Target language
|
|
7
10
|
glossaryId: 'none', // Optional glossary ID
|
|
11
|
+
fallbackPathway: 'translate_groq', // Fallback pathway to use if AppTek fails
|
|
8
12
|
},
|
|
9
13
|
model: 'apptek-translate',
|
|
10
14
|
timeout: 120,
|
|
15
|
+
|
|
16
|
+
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
17
|
+
const pathwayResolver = resolver;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Execute the primary AppTek translation
|
|
21
|
+
const result = await runAllPrompts(args);
|
|
22
|
+
return result;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// If AppTek translation fails, use the configured fallback pathway
|
|
25
|
+
const fallbackPathway = args.fallbackPathway || 'translate_groq';
|
|
26
|
+
logger.warn(`AppTek translation failed: ${error.message}. Falling back to ${fallbackPathway}.`);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Call the fallback pathway
|
|
30
|
+
const fallbackResult = await callPathway(fallbackPathway, {
|
|
31
|
+
text: args.text,
|
|
32
|
+
to: args.to || pathwayResolver.pathway.inputParameters.to,
|
|
33
|
+
}, pathwayResolver);
|
|
34
|
+
|
|
35
|
+
logger.verbose(`Successfully used ${fallbackPathway} as fallback`);
|
|
36
|
+
return fallbackResult;
|
|
37
|
+
} catch (fallbackError) {
|
|
38
|
+
// If even the fallback fails, log it and rethrow the original error
|
|
39
|
+
logger.error(`${fallbackPathway} fallback also failed: ${fallbackError.message}`);
|
|
40
|
+
throw error; // Throw the original AppTek error, not the fallback error
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
11
44
|
}
|
|
@@ -7,17 +7,6 @@ const { callPathway } = await import('../../lib/pathwayTools.js');
|
|
|
7
7
|
class ApptekTranslatePlugin extends ModelPlugin {
|
|
8
8
|
constructor(pathway, model) {
|
|
9
9
|
super(pathway, model);
|
|
10
|
-
|
|
11
|
-
// Get API configuration from environment variables through the base class
|
|
12
|
-
const apiEndpoint = this.environmentVariables.APPTEK_API_ENDPOINT;
|
|
13
|
-
const apiKey = this.environmentVariables.APPTEK_API_KEY;
|
|
14
|
-
|
|
15
|
-
if (!apiEndpoint || !apiKey) {
|
|
16
|
-
throw new Error('AppTek API configuration missing. Please check APPTEK_API_ENDPOINT and APPTEK_API_KEY environment variables.');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
this.apiEndpoint = apiEndpoint;
|
|
20
|
-
this.apiKey = apiKey;
|
|
21
10
|
}
|
|
22
11
|
|
|
23
12
|
// Set up parameters specific to the AppTek Translate API
|
|
@@ -38,107 +27,73 @@ class ApptekTranslatePlugin extends ModelPlugin {
|
|
|
38
27
|
|
|
39
28
|
// Execute the request to the AppTek Translate API
|
|
40
29
|
async execute(text, parameters, prompt, cortexRequest) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const warnMsg = `ApptekTranslatePlugin: Language detection for 'auto' did not return a language. Proceeding with 'auto' or default.`;
|
|
56
|
-
logger.warn(warnMsg)
|
|
57
|
-
// sourceLanguage remains 'auto'. The comparison 'auto' === to will likely be false.
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// At this point, sourceLanguage is either the initially provided 'from' language,
|
|
61
|
-
// or the detected language if 'from' was 'auto' and detection was successful.
|
|
62
|
-
// requestParameters.params.from is also updated if detection occurred.
|
|
63
|
-
|
|
64
|
-
// Check if source and target languages are the same
|
|
65
|
-
// Ensure 'to' is a valid language string, not empty or null.
|
|
66
|
-
if (to && sourceLanguage && sourceLanguage !== 'auto' && sourceLanguage === to) {
|
|
67
|
-
const logMessage = `ApptekTranslatePlugin: Source language (${sourceLanguage}) matches target language (${to}). Skipping translation.`;
|
|
68
|
-
logger.verbose(logMessage)
|
|
69
|
-
// Return the original text. Ensure the return format matches what `this.executeRequest`
|
|
70
|
-
// would return for a successful translation (e.g., string or object).
|
|
71
|
-
// Assuming it's a string based on typical translation plugin behavior.
|
|
72
|
-
return text;
|
|
30
|
+
const requestParameters = this.getRequestParameters(text, parameters, prompt);
|
|
31
|
+
const { from = 'auto', to } = requestParameters.params;
|
|
32
|
+
|
|
33
|
+
let sourceLanguage = from;
|
|
34
|
+
|
|
35
|
+
// If source language is 'auto', detect it
|
|
36
|
+
if (from === 'auto') {
|
|
37
|
+
const detectedLang = await this.detectLanguage(requestParameters.data, cortexRequest);
|
|
38
|
+
if (detectedLang) {
|
|
39
|
+
sourceLanguage = detectedLang;
|
|
40
|
+
requestParameters.params.from = detectedLang;
|
|
41
|
+
} else {
|
|
42
|
+
const warnMsg = `ApptekTranslatePlugin: Language detection for 'auto' did not return a language. Proceeding with 'auto' or default.`;
|
|
43
|
+
logger.warn(warnMsg)
|
|
73
44
|
}
|
|
45
|
+
}
|
|
74
46
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
'x-token': this.apiKey,
|
|
82
|
-
'Accept': 'application/json',
|
|
83
|
-
'Content-Type': 'text/plain'
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// Add glossary_id parameter if it's provided and not 'none'
|
|
87
|
-
if (requestParameters.params.glossaryId && requestParameters.params.glossaryId !== 'none') {
|
|
88
|
-
const url = new URL(cortexRequest.url);
|
|
89
|
-
url.searchParams.append('glossary_id', requestParameters.params.glossaryId);
|
|
90
|
-
cortexRequest.url = url.toString();
|
|
91
|
-
|
|
92
|
-
const glossaryLogMessage = `ApptekTranslatePlugin: Using glossary ID: ${requestParameters.params.glossaryId}`;
|
|
93
|
-
logger.verbose(glossaryLogMessage)
|
|
94
|
-
}
|
|
47
|
+
// Check if source and target languages are the same
|
|
48
|
+
if (to && sourceLanguage && sourceLanguage !== 'auto' && sourceLanguage === to) {
|
|
49
|
+
const logMessage = `ApptekTranslatePlugin: Source language (${sourceLanguage}) matches target language (${to}). Skipping translation.`;
|
|
50
|
+
logger.verbose(logMessage)
|
|
51
|
+
return text;
|
|
52
|
+
}
|
|
95
53
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
54
|
+
// Transform the base URL for translation
|
|
55
|
+
const langPair = `${requestParameters.params.from}-${to}`;
|
|
56
|
+
const translateUrl = `${cortexRequest.url}/api/v2/quicktranslate/${langPair}`;
|
|
57
|
+
|
|
58
|
+
// Set up the request using the standard pattern
|
|
59
|
+
cortexRequest.url = translateUrl;
|
|
60
|
+
cortexRequest.data = requestParameters.data;
|
|
61
|
+
cortexRequest.method = 'POST';
|
|
62
|
+
|
|
63
|
+
// Add glossary_id parameter if it's provided and not 'none'
|
|
64
|
+
if (requestParameters.params.glossaryId && requestParameters.params.glossaryId !== 'none') {
|
|
65
|
+
const url = new URL(cortexRequest.url);
|
|
66
|
+
url.searchParams.append('glossary_id', requestParameters.params.glossaryId);
|
|
67
|
+
cortexRequest.url = url.toString();
|
|
100
68
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const { callPathway } = await import('../../lib/pathwayTools.js');
|
|
104
|
-
|
|
105
|
-
// Call the Groq translate pathway as a fallback
|
|
106
|
-
const result = await callPathway('translate_groq', {
|
|
107
|
-
text,
|
|
108
|
-
to: parameters.to || this.promptParameters.to,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
logger.verbose('Successfully used Groq translate as fallback');
|
|
112
|
-
return result;
|
|
113
|
-
} catch (fallbackError) {
|
|
114
|
-
// If even the fallback fails, log it and rethrow the original error
|
|
115
|
-
logger.error(`Groq translate fallback also failed: ${fallbackError.message}`);
|
|
116
|
-
throw fallbackError;
|
|
117
|
-
}
|
|
69
|
+
const glossaryLogMessage = `ApptekTranslatePlugin: Using glossary ID: ${requestParameters.params.glossaryId}`;
|
|
70
|
+
logger.verbose(glossaryLogMessage)
|
|
118
71
|
}
|
|
72
|
+
|
|
73
|
+
return this.executeRequest(cortexRequest);
|
|
119
74
|
}
|
|
120
75
|
|
|
121
76
|
// Detect language using AppTek's language detection API
|
|
122
|
-
async detectLanguage(text) {
|
|
77
|
+
async detectLanguage(text, cortexRequest) {
|
|
123
78
|
try {
|
|
79
|
+
// Transform the base URL for language detection
|
|
80
|
+
const detectUrl = `${cortexRequest.url}/api/v2/quick_lid`;
|
|
81
|
+
|
|
124
82
|
// Make language detection request
|
|
125
|
-
const resultResponse = await fetch(
|
|
83
|
+
const resultResponse = await fetch(detectUrl, {
|
|
126
84
|
method: 'POST',
|
|
127
85
|
headers: {
|
|
128
|
-
|
|
129
|
-
'Accept': 'application/json',
|
|
130
|
-
'Content-Type': 'text/plain'
|
|
86
|
+
...cortexRequest.headers
|
|
131
87
|
},
|
|
132
88
|
body: text
|
|
133
89
|
});
|
|
134
90
|
|
|
135
91
|
let detectedLanguage = null;
|
|
136
92
|
|
|
137
|
-
|
|
138
93
|
if (resultResponse.status === 200) {
|
|
139
94
|
const result = await resultResponse.text();
|
|
140
95
|
detectedLanguage = result.split('\n')[0].split(';')[0];
|
|
141
|
-
}else {
|
|
96
|
+
} else {
|
|
142
97
|
logger.error(`Apptek Language detection failed with status: ${resultResponse.status}`);
|
|
143
98
|
logger.debug({error: resultResponse, text})
|
|
144
99
|
}
|
|
@@ -50,8 +50,6 @@ test.afterEach.always((t) => {
|
|
|
50
50
|
|
|
51
51
|
test('constructor initializes with correct configuration', (t) => {
|
|
52
52
|
const plugin = t.context.plugin;
|
|
53
|
-
t.is(plugin.apiEndpoint, 'https://api.mock-apptek.com');
|
|
54
|
-
t.is(plugin.apiKey, 'mock-api-key');
|
|
55
53
|
t.is(plugin.config, config); // Verify that it uses the imported config
|
|
56
54
|
t.is(plugin.pathwayPrompt, mockPathway.prompt);
|
|
57
55
|
t.is(plugin.modelName, mockModel.name);
|
|
@@ -1,33 +1,19 @@
|
|
|
1
1
|
import test from 'ava';
|
|
2
|
-
import
|
|
3
|
-
import CortexRequest from '../../lib/cortexRequest.js';
|
|
4
|
-
import { createLimiter } from '../../lib/requestExecutor.js';
|
|
5
|
-
import RequestMonitor from '../../lib/requestMonitor.js';
|
|
6
|
-
import { Prompt } from '../../server/prompt.js';
|
|
7
|
-
// Skip tests if API credentials are not available
|
|
8
|
-
const skipIfNoCredentials = (t) => {
|
|
9
|
-
if (!process.env.APPTEK_API_ENDPOINT || !process.env.APPTEK_API_KEY) {
|
|
10
|
-
t.skip('AppTek API credentials not available. Set APPTEK_API_ENDPOINT and APPTEK_API_KEY environment variables.');
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
return false;
|
|
14
|
-
};
|
|
2
|
+
import serverFactory from '../../index.js';
|
|
15
3
|
|
|
16
|
-
|
|
17
|
-
const mockModel = {
|
|
18
|
-
name: 'apptek-translate',
|
|
19
|
-
type: 'APPTEK-TRANSLATE',
|
|
20
|
-
endpoints: [{
|
|
21
|
-
name: 'apptek-translate',
|
|
22
|
-
type: 'APPTEK-TRANSLATE',
|
|
23
|
-
apiEndpoint: process.env.APPTEK_API_ENDPOINT,
|
|
24
|
-
apiKey: process.env.APPTEK_API_KEY,
|
|
25
|
-
}],
|
|
26
|
-
};
|
|
4
|
+
let testServer;
|
|
27
5
|
|
|
28
|
-
|
|
29
|
-
|
|
6
|
+
test.before(async () => {
|
|
7
|
+
const { server, startServer } = await serverFactory();
|
|
8
|
+
startServer && await startServer();
|
|
9
|
+
testServer = server;
|
|
10
|
+
});
|
|
30
11
|
|
|
12
|
+
test.after.always('cleanup', async () => {
|
|
13
|
+
if (testServer) {
|
|
14
|
+
await testServer.stop();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
31
17
|
|
|
32
18
|
// Test data for different languages
|
|
33
19
|
const testCases = [
|
|
@@ -78,79 +64,159 @@ const testCases = [
|
|
|
78
64
|
// Direct AppTek plugin tests
|
|
79
65
|
testCases.forEach((testCase) => {
|
|
80
66
|
test.serial(`AppTek Plugin: ${testCase.name}`, async (t) => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// Create plugin instance
|
|
92
|
-
const plugin = new ApptekTranslatePlugin(pathway, mockModel);
|
|
67
|
+
const response = await testServer.executeOperation({
|
|
68
|
+
query: 'query translate_apptek($text: String!, $from: String, $to: String) { translate_apptek(text: $text, from: $from, to: $to) { result } }',
|
|
69
|
+
variables: {
|
|
70
|
+
text: testCase.text,
|
|
71
|
+
from: testCase.from,
|
|
72
|
+
to: testCase.to
|
|
73
|
+
}
|
|
74
|
+
});
|
|
93
75
|
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
from: testCase.from,
|
|
97
|
-
to: testCase.to
|
|
98
|
-
};
|
|
76
|
+
t.is(response.body?.singleResult?.errors, undefined);
|
|
77
|
+
const result = response.body?.singleResult?.data?.translate_apptek?.result;
|
|
99
78
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
t.is(typeof result, 'string', 'Result should be a string');
|
|
111
|
-
|
|
112
|
-
// Verify the result is not empty
|
|
113
|
-
t.true(result.length > 0, 'Result should not be empty');
|
|
114
|
-
|
|
115
|
-
// Log the translation for manual verification
|
|
116
|
-
console.log(`\n${testCase.name}:`);
|
|
117
|
-
console.log(`Source (${testCase.from}): ${testCase.text}`);
|
|
118
|
-
console.log(`Target (${testCase.to}): ${result}`);
|
|
119
|
-
|
|
120
|
-
} catch (error) {
|
|
121
|
-
t.fail(`Translation failed: ${error.message}`);
|
|
122
|
-
}
|
|
79
|
+
// Verify the result is a string
|
|
80
|
+
t.is(typeof result, 'string', 'Result should be a string');
|
|
81
|
+
|
|
82
|
+
// Verify the result is not empty
|
|
83
|
+
t.true(result.length > 0, 'Result should not be empty');
|
|
84
|
+
|
|
85
|
+
// Log the translation for manual verification
|
|
86
|
+
console.log(`\n${testCase.name}:`);
|
|
87
|
+
console.log(`Source (${testCase.from}): ${testCase.text}`);
|
|
88
|
+
console.log(`Target (${testCase.to}): ${result}`);
|
|
123
89
|
});
|
|
124
90
|
});
|
|
125
91
|
|
|
92
|
+
// Test AppTek failure with GPT-4 Omni fallback
|
|
93
|
+
test.serial('AppTek Plugin: Force failure and test GPT-4 Omni fallback', async (t) => {
|
|
94
|
+
// Store original environment variables
|
|
95
|
+
const originalEndpoint = process.env.APPTEK_API_ENDPOINT;
|
|
96
|
+
const originalApiKey = process.env.APPTEK_API_KEY;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Force AppTek to fail by setting invalid endpoint
|
|
100
|
+
process.env.APPTEK_API_ENDPOINT = 'https://invalid-apptek-endpoint-that-will-fail.com';
|
|
101
|
+
process.env.APPTEK_API_KEY = 'invalid-api-key';
|
|
102
|
+
|
|
103
|
+
const testText = 'Hello, this is a test for fallback translation.';
|
|
104
|
+
|
|
105
|
+
const response = await testServer.executeOperation({
|
|
106
|
+
query: `
|
|
107
|
+
query translate_apptek_with_fallback($text: String!, $from: String, $to: String, $fallbackPathway: String) {
|
|
108
|
+
translate_apptek(text: $text, from: $from, to: $to, fallbackPathway: $fallbackPathway) {
|
|
109
|
+
result
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`,
|
|
113
|
+
variables: {
|
|
114
|
+
text: testText,
|
|
115
|
+
from: 'en',
|
|
116
|
+
to: 'es',
|
|
117
|
+
fallbackPathway: 'translate_gpt4_omni'
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Check for errors in the response
|
|
122
|
+
t.is(response.body?.singleResult?.errors, undefined, 'Should not have GraphQL errors');
|
|
123
|
+
|
|
124
|
+
const result = response.body?.singleResult?.data?.translate_apptek?.result;
|
|
125
|
+
|
|
126
|
+
// Verify the result is a string
|
|
127
|
+
t.is(typeof result, 'string', 'Result should be a string');
|
|
128
|
+
|
|
129
|
+
// Verify the result is not empty
|
|
130
|
+
t.true(result.length > 0, 'Result should not be empty');
|
|
131
|
+
|
|
132
|
+
// Verify it's not the original text (translation should have occurred)
|
|
133
|
+
t.not(result, testText, 'Result should be translated, not the original text');
|
|
134
|
+
|
|
135
|
+
// Log the fallback translation for manual verification
|
|
136
|
+
console.log('\nAppTek Failure with GPT-4 Omni Fallback:');
|
|
137
|
+
console.log(`Source (en): ${testText}`);
|
|
138
|
+
console.log(`Target (es): ${result}`);
|
|
139
|
+
console.log('✅ AppTek failed as expected and GPT-4 Omni fallback worked!');
|
|
140
|
+
|
|
141
|
+
} finally {
|
|
142
|
+
// Restore original environment variables
|
|
143
|
+
if (originalEndpoint) {
|
|
144
|
+
process.env.APPTEK_API_ENDPOINT = originalEndpoint;
|
|
145
|
+
} else {
|
|
146
|
+
delete process.env.APPTEK_API_ENDPOINT;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (originalApiKey) {
|
|
150
|
+
process.env.APPTEK_API_KEY = originalApiKey;
|
|
151
|
+
} else {
|
|
152
|
+
delete process.env.APPTEK_API_KEY;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
126
156
|
|
|
127
|
-
// Test
|
|
128
|
-
test.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
// Test AppTek failure with default fallback (translate_groq)
|
|
158
|
+
test.skip('AppTek Plugin: Force failure and test default fallback', async (t) => {
|
|
159
|
+
// Set a longer timeout for this test since Groq might be slower
|
|
160
|
+
t.timeout(180000); // 3 minutes
|
|
161
|
+
|
|
162
|
+
// Store original environment variables
|
|
163
|
+
const originalEndpoint = process.env.APPTEK_API_ENDPOINT;
|
|
164
|
+
const originalApiKey = process.env.APPTEK_API_KEY;
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Force AppTek to fail by setting invalid endpoint
|
|
168
|
+
process.env.APPTEK_API_ENDPOINT = 'https://invalid-apptek-endpoint-that-will-fail.com';
|
|
169
|
+
process.env.APPTEK_API_KEY = 'invalid-api-key';
|
|
170
|
+
|
|
171
|
+
const testText = 'Hello, this is a test for default fallback translation.';
|
|
172
|
+
|
|
173
|
+
const response = await testServer.executeOperation({
|
|
174
|
+
query: `
|
|
175
|
+
query translate_apptek_default_fallback($text: String!, $from: String, $to: String) {
|
|
176
|
+
translate_apptek(text: $text, from: $from, to: $to) {
|
|
177
|
+
result
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
`,
|
|
181
|
+
variables: {
|
|
182
|
+
text: testText,
|
|
183
|
+
from: 'en',
|
|
184
|
+
to: 'fr'
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Check for errors in the response
|
|
189
|
+
t.is(response.body?.singleResult?.errors, undefined, 'Should not have GraphQL errors');
|
|
190
|
+
|
|
191
|
+
const result = response.body?.singleResult?.data?.translate_apptek?.result;
|
|
192
|
+
|
|
193
|
+
// Verify the result is a string
|
|
194
|
+
t.is(typeof result, 'string', 'Result should be a string');
|
|
195
|
+
|
|
196
|
+
// Verify the result is not empty
|
|
197
|
+
t.true(result.length > 0, 'Result should not be empty');
|
|
198
|
+
|
|
199
|
+
// Verify it's not the original text (translation should have occurred)
|
|
200
|
+
t.not(result, testText, 'Result should be translated, not the original text');
|
|
201
|
+
|
|
202
|
+
// Log the fallback translation for manual verification
|
|
203
|
+
console.log('\nAppTek Failure with Default Fallback:');
|
|
204
|
+
console.log(`Source (en): ${testText}`);
|
|
205
|
+
console.log(`Target (fr): ${result}`);
|
|
206
|
+
console.log('✅ AppTek failed as expected and default fallback worked!');
|
|
207
|
+
|
|
208
|
+
} finally {
|
|
209
|
+
// Restore original environment variables
|
|
210
|
+
if (originalEndpoint) {
|
|
211
|
+
process.env.APPTEK_API_ENDPOINT = originalEndpoint;
|
|
212
|
+
} else {
|
|
213
|
+
delete process.env.APPTEK_API_ENDPOINT;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (originalApiKey) {
|
|
217
|
+
process.env.APPTEK_API_KEY = originalApiKey;
|
|
218
|
+
} else {
|
|
219
|
+
delete process.env.APPTEK_API_KEY;
|
|
154
220
|
}
|
|
155
221
|
}
|
|
156
222
|
});
|
|
@@ -115,3 +115,19 @@ test('resolver uses correct model', (t) => {
|
|
|
115
115
|
const model = resolver.model;
|
|
116
116
|
t.is(model.type, 'APPTEK-TRANSLATE');
|
|
117
117
|
});
|
|
118
|
+
|
|
119
|
+
test('pathway has fallback pathway parameter', async (t) => {
|
|
120
|
+
// Import the actual pathway to test the new parameter
|
|
121
|
+
const pathway = await import('../pathways/translate_apptek.js');
|
|
122
|
+
|
|
123
|
+
t.truthy(pathway.default.inputParameters.fallbackPathway);
|
|
124
|
+
t.is(pathway.default.inputParameters.fallbackPathway, 'translate_groq');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('pathway has executePathway function', async (t) => {
|
|
128
|
+
// Import the actual pathway to test the executePathway function
|
|
129
|
+
const pathway = await import('../pathways/translate_apptek.js');
|
|
130
|
+
|
|
131
|
+
t.truthy(pathway.default.executePathway);
|
|
132
|
+
t.is(typeof pathway.default.executePathway, 'function');
|
|
133
|
+
});
|