@tstdl/base 0.92.29 → 0.92.31

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.
@@ -7,6 +7,9 @@ export type SpecializedGenerationResult<T> = {
7
7
  result: T;
8
8
  raw: GenerationResult;
9
9
  };
10
+ export type SpecializedGenerationResultGenerator<T> = AsyncGenerator<T> & {
11
+ raw: Promise<GenerationResult>;
12
+ };
10
13
  export declare class AiServiceOptions {
11
14
  apiKey: string;
12
15
  defaultModel?: AiModel;
@@ -44,10 +47,14 @@ export declare class AiService implements Resolvable<AiServiceArgument> {
44
47
  }): Promise<SpecializedGenerationResult<AnalyzeContentResult<T>>>;
45
48
  getAnalyzeContentConents(parts: OneOrMany<ContentPart>): Content[];
46
49
  callFunctions<const T extends SchemaFunctionDeclarations>(options: CallFunctionsOptions<T>): Promise<SpecializedGenerationResult<SchemaFunctionDeclarationsResult<T>[]>>;
50
+ callFunctionsStream<const T extends SchemaFunctionDeclarations>(options: CallFunctionsOptions<T>): SpecializedGenerationResultGenerator<SchemaFunctionDeclarationsResult<T>>;
47
51
  generate(request: GenerationRequest): Promise<GenerationResult>;
52
+ generateStream(request: GenerationRequest): AsyncGenerator<GenerationResult>;
53
+ private _callFunctionsStream;
48
54
  private convertContents;
49
55
  private convertContent;
50
56
  private convertFunctions;
51
57
  private convertGoogleContent;
52
58
  private getModel;
53
59
  }
60
+ export declare function mergeGenerationStreamItems(items: GenerationResult[]): GenerationResult;
package/ai/ai.service.js CHANGED
@@ -8,13 +8,15 @@ import { FinishReason, FunctionCallingMode as GoogleFunctionCallingMode, GoogleG
8
8
  import { NotSupportedError } from '../errors/not-supported.error.js';
9
9
  import { Singleton } from '../injector/decorators.js';
10
10
  import { inject, injectArgument } from '../injector/inject.js';
11
+ import { DeferredPromise } from '../promise/deferred-promise.js';
12
+ import { LazyPromise } from '../promise/lazy-promise.js';
11
13
  import { convertToOpenApiSchema } from '../schema/converters/openapi-converter.js';
12
14
  import { array, enumeration, nullable, object, string } from '../schema/index.js';
13
15
  import { toArray } from '../utils/array/array.js';
14
16
  import { mapAsync } from '../utils/async-iterable-helpers/map.js';
15
17
  import { toArrayAsync } from '../utils/async-iterable-helpers/to-array.js';
16
18
  import { hasOwnProperty, objectEntries } from '../utils/object/object.js';
17
- import { assertDefinedPass, assertNotNullPass, isDefined } from '../utils/type-guards.js';
19
+ import { assertDefinedPass, assertNotNullPass, isDefined, isUndefined } from '../utils/type-guards.js';
18
20
  import { resolveValueOrAsyncProvider } from '../utils/value-or-provider.js';
19
21
  import { AiFileService } from './ai-file.service.js';
20
22
  import { AiSession } from './ai-session.js';
@@ -171,7 +173,20 @@ Always output the content and tags in ${options?.targetLanguage ?? 'the same lan
171
173
  raw: generation
172
174
  };
173
175
  }
