@tstdl/base 0.93.159 → 0.93.161
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.
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { vertexAI } from '@genkit-ai/google-genai';
|
|
2
2
|
import { GenkitError, modelRef } from 'genkit';
|
|
3
3
|
import { genkitPlugin } from 'genkit/plugin';
|
|
4
|
-
import { shuffle } from '../../utils/array/index.js';
|
|
4
|
+
import { distinct, shuffle } from '../../utils/array/index.js';
|
|
5
5
|
import { isInstanceOf, isNullOrUndefined } from '../../utils/type-guards.js';
|
|
6
6
|
import { millisecondsPerMinute, millisecondsPerSecond } from '../../utils/units.js';
|
|
7
7
|
const pluginKey = 'vertexai-multi-location';
|
|
8
8
|
const geminiModelReference = vertexAI.model('gemini-2.5-flash');
|
|
9
9
|
export function vertexAiMultiLocation(options) {
|
|
10
|
+
if (options.locations.length == 0) {
|
|
11
|
+
throw new GenkitError({
|
|
12
|
+
status: 'INVALID_ARGUMENT',
|
|
13
|
+
message: 'At least one location must be provided for vertexAiMultiLocation',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
10
16
|
const locationConfigs = options.locations.map((location) => {
|
|
11
17
|
const circuitBreakerKey = `genkit:vertex-ai:location:${location}`;
|
|
12
18
|
const tokenLimitCircuitBreakerKey = `${circuitBreakerKey}:token-limit`;
|
|
@@ -41,16 +47,19 @@ export function vertexAiMultiLocation(options) {
|
|
|
41
47
|
const shuffledConfigs = shuffle([...locationConfigs]);
|
|
42
48
|
let lastError;
|
|
43
49
|
let isLargeRequest = false;
|
|
50
|
+
const skippedLocations = [];
|
|
44
51
|
for (const { location, circuitBreaker, tokenLimitCircuitBreaker } of shuffledConfigs) {
|
|
45
52
|
const check = await circuitBreaker.check();
|
|
46
53
|
if (!check.allowed) {
|
|
47
54
|
options.logger.warn(`Location ${location} is currently unhealthy. Skipping...`);
|
|
55
|
+
skippedLocations.push({ location, reason: 'unhealthy' });
|
|
48
56
|
continue;
|
|
49
57
|
}
|
|
50
58
|
if (isLargeRequest) {
|
|
51
59
|
const tokenCheck = await tokenLimitCircuitBreaker.check();
|
|
52
60
|
if (!tokenCheck.allowed) {
|
|
53
61
|
options.logger.warn(`Location ${location} is known to have a low token limit. Skipping for this large request...`);
|
|
62
|
+
skippedLocations.push({ location, reason: 'known to have low token limits' });
|
|
54
63
|
continue;
|
|
55
64
|
}
|
|
56
65
|
}
|
|
@@ -88,6 +97,14 @@ export function vertexAiMultiLocation(options) {
|
|
|
88
97
|
}
|
|
89
98
|
}
|
|
90
99
|
}
|
|
100
|
+
if (isNullOrUndefined(lastError)) {
|
|
101
|
+
const uniqueReasons = distinct(skippedLocations.map((s) => s.reason));
|
|
102
|
+
const reasonsString = uniqueReasons.join(' or ');
|
|
103
|
+
throw new GenkitError({
|
|
104
|
+
status: 'UNAVAILABLE',
|
|
105
|
+
message: `All locations were skipped because they are ${reasonsString}`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
91
108
|
throw lastError;
|
|
92
109
|
});
|
|
93
110
|
};
|
|
@@ -138,4 +138,42 @@ describe('Genkit vertexai-multi-location Plugin Tests', () => {
|
|
|
138
138
|
});
|
|
139
139
|
expect(response2.text).toBe('success from cb-success');
|
|
140
140
|
});
|
|
141
|
+
it('should throw if no locations are provided', async () => {
|
|
142
|
+
expect(() => {
|
|
143
|
+
vertexAiMultiLocation({
|
|
144
|
+
locations: [],
|
|
145
|
+
circuitBreakerProvider: cbProvider,
|
|
146
|
+
logger,
|
|
147
|
+
});
|
|
148
|
+
}).toThrow('At least one location must be provided');
|
|
149
|
+
});
|
|
150
|
+
it('should throw if all locations are unhealthy', async () => {
|
|
151
|
+
const ai2 = genkit({
|
|
152
|
+
plugins: [
|
|
153
|
+
vertexAiMultiLocation({
|
|
154
|
+
locations: ['unhealthy-1'],
|
|
155
|
+
circuitBreakerProvider: cbProvider,
|
|
156
|
+
logger,
|
|
157
|
+
circuitBreakerConfig: { resetTimeout: 1000000, threshold: 1 },
|
|
158
|
+
}),
|
|
159
|
+
],
|
|
160
|
+
});
|
|
161
|
+
// Manually trip the circuit breaker
|
|
162
|
+
const cb = cbProvider.provide('genkit:vertex-ai:location:unhealthy-1', { threshold: 1, resetTimeout: 1000000 });
|
|
163
|
+
await cb.recordFailure();
|
|
164
|
+
ai2.defineModel({
|
|
165
|
+
name: 'vertexai/gemini-2.5-flash',
|
|
166
|
+
}, async () => {
|
|
167
|
+
return {
|
|
168
|
+
message: {
|
|
169
|
+
role: 'model',
|
|
170
|
+
content: [{ text: 'success' }],
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
await expect(ai2.generate({
|
|
175
|
+
model: 'vertexai-multi-location/gemini-2.5-flash',
|
|
176
|
+
prompt: 'test',
|
|
177
|
+
})).rejects.toThrow('All locations were skipped because they are unhealthy');
|
|
178
|
+
});
|
|
141
179
|
});
|
package/errors/custom.error.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -787,7 +787,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
787
787
|
if (isUndefined(updatedTask)) {
|
|
788
788
|
return;
|
|
789
789
|
}
|
|
790
|
-
if (updatedTask.status == TaskStatus.Completed) {
|
|
790
|
+
if (updatedTask.status == TaskStatus.Completed || updatedTask.status == TaskStatus.WaitingChildren) {
|
|
791
791
|
await this.#circuitBreaker.recordSuccess();
|
|
792
792
|
}
|
|
793
793
|
await this.resolveDependencies(task.id, updatedTask.status, { namespace: task.namespace, transaction: tx });
|
|
@@ -828,7 +828,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
828
828
|
.select({ id: updated.id, status: updated.status })
|
|
829
829
|
.from(updated);
|
|
830
830
|
if (updatedRows.length > 0) {
|
|
831
|
-
if (updatedRows.some((r) => r.status == TaskStatus.Completed)) {
|
|
831
|
+
if (updatedRows.some((r) => r.status == TaskStatus.Completed || r.status == TaskStatus.WaitingChildren)) {
|
|
832
832
|
await this.#circuitBreaker.recordSuccess();
|
|
833
833
|
}
|
|
834
834
|
await this.resolveDependenciesMany(updatedRows.map((r) => ({ id: r.id, status: r.status })), { transaction: tx });
|
|
@@ -1171,6 +1171,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
1171
1171
|
.where(inArray(taskTable.id, tx.pgTransaction.select().from(selection)))
|
|
1172
1172
|
.returning({ id: taskTable.id, status: taskTable.status });
|
|
1173
1173
|
if (updatedRows.length > 0) {
|
|
1174
|
+
await this.#circuitBreaker.recordFailures(updatedRows.length);
|
|
1174
1175
|
await this.resolveDependenciesMany(updatedRows.map((r) => ({ id: r.id, status: r.status, namespace: this.#namespace })), { transaction: tx });
|
|
1175
1176
|
}
|
|
1176
1177
|
return updatedRows;
|
package/task-queue/task.error.js
CHANGED