@tstdl/base 0.92.10 → 0.92.12
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/ai-file.service.d.ts +28 -0
- package/ai/ai-file.service.js +174 -0
- package/ai/ai-session.d.ts +9 -0
- package/ai/ai-session.js +18 -0
- package/ai/ai.service.d.ts +16 -39
- package/ai/ai.service.js +133 -361
- package/ai/functions.d.ts +1 -1
- package/ai/functions.js +1 -1
- package/ai/index.d.ts +4 -0
- package/ai/index.js +4 -0
- package/ai/types.d.ts +71 -14
- package/ai/types.js +7 -0
- package/api/response.d.ts +1 -1
- package/api/response.js +1 -1
- package/data-structures/weak-ref-map.d.ts +2 -2
- package/data-structures/weak-ref-map.js +3 -3
- package/document-management/api/document-management.api.d.ts +4 -4
- package/package.json +2 -2
- package/schema/schema.d.ts +1 -1
- package/schema/schemas/array.js +7 -1
- package/schema/schemas/one-or-many.d.ts +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import '../polyfills.js';
|
|
2
|
+
import { Resolvable, type resolveArgumentType } from '../injector/interfaces.js';
|
|
3
|
+
import { FileContentPart, FileInput, AiModel } from './types.js';
|
|
4
|
+
export type AiFileServiceOptions = {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
defaultModel?: AiModel;
|
|
7
|
+
};
|
|
8
|
+
export type AiFileServiceArgument = AiFileServiceOptions;
|
|
9
|
+
type File = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
uri: string;
|
|
13
|
+
mimeType: string;
|
|
14
|
+
};
|
|
15
|
+
export declare class AiFileService implements Resolvable<AiFileServiceArgument> {
|
|
16
|
+
#private;
|
|
17
|
+
readonly [resolveArgumentType]: AiFileServiceArgument;
|
|
18
|
+
processFile(fileInput: FileInput): Promise<FileContentPart>;
|
|
19
|
+
processFiles(fileInputs: FileInput[]): Promise<FileContentPart[]>;
|
|
20
|
+
getFileById(id: string): File | undefined;
|
|
21
|
+
getFileByUri(uri: string): File | undefined;
|
|
22
|
+
private getFile;
|
|
23
|
+
private getFiles;
|
|
24
|
+
private uploadFile;
|
|
25
|
+
private waitForFileActive;
|
|
26
|
+
private waitForFilesActive;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
8
|
+
if (value !== null && value !== void 0) {
|
|
9
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
10
|
+
var dispose, inner;
|
|
11
|
+
if (async) {
|
|
12
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
13
|
+
dispose = value[Symbol.asyncDispose];
|
|
14
|
+
}
|
|
15
|
+
if (dispose === void 0) {
|
|
16
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
17
|
+
dispose = value[Symbol.dispose];
|
|
18
|
+
if (async) inner = dispose;
|
|
19
|
+
}
|
|
20
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
21
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
22
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
23
|
+
}
|
|
24
|
+
else if (async) {
|
|
25
|
+
env.stack.push({ async: true });
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
29
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
30
|
+
return function (env) {
|
|
31
|
+
function fail(e) {
|
|
32
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
33
|
+
env.hasError = true;
|
|
34
|
+
}
|
|
35
|
+
var r, s = 0;
|
|
36
|
+
function next() {
|
|
37
|
+
while (r = env.stack.pop()) {
|
|
38
|
+
try {
|
|
39
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
40
|
+
if (r.dispose) {
|
|
41
|
+
var result = r.dispose.call(r.value);
|
|
42
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
43
|
+
}
|
|
44
|
+
else s |= 1;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
fail(e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
51
|
+
if (env.hasError) throw env.error;
|
|
52
|
+
}
|
|
53
|
+
return next();
|
|
54
|
+
};
|
|
55
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
56
|
+
var e = new Error(message);
|
|
57
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
58
|
+
});
|
|
59
|
+
import '../polyfills.js';
|
|
60
|
+
import { stat, unlink, writeFile } from 'node:fs/promises';
|
|
61
|
+
import { tmpdir } from 'node:os';
|
|
62
|
+
import { join } from 'node:path';
|
|
63
|
+
import { FileState, GoogleAIFileManager } from '@google/generative-ai/server';
|
|
64
|
+
import { AsyncEnumerable } from '../enumerable/async-enumerable.js';
|
|
65
|
+
import { DetailsError } from '../errors/details.error.js';
|
|
66
|
+
import { Singleton } from '../injector/decorators.js';
|
|
67
|
+
import { inject, injectArgument } from '../injector/inject.js';
|
|
68
|
+
import { Logger } from '../logger/logger.js';
|
|
69
|
+
import { createArray } from '../utils/array/array.js';
|
|
70
|
+
import { formatBytes } from '../utils/format.js';
|
|
71
|
+
import { timeout } from '../utils/timing.js';
|
|
72
|
+
import { tryIgnoreAsync } from '../utils/try-ignore.js';
|
|
73
|
+
import { isBlob } from '../utils/type-guards.js';
|
|
74
|
+
import { millisecondsPerSecond } from '../utils/units.js';
|
|
75
|
+
let AiFileService = class AiFileService {
|
|
76
|
+
#options = injectArgument(this);
|
|
77
|
+
#fileManager = new GoogleAIFileManager(this.#options.apiKey);
|
|
78
|
+
#fileMap = new Map();
|
|
79
|
+
#fileUriMap = new Map();
|
|
80
|
+
#logger = inject(Logger, 'AiFileService');
|
|
81
|
+
async processFile(fileInput) {
|
|
82
|
+
const file = await this.getFile(fileInput);
|
|
83
|
+
this.#fileMap.set(file.id, file);
|
|
84
|
+
this.#fileUriMap.set(file.uri, file);
|
|
85
|
+
return { file: file.id };
|
|
86
|
+
}
|
|
87
|
+
async processFiles(fileInputs) {
|
|
88
|
+
const files = await this.getFiles(fileInputs);
|
|
89
|
+
return files.map((file) => {
|
|
90
|
+
this.#fileMap.set(file.id, file);
|
|
91
|
+
this.#fileUriMap.set(file.uri, file);
|
|
92
|
+
return { file: file.id };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
getFileById(id) {
|
|
96
|
+
return this.#fileMap.get(id);
|
|
97
|
+
}
|
|
98
|
+
getFileByUri(uri) {
|
|
99
|
+
return this.#fileUriMap.get(uri);
|
|
100
|
+
}
|
|
101
|
+
async getFile(fileInput) {
|
|
102
|
+
const id = crypto.randomUUID();
|
|
103
|
+
const uploadResponse = await this.uploadFile(fileInput, id);
|
|
104
|
+
this.#logger.verbose(`Processing file "${id}"...`);
|
|
105
|
+
const response = await this.waitForFileActive(uploadResponse);
|
|
106
|
+
return {
|
|
107
|
+
id,
|
|
108
|
+
name: response.name,
|
|
109
|
+
uri: response.uri,
|
|
110
|
+
mimeType: response.mimeType
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async getFiles(files) {
|
|
114
|
+
const ids = createArray(files.length, () => crypto.randomUUID());
|
|
115
|
+
const uploadResponses = await AsyncEnumerable.from(files).parallelMap(5, true, async (file, index) => this.uploadFile(file, ids[index])).toArray();
|
|
116
|
+
this.#logger.verbose(`Processing ${files.length} files...`);
|
|
117
|
+
const responses = await this.waitForFilesActive(uploadResponses);
|
|
118
|
+
return responses.map((response, index) => ({
|
|
119
|
+
id: ids[index],
|
|
120
|
+
name: response.name,
|
|
121
|
+
uri: response.uri,
|
|
122
|
+
mimeType: response.mimeType
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
async uploadFile(fileInput, id) {
|
|
126
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
127
|
+
try {
|
|
128
|
+
const path = isBlob(fileInput) ? join(tmpdir(), crypto.randomUUID()) : fileInput.path;
|
|
129
|
+
const mimeType = isBlob(fileInput) ? fileInput.type : fileInput.mimeType;
|
|
130
|
+
const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
131
|
+
if (isBlob(fileInput)) {
|
|
132
|
+
this.#logger.verbose(`Preparing file "${id}"...`);
|
|
133
|
+
stack.defer(async () => tryIgnoreAsync(async () => unlink(path)));
|
|
134
|
+
await writeFile(path, fileInput.stream());
|
|
135
|
+
}
|
|
136
|
+
const fileSize = isBlob(fileInput) ? fileInput.size : (await stat(path)).size;
|
|
137
|
+
this.#logger.verbose(`Uploading file "${id}" (${formatBytes(fileSize)})...`);
|
|
138
|
+
const response = await this.#fileManager.uploadFile(path, { mimeType });
|
|
139
|
+
return response.file;
|
|
140
|
+
}
|
|
141
|
+
catch (e_1) {
|
|
142
|
+
env_1.error = e_1;
|
|
143
|
+
env_1.hasError = true;
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
const result_1 = __disposeResources(env_1);
|
|
147
|
+
if (result_1)
|
|
148
|
+
await result_1;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async waitForFileActive(fileMetadata) {
|
|
152
|
+
let file = await this.#fileManager.getFile(fileMetadata.name);
|
|
153
|
+
while (file.state == FileState.PROCESSING) {
|
|
154
|
+
await timeout(millisecondsPerSecond);
|
|
155
|
+
file = await this.#fileManager.getFile(fileMetadata.name);
|
|
156
|
+
}
|
|
157
|
+
if (file.state == FileState.FAILED) {
|
|
158
|
+
throw new DetailsError(file.error?.message ?? `Failed to process file ${file.name}`, file.error?.details);
|
|
159
|
+
}
|
|
160
|
+
return file;
|
|
161
|
+
}
|
|
162
|
+
async waitForFilesActive(files) {
|
|
163
|
+
const responses = [];
|
|
164
|
+
for (const file of files) {
|
|
165
|
+
const respones = await this.waitForFileActive(file);
|
|
166
|
+
responses.push(respones);
|
|
167
|
+
}
|
|
168
|
+
return responses;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
AiFileService = __decorate([
|
|
172
|
+
Singleton()
|
|
173
|
+
], AiFileService);
|
|
174
|
+
export { AiFileService };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TypedOmit } from '../types.js';
|
|
2
|
+
import type { AiService } from './ai.service.js';
|
|
3
|
+
import type { Content, GenerationRequest, GenerationResult } from './types.js';
|
|
4
|
+
export declare class AiSession {
|
|
5
|
+
#private;
|
|
6
|
+
readonly contents: Content[];
|
|
7
|
+
constructor(aiService: AiService);
|
|
8
|
+
generate(content: Content | [Content, ...Content[]], request?: TypedOmit<GenerationRequest, 'contents'>): Promise<GenerationResult>;
|
|
9
|
+
}
|
package/ai/ai-session.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { toArray } from '../utils/array/array.js';
|
|
2
|
+
export class AiSession {
|
|
3
|
+
#aiService;
|
|
4
|
+
contents = [];
|
|
5
|
+
constructor(aiService) {
|
|
6
|
+
this.#aiService = aiService;
|
|
7
|
+
}
|
|
8
|
+
async generate(content, request) {
|
|
9
|
+
const newContents = toArray(content);
|
|
10
|
+
const result = await this.#aiService.generate({
|
|
11
|
+
...request,
|
|
12
|
+
contents: [...this.contents, ...newContents]
|
|
13
|
+
});
|
|
14
|
+
this.contents.push(...newContents);
|
|
15
|
+
this.contents.push(result.content);
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/ai/ai.service.d.ts
CHANGED
|
@@ -1,53 +1,33 @@
|
|
|
1
1
|
import '../polyfills.js';
|
|
2
|
-
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
3
|
-
import { type FileMetadataResponse, GoogleAIFileManager } from '@google/generative-ai/server';
|
|
4
|
-
import { LiteralUnion } from 'type-fest';
|
|
5
2
|
import { Resolvable, type resolveArgumentType } from '../injector/interfaces.js';
|
|
6
3
|
import { OneOrMany, SchemaTestable } from '../schema/index.js';
|
|
7
4
|
import { Enumeration as EnumerationType, EnumerationValue } from '../types.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
export type
|
|
11
|
-
model?: GenerativeAIModel;
|
|
12
|
-
systemInstruction?: string;
|
|
13
|
-
maxOutputTokens?: number;
|
|
14
|
-
temperature?: number;
|
|
15
|
-
topP?: number;
|
|
16
|
-
topK?: number;
|
|
17
|
-
presencePenalty?: number;
|
|
18
|
-
frequencyPenalty?: number;
|
|
19
|
-
forceFunctionCall?: boolean;
|
|
20
|
-
};
|
|
21
|
-
export type GenerationResult<T> = {
|
|
5
|
+
import { AiSession } from './ai-session.js';
|
|
6
|
+
import { AiModel, Content, FileContentPart, FileInput, GenerationOptions, GenerationRequest, GenerationResult, GenerationUsage, SchemaFunctionDeclarations, SchemaFunctionDeclarationsResult } from './types.js';
|
|
7
|
+
export type SpecializedGenerationResult<T> = {
|
|
22
8
|
result: T;
|
|
23
|
-
usage
|
|
24
|
-
promptTokenCount: number;
|
|
25
|
-
candidatesTokenCount: number;
|
|
26
|
-
totalTokenCount: number;
|
|
27
|
-
};
|
|
9
|
+
usage: GenerationUsage;
|
|
28
10
|
};
|
|
29
11
|
export type AiServiceOptions = {
|
|
30
12
|
apiKey: string;
|
|
31
|
-
defaultModel?:
|
|
13
|
+
defaultModel?: AiModel;
|
|
32
14
|
};
|
|
33
15
|
export type AiServiceArgument = AiServiceOptions;
|
|
34
16
|
export declare class AiService implements Resolvable<AiServiceArgument> {
|
|
35
17
|
#private;
|
|
36
|
-
readonly
|
|
37
|
-
readonly fileManager: GoogleAIFileManager;
|
|
38
|
-
readonly defaultModel: GenerativeAIModel;
|
|
18
|
+
readonly defaultModel: AiModel;
|
|
39
19
|
readonly [resolveArgumentType]: AiServiceArgument;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
classify<T extends EnumerationType>(
|
|
20
|
+
newSession(): AiSession;
|
|
21
|
+
processFile(fileInput: FileInput): Promise<FileContentPart>;
|
|
22
|
+
processFiles(fileInputs: FileInput[]): Promise<FileContentPart[]>;
|
|
23
|
+
classify<T extends EnumerationType>(files: OneOrMany<FileContentPart>, types: T, model?: AiModel): Promise<SpecializedGenerationResult<{
|
|
44
24
|
types: {
|
|
45
25
|
type: EnumerationValue<T>;
|
|
46
26
|
confidence: 'high' | 'medium' | 'low';
|
|
47
27
|
}[] | null;
|
|
48
28
|
}>>;
|
|
49
|
-
extractData<T>(
|
|
50
|
-
analyzeDocument<T extends EnumerationType>(
|
|
29
|
+
extractData<T>(files: OneOrMany<FileContentPart>, schema: SchemaTestable<T>, options?: GenerationOptions): Promise<SpecializedGenerationResult<T>>;
|
|
30
|
+
analyzeDocument<T extends EnumerationType>(file: FileContentPart, types: T, options?: GenerationOptions): Promise<SpecializedGenerationResult<{
|
|
51
31
|
content: string[];
|
|
52
32
|
documentTypes: {
|
|
53
33
|
type: EnumerationValue<T>;
|
|
@@ -55,14 +35,11 @@ export declare class AiService implements Resolvable<AiServiceArgument> {
|
|
|
55
35
|
}[];
|
|
56
36
|
tags: string[];
|
|
57
37
|
}>>;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
functions: any;
|
|
61
|
-
}>>;
|
|
62
|
-
callFunctions<const T extends SchemaFunctionDeclarations>(functions: T, contents: Content[], options?: GenerationOptions): Promise<void>;
|
|
63
|
-
private waitForFileActive;
|
|
64
|
-
private waitForFilesActive;
|
|
38
|
+
callFunctions<const T extends SchemaFunctionDeclarations>(functions: T, contents: Content[], options?: Pick<GenerationRequest, 'model' | 'systemInstruction'> & GenerationOptions): Promise<SchemaFunctionDeclarationsResult<T>[]>;
|
|
39
|
+
generate(request: GenerationRequest): Promise<GenerationResult>;
|
|
65
40
|
private convertContents;
|
|
66
41
|
private convertContent;
|
|
42
|
+
private convertFunctions;
|
|
43
|
+
private convertGoogleContent;
|
|
67
44
|
private getModel;
|
|
68
45
|
}
|
package/ai/ai.service.js
CHANGED
|
@@ -4,241 +4,69 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
8
|
-
if (value !== null && value !== void 0) {
|
|
9
|
-
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
10
|
-
var dispose, inner;
|
|
11
|
-
if (async) {
|
|
12
|
-
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
13
|
-
dispose = value[Symbol.asyncDispose];
|
|
14
|
-
}
|
|
15
|
-
if (dispose === void 0) {
|
|
16
|
-
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
17
|
-
dispose = value[Symbol.dispose];
|
|
18
|
-
if (async) inner = dispose;
|
|
19
|
-
}
|
|
20
|
-
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
21
|
-
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
22
|
-
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
23
|
-
}
|
|
24
|
-
else if (async) {
|
|
25
|
-
env.stack.push({ async: true });
|
|
26
|
-
}
|
|
27
|
-
return value;
|
|
28
|
-
};
|
|
29
|
-
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
30
|
-
return function (env) {
|
|
31
|
-
function fail(e) {
|
|
32
|
-
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
33
|
-
env.hasError = true;
|
|
34
|
-
}
|
|
35
|
-
var r, s = 0;
|
|
36
|
-
function next() {
|
|
37
|
-
while (r = env.stack.pop()) {
|
|
38
|
-
try {
|
|
39
|
-
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
40
|
-
if (r.dispose) {
|
|
41
|
-
var result = r.dispose.call(r.value);
|
|
42
|
-
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
43
|
-
}
|
|
44
|
-
else s |= 1;
|
|
45
|
-
}
|
|
46
|
-
catch (e) {
|
|
47
|
-
fail(e);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
51
|
-
if (env.hasError) throw env.error;
|
|
52
|
-
}
|
|
53
|
-
return next();
|
|
54
|
-
};
|
|
55
|
-
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
56
|
-
var e = new Error(message);
|
|
57
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
58
|
-
});
|
|
59
7
|
import '../polyfills.js';
|
|
60
|
-
import {
|
|
61
|
-
import { unlink, writeFile } from 'node:fs/promises';
|
|
62
|
-
import { tmpdir } from 'node:os';
|
|
63
|
-
import { join } from 'node:path';
|
|
64
|
-
import { inspect } from 'node:util';
|
|
65
|
-
import { FunctionCallingMode, GoogleGenerativeAI, SchemaType } from '@google/generative-ai';
|
|
66
|
-
import { FileState, GoogleAIFileManager } from '@google/generative-ai/server';
|
|
67
|
-
import { AsyncEnumerable } from '../enumerable/async-enumerable.js';
|
|
68
|
-
import { DetailsError } from '../errors/details.error.js';
|
|
8
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
69
9
|
import { NotSupportedError } from '../errors/not-supported.error.js';
|
|
70
10
|
import { Singleton } from '../injector/decorators.js';
|
|
71
11
|
import { inject, injectArgument } from '../injector/inject.js';
|
|
72
|
-
import { Logger } from '../logger/logger.js';
|
|
73
12
|
import { convertToOpenApiSchema } from '../schema/converters/openapi-converter.js';
|
|
74
|
-
import { array, enumeration, nullable, object,
|
|
13
|
+
import { array, enumeration, nullable, object, string } from '../schema/index.js';
|
|
75
14
|
import { toArray } from '../utils/array/array.js';
|
|
76
|
-
import { digest } from '../utils/cryptography.js';
|
|
77
|
-
import { formatBytes } from '../utils/format.js';
|
|
78
15
|
import { hasOwnProperty, objectEntries } from '../utils/object/object.js';
|
|
79
|
-
import {
|
|
80
|
-
import {
|
|
81
|
-
import {
|
|
82
|
-
import {
|
|
16
|
+
import { assertDefinedPass, assertNotNullPass, isDefined } from '../utils/type-guards.js';
|
|
17
|
+
import { AiFileService } from './ai-file.service.js';
|
|
18
|
+
import { AiSession } from './ai-session.js';
|
|
19
|
+
import { isSchemaFunctionDeclarationWithHandler } from './types.js';
|
|
83
20
|
let AiService = class AiService {
|
|
84
21
|
#options = injectArgument(this);
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
genAI = new GoogleGenerativeAI(this.#options.apiKey);
|
|
88
|
-
fileManager = new GoogleAIFileManager(this.#options.apiKey);
|
|
22
|
+
#fileService = inject(AiFileService, this.#options);
|
|
23
|
+
#genAI = new GoogleGenerativeAI(this.#options.apiKey);
|
|
89
24
|
defaultModel = this.#options.defaultModel ?? 'gemini-2.0-flash-exp';
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const mimeType = isBlob(fileInput) ? fileInput.type : fileInput.mimeType;
|
|
93
|
-
const blob = isBlob(fileInput) ? fileInput : await openAsBlob(path, { type: mimeType });
|
|
94
|
-
const buffer = await blob.arrayBuffer();
|
|
95
|
-
const byteArray = new Uint8Array(buffer);
|
|
96
|
-
const fileHash = await digest('SHA-1', byteArray).toBase64();
|
|
97
|
-
const fileKey = `${fileHash}:${byteArray.length}`;
|
|
98
|
-
if (this.#fileCache.has(fileKey)) {
|
|
99
|
-
try {
|
|
100
|
-
this.#logger.verbose(`Fetching file "${fileHash}" from cache...`);
|
|
101
|
-
const cachedFile = await this.#fileCache.get(fileKey);
|
|
102
|
-
return await this.fileManager.getFile(cachedFile.name);
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
this.#fileCache.delete(fileKey);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const filePromise = (async () => {
|
|
109
|
-
try {
|
|
110
|
-
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
111
|
-
try {
|
|
112
|
-
const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
113
|
-
if (isBlob(fileInput)) {
|
|
114
|
-
stack.defer(async () => tryIgnoreAsync(async () => unlink(path)));
|
|
115
|
-
await writeFile(path, byteArray);
|
|
116
|
-
}
|
|
117
|
-
this.#logger.verbose(`Uploading file "${fileHash}" (${formatBytes(byteArray.length)})...`);
|
|
118
|
-
const result = await this.fileManager.uploadFile(path, { mimeType });
|
|
119
|
-
this.#logger.verbose(`Processing file "${fileHash}"...`);
|
|
120
|
-
return await this.waitForFileActive(result.file);
|
|
121
|
-
}
|
|
122
|
-
catch (e_1) {
|
|
123
|
-
env_1.error = e_1;
|
|
124
|
-
env_1.hasError = true;
|
|
125
|
-
}
|
|
126
|
-
finally {
|
|
127
|
-
const result_1 = __disposeResources(env_1);
|
|
128
|
-
if (result_1)
|
|
129
|
-
await result_1;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
this.#fileCache.delete(fileKey);
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
})();
|
|
137
|
-
this.#fileCache.set(fileKey, filePromise);
|
|
138
|
-
return filePromise;
|
|
25
|
+
newSession() {
|
|
26
|
+
return new AiSession(this);
|
|
139
27
|
}
|
|
140
|
-
async
|
|
141
|
-
return
|
|
28
|
+
async processFile(fileInput) {
|
|
29
|
+
return this.#fileService.processFile(fileInput);
|
|
142
30
|
}
|
|
143
|
-
async
|
|
144
|
-
|
|
145
|
-
generationConfig: {
|
|
146
|
-
maxOutputTokens: 4096
|
|
147
|
-
},
|
|
148
|
-
tools: [{
|
|
149
|
-
functionDeclarations: [
|
|
150
|
-
{
|
|
151
|
-
name: 'outputNumber',
|
|
152
|
-
description: 'outputs a number',
|
|
153
|
-
parameters: {
|
|
154
|
-
type: SchemaType.OBJECT,
|
|
155
|
-
properties: {
|
|
156
|
-
number: { type: SchemaType.INTEGER }
|
|
157
|
-
},
|
|
158
|
-
required: ['number']
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
],
|
|
162
|
-
}],
|
|
163
|
-
toolConfig: {
|
|
164
|
-
functionCallingConfig: {
|
|
165
|
-
mode: FunctionCallingMode.ANY
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
contents: [
|
|
169
|
-
{ role: 'user', parts: [{ text: 'Output 5 increasing numbers starting at 10' }] },
|
|
170
|
-
{
|
|
171
|
-
role: 'model',
|
|
172
|
-
parts: [
|
|
173
|
-
{ functionCall: { name: 'outputNumber', args: { number: 10 } } },
|
|
174
|
-
{ functionCall: { name: 'outputNumber', args: { number: 11 } } },
|
|
175
|
-
{ functionCall: { name: 'outputNumber', args: { number: 12 } } },
|
|
176
|
-
{ functionCall: { name: 'outputNumber', args: { number: 13 } } },
|
|
177
|
-
{ functionCall: { name: 'outputNumber', args: { number: 14 } } }
|
|
178
|
-
]
|
|
179
|
-
},
|
|
180
|
-
/*{
|
|
181
|
-
role: 'user',
|
|
182
|
-
parts: [
|
|
183
|
-
{ functionResponse: { name: 'outputNumber', response: { result: 'outputted number 10' } } },
|
|
184
|
-
{ functionResponse: { name: 'outputNumber', response: { result: 'outputted number 11' } } },
|
|
185
|
-
{ functionResponse: { name: 'outputNumber', response: { result: 'outputted number 12' } } },
|
|
186
|
-
{ functionResponse: { name: 'outputNumber', response: { result: 'outputted number 13' } } },
|
|
187
|
-
{ functionResponse: { name: 'outputNumber', response: { result: 'outputted number 14' } } }
|
|
188
|
-
]
|
|
189
|
-
},*/
|
|
190
|
-
// { role: 'model', parts: [{ text: 'Okay, I\'ve outputted the numbers 10, 11, 12, 13, and 14.' }] },
|
|
191
|
-
{ role: 'user', parts: [{ text: 'Give me 5 more numbers. ONLY call functions, dont output text' }] }
|
|
192
|
-
]
|
|
193
|
-
});
|
|
194
|
-
console.log(inspect(result.response, { depth: null }));
|
|
31
|
+
async processFiles(fileInputs) {
|
|
32
|
+
return this.#fileService.processFiles(fileInputs);
|
|
195
33
|
}
|
|
196
|
-
async classify(
|
|
197
|
-
const
|
|
198
|
-
const resultSchema = object({
|
|
34
|
+
async classify(files, types, model) {
|
|
35
|
+
const generationSchema = object({
|
|
199
36
|
types: nullable(array(object({
|
|
200
37
|
type: enumeration(types, { description: 'Type of document' }),
|
|
201
38
|
confidence: enumeration(['high', 'medium', 'low'], { description: 'How sure/certain you are about the classficiation.' })
|
|
202
39
|
}), { description: 'One or more document types that matches' }))
|
|
203
40
|
});
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
temperature: 0.75,
|
|
210
|
-
...options,
|
|
211
|
-
responseMimeType: 'application/json',
|
|
212
|
-
responseSchema
|
|
41
|
+
const result = await this.generate({
|
|
42
|
+
model,
|
|
43
|
+
generationOptions: {
|
|
44
|
+
maxOutputTokens: 1048,
|
|
45
|
+
temperature: 0.5
|
|
213
46
|
},
|
|
47
|
+
generationSchema,
|
|
214
48
|
systemInstruction: 'You are a highly accurate document classification AI. Your task is to analyze the content of a given document and determine its type based on a predefined list of possible document types.',
|
|
215
|
-
contents: [
|
|
216
|
-
{
|
|
49
|
+
contents: [{
|
|
217
50
|
role: 'user',
|
|
218
51
|
parts: [
|
|
219
|
-
...files
|
|
220
|
-
{ text:
|
|
52
|
+
...toArray(files),
|
|
53
|
+
{ text: 'Classify the document. Output as JSON using the provided schema\n\nIf none of the provided document types are a suitable match, return null for types.' }
|
|
221
54
|
]
|
|
222
|
-
}
|
|
223
|
-
]
|
|
55
|
+
}]
|
|
224
56
|
});
|
|
225
57
|
return {
|
|
226
|
-
usage: result.
|
|
227
|
-
result:
|
|
58
|
+
usage: result.usage,
|
|
59
|
+
result: JSON.parse(assertNotNullPass(result.text, 'No text returned.'))
|
|
228
60
|
};
|
|
229
61
|
}
|
|
230
|
-
async extractData(
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
this.#logger.verbose('Extracting data...');
|
|
234
|
-
const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
|
|
235
|
-
generationConfig: {
|
|
62
|
+
async extractData(files, schema, options) {
|
|
63
|
+
const result = await this.generate({
|
|
64
|
+
generationOptions: {
|
|
236
65
|
maxOutputTokens: 8192,
|
|
237
|
-
temperature: 0.
|
|
238
|
-
...options
|
|
239
|
-
responseMimeType: 'application/json',
|
|
240
|
-
responseSchema
|
|
66
|
+
temperature: 0.5,
|
|
67
|
+
...options
|
|
241
68
|
},
|
|
69
|
+
generationSchema: schema,
|
|
242
70
|
systemInstruction: `You are a highly skilled data extraction AI, specializing in accurately identifying and extracting information from unstructured text documents and converting it into a structured JSON format. Your primary goal is to meticulously follow the provided JSON schema and populate it with data extracted from the given document.
|
|
243
71
|
|
|
244
72
|
**Instructions:**
|
|
@@ -247,37 +75,33 @@ Carefully read and analyze the provided document. Identify relevant information
|
|
|
247
75
|
{
|
|
248
76
|
role: 'user',
|
|
249
77
|
parts: [
|
|
250
|
-
...files
|
|
251
|
-
{ text:
|
|
78
|
+
...toArray(files),
|
|
79
|
+
{ text: 'Classify the document. Output as JSON.' }
|
|
252
80
|
]
|
|
253
81
|
}
|
|
254
82
|
]
|
|
255
83
|
});
|
|
256
84
|
return {
|
|
257
|
-
usage: result.
|
|
258
|
-
result:
|
|
85
|
+
usage: result.usage,
|
|
86
|
+
result: JSON.parse(assertNotNullPass(result.text, 'No text returned.'))
|
|
259
87
|
};
|
|
260
88
|
}
|
|
261
|
-
async analyzeDocument(
|
|
262
|
-
const file = await this.getFile(fileInput);
|
|
89
|
+
async analyzeDocument(file, types, options) {
|
|
263
90
|
const schema = object({
|
|
264
|
-
content:
|
|
91
|
+
content: string({ description: 'Content of the document with important details only' }),
|
|
265
92
|
documentTypes: array(object({
|
|
266
93
|
type: enumeration(types, { description: 'Type of document' }),
|
|
267
|
-
confidence: enumeration(['high', 'medium', 'low'], { description: 'How sure/certain you are about the classficiation
|
|
94
|
+
confidence: enumeration(['high', 'medium', 'low'], { description: 'How sure/certain you are about the classficiation' })
|
|
268
95
|
}), { description: 'One or more document types that matches' }),
|
|
269
|
-
tags: array(string({ description: 'Tag which describes the content' }), { description: 'List of tags' })
|
|
96
|
+
tags: array(string({ description: 'Tag which describes the content' }), { description: 'List of tags', maximum: 5 })
|
|
270
97
|
});
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
|
|
274
|
-
generationConfig: {
|
|
98
|
+
const result = await this.generate({
|
|
99
|
+
generationOptions: {
|
|
275
100
|
maxOutputTokens: 2048,
|
|
276
|
-
temperature: 0.
|
|
277
|
-
...options
|
|
278
|
-
responseMimeType: 'application/json',
|
|
279
|
-
responseSchema
|
|
101
|
+
temperature: 0.5,
|
|
102
|
+
...options
|
|
280
103
|
},
|
|
104
|
+
generationSchema: schema,
|
|
281
105
|
systemInstruction: `You are a highly skilled data extraction AI, specializing in accurately identifying and extracting information from unstructured text documents and converting it into a structured JSON format. Your primary goal is to meticulously follow the provided JSON schema and populate it with data extracted from the given document.
|
|
282
106
|
|
|
283
107
|
**Instructions:**
|
|
@@ -291,175 +115,123 @@ Always output the content and tags in Deutsch.`,
|
|
|
291
115
|
{
|
|
292
116
|
role: 'user',
|
|
293
117
|
parts: [
|
|
294
|
-
|
|
295
|
-
{ text:
|
|
296
|
-
]
|
|
297
|
-
}
|
|
298
|
-
]
|
|
299
|
-
});
|
|
300
|
-
return {
|
|
301
|
-
usage: result.response.usageMetadata,
|
|
302
|
-
result: Schema.parse(schema, JSON.parse(result.response.text()))
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
async findActions(fileInput, options) {
|
|
306
|
-
const files = await this.getFiles(toArray(fileInput));
|
|
307
|
-
this.#logger.verbose('Extracting data...');
|
|
308
|
-
const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
|
|
309
|
-
generationConfig: {
|
|
310
|
-
maxOutputTokens: 4096,
|
|
311
|
-
temperature: 0.75,
|
|
312
|
-
...options
|
|
313
|
-
},
|
|
314
|
-
tools: [
|
|
315
|
-
{
|
|
316
|
-
functionDeclarations: [
|
|
317
|
-
{
|
|
318
|
-
name: 'createNewRealEstate',
|
|
319
|
-
description: 'creates a new real estate',
|
|
320
|
-
parameters: {
|
|
321
|
-
type: SchemaType.OBJECT,
|
|
322
|
-
properties: {
|
|
323
|
-
address: { type: SchemaType.STRING },
|
|
324
|
-
realEstateNumber: { type: SchemaType.STRING }
|
|
325
|
-
},
|
|
326
|
-
required: ['address', 'realEstateNumber']
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
name: 'createNewUnit',
|
|
331
|
-
description: 'creates a new unit in a real estate. Make sure to get the correct unit number, each unit has to get its own',
|
|
332
|
-
parameters: {
|
|
333
|
-
type: SchemaType.OBJECT,
|
|
334
|
-
description: 'data of the unit from documents',
|
|
335
|
-
properties: {
|
|
336
|
-
unitNumber: { type: SchemaType.STRING },
|
|
337
|
-
owner: { type: SchemaType.STRING },
|
|
338
|
-
dweller: { type: SchemaType.STRING }
|
|
339
|
-
},
|
|
340
|
-
required: ['unitNumber', 'owner', 'dweller']
|
|
341
|
-
}
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
name: 'createNewDevice',
|
|
345
|
-
description: 'creates a new device in a real estate / unit, for example HKV or WWZ',
|
|
346
|
-
parameters: {
|
|
347
|
-
type: SchemaType.OBJECT,
|
|
348
|
-
description: 'data of the device from documents like Ablesebeleg.',
|
|
349
|
-
properties: {
|
|
350
|
-
unitNumber: { type: SchemaType.STRING },
|
|
351
|
-
deviceNumber: { type: SchemaType.STRING },
|
|
352
|
-
factor: { type: SchemaType.NUMBER },
|
|
353
|
-
deviceType: { type: SchemaType.STRING },
|
|
354
|
-
roomType: { type: SchemaType.STRING }
|
|
355
|
-
},
|
|
356
|
-
required: ['unitNumber', 'deviceNumber', 'factor', 'deviceType', 'roomType']
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
]
|
|
360
|
-
}
|
|
361
|
-
],
|
|
362
|
-
toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.ANY } },
|
|
363
|
-
systemInstruction: `You are a specialized AI assistant designed to extract structured data from various document types related to real estate and device management. Your goal is to understand the document's content and identify key information to populate specific function calls. You will be provided with document text and a set of predefined functions.
|
|
364
|
-
|
|
365
|
-
**Your task is:**
|
|
366
|
-
|
|
367
|
-
1. **Analyze the document:** Carefully read the provided document text.
|
|
368
|
-
2. **Identify relevant information:** Extract the key data points related to real estate (e.g., address, size, type) and devices (e.g., type, model, serial number, location).
|
|
369
|
-
3. **Determine the appropriate function:** Based on the extracted information, select the most suitable function from the list below to perform the intended action.
|
|
370
|
-
4. **Generate function call:** Output the selected function name along with its arguments, populated with the extracted data. Use JSON format for the output.`,
|
|
371
|
-
contents: [
|
|
372
|
-
{
|
|
373
|
-
role: 'user',
|
|
374
|
-
parts: [
|
|
375
|
-
...files.map((file) => ({ fileData: { mimeType: file.mimeType, fileUri: file.uri } })),
|
|
376
|
-
{ text: 'Extract the data from the document and call suitable functions.' }
|
|
118
|
+
file,
|
|
119
|
+
{ text: 'Classify the document. Output as JSON.' }
|
|
377
120
|
]
|
|
378
121
|
}
|
|
379
122
|
]
|
|
380
123
|
});
|
|
381
124
|
return {
|
|
382
|
-
usage: result.
|
|
383
|
-
result:
|
|
384
|
-
functions: result.response.functionCalls(),
|
|
385
|
-
text: result.response.text()
|
|
386
|
-
}
|
|
125
|
+
usage: result.usage,
|
|
126
|
+
result: JSON.parse(assertNotNullPass(result.text, 'No text returned.'))
|
|
387
127
|
};
|
|
388
128
|
}
|
|
389
129
|
async callFunctions(functions, contents, options) {
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
description: declaration.description,
|
|
394
|
-
parameters: convertToOpenApiSchema(declaration.parameters)
|
|
395
|
-
}));
|
|
396
|
-
const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
|
|
397
|
-
generationConfig: {
|
|
130
|
+
const generation = await this.generate({
|
|
131
|
+
model: options?.model,
|
|
132
|
+
generationOptions: {
|
|
398
133
|
maxOutputTokens: 4096,
|
|
399
|
-
temperature: 0.
|
|
134
|
+
temperature: 0.5,
|
|
400
135
|
...options
|
|
401
136
|
},
|
|
402
137
|
systemInstruction: options?.systemInstruction,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
mode: (options?.forceFunctionCall == false) ? FunctionCallingMode.AUTO : FunctionCallingMode.ANY
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
contents: googleContent
|
|
138
|
+
functions,
|
|
139
|
+
functionCallingMode: 'force',
|
|
140
|
+
contents
|
|
410
141
|
});
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
const
|
|
142
|
+
const result = [];
|
|
143
|
+
for (const call of generation.functionCalls) {
|
|
144
|
+
const fn = assertDefinedPass(functions[call.name], 'Function in response not declared.');
|
|
145
|
+
const parameters = fn.parameters.parse(call.parameters);
|
|
146
|
+
const handlerResult = isSchemaFunctionDeclarationWithHandler(fn) ? await fn.handler(parameters) : undefined;
|
|
147
|
+
result.push({ functionName: call.name, parameters: parameters, handlerResult: handlerResult });
|
|
415
148
|
}
|
|
149
|
+
return result;
|
|
416
150
|
}
|
|
417
|
-
async
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
151
|
+
async generate(request) {
|
|
152
|
+
const googleContent = this.convertContents(request.contents);
|
|
153
|
+
const googleFunctionDeclarations = isDefined(request.functions) ? this.convertFunctions(request.functions) : undefined;
|
|
154
|
+
const generationConfig = isDefined(request.generationSchema)
|
|
155
|
+
? { ...request.generationOptions, responseMimeType: 'application/json', responseSchema: convertToOpenApiSchema(request.generationSchema) }
|
|
156
|
+
: request.generationOptions;
|
|
157
|
+
const generation = await this.getModel(request.model ?? this.defaultModel).generateContent({
|
|
158
|
+
generationConfig,
|
|
159
|
+
systemInstruction: request.systemInstruction,
|
|
160
|
+
tools: isDefined(googleFunctionDeclarations) ? [{ functionDeclarations: googleFunctionDeclarations }] : undefined,
|
|
161
|
+
toolConfig: isDefined(request.functionCallingMode)
|
|
162
|
+
? { functionCallingConfig: { mode: (request.functionCallingMode ?? 'auto').toUpperCase() } }
|
|
163
|
+
: undefined,
|
|
164
|
+
contents: googleContent
|
|
165
|
+
});
|
|
166
|
+
const content = this.convertGoogleContent(generation.response.candidates.at(0).content);
|
|
167
|
+
const textParts = content.parts.filter((part) => hasOwnProperty(part, 'text')).map((part) => part.text);
|
|
168
|
+
const functionCallParts = content.parts.filter((part) => hasOwnProperty(part, 'functionCall')).map((part) => part.functionCall);
|
|
169
|
+
return {
|
|
170
|
+
content,
|
|
171
|
+
text: textParts.length > 0 ? textParts.join('') : null,
|
|
172
|
+
functionCalls: functionCallParts,
|
|
173
|
+
usage: {
|
|
174
|
+
prompt: generation.response.usageMetadata.promptTokenCount,
|
|
175
|
+
output: generation.response.usageMetadata.candidatesTokenCount,
|
|
176
|
+
total: generation.response.usageMetadata.totalTokenCount
|
|
177
|
+
}
|
|
178
|
+
};
|
|
435
179
|
}
|
|
436
|
-
|
|
437
|
-
return
|
|
438
|
-
.map(async (content) => this.convertContent(content))
|
|
439
|
-
.toArray();
|
|
180
|
+
convertContents(contents) {
|
|
181
|
+
return contents.map((content) => this.convertContent(content));
|
|
440
182
|
}
|
|
441
|
-
|
|
183
|
+
convertContent(content) {
|
|
442
184
|
return {
|
|
443
185
|
role: content.role,
|
|
444
|
-
parts:
|
|
445
|
-
.map(async (part) => {
|
|
186
|
+
parts: content.parts.map((part) => {
|
|
446
187
|
if (hasOwnProperty(part, 'text')) {
|
|
447
188
|
return { text: part.text };
|
|
448
189
|
}
|
|
449
190
|
if (hasOwnProperty(part, 'file')) {
|
|
450
|
-
const
|
|
451
|
-
return { fileData: { fileUri:
|
|
191
|
+
const file = assertDefinedPass(this.#fileService.getFileById(part.file), `File ${part.file} not found.`);
|
|
192
|
+
return { fileData: { fileUri: file.uri, mimeType: file.mimeType } };
|
|
452
193
|
}
|
|
453
194
|
if (hasOwnProperty(part, 'functionResult')) {
|
|
454
|
-
return { functionResponse: { name: part.functionResult.name, response:
|
|
195
|
+
return { functionResponse: { name: part.functionResult.name, response: part.functionResult.value } };
|
|
196
|
+
}
|
|
197
|
+
if (hasOwnProperty(part, 'functionCall')) {
|
|
198
|
+
return { functionCall: { name: part.functionCall.name, args: part.functionCall.parameters } };
|
|
199
|
+
}
|
|
200
|
+
throw new NotSupportedError('Unsupported content part.');
|
|
201
|
+
})
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
convertFunctions(functions) {
|
|
205
|
+
return objectEntries(functions).map(([name, declaration]) => ({
|
|
206
|
+
name,
|
|
207
|
+
description: declaration.description,
|
|
208
|
+
parameters: convertToOpenApiSchema(declaration.parameters)
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
convertGoogleContent(content) {
|
|
212
|
+
return {
|
|
213
|
+
role: content.role,
|
|
214
|
+
parts: content.parts.map((part) => {
|
|
215
|
+
if (isDefined(part.text)) {
|
|
216
|
+
return { text: part.text };
|
|
217
|
+
}
|
|
218
|
+
if (isDefined(part.fileData)) {
|
|
219
|
+
const file = assertDefinedPass(this.#fileService.getFileByUri(part.fileData.fileUri), `File not found.`);
|
|
220
|
+
return { file: file.id };
|
|
221
|
+
}
|
|
222
|
+
;
|
|
223
|
+
if (isDefined(part.functionResponse)) {
|
|
224
|
+
return { functionResult: { name: part.functionResponse.name, value: part.functionResponse.response } };
|
|
225
|
+
}
|
|
226
|
+
if (isDefined(part.functionCall)) {
|
|
227
|
+
return { functionCall: { name: part.functionCall.name, parameters: part.functionCall.args } };
|
|
455
228
|
}
|
|
456
229
|
throw new NotSupportedError('Unsupported content part.');
|
|
457
230
|
})
|
|
458
|
-
.toArray()
|
|
459
231
|
};
|
|
460
232
|
}
|
|
461
233
|
getModel(model) {
|
|
462
|
-
return this
|
|
234
|
+
return this.#genAI.getGenerativeModel({ model });
|
|
463
235
|
}
|
|
464
236
|
};
|
|
465
237
|
AiService = __decorate([
|
package/ai/functions.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FunctionDeclaration } from '@google/generative-ai';
|
|
2
2
|
import type { AbstractConstructor } from '../types.js';
|
|
3
3
|
import type { SchemaFunctionDeclarations } from './types.js';
|
|
4
|
-
export declare function convertFunctionDeclarations(declarations: SchemaFunctionDeclarations):
|
|
4
|
+
export declare function convertFunctionDeclarations(declarations: SchemaFunctionDeclarations): FunctionDeclaration[];
|
|
5
5
|
export declare function getFunctionDeclarations(type: AbstractConstructor): FunctionDeclaration[];
|
package/ai/functions.js
CHANGED
|
@@ -3,7 +3,7 @@ import { FunctionSchema, getObjectSchema, object } from '../schema/index.js';
|
|
|
3
3
|
import { fromEntries, objectEntries } from '../utils/object/object.js';
|
|
4
4
|
import { isNotNull, isNull, isString } from '../utils/type-guards.js';
|
|
5
5
|
export function convertFunctionDeclarations(declarations) {
|
|
6
|
-
|
|
6
|
+
return objectEntries(declarations)
|
|
7
7
|
.map(([name, declaration]) => ({
|
|
8
8
|
name,
|
|
9
9
|
description: declaration.description,
|
package/ai/index.d.ts
CHANGED
package/ai/index.js
CHANGED
package/ai/types.d.ts
CHANGED
|
@@ -1,27 +1,84 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { LiteralUnion } from 'type-fest';
|
|
2
|
+
import type { ObjectSchema, SchemaOutput, SchemaTestable } from '../schema/index.js';
|
|
3
|
+
import type { Record, UndefinableJsonObject } from '../types.js';
|
|
3
4
|
export type FileInput = {
|
|
4
5
|
path: string;
|
|
5
6
|
mimeType: string;
|
|
6
7
|
} | Blob;
|
|
7
8
|
export type SchemaFunctionDeclarations = Record<string, SchemaFunctionDeclaration<any>>;
|
|
8
|
-
export type
|
|
9
|
+
export type SchemaFunctionDeclarationWithoutHandler<T extends Record = Record> = {
|
|
9
10
|
description: string;
|
|
10
11
|
parameters: ObjectSchema<T>;
|
|
12
|
+
};
|
|
13
|
+
export type SchemaFunctionDeclarationWithHandler<T extends Record = Record, R = unknown> = SchemaFunctionDeclarationWithoutHandler<T> & {
|
|
11
14
|
handler: (parameters: T) => R | Promise<R>;
|
|
12
15
|
};
|
|
16
|
+
export type SchemaFunctionDeclaration<T extends Record = Record, R = unknown> = SchemaFunctionDeclarationWithoutHandler<T> | SchemaFunctionDeclarationWithHandler<T, R>;
|
|
17
|
+
export type SchemaFunctionDeclarationHandlerResult<T extends SchemaFunctionDeclaration> = T extends SchemaFunctionDeclarationWithHandler<any, infer R> ? R : never;
|
|
18
|
+
export type SchemaFunctionDeclarationsResult<T extends SchemaFunctionDeclarations = SchemaFunctionDeclarations> = {
|
|
19
|
+
[P in keyof T]: {
|
|
20
|
+
functionName: P;
|
|
21
|
+
parameters: SchemaOutput<T[P]['parameters']>;
|
|
22
|
+
handlerResult: SchemaFunctionDeclarationHandlerResult<T[P]>;
|
|
23
|
+
};
|
|
24
|
+
}[keyof T];
|
|
25
|
+
export type ContentRole = 'user' | 'model';
|
|
26
|
+
export type TextContentPart = {
|
|
27
|
+
text: string;
|
|
28
|
+
};
|
|
29
|
+
export type FileContentPart = {
|
|
30
|
+
file: string;
|
|
31
|
+
};
|
|
32
|
+
export type FunctionCall = {
|
|
33
|
+
name: string;
|
|
34
|
+
parameters: UndefinableJsonObject;
|
|
35
|
+
};
|
|
36
|
+
export type FunctionCallContentPart = {
|
|
37
|
+
functionCall: FunctionCall;
|
|
38
|
+
};
|
|
39
|
+
export type FunctionResult = {
|
|
40
|
+
name: string;
|
|
41
|
+
value: UndefinableJsonObject;
|
|
42
|
+
};
|
|
43
|
+
export type FunctionResultContentPart = {
|
|
44
|
+
functionResult: FunctionResult;
|
|
45
|
+
};
|
|
46
|
+
export type ContentPart = TextContentPart | FileContentPart | FunctionCallContentPart | FunctionResultContentPart;
|
|
13
47
|
export type Content = {
|
|
14
|
-
role:
|
|
48
|
+
role: ContentRole;
|
|
15
49
|
parts: ContentPart[];
|
|
16
50
|
};
|
|
17
|
-
export type
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
51
|
+
export type FunctionCallingMode = 'auto' | 'force' | 'none';
|
|
52
|
+
export type AiModel = LiteralUnion<'gemini-2.0-flash-exp' | 'gemini-exp-1206' | 'gemini-2.0-flash-thinking-exp-1219', string>;
|
|
53
|
+
export type GenerationOptions = {
|
|
54
|
+
maxOutputTokens?: number;
|
|
55
|
+
temperature?: number;
|
|
56
|
+
topP?: number;
|
|
57
|
+
topK?: number;
|
|
58
|
+
presencePenalty?: number;
|
|
59
|
+
frequencyPenalty?: number;
|
|
60
|
+
};
|
|
61
|
+
export type GenerationRequest = {
|
|
62
|
+
model?: AiModel;
|
|
63
|
+
systemInstruction?: string;
|
|
64
|
+
contents: Content[];
|
|
65
|
+
functions?: SchemaFunctionDeclarations;
|
|
66
|
+
functionCallingMode?: FunctionCallingMode;
|
|
67
|
+
generationSchema?: SchemaTestable;
|
|
68
|
+
generationOptions?: GenerationOptions;
|
|
69
|
+
};
|
|
70
|
+
export type GenerationUsage = {
|
|
71
|
+
prompt: number;
|
|
72
|
+
output: number;
|
|
73
|
+
total: number;
|
|
74
|
+
};
|
|
75
|
+
export type GenerationResult = {
|
|
76
|
+
content: Content;
|
|
77
|
+
text: string | null;
|
|
78
|
+
functionCalls: FunctionCall[];
|
|
79
|
+
usage: GenerationUsage;
|
|
26
80
|
};
|
|
27
|
-
export declare function
|
|
81
|
+
export declare function declareFunctions<T extends SchemaFunctionDeclarations>(declarations: T): T;
|
|
82
|
+
export declare function declareFunction<T extends Record = Record>(description: string, parameters: ObjectSchema<T>): SchemaFunctionDeclaration<T, never>;
|
|
83
|
+
export declare function declareFunction<T extends Record = Record, R = never>(description: string, parameters: ObjectSchema<T>, handler: (parameters: T) => R | Promise<R>): SchemaFunctionDeclaration<T, R>;
|
|
84
|
+
export declare function isSchemaFunctionDeclarationWithHandler(declaration: SchemaFunctionDeclaration): declaration is SchemaFunctionDeclarationWithHandler;
|
package/ai/types.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { hasOwnProperty } from '../utils/object/object.js';
|
|
2
|
+
export function declareFunctions(declarations) {
|
|
3
|
+
return declarations;
|
|
4
|
+
}
|
|
1
5
|
export function declareFunction(description, parameters, handler) {
|
|
2
6
|
return { description, parameters, handler };
|
|
3
7
|
}
|
|
8
|
+
export function isSchemaFunctionDeclarationWithHandler(declaration) {
|
|
9
|
+
return hasOwnProperty(declaration, 'handler');
|
|
10
|
+
}
|
package/api/response.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CustomError, type CustomErrorStatic } from '../errors/index.js';
|
|
2
2
|
import type { UndefinableJson } from '../types.js';
|
|
3
3
|
export type ErrorHandlerData = undefined | UndefinableJson;
|
|
4
4
|
export type ErrorSerializer<T extends CustomError, TData extends ErrorHandlerData> = (error: T) => TData;
|
package/api/response.js
CHANGED
|
@@ -28,7 +28,7 @@ export function createErrorResponse(errorOrName, message = '', details) {
|
|
|
28
28
|
let response;
|
|
29
29
|
if (errorOrName instanceof Error) {
|
|
30
30
|
const handler = errorHandlers.get(errorOrName.name);
|
|
31
|
-
if (handler
|
|
31
|
+
if (isDefined(handler)) {
|
|
32
32
|
const data = handler.serializer(errorOrName);
|
|
33
33
|
response = {
|
|
34
34
|
error: {
|
|
@@ -5,7 +5,7 @@ export declare class WeakRefMap<K, V extends object> extends Collection<[K, V],
|
|
|
5
5
|
private finalizationRegistry;
|
|
6
6
|
readonly [Symbol.toStringTag] = "WeakRefMap";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Provides the real size. This is slow because it requires a cleanup iteration
|
|
9
9
|
*/
|
|
10
10
|
get realSize(): number;
|
|
11
11
|
static get supported(): boolean;
|
|
@@ -17,7 +17,7 @@ export declare class WeakRefMap<K, V extends object> extends Collection<[K, V],
|
|
|
17
17
|
addMany(values: Iterable<[K, V]>): void;
|
|
18
18
|
set(key: K, value: V): this;
|
|
19
19
|
delete(key: K): boolean;
|
|
20
|
-
/**
|
|
20
|
+
/** Prune garbage collected entries */
|
|
21
21
|
cleanup(): void;
|
|
22
22
|
clone(): WeakRefMap<K, V>;
|
|
23
23
|
forEach(callback: (value: V, key: K, map: WeakRefMap<K, V>) => void, thisArg?: any): void;
|
|
@@ -8,7 +8,7 @@ export class WeakRefMap extends Collection {
|
|
|
8
8
|
finalizationRegistry;
|
|
9
9
|
[Symbol.toStringTag] = 'WeakRefMap';
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Provides the real size. This is slow because it requires a cleanup iteration
|
|
12
12
|
*/
|
|
13
13
|
get realSize() {
|
|
14
14
|
this.cleanup();
|
|
@@ -77,10 +77,10 @@ export class WeakRefMap extends Collection {
|
|
|
77
77
|
}
|
|
78
78
|
return deleted;
|
|
79
79
|
}
|
|
80
|
-
/**
|
|
80
|
+
/** Prune garbage collected entries */
|
|
81
81
|
cleanup() {
|
|
82
82
|
for (const _ of this) {
|
|
83
|
-
//
|
|
83
|
+
// Ignore
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
clone() {
|
|
@@ -32,8 +32,8 @@ export declare const documentManagementApiDefinition: {
|
|
|
32
32
|
resource: string;
|
|
33
33
|
method: "GET";
|
|
34
34
|
parameters: import("../../schema/index.js").ObjectSchema<{
|
|
35
|
-
readonly title: string | null;
|
|
36
35
|
readonly id: string;
|
|
36
|
+
readonly title: string | null;
|
|
37
37
|
readonly download?: boolean | undefined;
|
|
38
38
|
}>;
|
|
39
39
|
result: Uint8ArrayConstructor;
|
|
@@ -43,8 +43,8 @@ export declare const documentManagementApiDefinition: {
|
|
|
43
43
|
resource: string;
|
|
44
44
|
method: "GET";
|
|
45
45
|
parameters: import("../../schema/index.js").ObjectSchema<{
|
|
46
|
-
readonly title: string | null;
|
|
47
46
|
readonly id: string;
|
|
47
|
+
readonly title: string | null;
|
|
48
48
|
readonly download?: boolean | undefined;
|
|
49
49
|
}>;
|
|
50
50
|
result: import("../../schema/index.js").StringSchema;
|
|
@@ -406,8 +406,8 @@ declare const _DocumentManagementApi: import("../../api/index.js").ApiClient<{
|
|
|
406
406
|
resource: string;
|
|
407
407
|
method: "GET";
|
|
408
408
|
parameters: import("../../schema/index.js").ObjectSchema<{
|
|
409
|
-
readonly title: string | null;
|
|
410
409
|
readonly id: string;
|
|
410
|
+
readonly title: string | null;
|
|
411
411
|
readonly download?: boolean | undefined;
|
|
412
412
|
}>;
|
|
413
413
|
result: Uint8ArrayConstructor;
|
|
@@ -417,8 +417,8 @@ declare const _DocumentManagementApi: import("../../api/index.js").ApiClient<{
|
|
|
417
417
|
resource: string;
|
|
418
418
|
method: "GET";
|
|
419
419
|
parameters: import("../../schema/index.js").ObjectSchema<{
|
|
420
|
-
readonly title: string | null;
|
|
421
420
|
readonly id: string;
|
|
421
|
+
readonly title: string | null;
|
|
422
422
|
readonly download?: boolean | undefined;
|
|
423
423
|
}>;
|
|
424
424
|
result: import("../../schema/index.js").StringSchema;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.12",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
"@types/node": "22",
|
|
135
135
|
"@types/nodemailer": "6.4",
|
|
136
136
|
"@types/pg": "8.11",
|
|
137
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
137
|
+
"@typescript-eslint/eslint-plugin": "8.20",
|
|
138
138
|
"concurrently": "9.1",
|
|
139
139
|
"drizzle-kit": "0.30",
|
|
140
140
|
"eslint": "8.57",
|
package/schema/schema.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export type SchemaTestResult<T> = {
|
|
|
19
19
|
error: SchemaError;
|
|
20
20
|
};
|
|
21
21
|
type NormalizePrimitiveToConstructor<T> = Or<IsEqual<T, string>, IsEqual<T, String>> extends true ? typeof String : Or<IsEqual<T, number>, IsEqual<T, Number>> extends true ? typeof Number : Or<IsEqual<T, boolean>, IsEqual<T, Boolean>> extends true ? typeof Boolean : Or<IsEqual<T, bigint>, IsEqual<T, BigInt>> extends true ? typeof BigInt : Or<IsEqual<T, symbol>, IsEqual<T, Symbol>> extends true ? typeof Symbol : never;
|
|
22
|
-
export type SchemaTestable<T =
|
|
22
|
+
export type SchemaTestable<T = any> = Schema<T> | AbstractConstructor<T> | NormalizePrimitiveToConstructor<T>;
|
|
23
23
|
export type SchemaOutput<T extends SchemaTestable> = T extends SchemaTestable<infer U> ? U : never;
|
|
24
24
|
export declare const OPTIONAL: unique symbol;
|
|
25
25
|
export type SchemaOptions<_T> = {
|
package/schema/schemas/array.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SchemaError } from '../../schema/schema.error.js';
|
|
2
2
|
import { lazyProperty } from '../../utils/object/lazy-property.js';
|
|
3
|
-
import { isArray } from '../../utils/type-guards.js';
|
|
3
|
+
import { isArray, isNotNull } from '../../utils/type-guards.js';
|
|
4
4
|
import { typeOf } from '../../utils/type-of.js';
|
|
5
5
|
import { PropertySchema } from '../decorators/index.js';
|
|
6
6
|
import { Schema } from '../schema.js';
|
|
@@ -26,6 +26,12 @@ export class ArraySchema extends Schema {
|
|
|
26
26
|
}
|
|
27
27
|
return { valid: false, error: SchemaError.expectedButGot('array', typeOf(value), path) };
|
|
28
28
|
}
|
|
29
|
+
if (isNotNull(this.maximum) && (value.length > this.maximum)) {
|
|
30
|
+
throw new Error(`A maximum of ${this.maximum} items are allowed.`);
|
|
31
|
+
}
|
|
32
|
+
if (isNotNull(this.minimum) && (value.length < this.minimum)) {
|
|
33
|
+
throw new Error(`A minimum of ${this.minimum} items are required.`);
|
|
34
|
+
}
|
|
29
35
|
const values = [];
|
|
30
36
|
for (let i = 0; i < value.length; i++) {
|
|
31
37
|
const result = this.itemSchema._test(value[i], path.add(i), options);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JsonPath } from '../../json-path/json-path.js';
|
|
2
2
|
import type { OneOrMany as OneOrManyType } from '../../types.js';
|
|
3
|
-
import { type
|
|
3
|
+
import { type SchemaDecoratorOptions, type SchemaPropertyDecorator } from '../decorators/index.js';
|
|
4
4
|
import { Schema, type SchemaOptions, type SchemaTestable, type SchemaTestOptions, type SchemaTestResult } from '../schema.js';
|
|
5
5
|
export type OneOrManySchemaOptions<T> = SchemaOptions<T | T[]>;
|
|
6
6
|
export type OneOrMany<T> = OneOrManyType<T>;
|