176
+ callFunctionsStream(options) {
177
+ const itemsPromise = new DeferredPromise();
178
+ const generator = this._callFunctionsStream(options, itemsPromise);
179
+ generator.raw = new LazyPromise(async () => {
180
+ const items = await itemsPromise;
181
+ return mergeGenerationStreamItems(items);
182
+ });
183
+ return generator;
184
+ }
174
185
  async generate(request) {
186
+ const items = await toArrayAsync(this.generateStream(request));
187
+ return mergeGenerationStreamItems(items);
188
+ }
189
+ async *generateStream(request) {
175
190
  const googleFunctionDeclarations = isDefined(request.functions) ? await this.convertFunctions(request.functions) : undefined;
176
191
  const generationConfig = {
177
192
  maxOutputTokens: request.generationOptions?.maxOutputTokens,
@@ -183,15 +198,14 @@ Always output the content and tags in ${options?.targetLanguage ?? 'the same lan
183
198
  presencePenalty: request.generationOptions?.presencePenalty,
184
199
  frequencyPenalty: request.generationOptions?.frequencyPenalty
185
200
  };
186
- const googleContent = this.convertContents(request.contents);
187
- const generationContent = { role: 'model', parts: [] };
201
+ const inputContent = this.convertContents(request.contents);
188
202
  const maxTotalOutputTokens = request.generationOptions?.maxOutputTokens ?? 8192;
189
203
  let iterations = 0;
190
204
  let totalPromptTokens = 0;
191
205
  let totalOutputTokens = 0;
192
- let candidate;
206
+ let totalTokens = 0;
193
207
  while (totalOutputTokens < maxTotalOutputTokens) {
194
- const generation = await this.getModel(request.model ?? this.defaultModel).generateContent({
208
+ const generation = await this.getModel(request.model ?? this.defaultModel).generateContentStream({
195
209
  generationConfig: {
196
210
  ...generationConfig,
197
211
  maxOutputTokens: Math.min(8192, maxTotalOutputTokens - totalOutputTokens)
@@ -201,40 +215,80 @@ Always output the content and tags in ${options?.targetLanguage ?? 'the same lan
201
215
  toolConfig: isDefined(request.functionCallingMode)
202
216
  ? { functionCallingConfig: { mode: functionCallingModeMap[request.functionCallingMode] } }
203
217
  : undefined,
204
- contents: googleContent
218
+ contents: inputContent
205
219
  });
206
220
  iterations++;
207
- candidate = generation.response.candidates.at(0);
208
- // On first generation only
209
- if (generationContent.parts.length == 0) {
210
- googleContent.push(generationContent);
221
+ let lastUsageMetadata;
222
+ let candidate;
223
+ for await (const generationResponse of generation.stream) {
224
+ candidate = generationResponse.candidates.at(0);
225
+ inputContent.push(candidate.content);
226
+ const { promptTokenCount = 0, candidatesTokenCount = 0 } = generationResponse.usageMetadata ?? {};
227
+ lastUsageMetadata = generationResponse.usageMetadata;
228
+ const content = this.convertGoogleContent(candidate.content);
229
+ let text;
230
+ let functionCallParts;
231
+ const result = {
232
+ content,
233
+ get text() {
234
+ if (isUndefined(text)) {
235
+ const textParts = content.parts.filter((part) => hasOwnProperty(part, 'text')).map((part) => part.text);
236
+ text = (textParts.length > 0) ? textParts.join('') : null;
237
+ }
238
+ return text;
239
+ },
240
+ get functionCalls() {
241
+ if (isUndefined(functionCallParts)) {
242
+ functionCallParts = content.parts.filter((part) => hasOwnProperty(part, 'functionCall')).map((part) => part.functionCall);
243
+ }
244
+ return functionCallParts;
245
+ },
246
+ finishReason: candidate.finishReason == FinishReason.MAX_TOKENS
247
+ ? 'maxTokens'
248
+ : candidate.finishReason == FinishReason.STOP
249
+ ? 'stop'
250
+ : 'unknown',
251
+ usage: {
252
+ iterations,
253
+ prompt: totalPromptTokens + promptTokenCount,
254
+ output: totalOutputTokens + candidatesTokenCount,
255
+ total: totalTokens + promptTokenCount + candidatesTokenCount
256
+ }
257
+ };
258
+ yield result;
211
259
  }
212
- generationContent.parts.push(...candidate.content.parts);
213
- totalPromptTokens += generation.response.usageMetadata.promptTokenCount;
214
- totalOutputTokens += generation.response.usageMetadata.candidatesTokenCount;
215
- if (candidate.finishReason != FinishReason.MAX_TOKENS) {
260
+ totalPromptTokens += lastUsageMetadata?.promptTokenCount ?? 0;
261
+ totalOutputTokens += lastUsageMetadata?.candidatesTokenCount ?? 0;
262
+ totalTokens += lastUsageMetadata?.totalTokenCount ?? 0;
263
+ if (candidate?.finishReason != FinishReason.MAX_TOKENS) {
216
264
  break;
217
265
  }
218
266
  }
219
- const content = this.convertGoogleContent(generationContent);
220
- const textParts = content.parts.filter((part) => hasOwnProperty(part, 'text')).map((part) => part.text);
221
- const functionCallParts = content.parts.filter((part) => hasOwnProperty(part, 'functionCall')).map((part) => part.functionCall);
222
- return {
223
- content,
224
- text: textParts.length > 0 ? textParts.join('') : null,
225
- functionCalls: functionCallParts,
226
- finishReason: candidate.finishReason == FinishReason.MAX_TOKENS
227
- ? 'maxTokens'
228
- : candidate.finishReason == FinishReason.STOP
229
- ? 'stop'
230
- : 'unknown',
231
- usage: {
232
- iterations,
233
- prompt: totalPromptTokens,
234
- output: totalOutputTokens,
235
- total: totalPromptTokens + totalOutputTokens
267
+ }
268
+ async *_callFunctionsStream(options, itemsPromise) {
269
+ const generationStream = this.generateStream({
270
+ model: options.model,
271
+ generationOptions: {
272
+ temperature: 0.5,
273
+ ...options
274
+ },
275
+ systemInstruction: options.systemInstruction,
276
+ functions: options.functions,
277
+ functionCallingMode: 'force',
278
+ contents: options.contents
279
+ });
280
+ const items = [];
281
+ for await (const generation of generationStream) {
282
+ items.push(generation);
283
+ for (const call of generation.functionCalls) {
284
+ const fn = assertDefinedPass(options.functions[call.name], 'Function in response not declared.');
285
+ const parametersSchema = await resolveValueOrAsyncProvider(fn.parameters);
286
+ const parameters = parametersSchema.parse(call.parameters);
287
+ const handlerResult = isSchemaFunctionDeclarationWithHandler(fn) ? await fn.handler(parameters) : undefined;
288
+ yield { functionName: call.name, parameters: parameters, handlerResult: handlerResult };
236
289
  }
237
- };
290
+ }
291
+ itemsPromise.resolve(items);
238
292
  }
239
293
  convertContents(contents) {
240
294
  return toArray(contents).map((content) => this.convertContent(content));
@@ -306,3 +360,26 @@ AiService = __decorate([
306
360
  Singleton()
307
361
  ], AiService);
308
362
  export { AiService };
363
+ export function mergeGenerationStreamItems(items) {
364
+ const parts = items.flatMap((item) => item.content.parts);
365
+ let text;
366
+ let functionCallParts;
367
+ return {
368
+ content: { role: 'model', parts },
369
+ get text() {
370
+ if (isUndefined(text)) {
371
+ const textParts = parts.filter((part) => hasOwnProperty(part, 'text')).map((part) => part.text);
372
+ text = (textParts.length > 0) ? textParts.join('') : null;
373
+ }
374
+ return text;
375
+ },
376
+ get functionCalls() {
377
+ if (isUndefined(functionCallParts)) {
378
+ functionCallParts = parts.filter((part) => hasOwnProperty(part, 'functionCall')).map((part) => part.functionCall);
379
+ }
380
+ return functionCallParts;
381
+ },
382
+ finishReason: items.at(-1).finishReason,
383
+ usage: items.at(-1).usage
384
+ };
385
+ }
@@ -1,8 +1,5 @@
1
- import type { Tagged } from 'type-fest';
2
1
  import type { EnumerationObject } from '../types.js';
3
2
  export type EnumType<T extends EnumerationObject> = T[keyof T];
4
- export declare function defineEnum<const Name extends string, const T extends EnumerationObject>(name: Name, enumObject: T): {
5
- [P in keyof T]: Tagged<T[P], Name>;
6
- };
3
+ export declare function defineEnum<const T extends EnumerationObject>(name: string, enumObject: T): T;
7
4
  export declare function tryGetEnumName(enumeration: EnumerationObject): string | undefined;
8
5
  export declare function getEnumName(enumeration: EnumerationObject): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.92.29",
3
+ "version": "0.92.31",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -163,9 +163,9 @@
163
163
  "minio": "^8.0",
164
164
  "mjml": "^4.15",
165
165
  "mongodb": "^6.12",
166
- "nodemailer": "^6.9",
166
+ "nodemailer": "^6.10",
167
167
  "pg": "8.13",
168
- "playwright": "^1.49",
168
+ "playwright": "^1.50",
169
169
  "preact": "^10.25",
170
170
  "preact-render-to-string": "^6.5",
171
171
  "undici": "^7.3",
package/types.d.ts CHANGED
@@ -49,7 +49,7 @@ export type EnumerationArray = readonly [string | number, ...(string | number)[]
49
49
  export type EnumerationObject<V extends string | number = string | number> = Record<string, V>;
50
50
  export type EnumerationKey<T extends EnumerationObject = EnumerationObject> = Extract<keyof T, string>;
51
51
  export type EnumerationMap<T extends EnumerationObject = EnumerationObject> = SimplifyObject<{
52
- [P in EnumerationKey<T>]: (T[P] extends number ? (`${T[P]}` extends `${infer U extends number}` ? U : never) : `${T[P]}`) | T[P];
52
+ [P in EnumerationKey<T>]: (T[P] extends number ? (`${T[P]}` extends `${infer U extends number}` ? U : never) : T[P]) | T[P];
53
53
  }>;
54
54
  export type EnumerationValue<T extends Enumeration = Enumeration> = T extends EnumerationObject ? SimplifyDeep<EnumerationMap<T>[keyof EnumerationMap<T>]> : T extends EnumerationArray ? T[number] : never;
55
55
  export type EnumerationEntry<T extends EnumerationObject = EnumerationObject> = {