@adobe/spacecat-shared-gpt-client 1.0.0 → 1.1.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-gpt-client-v1.1.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-gpt-client-v1.1.0...@adobe/spacecat-shared-gpt-client-v1.1.1) (2024-02-08)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * waiting job status ([#145](https://github.com/adobe/spacecat-shared/issues/145)) ([9fc9b6c](https://github.com/adobe/spacecat-shared/commit/9fc9b6c356f7438c52044cdc114279777f4af7ae))
7
+
8
+ # [@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)
9
+
10
+
11
+ ### Features
12
+
13
+ * switch to async firefall api ([#143](https://github.com/adobe/spacecat-shared/issues/143)) ([a431118](https://github.com/adobe/spacecat-shared/commit/a43111817c49befe66f45f16db69b7db9d469355))
14
+
1
15
  # @adobe/spacecat-shared-gpt-client-v1.0.0 (2024-02-06)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-gpt-client",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Shared modules of the Spacecat Services - GPT Client",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -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
- if (!isObject(response) || !Array.isArray(response.insights)) {
27
- return false;
28
- }
29
-
30
- return response.insights.every((item) => item
31
- && typeof item === 'object'
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
- apiAuth,
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.apiAuth - The Bearer authorization token for Firefall.
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 #apiCall(prompt) {
89
+ async #submitJob(prompt) {
94
90
  const body = JSON.stringify({
95
- dialogue: { question: prompt },
96
- llm_metadata: LLM_CONFIG,
91
+ input: prompt,
92
+ capability_name: this.config.capabilityName,
97
93
  });
98
- return httpFetch(
99
- createUrl(this.config.apiEndpoint),
100
- {
101
- method: 'POST',
102
- headers: {
103
- 'Content-Type': 'application/json',
104
- Authorization: `Bearer ${this.config.apiAuth}`,
105
- 'x-api-key': this.config.apiKey,
106
- 'x-gw-ims-org-id': this.config.imsOrg,
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' || jobStatusResponse.status === 'WAITING');
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 response = await this.#apiCall(prompt);
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
- if (!response.ok) {
124
- this.log.error(`Firefall API returned status code ${response.status}`);
125
- throw new Error(`Firefall API returned status code ${response.status}`);
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(parsedResponse)) {
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
- return parsedResponse;
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;