@adobe/spacecat-shared-gpt-client 1.0.0 → 1.1.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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/clients/firefall-client.js +84 -56
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-gpt-client-v1.1.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-gpt-client-v1.0.0...@adobe/spacecat-shared-gpt-client-v1.1.0) (2024-02-08)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* switch to async firefall api ([#143](https://github.com/adobe/spacecat-shared/issues/143)) ([a431118](https://github.com/adobe/spacecat-shared/commit/a43111817c49befe66f45f16db69b7db9d469355))
|
|
7
|
+
|
|
1
8
|
# @adobe/spacecat-shared-gpt-client-v1.0.0 (2024-02-06)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -15,33 +15,25 @@ import { hasText, isObject, isValidUrl } from '@adobe/spacecat-shared-utils';
|
|
|
15
15
|
|
|
16
16
|
import { fetch as httpFetch } from '../utils.js';
|
|
17
17
|
|
|
18
|
-
const LLM_CONFIG = {
|
|
19
|
-
max_tokens: 4000,
|
|
20
|
-
llm_type: 'azure_chat_openai',
|
|
21
|
-
model_name: 'gpt-4-32k',
|
|
22
|
-
temperature: 0.5,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
18
|
function validateFirefallResponse(response) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
&& typeof item.insight === 'string'
|
|
33
|
-
&& typeof item.recommendation === 'string'
|
|
34
|
-
&& typeof item.code === 'string');
|
|
19
|
+
return !(!isObject(response)
|
|
20
|
+
|| !Array.isArray(response.generations)
|
|
21
|
+
|| response.generations.length === 0
|
|
22
|
+
|| !Array.isArray(response.generations[0])
|
|
23
|
+
|| !isObject(response.generations[0][0])
|
|
24
|
+
|| !hasText(response.generations[0][0].text));
|
|
35
25
|
}
|
|
36
26
|
|
|
37
27
|
export default class FirefallClient {
|
|
38
28
|
static createFrom(context) {
|
|
39
|
-
const { log } = context;
|
|
29
|
+
const { log = console } = context;
|
|
40
30
|
const {
|
|
41
31
|
FIREFALL_API_ENDPOINT: apiEndpoint,
|
|
42
32
|
FIREFALL_IMS_ORG: imsOrg,
|
|
43
33
|
FIREFALL_API_KEY: apiKey,
|
|
44
34
|
FIREFALL_API_AUTH: apiAuth,
|
|
35
|
+
FIREFALL_API_POLL_INTERVAL: pollInterval = 2000,
|
|
36
|
+
FIREFALL_API_CAPABILITY_NAME: capabilityName = 'gpt4_32k_completions_capability',
|
|
45
37
|
} = context.env;
|
|
46
38
|
|
|
47
39
|
if (!isValidUrl(apiEndpoint)) {
|
|
@@ -61,10 +53,12 @@ export default class FirefallClient {
|
|
|
61
53
|
}
|
|
62
54
|
|
|
63
55
|
return new FirefallClient({
|
|
56
|
+
apiAuth,
|
|
64
57
|
apiEndpoint,
|
|
65
|
-
imsOrg,
|
|
66
58
|
apiKey,
|
|
67
|
-
|
|
59
|
+
capabilityName,
|
|
60
|
+
imsOrg,
|
|
61
|
+
pollInterval,
|
|
68
62
|
}, log);
|
|
69
63
|
}
|
|
70
64
|
|
|
@@ -72,10 +66,12 @@ export default class FirefallClient {
|
|
|
72
66
|
* Creates a new Firefall client
|
|
73
67
|
*
|
|
74
68
|
* @param {Object} config - The configuration object.
|
|
69
|
+
* @param {string} config.apiAuth - The Bearer authorization token for Firefall.
|
|
75
70
|
* @param {string} config.apiEndpoint - The API endpoint for Firefall.
|
|
76
|
-
* @param {string} config.imsOrg - The IMS Org for Firefall.
|
|
77
71
|
* @param {string} config.apiKey - The API Key for Firefall.
|
|
78
|
-
* @param {string} config.
|
|
72
|
+
* @param {string} config.capabilityName - The capability name for Firefall.
|
|
73
|
+
* @param {string} config.imsOrg - The IMS Org for Firefall.
|
|
74
|
+
* @param {number} config.pollInterval - The interval to poll for job status.
|
|
79
75
|
* @param {Object} log - The Logger.
|
|
80
76
|
* @returns {FirefallClient} - the Firefall client.
|
|
81
77
|
*/
|
|
@@ -90,24 +86,63 @@ export default class FirefallClient {
|
|
|
90
86
|
this.log.debug(`${message}: took ${duration}ms`);
|
|
91
87
|
}
|
|
92
88
|
|
|
93
|
-
async #
|
|
89
|
+
async #submitJob(prompt) {
|
|
94
90
|
const body = JSON.stringify({
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
input: prompt,
|
|
92
|
+
capability_name: this.config.capabilityName,
|
|
97
93
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
},
|
|
108
|
-
body,
|
|
94
|
+
|
|
95
|
+
const url = createUrl(`${this.config.apiEndpoint}/v2/capability_execution/job`);
|
|
96
|
+
const response = await httpFetch(url, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: {
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
Authorization: `Bearer ${this.config.apiAuth}`,
|
|
101
|
+
'x-api-key': this.config.apiKey,
|
|
102
|
+
'x-gw-ims-org-id': this.config.imsOrg,
|
|
109
103
|
},
|
|
110
|
-
|
|
104
|
+
body,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
throw new Error(`Job submission failed with status code ${response.status}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return response.json();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* eslint-disable no-await-in-loop */
|
|
115
|
+
async #pollJobStatus(jobId) {
|
|
116
|
+
let jobStatusResponse;
|
|
117
|
+
do {
|
|
118
|
+
await new Promise(
|
|
119
|
+
(resolve) => { setTimeout(resolve, this.config.pollInterval); },
|
|
120
|
+
); // Wait for 2 seconds before polling
|
|
121
|
+
|
|
122
|
+
const response = await httpFetch(
|
|
123
|
+
createUrl(`${this.config.apiEndpoint}/v2/capability_execution/job/${jobId}`),
|
|
124
|
+
{
|
|
125
|
+
method: 'GET',
|
|
126
|
+
headers: {
|
|
127
|
+
Authorization: `Bearer ${this.config.apiAuth}`,
|
|
128
|
+
'x-api-key': this.config.apiKey,
|
|
129
|
+
'x-gw-ims-org-id': this.config.imsOrg,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
throw new Error(`Job polling failed with status code ${response.status}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
jobStatusResponse = await response.json();
|
|
139
|
+
} while (jobStatusResponse.status === 'PROCESSING');
|
|
140
|
+
|
|
141
|
+
if (jobStatusResponse.status !== 'SUCCEEDED') {
|
|
142
|
+
throw new Error(`Job did not succeed, status: ${jobStatusResponse.status}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return jobStatusResponse;
|
|
111
146
|
}
|
|
112
147
|
|
|
113
148
|
async fetch(prompt) {
|
|
@@ -117,33 +152,26 @@ export default class FirefallClient {
|
|
|
117
152
|
|
|
118
153
|
try {
|
|
119
154
|
const startTime = process.hrtime.bigint();
|
|
120
|
-
const
|
|
155
|
+
const jobSubmissionResponse = await this.#submitJob(prompt);
|
|
156
|
+
const jobStatusResponse = await this.#pollJobStatus(jobSubmissionResponse.job_id);
|
|
121
157
|
this.#logDuration('Firefall API call', startTime);
|
|
122
158
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
throw new Error(
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const responseData = await response.json();
|
|
129
|
-
if (!responseData.generations?.[0]?.[0]) {
|
|
130
|
-
this.log.error('Could not obtain data from Firefall: Generations object is missing.');
|
|
131
|
-
throw new Error('Generations object is missing.');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let parsedResponse;
|
|
135
|
-
try {
|
|
136
|
-
parsedResponse = JSON.parse(responseData.generations[0][0].text);
|
|
137
|
-
} catch (e) {
|
|
138
|
-
this.log.error('Returned Data from Firefall is not a JSON object.');
|
|
139
|
-
throw new Error('Returned Data from Firefall is not a JSON object.');
|
|
159
|
+
const { output } = jobStatusResponse;
|
|
160
|
+
if (!output || !output.capability_response) {
|
|
161
|
+
throw new Error('Job completed but no output was found');
|
|
140
162
|
}
|
|
141
163
|
|
|
142
|
-
if (!validateFirefallResponse(
|
|
164
|
+
if (!validateFirefallResponse(output.capability_response)) {
|
|
143
165
|
this.log.error('Could not obtain data from Firefall: Invalid response format.');
|
|
144
166
|
throw new Error('Invalid response format.');
|
|
145
167
|
}
|
|
146
|
-
|
|
168
|
+
|
|
169
|
+
const result = output.capability_response.generations[0][0];
|
|
170
|
+
|
|
171
|
+
this.log.info(`Generation Info: ${JSON.stringify(result.generation_info)}`);
|
|
172
|
+
this.log.info(`LLM Info: ${JSON.stringify(output.capability_response.llm_output)}`);
|
|
173
|
+
|
|
174
|
+
return result.text;
|
|
147
175
|
} catch (error) {
|
|
148
176
|
this.log.error('Error while fetching data from Firefall API: ', error.message);
|
|
149
177
|
throw error;
|