@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.
@@ -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, toArray } from '../../utils/array/index.js';
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
- return genkitPlugin('vertexai-multi-location', (ai) => {
9
- const modelNames = toArray(options.modelNames);
10
- for (const modelName of modelNames) {
11
- ai.defineModel({
12
- name: `vertexai-multi-location/${modelName}`,
13
- versions: geminiModelReference.info?.versions,
14
- supports: geminiModelReference.info?.supports,
15
- configSchema: geminiModelReference.configSchema,
16
- label: `${geminiModelReference.info?.label} (Multi-Location Routing)`,
17
- }, async (request) => {
18
- const shuffledLocations = shuffle([...options.locations]);
19
- let lastError;
20
- for (const location of shuffledLocations) {
21
- const circuitBreakerKey = `genkit:vertex-ai:location:${location}`;
22
- const circuitBreaker = options.circuitBreakerProvider.provide(circuitBreakerKey, {
23
- threshold: 1, // Aggressive for 429
24
- resetTimeout: options.circuitBreakerConfig?.resetTimeout ?? 30000,
25
- ...options.circuitBreakerConfig,
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
- const check = await circuitBreaker.check();
28
- if (!check.allowed) {
29
- options.logger.warn(`Location ${location} is currently unhealthy. Skipping...`);
30
- continue;
31
- }
32
- try {
33
- const result = await ai.generate({
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
- catch (error) {
45
- lastError = error;
46
- if (!isInstanceOf(error, GenkitError)) {
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
- throw lastError;
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: `vertexai-multi-location/${baseModel.name.replace('vertexai/', '')}`,
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,
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.111",
3
+ "version": "0.93.112",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
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
- const promises = createArray(1000, async () => await genkit.generate({
67
+ console.log('--- TEST 1: Simple Generation ---');
68
+ const response1 = await genkit.generate({
76
69
  model,
77
- prompt: 'Hello, world!',
78
- }));
79
- const responses = await Promise.all(promises);
80
- for (const response of responses) {
81
- console.log(response.text);
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),