@adobe/spacecat-shared-gpt-client 1.2.22 → 1.3.1
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 +14 -0
- package/README.md +46 -1
- package/package.json +1 -1
- package/src/clients/firefall-client.js +153 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-gpt-client-v1.3.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-gpt-client-v1.3.0...@adobe/spacecat-shared-gpt-client-v1.3.1) (2024-11-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* SITES-26948 [Import Assistant] Allow calls to Firefall to use specified ims org id ([#443](https://github.com/adobe/spacecat-shared/issues/443)) ([a63016c](https://github.com/adobe/spacecat-shared/commit/a63016c426ae8a4c0f7f66653e49f1098d466ee8))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-gpt-client-v1.3.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-gpt-client-v1.2.22...@adobe/spacecat-shared-gpt-client-v1.3.0) (2024-11-12)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* SITES-26591 [Import Assistant] Expand GPT client to user chat endpoint ([#436](https://github.com/adobe/spacecat-shared/issues/436)) ([4aaca2e](https://github.com/adobe/spacecat-shared/commit/4aaca2ede0fb9b019c8c88c2f7afd396065088b1))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-gpt-client-v1.2.22](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-gpt-client-v1.2.21...@adobe/spacecat-shared-gpt-client-v1.2.22) (2024-11-11)
|
|
2
16
|
|
|
3
17
|
|
package/README.md
CHANGED
|
@@ -10,6 +10,11 @@ To use the `FirefallClient`, you need to configure it with the following paramet
|
|
|
10
10
|
- `FIREFALL_API_KEY`: Your API key for accessing the Firefall API.
|
|
11
11
|
- `FIREFALL_API_CAPABILITY_NAME`: The capability name for the Firefall API.
|
|
12
12
|
|
|
13
|
+
Optionally, you can specify the IMS ORG ID to use when calling the Firefall APIs. If this value is not specified, the IMS_CLIENT_ID (see below) will
|
|
14
|
+
be used for the header's value:
|
|
15
|
+
|
|
16
|
+
- `FIREFALL_IMS_ORG_ID`: The IMS ORG ID to use when calling the Firefall APIs and tracking the request.
|
|
17
|
+
|
|
13
18
|
These parameters can be set through environment variables or passed directly to the `FirefallClient.createFrom` method.
|
|
14
19
|
|
|
15
20
|
Additionally, the configuration for the `@adobe/spacecat-shared-ims-client` library is required to fetch the service access token from the IMS API:
|
|
@@ -42,7 +47,12 @@ try {
|
|
|
42
47
|
|
|
43
48
|
### Fetching Insights
|
|
44
49
|
|
|
50
|
+
#### Via Capability Execution endpoint
|
|
51
|
+
|
|
45
52
|
```javascript
|
|
53
|
+
/**
|
|
54
|
+
* Fetch insights using the Firefall's capability execution endpoint.
|
|
55
|
+
*/
|
|
46
56
|
async function fetchInsights(prompt) {
|
|
47
57
|
try {
|
|
48
58
|
const client = FirefallClient.createFrom({
|
|
@@ -58,7 +68,7 @@ async function fetchInsights(prompt) {
|
|
|
58
68
|
log: console,
|
|
59
69
|
});
|
|
60
70
|
|
|
61
|
-
const insights = await client.
|
|
71
|
+
const insights = await client.fetchCapabilityExecution(prompt);
|
|
62
72
|
console.log('Insights:', insights);
|
|
63
73
|
} catch (error) {
|
|
64
74
|
console.error('Failed to fetch insights:', error.message);
|
|
@@ -68,6 +78,41 @@ async function fetchInsights(prompt) {
|
|
|
68
78
|
fetchInsights('How can we improve customer satisfaction?');
|
|
69
79
|
```
|
|
70
80
|
|
|
81
|
+
#### Via Chat Completions endpoint
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
/**
|
|
85
|
+
* Fetch completions using the Firefall's chat completions endpoint.
|
|
86
|
+
*/
|
|
87
|
+
async function fetchCompletions(prompt) {
|
|
88
|
+
try {
|
|
89
|
+
const client = FirefallClient.createFrom({
|
|
90
|
+
env: {
|
|
91
|
+
FIREFALL_API_ENDPOINT: 'https://api.firefall.example.com',
|
|
92
|
+
FIREFALL_API_KEY: 'yourApiKey',
|
|
93
|
+
IMS_HOST: 'ims.example.com',
|
|
94
|
+
IMS_CLIENT_ID: 'yourClientId',
|
|
95
|
+
IMS_CLIENT_CODE: 'yourClientCode',
|
|
96
|
+
IMS_CLIENT_SECRET: 'yourClientSecret',
|
|
97
|
+
},
|
|
98
|
+
log: console,
|
|
99
|
+
});
|
|
100
|
+
const options = {
|
|
101
|
+
imageUrls: ['data:image/png;base64,iVBORw0KGgoAAAA...='],
|
|
102
|
+
model:'gpt-4-vision',
|
|
103
|
+
responseFormat: undefined,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const response = await client.fetchChatCompletion(prompt, { options });
|
|
107
|
+
console.log('Response:', JSON.stringify(response));
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('Failed to fetch chat completion:', error.message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fetchCompletions('Identify all food items in this image', { imageUrls: ['data:image/png;base64,iVBORw0KGgoAAAA...='] });
|
|
114
|
+
```
|
|
115
|
+
|
|
71
116
|
Ensure that you replace `'path/to/firefall-client'` with the actual path to the `FirefallClient` class in your project and adjust the configuration parameters according to your Firefall API credentials.
|
|
72
117
|
|
|
73
118
|
## Testing
|
package/package.json
CHANGED
|
@@ -16,7 +16,14 @@ import { hasText, isObject, isValidUrl } from '@adobe/spacecat-shared-utils';
|
|
|
16
16
|
|
|
17
17
|
import { fetch as httpFetch } from '../utils.js';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
const USER_ROLE_IMAGE_URL_TYPE = 'image_url';
|
|
20
|
+
const USER_ROLE_TEXT_TYPE = 'text';
|
|
21
|
+
const SYSTEM_ROLE = 'system';
|
|
22
|
+
const USER_ROLE = 'user';
|
|
23
|
+
const AZURE_CHAT_OPENAI_LLM_TYPE = 'azure_chat_openai';
|
|
24
|
+
const JSON_OBJECT_RESPONSE_FORMAT = 'json_object';
|
|
25
|
+
|
|
26
|
+
function validateCapabilityExecutionResponse(response) {
|
|
20
27
|
return !(!isObject(response)
|
|
21
28
|
|| !Array.isArray(response.generations)
|
|
22
29
|
|| response.generations.length === 0
|
|
@@ -25,6 +32,17 @@ function validateFirefallResponse(response) {
|
|
|
25
32
|
|| !hasText(response.generations[0][0].text));
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
function validateChatCompletionResponse(response) {
|
|
36
|
+
return isObject(response)
|
|
37
|
+
&& Array.isArray(response?.choices)
|
|
38
|
+
&& response.choices.length > 0
|
|
39
|
+
&& response.choices[0]?.message;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isBase64UrlImage(base64String) {
|
|
43
|
+
return base64String.startsWith('data:image') && base64String.endsWith('=') && base64String.includes('base64');
|
|
44
|
+
}
|
|
45
|
+
|
|
28
46
|
export default class FirefallClient {
|
|
29
47
|
static createFrom(context) {
|
|
30
48
|
const { log = console } = context;
|
|
@@ -32,7 +50,8 @@ export default class FirefallClient {
|
|
|
32
50
|
|
|
33
51
|
const {
|
|
34
52
|
FIREFALL_API_ENDPOINT: apiEndpoint,
|
|
35
|
-
IMS_CLIENT_ID:
|
|
53
|
+
IMS_CLIENT_ID: imsClientId,
|
|
54
|
+
FIREFALL_IMS_ORG_ID: firefallImsOrgId,
|
|
36
55
|
FIREFALL_API_KEY: apiKey,
|
|
37
56
|
FIREFALL_API_POLL_INTERVAL: pollInterval = 2000,
|
|
38
57
|
FIREFALL_API_CAPABILITY_NAME: capabilityName = 'gpt4_32k_completions_capability',
|
|
@@ -51,7 +70,7 @@ export default class FirefallClient {
|
|
|
51
70
|
apiKey,
|
|
52
71
|
capabilityName,
|
|
53
72
|
imsClient,
|
|
54
|
-
imsOrg,
|
|
73
|
+
imsOrg: firefallImsOrgId || imsClientId,
|
|
55
74
|
pollInterval,
|
|
56
75
|
}, log);
|
|
57
76
|
}
|
|
@@ -89,15 +108,16 @@ export default class FirefallClient {
|
|
|
89
108
|
this.log.debug(`${message}: took ${duration}ms`);
|
|
90
109
|
}
|
|
91
110
|
|
|
92
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Submit a prompt to the Firefall API.
|
|
113
|
+
* @param body The body of the request.
|
|
114
|
+
* @param path The Firefall API path.
|
|
115
|
+
* @returns {Promise<unknown>}
|
|
116
|
+
*/
|
|
117
|
+
async #submitPrompt(body, path) {
|
|
93
118
|
const apiAuth = await this.#getApiAuth();
|
|
94
119
|
|
|
95
|
-
const
|
|
96
|
-
input: prompt,
|
|
97
|
-
capability_name: this.config.capabilityName,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const url = createUrl(`${this.config.apiEndpoint}/v2/capability_execution/job`);
|
|
120
|
+
const url = createUrl(`${this.config.apiEndpoint}${path}`);
|
|
101
121
|
const headers = {
|
|
102
122
|
'Content-Type': 'application/json',
|
|
103
123
|
Authorization: `Bearer ${apiAuth}`,
|
|
@@ -121,7 +141,7 @@ export default class FirefallClient {
|
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
/* eslint-disable no-await-in-loop */
|
|
124
|
-
async #pollJobStatus(jobId) {
|
|
144
|
+
async #pollJobStatus(jobId, path) {
|
|
125
145
|
const apiAuth = await this.#getApiAuth();
|
|
126
146
|
|
|
127
147
|
let jobStatusResponse;
|
|
@@ -130,7 +150,7 @@ export default class FirefallClient {
|
|
|
130
150
|
(resolve) => { setTimeout(resolve, this.config.pollInterval); },
|
|
131
151
|
); // Wait for 2 seconds before polling
|
|
132
152
|
|
|
133
|
-
const url = `${this.config.apiEndpoint}
|
|
153
|
+
const url = `${this.config.apiEndpoint}${path}/${jobId}`;
|
|
134
154
|
const headers = {
|
|
135
155
|
Authorization: `Bearer ${apiAuth}`,
|
|
136
156
|
'x-api-key': this.config.apiKey,
|
|
@@ -161,23 +181,131 @@ export default class FirefallClient {
|
|
|
161
181
|
return jobStatusResponse;
|
|
162
182
|
}
|
|
163
183
|
|
|
164
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Fetches data from Firefall Chat Completion API.
|
|
186
|
+
* @param prompt The text prompt to provide to Firefall
|
|
187
|
+
* @param options The options for the call, with optional properties:
|
|
188
|
+
* - imageUrls: An array of URLs of the images to provide to Firefall
|
|
189
|
+
* - model: LLM Model to use (default: gpt-4-turbo). Use 'gpt-4-vision' with images.
|
|
190
|
+
* - responseFormat: The response format to request from Firefall (accepts: json_object)
|
|
191
|
+
* @returns {Object} - AI response
|
|
192
|
+
*/
|
|
193
|
+
async fetchChatCompletion(prompt, options = {}) {
|
|
194
|
+
const {
|
|
195
|
+
imageUrls,
|
|
196
|
+
responseFormat,
|
|
197
|
+
model: llmModel = 'gpt-4-turbo',
|
|
198
|
+
} = options || {};
|
|
199
|
+
const hasImageUrls = imageUrls && imageUrls.length > 0;
|
|
200
|
+
|
|
201
|
+
const getBody = () => {
|
|
202
|
+
const userRole = {
|
|
203
|
+
role: USER_ROLE,
|
|
204
|
+
content: [
|
|
205
|
+
{
|
|
206
|
+
type: USER_ROLE_TEXT_TYPE,
|
|
207
|
+
text: prompt,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (hasImageUrls) {
|
|
213
|
+
imageUrls
|
|
214
|
+
.filter((iu) => isValidUrl(iu) || isBase64UrlImage(iu))
|
|
215
|
+
.forEach((imageUrl) => {
|
|
216
|
+
userRole.content.push({
|
|
217
|
+
type: USER_ROLE_IMAGE_URL_TYPE,
|
|
218
|
+
image_url: {
|
|
219
|
+
url: imageUrl,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const body = {
|
|
226
|
+
llm_metadata: {
|
|
227
|
+
model_name: llmModel,
|
|
228
|
+
llm_type: AZURE_CHAT_OPENAI_LLM_TYPE,
|
|
229
|
+
},
|
|
230
|
+
messages: [
|
|
231
|
+
userRole,
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
if (responseFormat === JSON_OBJECT_RESPONSE_FORMAT) {
|
|
235
|
+
body.response_format = {
|
|
236
|
+
type: JSON_OBJECT_RESPONSE_FORMAT,
|
|
237
|
+
};
|
|
238
|
+
body.messages.push({
|
|
239
|
+
role: SYSTEM_ROLE,
|
|
240
|
+
content: 'You are a helpful assistant designed to output JSON.',
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return body;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Validate inputs
|
|
165
248
|
if (!hasText(prompt)) {
|
|
166
249
|
throw new Error('Invalid prompt received');
|
|
167
250
|
}
|
|
251
|
+
if (hasImageUrls && !Array.isArray(imageUrls)) {
|
|
252
|
+
throw new Error('imageUrls must be an array.');
|
|
253
|
+
}
|
|
168
254
|
|
|
255
|
+
let chatSubmissionResponse;
|
|
169
256
|
try {
|
|
170
257
|
const startTime = process.hrtime.bigint();
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
this.#
|
|
258
|
+
const body = getBody();
|
|
259
|
+
|
|
260
|
+
chatSubmissionResponse = await this.#submitPrompt(JSON.stringify(body), '/v2/chat/completions');
|
|
261
|
+
this.#logDuration('Firefall API Chat Completion call', startTime);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
this.log.error('Error while fetching data from Firefall chat API: ', error.message);
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!validateChatCompletionResponse(chatSubmissionResponse)) {
|
|
268
|
+
this.log.error(
|
|
269
|
+
'Could not obtain data from Firefall: Invalid response format.',
|
|
270
|
+
);
|
|
271
|
+
throw new Error('Invalid response format.');
|
|
272
|
+
}
|
|
273
|
+
if (!chatSubmissionResponse.choices.some((ch) => hasText(ch?.message?.content))) {
|
|
274
|
+
throw new Error('Prompt completed but no output was found.');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return chatSubmissionResponse;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Fetches data from Firefall API.
|
|
282
|
+
* @param prompt The text prompt to provide to Firefall
|
|
283
|
+
* @returns {string} - AI response
|
|
284
|
+
*/
|
|
285
|
+
async fetchCapabilityExecution(prompt) {
|
|
286
|
+
if (!hasText(prompt)) {
|
|
287
|
+
throw new Error('Invalid prompt received');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const startTime = process.hrtime.bigint();
|
|
292
|
+
|
|
293
|
+
const body = JSON.stringify({
|
|
294
|
+
input: prompt,
|
|
295
|
+
capability_name: this.config.capabilityName,
|
|
296
|
+
});
|
|
297
|
+
const path = '/v2/capability_execution/job';
|
|
298
|
+
|
|
299
|
+
const jobSubmissionResponse = await this.#submitPrompt(body, path);
|
|
300
|
+
const jobStatusResponse = await this.#pollJobStatus(jobSubmissionResponse.job_id, path);
|
|
301
|
+
this.#logDuration('Firefall API Capability Execution call', startTime);
|
|
174
302
|
|
|
175
303
|
const { output } = jobStatusResponse;
|
|
176
304
|
if (!output || !output.capability_response) {
|
|
177
305
|
throw new Error('Job completed but no output was found');
|
|
178
306
|
}
|
|
179
307
|
|
|
180
|
-
if (!
|
|
308
|
+
if (!validateCapabilityExecutionResponse(output.capability_response)) {
|
|
181
309
|
this.log.error('Could not obtain data from Firefall: Invalid response format.');
|
|
182
310
|
throw new Error('Invalid response format.');
|
|
183
311
|
}
|
|
@@ -189,8 +317,15 @@ export default class FirefallClient {
|
|
|
189
317
|
|
|
190
318
|
return result.text;
|
|
191
319
|
} catch (error) {
|
|
192
|
-
this.log.error('Error while fetching data from Firefall API: ', error.message);
|
|
320
|
+
this.log.error('Error while fetching data from Firefall Capability Execution API: ', error.message);
|
|
193
321
|
throw error;
|
|
194
322
|
}
|
|
195
323
|
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @deprecated since version 1.2.19. Use fetchCapabilityExecution instead.
|
|
327
|
+
*/
|
|
328
|
+
async fetch(prompt) {
|
|
329
|
+
return this.fetchCapabilityExecution(prompt);
|
|
330
|
+
}
|
|
196
331
|
}
|