@tstdl/base 0.93.111 → 0.93.112
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/ai/genkit/module.js
CHANGED
|
@@ -43,7 +43,6 @@ export function injectGenkit(options) {
|
|
|
43
43
|
: null;
|
|
44
44
|
const multiLocationVertexPlugin = isDefined(moduleOptions.multiLocationVertex)
|
|
45
45
|
? vertexAiMultiLocation({
|
|
46
|
-
modelNames: moduleOptions.multiLocationVertex.modelNames,
|
|
47
46
|
locations: moduleOptions.multiLocationVertex.locations,
|
|
48
47
|
circuitBreakerProvider: inject(CircuitBreakerProvider),
|
|
49
48
|
logger: inject(Logger, 'VertexAiMultiRegion'),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CircuitBreakerProvider } from '../../circuit-breaker/provider.js';
|
|
2
2
|
import type { Logger } from '../../logger/logger.js';
|
|
3
3
|
import type { VertexAiMultiLocationOptions } from './types.js';
|
|
4
|
+
export type GeminiModelReference = typeof geminiModelReference;
|
|
4
5
|
declare const geminiModelReference: import("genkit").ModelReference<import("zod").ZodObject<{
|
|
5
6
|
version: import("zod").ZodOptional<import("zod").ZodString>;
|
|
6
7
|
maxOutputTokens: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
@@ -413,7 +414,6 @@ declare const geminiModelReference: import("genkit").ModelReference<import("zod"
|
|
|
413
414
|
thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
|
|
414
415
|
}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
415
416
|
}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
416
|
-
export type GeminiModelReference = typeof geminiModelReference;
|
|
417
417
|
export declare function vertexAiMultiLocation(options: VertexAiMultiLocationOptions & {
|
|
418
418
|
circuitBreakerProvider: CircuitBreakerProvider;
|
|
419
419
|
logger: Logger;
|
|
@@ -1,67 +1,82 @@
|
|
|
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
|
|
5
|
-
import { isInstanceOf } from '../../utils/type-guards.js';
|
|
4
|
+
import { shuffle } from '../../utils/array/index.js';
|
|
5
|
+
import { isInstanceOf, isNullOrUndefined } from '../../utils/type-guards.js';
|
|
6
|
+
const pluginKey = 'vertexai-multi-location';
|
|
6
7
|
const geminiModelReference = vertexAI.model('gemini-2.5-flash');
|
|
7
8
|
export function vertexAiMultiLocation(options) {
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
9
|
+
const createVirtualizedModelAction = async (ai, modelName) => {
|
|
10
|
+
const baseModelName = `vertexai/${modelName}`;
|
|
11
|
+
const target = modelName;
|
|
12
|
+
const baseModelAction = await ai.registry.lookupAction(`/model/${baseModelName}`);
|
|
13
|
+
if (isNullOrUndefined(baseModelAction)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return ai.defineModel({
|
|
17
|
+
name: target,
|
|
18
|
+
versions: baseModelAction.__action.metadata?.['model']?.versions,
|
|
19
|
+
supports: baseModelAction.__action.metadata?.['model']?.supports,
|
|
20
|
+
configSchema: baseModelAction.__action.inputSchema?.shape?.config,
|
|
21
|
+
label: `${baseModelAction.__action.description ?? baseModelAction.__action.name} (Multi-Location Routing)`,
|
|
22
|
+
}, async (request, streamingCallback) => {
|
|
23
|
+
const shuffledLocations = shuffle([...options.locations]);
|
|
24
|
+
let lastError;
|
|
25
|
+
for (const location of shuffledLocations) {
|
|
26
|
+
const circuitBreakerKey = `genkit:vertex-ai:location:${location}`;
|
|
27
|
+
const circuitBreaker = options.circuitBreakerProvider.provide(circuitBreakerKey, {
|
|
28
|
+
threshold: 1, // Aggressive for 429
|
|
29
|
+
resetTimeout: options.circuitBreakerConfig?.resetTimeout ?? 30000,
|
|
30
|
+
...options.circuitBreakerConfig,
|
|
31
|
+
});
|
|
32
|
+
const check = await circuitBreaker.check();
|
|
33
|
+
if (!check.allowed) {
|
|
34
|
+
options.logger.warn(`Location ${location} is currently unhealthy. Skipping...`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const result = await baseModelAction({
|
|
39
|
+
...request,
|
|
40
|
+
config: {
|
|
41
|
+
...request.config,
|
|
42
|
+
location: location,
|
|
43
|
+
},
|
|
44
|
+
}, {
|
|
45
|
+
onChunk: streamingCallback,
|
|
26
46
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
model: `vertexai/${modelName}`,
|
|
35
|
-
...request,
|
|
36
|
-
config: {
|
|
37
|
-
...request.config,
|
|
38
|
-
location: location,
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
await circuitBreaker.recordSuccess();
|
|
42
|
-
return result;
|
|
47
|
+
await circuitBreaker.recordSuccess();
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
lastError = error;
|
|
52
|
+
if (!isInstanceOf(error, GenkitError)) {
|
|
53
|
+
throw error;
|
|
43
54
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
throw error;
|
|
48
|
-
}
|
|
49
|
-
const isRetryable = ((error.status == 'RESOURCE_EXHAUSTED') || (error.status == 'UNAVAILABLE') || error.message.includes('quota'));
|
|
50
|
-
if (!isRetryable) {
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
options.logger.warn(`Location ${location} responded with ${error.status}. Tripping circuit breaker and trying next location...`);
|
|
54
|
-
await circuitBreaker.recordFailure();
|
|
55
|
+
const isRetryable = ((error.status == 'RESOURCE_EXHAUSTED') || (error.status == 'UNAVAILABLE') || error.message.includes('quota'));
|
|
56
|
+
if (!isRetryable) {
|
|
57
|
+
throw error;
|
|
55
58
|
}
|
|
59
|
+
options.logger.warn(`Location ${location} responded with ${error.status}. Tripping circuit breaker and trying next location...`);
|
|
60
|
+
await circuitBreaker.recordFailure();
|
|
56
61
|
}
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
}
|
|
63
|
+
throw lastError;
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
return genkitPlugin(pluginKey, async (_ai) => {
|
|
67
|
+
// Register nothing initially, rely on lazy resolution via resolver
|
|
68
|
+
}, async (ai, actionType, target) => {
|
|
69
|
+
if (actionType != 'model') {
|
|
70
|
+
return;
|
|
59
71
|
}
|
|
72
|
+
// Register the action immediately so the lookup succeeds
|
|
73
|
+
// We must register it directly in the registry since ai.defineModel is async and might be too late
|
|
74
|
+
(ai.registry).registerActionAsync('model', target, createVirtualizedModelAction(ai, target), { namespace: pluginKey });
|
|
60
75
|
});
|
|
61
76
|
}
|
|
62
77
|
vertexAiMultiLocation.model = function multiLocationModel(baseModel) {
|
|
63
78
|
return modelRef({
|
|
64
|
-
name:
|
|
79
|
+
name: `${pluginKey}/${baseModel.name.replace('vertexai/', '')}`,
|
|
65
80
|
configSchema: baseModel.configSchema,
|
|
66
81
|
info: baseModel.info,
|
|
67
82
|
version: baseModel.version,
|
|
@@ -33,7 +33,6 @@ describe('Genkit vertexai-multi-location Plugin Tests', () => {
|
|
|
33
33
|
ai = genkit({
|
|
34
34
|
plugins: [
|
|
35
35
|
vertexAiMultiLocation({
|
|
36
|
-
modelNames: 'gemini-2.5-flash',
|
|
37
36
|
locations: ['region-1', 'region-2'],
|
|
38
37
|
circuitBreakerProvider: cbProvider,
|
|
39
38
|
logger,
|
|
@@ -99,7 +98,6 @@ describe('Genkit vertexai-multi-location Plugin Tests', () => {
|
|
|
99
98
|
ai = genkit({
|
|
100
99
|
plugins: [
|
|
101
100
|
vertexAiMultiLocation({
|
|
102
|
-
modelNames: 'gemini-2.5-flash',
|
|
103
101
|
locations,
|
|
104
102
|
circuitBreakerProvider: cbProvider,
|
|
105
103
|
logger,
|
package/ai/genkit/types.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { CircuitBreakerConfig } from '../../circuit-breaker/circuit-breaker.js';
|
|
2
2
|
export interface VertexAiMultiLocationOptions {
|
|
3
|
-
/** The model name(s) to be virtualized (e.g., 'gemini-2.5-flash'). */
|
|
4
|
-
modelNames: string | string[];
|
|
5
3
|
/** The Google Cloud locations to use for routing. */
|
|
6
4
|
locations: string[];
|
|
7
5
|
/**
|
package/package.json
CHANGED
package/test1.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import './polyfills.js';
|
|
2
|
+
import { z } from 'genkit';
|
|
3
|
+
import { convertToGenkitSchema } from './ai/genkit/helpers.js';
|
|
2
4
|
import { configureGenkit, injectGenkit, injectMultiLocationModel } from './ai/genkit/module.js';
|
|
3
5
|
import { Application, provideInitializer, provideModule, provideSignalHandler } from './application/index.js';
|
|
4
6
|
import { configurePostgresCircuitBreaker, migratePostgresCircuitBreaker } from './circuit-breaker/postgres/module.js';
|
|
5
7
|
import { inject, Injector, runInInjectionContext } from './injector/index.js';
|
|
6
8
|
import { PrettyPrintLogFormatter, provideConsoleLogTransport } from './logger/index.js';
|
|
7
9
|
import { configureOrm } from './orm/server/index.js';
|
|
10
|
+
import { object, string } from './schema/index.js';
|
|
8
11
|
import { configurePostgresTaskQueue, migratePostgresTaskQueueSchema } from './task-queue/postgres/module.js';
|
|
9
|
-
import { createArray } from './utils/array/array.js';
|
|
10
12
|
import * as configParser from './utils/config-parser.js';
|
|
11
|
-
import { string } from './utils/config-parser.js';
|
|
12
13
|
import { assert } from './utils/type-guards.js';
|
|
13
14
|
const config = {
|
|
14
15
|
database: {
|
|
@@ -19,11 +20,11 @@ const config = {
|
|
|
19
20
|
database: configParser.string('DATABASE_NAME', 'tstdl'),
|
|
20
21
|
},
|
|
21
22
|
ai: {
|
|
22
|
-
keyFile: string('AI_API_KEY_FILE', undefined),
|
|
23
|
+
keyFile: configParser.string('AI_API_KEY_FILE', undefined),
|
|
23
24
|
vertex: {
|
|
24
|
-
project: string('AI_VERTEX_PROJECT', undefined),
|
|
25
|
-
location: string('AI_VERTEX_LOCATION', undefined),
|
|
26
|
-
bucket: string('AI_VERTEX_BUCKET', undefined),
|
|
25
|
+
project: configParser.string('AI_VERTEX_PROJECT', undefined),
|
|
26
|
+
location: configParser.string('AI_VERTEX_LOCATION', undefined),
|
|
27
|
+
bucket: configParser.string('AI_VERTEX_BUCKET', undefined),
|
|
27
28
|
},
|
|
28
29
|
},
|
|
29
30
|
};
|
|
@@ -37,18 +38,9 @@ async function bootstrap() {
|
|
|
37
38
|
// keyFile: '/home/patrick/.environments/insolytix-application-service-key.json',
|
|
38
39
|
},
|
|
39
40
|
multiLocationVertex: {
|
|
40
|
-
modelNames: ['gemini-2.5-flash'],
|
|
41
41
|
locations: [
|
|
42
42
|
'europe-west4',
|
|
43
43
|
'europe-west9',
|
|
44
|
-
// 'europe-west3',
|
|
45
|
-
// 'europe-west1',
|
|
46
|
-
// 'europe-southwest1',
|
|
47
|
-
// 'europe-west8',
|
|
48
|
-
// 'europe-north1',
|
|
49
|
-
// 'europe-central2',
|
|
50
|
-
// 'europe-west2', // UK - not EU
|
|
51
|
-
// 'europe-west6', // Switzerland - not EU
|
|
52
44
|
],
|
|
53
45
|
},
|
|
54
46
|
});
|
|
@@ -72,14 +64,51 @@ async function bootstrap() {
|
|
|
72
64
|
async function main(_cancellationSignal) {
|
|
73
65
|
const genkit = injectGenkit();
|
|
74
66
|
const model = injectMultiLocationModel('gemini-2.5-flash');
|
|
75
|
-
|
|
67
|
+
console.log('--- TEST 1: Simple Generation ---');
|
|
68
|
+
const response1 = await genkit.generate({
|
|
76
69
|
model,
|
|
77
|
-
prompt: '
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
70
|
+
prompt: 'Say hello in one word.',
|
|
71
|
+
});
|
|
72
|
+
console.log('Response:', response1.text);
|
|
73
|
+
console.log('\n--- TEST 2: Structured Output ---');
|
|
74
|
+
const response2 = await genkit.generate({
|
|
75
|
+
model,
|
|
76
|
+
prompt: 'Generate a greeting.',
|
|
77
|
+
output: {
|
|
78
|
+
schema: convertToGenkitSchema(object({
|
|
79
|
+
greeting: string(),
|
|
80
|
+
})),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
console.log('Response:', JSON.stringify(response2.output));
|
|
84
|
+
console.log('\n--- TEST 3: Streaming ---');
|
|
85
|
+
const { response: response3Promise, stream } = genkit.generateStream({
|
|
86
|
+
model,
|
|
87
|
+
prompt: 'Write a very short poem about the ocean (max 20 words).',
|
|
88
|
+
});
|
|
89
|
+
process.stdout.write('Stream: ');
|
|
90
|
+
for await (const chunk of stream) {
|
|
91
|
+
process.stdout.write(chunk.text);
|
|
82
92
|
}
|
|
93
|
+
process.stdout.write('\n');
|
|
94
|
+
const response3 = await response3Promise;
|
|
95
|
+
console.log('Final Response length:', response3.text.length);
|
|
96
|
+
console.log('\n--- TEST 4: Tools ---');
|
|
97
|
+
const getWeather = genkit.defineTool({
|
|
98
|
+
name: 'getWeather',
|
|
99
|
+
description: 'Gets the weather for a location.',
|
|
100
|
+
inputSchema: z.object({ location: z.string() }),
|
|
101
|
+
outputSchema: z.object({ weather: z.string() }),
|
|
102
|
+
}, async (input) => {
|
|
103
|
+
console.log(` [Tool] getWeather called for ${input.location}`);
|
|
104
|
+
return { weather: `Sunny in ${input.location}` };
|
|
105
|
+
});
|
|
106
|
+
const response4 = await genkit.generate({
|
|
107
|
+
model,
|
|
108
|
+
prompt: 'What is the weather in Berlin?',
|
|
109
|
+
tools: [getWeather],
|
|
110
|
+
});
|
|
111
|
+
console.log('Response:', response4.text);
|
|
83
112
|
}
|
|
84
113
|
Application.run('Test', [
|
|
85
114
|
provideInitializer(bootstrap),
|