@tstdl/base 0.92.11 → 0.92.13
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 +138 -361
- package/ai/functions.d.ts +1 -1
- package/ai/functions.js +1 -1
- package/ai/index.d.ts +2 -0
- package/ai/index.js +2 -0
- package/ai/types.d.ts +71 -14
- package/ai/types.js +12 -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,74 @@ 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 { FunctionCallingMode as GoogleFunctionCallingMode, 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';
|
|
20
|
+
const functionCallingModeMap = {
|
|
21
|
+
auto: GoogleFunctionCallingMode.AUTO,
|
|
22
|
+
force: GoogleFunctionCallingMode.ANY,
|
|
23
|
+
none: GoogleFunctionCallingMode.NONE
|
|
24
|
+
};
|
|
83
25
|
let AiService = class AiService {
|
|
84
26
|
#options = injectArgument(this);
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
genAI = new GoogleGenerativeAI(this.#options.apiKey);
|
|
88
|
-
fileManager = new GoogleAIFileManager(this.#options.apiKey);
|
|
27
|
+
#fileService = inject(AiFileService, this.#options);
|
|
28
|
+
#genAI = new GoogleGenerativeAI(this.#options.apiKey);
|
|
89
29
|
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;
|
|
30
|
+
newSession() {
|
|
31
|
+
return new AiSession(this);
|
|
139
32
|
}
|
|
140
|
-
async
|
|
141
|
-
return
|
|
33
|
+
async processFile(fileInput) {
|
|
34
|
+
return this.#fileService.processFile(fileInput);
|
|
142
35
|
}
|
|
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 }));
|
|
36
|
+
async processFiles(fileInputs) {
|
|
37
|
+
return this.#fileService.processFiles(fileInputs);
|
|
195
38
|
}
|
|
196
|
-
async classify(
|
|
197
|
-
const
|
|
198
|
-
const resultSchema = object({
|
|
39
|
+
async classify(files, types, model) {
|
|
40
|
+
const generationSchema = object({
|
|
199
41
|
types: nullable(array(object({
|
|
200
42
|
type: enumeration(types, { description: 'Type of document' }),
|
|
201
43
|
confidence: enumeration(['high', 'medium', 'low'], { description: 'How sure/certain you are about the classficiation.' })
|
|
202
44
|
}), { description: 'One or more document types that matches' }))
|
|
203
45
|
});
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
temperature: 0.75,
|
|
210
|
-
...options,
|
|
211
|
-
responseMimeType: 'application/json',
|
|
212
|
-
responseSchema
|
|
46
|
+
const result = await this.generate({
|
|
47
|
+
model,
|
|
48
|
+
generationOptions: {
|
|
49
|
+
maxOutputTokens: 1048,
|
|
50
|
+
temperature: 0.5
|
|
213
51
|
},
|
|
52
|
+
generationSchema,
|
|
214
53
|
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
|
-
{
|
|
54
|
+
contents: [{
|
|
217
55
|
role: 'user',
|
|
218
56
|
parts: [
|
|
219
|
-
...files
|
|
220
|
-
{ text:
|
|
57
|
+
...toArray(files),
|
|
58
|
+
{ 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
59
|
]
|
|
222
|
-
}
|
|
223
|
-
]
|
|
60
|
+
}]
|
|
224
61
|
});
|
|
225
62
|
return {
|
|
226
|
-
usage: result.
|
|
227
|
-
result:
|
|
63
|
+
usage: result.usage,
|
|
64
|
+
result: JSON.parse(assertNotNullPass(result.text, 'No text returned.'))
|
|
228
65
|
};
|
|
229
66
|
}
|
|
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: {
|
|
67
|
+
async extractData(files, schema, options) {
|
|
68
|
+
const result = await this.generate({
|
|
69
|
+
generationOptions: {
|
|
236
70
|
maxOutputTokens: 8192,
|
|
237
|
-
temperature: 0.
|
|
238
|
-
...options
|
|
239
|
-
responseMimeType: 'application/json',
|
|
240
|
-
responseSchema
|
|
71
|
+
temperature: 0.5,
|
|
72
|
+
...options
|
|
241
73
|
},
|
|
74
|
+
generationSchema: schema,
|
|
242
75
|
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
76
|
|
|
244
77
|
**Instructions:**
|
|
@@ -247,37 +80,33 @@ Carefully read and analyze the provided document. Identify relevant information
|
|
|
247
80
|
{
|
|
248
81
|
role: 'user',
|
|
249
82
|
parts: [
|
|
250
|
-
...files
|
|
251
|
-
{ text:
|
|
83
|
+
...toArray(files),
|
|
84
|
+
{ text: 'Classify the document. Output as JSON.' }
|
|
252
85
|
]
|
|
253
86
|
}
|
|
254
87
|
]
|
|
255
88
|
});
|
|
256
89
|
return {
|
|
257
|
-
usage: result.
|
|
258
|
-
result:
|
|
90
|
+
usage: result.usage,
|
|
91
|
+
result: JSON.parse(assertNotNullPass(result.text, 'No text returned.'))
|
|
259
92
|
};
|
|
260
93
|
}
|
|
261
|
-
async analyzeDocument(
|
|
262
|
-
const file = await this.getFile(fileInput);
|
|
94
|
+
async analyzeDocument(file, types, options) {
|
|
263
95
|
const schema = object({
|
|
264
|
-
content:
|
|
96
|
+
content: string({ description: 'Content of the document with important details only' }),
|
|
265
97
|
documentTypes: array(object({
|
|
266
98
|
type: enumeration(types, { description: 'Type of document' }),
|
|
267
|
-
confidence: enumeration(['high', 'medium', 'low'], { description: 'How sure/certain you are about the classficiation
|
|
99
|
+
confidence: enumeration(['high', 'medium', 'low'], { description: 'How sure/certain you are about the classficiation' })
|
|
268
100
|
}), { description: 'One or more document types that matches' }),
|
|
269
|
-
tags: array(string({ description: 'Tag which describes the content' }), { description: 'List of tags' })
|
|
101
|
+
tags: array(string({ description: 'Tag which describes the content' }), { description: 'List of tags', maximum: 5 })
|
|
270
102
|
});
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
|
|
274
|
-
generationConfig: {
|
|
103
|
+
const result = await this.generate({
|
|
104
|
+
generationOptions: {
|
|
275
105
|
maxOutputTokens: 2048,
|
|
276
|
-
temperature: 0.
|
|
277
|
-
...options
|
|
278
|
-
responseMimeType: 'application/json',
|
|
279
|
-
responseSchema
|
|
106
|
+
temperature: 0.5,
|
|
107
|
+
...options
|
|
280
108
|
},
|
|
109
|
+
generationSchema: schema,
|
|
281
110
|
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
111
|
|
|
283
112
|
**Instructions:**
|
|
@@ -291,175 +120,123 @@ Always output the content and tags in Deutsch.`,
|
|
|
291
120
|
{
|
|
292
121
|
role: 'user',
|
|
293
122
|
parts: [
|
|
294
|
-
|
|
295
|
-
{ text:
|
|
123
|
+
file,
|
|
124
|
+
{ text: 'Classify the document. Output as JSON.' }
|
|
296
125
|
]
|
|
297
126
|
}
|
|
298
127
|
]
|
|
299
128
|
});
|
|
300
129
|
return {
|
|
301
|
-
usage: result.
|
|
302
|
-
result:
|
|
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.' }
|
|
377
|
-
]
|
|
378
|
-
}
|
|
379
|
-
]
|
|
380
|
-
});
|
|
381
|
-
return {
|
|
382
|
-
usage: result.response.usageMetadata,
|
|
383
|
-
result: {
|
|
384
|
-
functions: result.response.functionCalls(),
|
|
385
|
-
text: result.response.text()
|
|
386
|
-
}
|
|
130
|
+
usage: result.usage,
|
|
131
|
+
result: JSON.parse(assertNotNullPass(result.text, 'No text returned.'))
|
|
387
132
|
};
|
|
388
133
|
}
|
|
389
134
|
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: {
|
|
135
|
+
const generation = await this.generate({
|
|
136
|
+
model: options?.model,
|
|
137
|
+
generationOptions: {
|
|
398
138
|
maxOutputTokens: 4096,
|
|
399
|
-
temperature: 0.
|
|
139
|
+
temperature: 0.5,
|
|
400
140
|
...options
|
|
401
141
|
},
|
|
402
142
|
systemInstruction: options?.systemInstruction,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
mode: (options?.forceFunctionCall == false) ? FunctionCallingMode.AUTO : FunctionCallingMode.ANY
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
contents: googleContent
|
|
143
|
+
functions,
|
|
144
|
+
functionCallingMode: 'force',
|
|
145
|
+
contents
|
|
410
146
|
});
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
const
|
|
147
|
+
const result = [];
|
|
148
|
+
for (const call of generation.functionCalls) {
|
|
149
|
+
const fn = assertDefinedPass(functions[call.name], 'Function in response not declared.');
|
|
150
|
+
const parameters = fn.parameters.parse(call.parameters);
|
|
151
|
+
const handlerResult = isSchemaFunctionDeclarationWithHandler(fn) ? await fn.handler(parameters) : undefined;
|
|
152
|
+
result.push({ functionName: call.name, parameters: parameters, handlerResult: handlerResult });
|
|
415
153
|
}
|
|
154
|
+
return result;
|
|
416
155
|
}
|
|
417
|
-
async
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
156
|
+
async generate(request) {
|
|
157
|
+
const googleContent = this.convertContents(request.contents);
|
|
158
|
+
const googleFunctionDeclarations = isDefined(request.functions) ? this.convertFunctions(request.functions) : undefined;
|
|
159
|
+
const generationConfig = isDefined(request.generationSchema)
|
|
160
|
+
? { ...request.generationOptions, responseMimeType: 'application/json', responseSchema: convertToOpenApiSchema(request.generationSchema) }
|
|
161
|
+
: request.generationOptions;
|
|
162
|
+
const generation = await this.getModel(request.model ?? this.defaultModel).generateContent({
|
|
163
|
+
generationConfig,
|
|
164
|
+
systemInstruction: request.systemInstruction,
|
|
165
|
+
tools: isDefined(googleFunctionDeclarations) ? [{ functionDeclarations: googleFunctionDeclarations }] : undefined,
|
|
166
|
+
toolConfig: isDefined(request.functionCallingMode)
|
|
167
|
+
? { functionCallingConfig: { mode: functionCallingModeMap[request.functionCallingMode] } }
|
|
168
|
+
: undefined,
|
|
169
|
+
contents: googleContent
|
|
170
|
+
});
|
|
171
|
+
const content = this.convertGoogleContent(generation.response.candidates.at(0).content);
|
|
172
|
+
const textParts = content.parts.filter((part) => hasOwnProperty(part, 'text')).map((part) => part.text);
|
|
173
|
+
const functionCallParts = content.parts.filter((part) => hasOwnProperty(part, 'functionCall')).map((part) => part.functionCall);
|
|
174
|
+
return {
|
|
175
|
+
content,
|
|
176
|
+
text: textParts.length > 0 ? textParts.join('') : null,
|
|
177
|
+
functionCalls: functionCallParts,
|
|
178
|
+
usage: {
|
|
179
|
+
prompt: generation.response.usageMetadata.promptTokenCount,
|
|
180
|
+
output: generation.response.usageMetadata.candidatesTokenCount,
|
|
181
|
+
total: generation.response.usageMetadata.totalTokenCount
|
|
182
|
+
}
|
|
183
|
+
};
|
|
435
184
|
}
|
|
436
|
-
|
|
437
|
-
return
|
|
438
|
-
.map(async (content) => this.convertContent(content))
|
|
439
|
-
.toArray();
|
|
185
|
+
convertContents(contents) {
|
|
186
|
+
return contents.map((content) => this.convertContent(content));
|
|
440
187
|
}
|
|
441
|
-
|
|
188
|
+
convertContent(content) {
|
|
442
189
|
return {
|
|
443
190
|
role: content.role,
|
|
444
|
-
parts:
|
|
445
|
-
.map(async (part) => {
|
|
191
|
+
parts: content.parts.map((part) => {
|
|
446
192
|
if (hasOwnProperty(part, 'text')) {
|
|
447
193
|
return { text: part.text };
|
|
448
194
|
}
|
|
449
195
|
if (hasOwnProperty(part, 'file')) {
|
|
450
|
-
const
|
|
451
|
-
return { fileData: { fileUri:
|
|
196
|
+
const file = assertDefinedPass(this.#fileService.getFileById(part.file), `File ${part.file} not found.`);
|
|
197
|
+
return { fileData: { fileUri: file.uri, mimeType: file.mimeType } };
|
|
452
198
|
}
|
|
453
199
|
if (hasOwnProperty(part, 'functionResult')) {
|
|
454
|
-
return { functionResponse: { name: part.functionResult.name, response:
|
|
200
|
+
return { functionResponse: { name: part.functionResult.name, response: part.functionResult.value } };
|
|
201
|
+
}
|
|
202
|
+
if (hasOwnProperty(part, 'functionCall')) {
|
|
203
|
+
return { functionCall: { name: part.functionCall.name, args: part.functionCall.parameters } };
|
|
204
|
+
}
|
|
205
|
+
throw new NotSupportedError('Unsupported content part.');
|
|
206
|
+
})
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
convertFunctions(functions) {
|
|
210
|
+
return objectEntries(functions).map(([name, declaration]) => ({
|
|
211
|
+
name,
|
|
212
|
+
description: declaration.description,
|
|
213
|
+
parameters: convertToOpenApiSchema(declaration.parameters)
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
convertGoogleContent(content) {
|
|
217
|
+
return {
|
|
218
|
+
role: content.role,
|
|
219
|
+
parts: content.parts.map((part) => {
|
|
220
|
+
if (isDefined(part.text)) {
|
|
221
|
+
return { text: part.text };
|
|
222
|
+
}
|
|
223
|
+
if (isDefined(part.fileData)) {
|
|
224
|
+
const file = assertDefinedPass(this.#fileService.getFileByUri(part.fileData.fileUri), 'File not found.');
|
|
225
|
+
return { file: file.id };
|
|
226
|
+
}
|
|
227
|
+
;
|
|
228
|
+
if (isDefined(part.functionResponse)) {
|
|
229
|
+
return { functionResult: { name: part.functionResponse.name, value: part.functionResponse.response } };
|
|
230
|
+
}
|
|
231
|
+
if (isDefined(part.functionCall)) {
|
|
232
|
+
return { functionCall: { name: part.functionCall.name, parameters: part.functionCall.args } };
|
|
455
233
|
}
|
|
456
234
|
throw new NotSupportedError('Unsupported content part.');
|
|
457
235
|
})
|
|
458
|
-
.toArray()
|
|
459
236
|
};
|
|
460
237
|
}
|
|
461
238
|
getModel(model) {
|
|
462
|
-
return this
|
|
239
|
+
return this.#genAI.getGenerativeModel({ model });
|
|
463
240
|
}
|
|
464
241
|
};
|
|
465
242
|
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,14 @@
|
|
|
1
|
+
import { hasOwnProperty } from '../utils/object/object.js';
|
|
2
|
+
import { isDefined } from '../utils/type-guards.js';
|
|
3
|
+
export function declareFunctions(declarations) {
|
|
4
|
+
return declarations;
|
|
5
|
+
}
|
|
1
6
|
export function declareFunction(description, parameters, handler) {
|
|
2
|
-
|
|
7
|
+
if (isDefined(handler)) {
|
|
8
|
+
return { description, parameters, handler };
|
|
9
|
+
}
|
|
10
|
+
return { description, parameters };
|
|
11
|
+
}
|
|
12
|
+
export function isSchemaFunctionDeclarationWithHandler(declaration) {
|
|
13
|
+
return hasOwnProperty(declaration, 'handler');
|
|
3
14
|
}
|
|
@@ -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.13",
|
|
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>;
|