@tstdl/base 0.93.125 → 0.93.127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/genkit/tests/multi-region.test.js +6 -6
- package/ai/index.d.ts +2 -6
- package/ai/index.js +2 -6
- package/ai/parser/index.d.ts +1 -0
- package/ai/parser/index.js +1 -0
- package/ai/parser/parser.d.ts +12 -0
- package/ai/parser/parser.js +28 -0
- package/ai/prompts/build.d.ts +21 -0
- package/ai/prompts/build.js +25 -0
- package/ai/prompts/index.d.ts +2 -0
- package/ai/prompts/index.js +2 -0
- package/ai/prompts/instructions-formatter.d.ts +9 -22
- package/ai/prompts/instructions-formatter.js +20 -7
- package/ai/prompts/instructions.js +1 -1
- package/ai/prompts/steering.d.ts +27 -0
- package/ai/prompts/steering.js +54 -0
- package/ai/tests/instructions-formatter.test.js +115 -0
- package/ai/tests/steering.test.js +37 -0
- package/application/application.d.ts +2 -1
- package/application/application.js +3 -0
- package/authentication/client/module.d.ts +1 -1
- package/authentication/client/module.js +4 -5
- package/authentication/tests/authentication-ancillary.service.test.js +1 -1
- package/authentication/tests/authentication.api-controller.test.js +3 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
- package/authentication/tests/authentication.client-service.test.js +1 -1
- package/authentication/tests/authentication.service.test.js +1 -1
- package/authentication/tests/subject.service.test.js +1 -1
- package/circuit-breaker/tests/circuit-breaker.test.js +1 -1
- package/document-management/api/document-management.api.d.ts +16 -16
- package/document-management/api/document-management.api.js +12 -12
- package/document-management/models/ai-configuration.d.ts +59 -0
- package/document-management/models/ai-configuration.js +1 -0
- package/document-management/models/document-assignment-scope.model.js +2 -4
- package/document-management/models/document-assignment-task.model.js +2 -4
- package/document-management/models/document-collection-assignment.model.js +2 -4
- package/document-management/models/document-collection.model.js +2 -3
- package/document-management/models/document-content.model.d.ts +6 -0
- package/document-management/models/document-content.model.js +32 -0
- package/document-management/models/document-property-value.model.js +1 -2
- package/document-management/models/document-request-collection-assignment.model.js +2 -4
- package/document-management/models/document-request.model.js +2 -4
- package/document-management/models/document-tag-assignment.model.js +2 -3
- package/document-management/models/document-validation-execution-related-document.model.js +2 -4
- package/document-management/models/document-validation-execution.model.js +2 -5
- package/document-management/models/document-workflow.model.d.ts +2 -1
- package/document-management/models/document-workflow.model.js +4 -5
- package/document-management/models/document.model.js +2 -3
- package/document-management/models/index.d.ts +2 -0
- package/document-management/models/index.js +2 -0
- package/document-management/server/api/document-management.api.d.ts +7 -7
- package/document-management/server/api/document-management.api.js +9 -9
- package/document-management/server/configure.d.ts +4 -1
- package/document-management/server/configure.js +9 -4
- package/document-management/server/drizzle/{0000_complex_black_bird.sql → 0000_curious_nighthawk.sql} +7 -27
- package/document-management/server/drizzle/meta/0000_snapshot.json +12 -284
- package/document-management/server/drizzle/meta/_journal.json +2 -2
- package/document-management/server/module.d.ts +2 -0
- package/document-management/server/module.js +1 -0
- package/document-management/server/schemas.d.ts +2 -1
- package/document-management/server/services/document-file.service.d.ts +6 -6
- package/document-management/server/services/document-file.service.js +7 -81
- package/document-management/server/services/document-management-ai-provider.service.d.ts +66 -0
- package/document-management/server/services/document-management-ai-provider.service.js +2 -0
- package/document-management/server/services/document-management-ai.service.d.ts +44 -7
- package/document-management/server/services/document-management-ai.service.js +332 -329
- package/document-management/server/services/document-validation.service.d.ts +1 -1
- package/document-management/server/services/document-workflow.service.d.ts +4 -3
- package/document-management/server/services/document-workflow.service.js +26 -9
- package/document-management/server/services/document.service.d.ts +7 -3
- package/document-management/server/services/document.service.js +13 -4
- package/document-management/server/services/index.d.ts +1 -0
- package/document-management/server/services/index.js +1 -0
- package/document-management/server/validators/ai-validation-executor.d.ts +419 -12
- package/document-management/server/validators/ai-validation-executor.js +51 -46
- package/document-management/server/validators/single-document-validation-executor.d.ts +1 -3
- package/document-management/server/validators/single-document-validation-executor.js +2 -4
- package/document-management/service-models/document.service-model.d.ts +3 -3
- package/document-management/service-models/document.service-model.js +1 -1
- package/document-management/tests/ai-config-hierarchy.test.d.ts +1 -0
- package/document-management/tests/ai-config-hierarchy.test.js +64 -0
- package/document-management/tests/ai-config-integration.test.d.ts +1 -0
- package/document-management/tests/ai-config-integration.test.js +125 -0
- package/document-management/tests/ai-config-merge.test.d.ts +1 -0
- package/document-management/tests/ai-config-merge.test.js +38 -0
- package/document-management/tests/document-management-ai-overrides.test.d.ts +1 -0
- package/document-management/tests/document-management-ai-overrides.test.js +64 -0
- package/document-management/tests/document-management-core.test.js +6 -6
- package/document-management/tests/document-management.api.test.js +5 -5
- package/document-management/tests/document-statistics.service.test.js +10 -6
- package/document-management/tests/document-validation-ai-overrides.test.d.ts +1 -0
- package/document-management/tests/document-validation-ai-overrides.test.js +85 -0
- package/document-management/tests/document.service.test.js +15 -11
- package/document-management/tests/enum-helpers.test.js +5 -5
- package/examples/document-management/ai-provider.d.ts +20 -0
- package/examples/document-management/ai-provider.js +74 -0
- package/examples/document-management/main.js +9 -6
- package/examples/injector/graph-example.d.ts +1 -0
- package/examples/injector/graph-example.js +340 -0
- package/injector/decorators.d.ts +4 -4
- package/injector/decorators.js +5 -6
- package/injector/forward-ref.d.ts +15 -0
- package/injector/forward-ref.js +20 -0
- package/injector/graph.d.ts +113 -0
- package/injector/graph.js +652 -0
- package/injector/index.d.ts +2 -0
- package/injector/index.js +2 -0
- package/injector/inject.d.ts +15 -15
- package/injector/injector.d.ts +101 -13
- package/injector/injector.js +103 -59
- package/injector/resolve-chain.d.ts +20 -6
- package/injector/resolve-chain.js +39 -14
- package/injector/tests/advanced.test.d.ts +1 -0
- package/injector/tests/advanced.test.js +116 -0
- package/injector/tests/async-init.test.d.ts +1 -0
- package/injector/tests/async-init.test.js +77 -0
- package/injector/tests/basic.test.d.ts +1 -0
- package/injector/tests/basic.test.js +114 -0
- package/injector/tests/hierarchical.test.d.ts +1 -0
- package/injector/tests/hierarchical.test.js +59 -0
- package/injector/tests/lifecycles.test.d.ts +1 -0
- package/injector/tests/lifecycles.test.js +109 -0
- package/injector/token.d.ts +2 -1
- package/injector/token.js +4 -1
- package/injector/type-info.d.ts +1 -5
- package/injector/types.d.ts +4 -10
- package/logger/tests/pretty-print.test.d.ts +1 -0
- package/logger/{formatters → tests}/pretty-print.test.js +1 -1
- package/logger/transports/console.d.ts +3 -2
- package/logger/transports/console.js +4 -3
- package/notification/tests/notification-api.test.js +8 -5
- package/notification/tests/notification-client.test.d.ts +1 -0
- package/notification/tests/{unit/notification-client.test.js → notification-client.test.js} +5 -5
- package/notification/tests/notification-flow.test.js +6 -5
- package/notification/tests/notification-sse.service.test.js +1 -1
- package/notification/tests/notification-type.service.test.js +1 -1
- package/object-storage/s3/s3.object-storage.js +3 -0
- package/object-storage/s3/tests/s3.object-storage.integration.test.js +1 -1
- package/orm/tests/repository-attributes.test.js +10 -17
- package/orm/tests/repository-cti-mapping.test.js +2 -2
- package/orm/tests/repository-cti-soft-delete.test.js +1 -1
- package/orm/tests/repository-cti.test.js +19 -33
- package/orm/tests/repository-extra-coverage.test.js +1 -1
- package/orm/tests/repository-search.test.js +5 -2
- package/orm/tests/transaction-safety.test.js +1 -1
- package/package.json +7 -9
- package/rate-limit/tests/postgres-rate-limiter.test.js +6 -16
- package/renderer/d2.d.ts +77 -0
- package/renderer/d2.js +68 -0
- package/renderer/graphviz.d.ts +47 -0
- package/renderer/graphviz.js +58 -0
- package/renderer/index.d.ts +4 -0
- package/renderer/index.js +4 -0
- package/renderer/typst.d.ts +57 -0
- package/renderer/typst.js +62 -0
- package/rpc/adapters/readable-stream.adapter.d.ts +3 -0
- package/rpc/adapters/readable-stream.adapter.js +5 -1
- package/rpc/rpc.js +28 -3
- package/rpc/tests/rpc.integration.test.js +3 -1
- package/schema/schemas/nullable.js +1 -1
- package/task-queue/task-queue.d.ts +2 -0
- package/task-queue/task-queue.js +6 -2
- package/task-queue/tests/complex.test.js +1 -1
- package/task-queue/tests/dependencies.test.js +3 -3
- package/task-queue/tests/extensive-dependencies.test.js +1 -1
- package/task-queue/tests/queue.test.js +1 -1
- package/task-queue/tests/worker.test.js +4 -7
- package/test5.js +52 -8
- package/{unit-test → testing}/integration-setup.d.ts +1 -0
- package/{unit-test → testing}/integration-setup.js +13 -0
- package/utils/base64.d.ts +7 -0
- package/utils/base64.js +10 -1
- package/utils/noop.d.ts +7 -1
- package/utils/noop.js +7 -1
- package/ai/ai-file.service.d.ts +0 -57
- package/ai/ai-file.service.js +0 -233
- package/ai/ai-session.d.ts +0 -38
- package/ai/ai-session.js +0 -50
- package/ai/ai.service.d.ts +0 -126
- package/ai/ai.service.js +0 -481
- package/ai/functions.d.ts +0 -9
- package/ai/functions.js +0 -38
- package/ai/module.d.ts +0 -26
- package/ai/module.js +0 -25
- package/ai/types.d.ts +0 -229
- package/ai/types.js +0 -33
- package/latex/index.d.ts +0 -1
- package/latex/index.js +0 -1
- package/typst/index.d.ts +0 -1
- package/typst/index.js +0 -1
- package/typst/render.d.ts +0 -23
- package/typst/render.js +0 -32
- /package/{logger/formatters/pretty-print.test.d.ts → ai/tests/instructions-formatter.test.d.ts} +0 -0
- /package/{notification/tests/unit/notification-client.test.d.ts → ai/tests/steering.test.d.ts} +0 -0
- /package/{latex/render.d.ts → renderer/latex.d.ts} +0 -0
- /package/{latex/render.js → renderer/latex.js} +0 -0
- /package/{unit-test → testing}/index.d.ts +0 -0
- /package/{unit-test → testing}/index.js +0 -0
|
@@ -4,66 +4,12 @@ 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
|
var _a;
|
|
60
8
|
var DocumentManagementAiService_1;
|
|
61
|
-
import { readFile } from 'node:fs/promises';
|
|
62
9
|
import { and, isNull as drizzleIsNull, eq, inArray } from 'drizzle-orm';
|
|
63
10
|
import { P, match } from 'ts-pattern';
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import { TemporaryFile } from '../../../file/server/index.js';
|
|
11
|
+
import { genkitGenerationOptions, injectGenkit, injectModel } from '../../../ai/genkit/index.js';
|
|
12
|
+
import { buildPrompts, jsonOutputInstructions, orderedList } from '../../../ai/prompts/index.js';
|
|
67
13
|
import { inject } from '../../../injector/inject.js';
|
|
68
14
|
import { Logger } from '../../../logger/logger.js';
|
|
69
15
|
import { arrayAgg } from '../../../orm/index.js';
|
|
@@ -71,21 +17,21 @@ import { injectRepository } from '../../../orm/server/index.js';
|
|
|
71
17
|
import { array, boolean, enumeration, integer, nullable, number, object, string } from '../../../schema/index.js';
|
|
72
18
|
import { distinct } from '../../../utils/array/index.js';
|
|
73
19
|
import { numericDateToDateTime, tryDateObjectToNumericDate } from '../../../utils/date-time.js';
|
|
74
|
-
import { fromEntries, objectEntries } from '../../../utils/object/object.js';
|
|
75
|
-
import { assertDefined, assertDefinedPass, assertNotNull, isNotNull, isNull, isUndefined } from '../../../utils/type-guards.js';
|
|
76
|
-
import { Document, DocumentProperty, DocumentRequestState, DocumentTypeProperty } from '../../models/index.js';
|
|
20
|
+
import { fromEntries, hasOwnProperty, objectEntries, objectKeys } from '../../../utils/object/object.js';
|
|
21
|
+
import { assertDefined, assertDefinedPass, assertNotNull, isDefined, isNotNull, isNotNullOrUndefined, isNull, isString, isUndefined } from '../../../utils/type-guards.js';
|
|
22
|
+
import { Document, DocumentProperty, DocumentRequestState, DocumentType, DocumentTypeProperty, DocumentWorkflowStep } from '../../models/index.js';
|
|
23
|
+
import { DocumentManagementConfiguration } from '../module.js';
|
|
77
24
|
import { documentCategory, documentRequest, documentRequestCollectionAssignment, documentType } from '../schemas.js';
|
|
78
25
|
import { DocumentCategoryTypeService } from './document-category-type.service.js';
|
|
79
26
|
import { DocumentCollectionService } from './document-collection.service.js';
|
|
80
27
|
import { DocumentFileService } from './document-file.service.js';
|
|
28
|
+
import { DocumentManagementAiProviderService } from './document-management-ai-provider.service.js';
|
|
81
29
|
import { DocumentPropertyService } from './document-property.service.js';
|
|
82
30
|
import { DocumentTagService } from './document-tag.service.js';
|
|
83
31
|
import { DocumentManagementSingleton } from './singleton.js';
|
|
84
|
-
// ---
|
|
85
|
-
const
|
|
86
|
-
You are an expert OCR and Document Digitization engine.
|
|
87
|
-
|
|
88
|
-
${formatInstructions({
|
|
32
|
+
// --- Instructions ---
|
|
33
|
+
const contentExtractionSystemInstructions = {
|
|
34
|
+
'Role': 'You are an expert OCR and Document Digitization engine.',
|
|
89
35
|
'Primary Objective': 'Convert the provided document into semantically structured, clean Markdown.',
|
|
90
36
|
'Critical Constraints': orderedList([
|
|
91
37
|
'Output ONLY the Markdown content. Do not include introductory text, conversational filler, or code block fences (```).',
|
|
@@ -113,13 +59,10 @@ ${formatInstructions({
|
|
|
113
59
|
'Mark illegible text as `[Illegible]`.',
|
|
114
60
|
'Mark cut-off text as `[Cut off]`.',
|
|
115
61
|
],
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
You are a Document Taxonomy Specialist.
|
|
121
|
-
|
|
122
|
-
${formatInstructions({
|
|
62
|
+
};
|
|
63
|
+
const contentExtractionUserInstructions = { Task: 'Transcribe the attached document into Markdown following the system instructions.' };
|
|
64
|
+
const classifySystemInstructions = {
|
|
65
|
+
'Role': 'You are a Document Taxonomy Specialist.',
|
|
123
66
|
'Task': `Analyze the visual layout and text content of the document to categorize it into exactly one of the provided hierarchical types.`,
|
|
124
67
|
'Input Context': 'You will be provided with a list of valid category labels (e.g., "Finance -> Invoice").',
|
|
125
68
|
'Analysis Strategy': orderedList([
|
|
@@ -133,24 +76,18 @@ ${formatInstructions({
|
|
|
133
76
|
'Specificity: Always choose the most specific leaf-node category available.',
|
|
134
77
|
'Fallback: If ambiguous, choose the category that best describes the *primary* purpose of the document.',
|
|
135
78
|
]),
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
You are a Structured Data Extraction Analyst.
|
|
141
|
-
|
|
142
|
-
${formatInstructions({
|
|
79
|
+
...jsonOutputInstructions,
|
|
80
|
+
};
|
|
81
|
+
const classifyUserInstructions = { Task: 'Determine the single most accurate document type from the provided list based on the document following the system instructions.' };
|
|
82
|
+
const dataExtractionInstructionsRaw = {
|
|
83
|
+
'Role': 'You are a Structured Data Extraction Analyst.',
|
|
143
84
|
'Task': 'Analyze the document and extract metadata into the defined JSON schema.',
|
|
144
|
-
'General Guidelines': orderedList({
|
|
145
|
-
'Language': 'Ensure all generated text (titles, summaries) matches the primary language of the document.',
|
|
146
|
-
'Null Handling': 'If a specific field or property is not present in the document, return null. Do not guess or hallucinate values.',
|
|
147
|
-
}),
|
|
148
85
|
'Field Specific Instructions': {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
86
|
+
Title: 'Create a concise, searchable filename-style title (e.g., "Invoice - Oct 2023").',
|
|
87
|
+
Subtitle: 'Extract context usually found below the header (e.g., Project Name, Reference Number).',
|
|
88
|
+
Summary: 'Write a 2-3 sentence executive summary. Mention the type of information that can be found in the document and its purpose.',
|
|
89
|
+
Tags: 'Generate 3-5 keywords for categorization. Only use important information missing in title, subtitle and properties. Prioritize reusing of existing tags where possible.',
|
|
90
|
+
Date: 'Identify the *creation* date of the document. If multiple dates exist, prioritize the primary date (like invoice or letter Date). Return as object with year, month and day.',
|
|
154
91
|
},
|
|
155
92
|
'Property Extraction': [
|
|
156
93
|
'You will be given a list of specific dynamic properties to look for.',
|
|
@@ -159,12 +96,12 @@ ${formatInstructions({
|
|
|
159
96
|
'If a property is ambiguous, favor the value most prominent in the document layout.',
|
|
160
97
|
'If a property is missing, set its value to null.',
|
|
161
98
|
],
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
99
|
+
...jsonOutputInstructions,
|
|
100
|
+
};
|
|
101
|
+
const dataExtractionSystemInstructions = dataExtractionInstructionsRaw;
|
|
102
|
+
const dataExtractionUserInstructions = { Task: 'Analyze the document and extract metadata and specific properties defined in the output schema following the system instructions.' };
|
|
103
|
+
const assignCollectionSystemInstructions = {
|
|
104
|
+
'Role': 'You are a Digital Filing Assistant.',
|
|
168
105
|
'Task': `Assign the document to relevant collections based on its metadata and content.`,
|
|
169
106
|
'Input': 'Document Metadata and a list of Available Collections.',
|
|
170
107
|
'Matching Logic': orderedList([
|
|
@@ -173,13 +110,10 @@ ${formatInstructions({
|
|
|
173
110
|
'Project Association: If the document references a specific project code or name found in a collection name, assign it there.',
|
|
174
111
|
]),
|
|
175
112
|
'Output': 'Return an array of matching collection IDs. If no collection is a strong fit, return an empty array.',
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
You are a Workflow Routing Agent.
|
|
181
|
-
|
|
182
|
-
${formatInstructions({
|
|
113
|
+
};
|
|
114
|
+
const assignCollectionUserInstructions = { Task: 'Select the most appropriate collections for this document from the provided list following the system instructions.' };
|
|
115
|
+
const assignRequestSystemInstructions = {
|
|
116
|
+
'Role': 'You are a Workflow Routing Agent.',
|
|
183
117
|
'Task': 'Match the provided document to an existing Open Document Request.',
|
|
184
118
|
'Input': 'Document Metadata and a list of Open Requests.',
|
|
185
119
|
'Matching Rules': orderedList({
|
|
@@ -188,208 +122,161 @@ ${formatInstructions({
|
|
|
188
122
|
'Negative Match': 'If the document satisfies the metadata but violates a comment constraint, it is unsuitable.',
|
|
189
123
|
}),
|
|
190
124
|
'Output': 'The ID of the matching request, or null if no request matches.',
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const assignRequestUserPrompt = 'Evaluate the document against the list of open requests and find the best match following the system instructions.';
|
|
125
|
+
};
|
|
126
|
+
const assignRequestUserInstructions = { Task: 'Evaluate the document against the list of open requests and find the best match following the system instructions.' };
|
|
194
127
|
let DocumentManagementAiService = DocumentManagementAiService_1 = class DocumentManagementAiService {
|
|
195
128
|
#genkit = injectGenkit();
|
|
196
|
-
#
|
|
197
|
-
#classifyModel = injectModel('gemini-2.5-flash').withConfig({ temperature: 0.
|
|
198
|
-
#
|
|
199
|
-
#assignModel = injectModel('gemini-2.5-flash').withConfig({ temperature: 0.
|
|
129
|
+
#contentExtractionModel = injectModel('gemini-2.5-flash-lite').withConfig({ temperature: 0.1, topP: 0.75, topK: 8 });
|
|
130
|
+
#classifyModel = injectModel('gemini-2.5-flash').withConfig({ temperature: 0.1, topP: 0.75, topK: 8, thinkingConfig: { thinkingBudget: 0 } });
|
|
131
|
+
#dataExtractionModel = injectModel('gemini-2.5-flash').withConfig({ temperature: 0.1, topP: 0.75, topK: 8, thinkingConfig: { thinkingBudget: 0 } });
|
|
132
|
+
#assignModel = injectModel('gemini-2.5-flash').withConfig({ temperature: 0.1, topP: 0.75, topK: 8, thinkingConfig: { thinkingBudget: 0 } });
|
|
200
133
|
#documentCollectionService = inject(DocumentCollectionService);
|
|
201
134
|
#documentTagService = inject(DocumentTagService);
|
|
202
135
|
#documentCategoryTypeService = inject(DocumentCategoryTypeService);
|
|
203
136
|
#documentFileService = inject(DocumentFileService);
|
|
204
137
|
#documentPropertyService = inject(DocumentPropertyService);
|
|
138
|
+
#documentManagementConfiguration = inject(DocumentManagementConfiguration, undefined, { optional: true });
|
|
139
|
+
#aiProvider = inject(DocumentManagementAiProviderService, undefined, { optional: true });
|
|
205
140
|
#documentPropertyRepository = injectRepository(DocumentProperty);
|
|
206
141
|
#documentRepository = injectRepository(Document);
|
|
142
|
+
#documentTypeRepository = injectRepository(DocumentType);
|
|
207
143
|
#documentTypePropertyRepository = injectRepository(DocumentTypeProperty);
|
|
208
144
|
#logger = inject(Logger, DocumentManagementAiService_1.name);
|
|
209
|
-
async
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
output: { schema: convertToGenkitSchema(object({ content: string() })) },
|
|
222
|
-
system: ocrSystemPrompt,
|
|
223
|
-
prompt: [
|
|
224
|
-
{ media: { url: dataUrl } },
|
|
225
|
-
{ text: ocrUserPrompt },
|
|
226
|
-
],
|
|
227
|
-
});
|
|
228
|
-
if (isNull(result.output)) {
|
|
229
|
-
throw new Error(`AI returned null output for document "${document.id}".`);
|
|
230
|
-
}
|
|
231
|
-
const markdownBlockStripped = result.output.content.trim().replaceAll(/^```\w*\s*|```$/gi, '').trim();
|
|
232
|
-
return markdownBlockStripped;
|
|
233
|
-
}
|
|
234
|
-
catch (e_1) {
|
|
235
|
-
env_1.error = e_1;
|
|
236
|
-
env_1.hasError = true;
|
|
237
|
-
}
|
|
238
|
-
finally {
|
|
239
|
-
const result_1 = __disposeResources(env_1);
|
|
240
|
-
if (result_1)
|
|
241
|
-
await result_1;
|
|
242
|
-
}
|
|
145
|
+
async extractContent(tenantId, documentId) {
|
|
146
|
+
const document = await this.#documentRepository.loadByQuery({ tenantId, id: documentId });
|
|
147
|
+
this.#logger.trace(`Extracting content from document ${document.id}`);
|
|
148
|
+
const result = await this.runAi(tenantId, DocumentWorkflowStep.ContentExtraction, { document }, {
|
|
149
|
+
defaultModel: this.#contentExtractionModel,
|
|
150
|
+
system: contentExtractionSystemInstructions,
|
|
151
|
+
user: contentExtractionUserInstructions,
|
|
152
|
+
schema: object({ content: string() }),
|
|
153
|
+
document,
|
|
154
|
+
});
|
|
155
|
+
const markdownBlockStripped = result.content.trim().replaceAll(/^```\w*\s*|```$/gi, '').trim();
|
|
156
|
+
return markdownBlockStripped;
|
|
243
157
|
}
|
|
244
158
|
async classifyDocumentType(tenantId, documentId) {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (isNotNull(document.typeId)) {
|
|
249
|
-
return document.typeId;
|
|
250
|
-
}
|
|
251
|
-
const fileContentStream = this.#documentFileService.getContentStream(document);
|
|
252
|
-
const tmpFile = __addDisposableResource(env_2, await TemporaryFile.from(fileContentStream), true);
|
|
253
|
-
const buffer = await readFile(tmpFile.path);
|
|
254
|
-
const base64Data = buffer.toString('base64');
|
|
255
|
-
const dataUrl = `data:${document.mimeType};base64,${base64Data}`;
|
|
256
|
-
const categories = await this.#documentCategoryTypeService.loadCategoryViews(tenantId);
|
|
257
|
-
const typeLabelEntries = getDescriptiveTypeLabels(categories);
|
|
258
|
-
const typeLabels = typeLabelEntries.map(({ label }) => label);
|
|
259
|
-
this.#logger.trace(`Classifying document ${document.id}`);
|
|
260
|
-
const result = await this.#genkit.generate(genkitGenerationOptions({
|
|
261
|
-
model: this.#classifyModel,
|
|
262
|
-
config: {
|
|
263
|
-
maxOutputTokens: 128,
|
|
264
|
-
thinkingConfig: { thinkingBudget: 0 },
|
|
265
|
-
},
|
|
266
|
-
output: {
|
|
267
|
-
schema: object({
|
|
268
|
-
documentType: enumeration(typeLabels),
|
|
269
|
-
}),
|
|
270
|
-
},
|
|
271
|
-
system: classifySystemPrompt,
|
|
272
|
-
prompt: [
|
|
273
|
-
{ media: { url: dataUrl } },
|
|
274
|
-
{ text: classifyUserPrompt },
|
|
275
|
-
],
|
|
276
|
-
}));
|
|
277
|
-
if (isNull(result.output)) {
|
|
278
|
-
throw new Error(`AI returned null output for document classification "${document.id}".`);
|
|
279
|
-
}
|
|
280
|
-
const output = result.output;
|
|
281
|
-
const typeId = typeLabelEntries.find((entry) => entry.label == output.documentType)?.id;
|
|
282
|
-
assertDefined(typeId, `Could not classify document ${document.id}`);
|
|
283
|
-
return typeId;
|
|
159
|
+
const document = await this.#documentRepository.loadByQuery({ tenantId, id: documentId });
|
|
160
|
+
if (isNotNull(document.typeId)) {
|
|
161
|
+
return document.typeId;
|
|
284
162
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
async extractDocumentInformation(tenantId, documentId) {
|
|
296
|
-
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
297
|
-
try {
|
|
298
|
-
const document = await this.#documentRepository.loadByQuery({ tenantId, id: documentId });
|
|
299
|
-
const existingTags = await this.#documentTagService.loadTags(tenantId);
|
|
300
|
-
const fileContentStream = this.#documentFileService.getContentStream(document);
|
|
301
|
-
const tmpFile = __addDisposableResource(env_3, await TemporaryFile.from(fileContentStream), true);
|
|
302
|
-
const buffer = await readFile(tmpFile.path);
|
|
303
|
-
const base64Data = buffer.toString('base64');
|
|
304
|
-
const dataUrl = `data:${document.mimeType};base64,${base64Data}`;
|
|
305
|
-
if (isNull(document.typeId)) {
|
|
306
|
-
throw new Error(`Document ${document.id} has no type`);
|
|
307
|
-
}
|
|
308
|
-
const typeProperties = await this.#documentTypePropertyRepository.loadManyByQuery({ tenantId, typeId: document.typeId });
|
|
309
|
-
const propertyIds = typeProperties.map((property) => property.propertyId);
|
|
310
|
-
const properties = (propertyIds.length > 0) ? await this.#documentPropertyRepository.loadManyByQuery({ tenantId, id: { $in: propertyIds } }) : undefined;
|
|
311
|
-
const propertiesSchemaEntries = properties?.map((property) => {
|
|
312
|
-
const schema = match(property.dataType)
|
|
313
|
-
.with('text', () => nullable(string()))
|
|
314
|
-
.with('integer', () => nullable(integer()))
|
|
315
|
-
.with('decimal', () => nullable(number()))
|
|
316
|
-
.with('boolean', () => nullable(boolean()))
|
|
317
|
-
.with('date', () => nullable(object({ year: integer(), month: integer(), day: integer() })))
|
|
318
|
-
.exhaustive();
|
|
319
|
-
return [property.label, schema];
|
|
320
|
-
});
|
|
321
|
-
const generationSchema = object({
|
|
322
|
-
documentTitle: string(),
|
|
323
|
-
documentSubtitle: nullable(string()),
|
|
324
|
-
documentSummary: string(),
|
|
325
|
-
documentTags: array(string()),
|
|
326
|
-
documentDate: nullable(object({ year: integer(), month: integer(), day: integer() })),
|
|
327
|
-
...((isUndefined(propertiesSchemaEntries) || (propertiesSchemaEntries.length == 0))
|
|
328
|
-
? {}
|
|
329
|
-
: { documentProperties: object(fromEntries(propertiesSchemaEntries)) }),
|
|
330
|
-
});
|
|
331
|
-
const tagLabels = existingTags.map((tag) => tag.label);
|
|
332
|
-
this.#logger.trace(`Extracting document ${document.id}`);
|
|
333
|
-
const result = await this.#genkit.generate(genkitGenerationOptions({
|
|
334
|
-
model: this.#extractModel,
|
|
335
|
-
output: { schema: generationSchema },
|
|
336
|
-
config: {
|
|
337
|
-
maxOutputTokens: 2048,
|
|
338
|
-
thinkingConfig: { thinkingBudget: 0 },
|
|
339
|
-
},
|
|
340
|
-
system: extractSystemPrompt,
|
|
341
|
-
prompt: [
|
|
342
|
-
{ media: { url: dataUrl } },
|
|
343
|
-
{
|
|
344
|
-
text: `
|
|
345
|
-
${formatData({ existingTags: tagLabels })}
|
|
346
|
-
|
|
347
|
-
${extractUserPrompt}
|
|
348
|
-
`.trim(),
|
|
349
|
-
},
|
|
350
|
-
],
|
|
351
|
-
}));
|
|
352
|
-
if (isNull(result.output)) {
|
|
353
|
-
throw new Error(`AI returned null output for document extraction "${document.id}".`);
|
|
163
|
+
const categories = await this.#documentCategoryTypeService.loadCategoryViews(tenantId);
|
|
164
|
+
const typeLabelEntries = getDescriptiveTypeLabels(categories);
|
|
165
|
+
const typeLabels = typeLabelEntries.map(({ label }) => label);
|
|
166
|
+
this.#logger.trace(`Classifying document ${document.id}`);
|
|
167
|
+
const stepData = { document, categories };
|
|
168
|
+
const aiConfig = await this.resolveAiConfiguration(tenantId, DocumentWorkflowStep.Classification, stepData);
|
|
169
|
+
const systemInstructions = isDefined(aiConfig.classification)
|
|
170
|
+
? {
|
|
171
|
+
...classifySystemInstructions,
|
|
172
|
+
'Classification Overrides': mergeInstructions('Follow these additional classification rules.', [aiConfig.classification]),
|
|
354
173
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
title: extraction.documentTitle,
|
|
377
|
-
subtitle: extraction.documentSubtitle,
|
|
378
|
-
date,
|
|
379
|
-
summary: extraction.documentSummary,
|
|
380
|
-
tags: filteredDocumentTags,
|
|
381
|
-
properties: parsedProperties,
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
catch (e_3) {
|
|
385
|
-
env_3.error = e_3;
|
|
386
|
-
env_3.hasError = true;
|
|
387
|
-
}
|
|
388
|
-
finally {
|
|
389
|
-
const result_3 = __disposeResources(env_3);
|
|
390
|
-
if (result_3)
|
|
391
|
-
await result_3;
|
|
174
|
+
: classifySystemInstructions;
|
|
175
|
+
const result = await this.runAi(tenantId, DocumentWorkflowStep.Classification, stepData, {
|
|
176
|
+
defaultModel: this.#classifyModel,
|
|
177
|
+
system: systemInstructions,
|
|
178
|
+
user: classifyUserInstructions,
|
|
179
|
+
schema: object({
|
|
180
|
+
documentType: enumeration(typeLabels),
|
|
181
|
+
}),
|
|
182
|
+
document,
|
|
183
|
+
config: { maxOutputTokens: 128 },
|
|
184
|
+
aiConfig,
|
|
185
|
+
});
|
|
186
|
+
const typeId = typeLabelEntries.find((entry) => entry.label == result.documentType)?.id;
|
|
187
|
+
assertDefined(typeId, `Could not classify document ${document.id}`);
|
|
188
|
+
return typeId;
|
|
189
|
+
}
|
|
190
|
+
async extractData(tenantId, documentId) {
|
|
191
|
+
const document = await this.#documentRepository.loadByQuery({ tenantId, id: documentId });
|
|
192
|
+
const existingTags = await this.#documentTagService.loadTags(tenantId);
|
|
193
|
+
if (isNull(document.typeId)) {
|
|
194
|
+
throw new Error(`Document ${document.id} has no type`);
|
|
392
195
|
}
|
|
196
|
+
const typeProperties = await this.#documentTypePropertyRepository.loadManyByQuery({ tenantId, typeId: document.typeId });
|
|
197
|
+
const propertyIds = typeProperties.map((property) => property.propertyId);
|
|
198
|
+
const properties = (propertyIds.length > 0) ? await this.#documentPropertyRepository.loadManyByQuery({ tenantId, id: { $in: propertyIds } }) : undefined;
|
|
199
|
+
const propertiesSchemaEntries = properties?.map((property) => {
|
|
200
|
+
const schema = match(property.dataType)
|
|
201
|
+
.with('text', () => nullable(string()))
|
|
202
|
+
.with('integer', () => nullable(integer()))
|
|
203
|
+
.with('decimal', () => nullable(number()))
|
|
204
|
+
.with('boolean', () => nullable(boolean()))
|
|
205
|
+
.with('date', () => nullable(object({ year: integer(), month: integer(), day: integer() })))
|
|
206
|
+
.exhaustive();
|
|
207
|
+
return [property.label, schema];
|
|
208
|
+
});
|
|
209
|
+
const generationSchema = object({
|
|
210
|
+
documentTitle: string(),
|
|
211
|
+
documentSubtitle: nullable(string()),
|
|
212
|
+
documentSummary: string(),
|
|
213
|
+
documentTags: array(string()),
|
|
214
|
+
documentDate: nullable(object({ year: integer(), month: integer(), day: integer() })),
|
|
215
|
+
...((isUndefined(propertiesSchemaEntries) || (propertiesSchemaEntries.length == 0))
|
|
216
|
+
? {}
|
|
217
|
+
: { documentProperties: object(fromEntries(propertiesSchemaEntries)) }),
|
|
218
|
+
});
|
|
219
|
+
const tagLabels = existingTags.map((tag) => tag.label);
|
|
220
|
+
this.#logger.trace(`Extracting document ${document.id}`);
|
|
221
|
+
const documentTypeEntity = await this.#documentTypeRepository.load(document.typeId);
|
|
222
|
+
const stepData = { document, type: documentTypeEntity, properties };
|
|
223
|
+
const aiConfig = await this.resolveAiConfiguration(tenantId, DocumentWorkflowStep.DataExtraction, stepData);
|
|
224
|
+
const mergedFieldInstructions = {
|
|
225
|
+
Title: mergeFieldInstructions('Title', 'title', aiConfig),
|
|
226
|
+
Subtitle: mergeFieldInstructions('Subtitle', 'subtitle', aiConfig),
|
|
227
|
+
Date: mergeFieldInstructions('Date', 'date', aiConfig),
|
|
228
|
+
Summary: mergeFieldInstructions('Summary', 'summary', aiConfig),
|
|
229
|
+
Tags: mergeFieldInstructions('Tags', 'tags', aiConfig),
|
|
230
|
+
};
|
|
231
|
+
const mergedPropertyInstructions = properties?.map((property) => {
|
|
232
|
+
const override = (isNotNull(property.key) ? aiConfig.extraction?.properties?.[property.key] : undefined) ?? aiConfig.extraction?.properties?.[property.label];
|
|
233
|
+
return isDefined(override) ? mergeInstructions(`Extract value for property "${property.label}".`, [override]) : undefined;
|
|
234
|
+
}).filter(isDefined);
|
|
235
|
+
const systemInstructions = {
|
|
236
|
+
...dataExtractionSystemInstructions,
|
|
237
|
+
'Field Specific Instructions': mergedFieldInstructions,
|
|
238
|
+
'Property Extraction': isDefined(mergedPropertyInstructions) && (mergedPropertyInstructions.length > 0)
|
|
239
|
+
? [...dataExtractionInstructionsRaw['Property Extraction'], ...mergedPropertyInstructions]
|
|
240
|
+
: dataExtractionInstructionsRaw['Property Extraction'],
|
|
241
|
+
};
|
|
242
|
+
const extraction = await this.runAi(tenantId, DocumentWorkflowStep.DataExtraction, stepData, {
|
|
243
|
+
targetId: documentTypeEntity.key ?? undefined,
|
|
244
|
+
defaultModel: this.#dataExtractionModel,
|
|
245
|
+
system: systemInstructions,
|
|
246
|
+
user: dataExtractionUserInstructions,
|
|
247
|
+
data: { existingTags: tagLabels },
|
|
248
|
+
schema: generationSchema,
|
|
249
|
+
document,
|
|
250
|
+
config: { maxOutputTokens: 2048 },
|
|
251
|
+
aiConfig,
|
|
252
|
+
});
|
|
253
|
+
const filteredDocumentTags = extraction.documentTags.filter((tag) => (tag != extraction.documentTitle) && (tag != extraction.documentSubtitle));
|
|
254
|
+
const date = isNotNull(extraction.documentDate) ? tryAiOutputDateObjectToNumericDate(extraction.documentDate) : null;
|
|
255
|
+
const parsedProperties = isUndefined(extraction.documentProperties)
|
|
256
|
+
? []
|
|
257
|
+
: objectEntries(extraction.documentProperties)
|
|
258
|
+
.map(([propertyLabel, rawValue]) => {
|
|
259
|
+
if (isNull(rawValue)) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
const property = assertDefinedPass(properties?.find((property) => property.label == propertyLabel));
|
|
263
|
+
const value = match(rawValue)
|
|
264
|
+
.with({ year: P.number }, (value) => tryAiOutputDateObjectToNumericDate(value))
|
|
265
|
+
.otherwise((value) => value);
|
|
266
|
+
if (isNull(value)) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
return { propertyId: property.id, dataType: property.dataType, value };
|
|
270
|
+
})
|
|
271
|
+
.filter(isNotNull);
|
|
272
|
+
return {
|
|
273
|
+
title: extraction.documentTitle,
|
|
274
|
+
subtitle: extraction.documentSubtitle,
|
|
275
|
+
date,
|
|
276
|
+
summary: extraction.documentSummary,
|
|
277
|
+
tags: filteredDocumentTags,
|
|
278
|
+
properties: parsedProperties,
|
|
279
|
+
};
|
|
393
280
|
}
|
|
394
281
|
async findSuitableCollectionsForDocument(document, collectionIds) {
|
|
395
282
|
assertNotNull(document.typeId, 'Document has no type');
|
|
@@ -411,29 +298,17 @@ ${extractUserPrompt}
|
|
|
411
298
|
tags: (documentTagLabels.length > 0) ? documentTagLabels : null,
|
|
412
299
|
summary: document.summary ?? null,
|
|
413
300
|
};
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
},
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
text: `
|
|
426
|
-
${formatData({ document: documentData, documentProperties: fromEntries(propertyEntries) })}
|
|
427
|
-
|
|
428
|
-
${formatData({ collections })}
|
|
429
|
-
|
|
430
|
-
${assignCollectionUserPrompt}`,
|
|
431
|
-
}],
|
|
432
|
-
}));
|
|
433
|
-
if (isNull(result.output)) {
|
|
434
|
-
throw new Error(`AI returned null output for collection assignment "${document.id}".`);
|
|
435
|
-
}
|
|
436
|
-
return result.output.collectionIds;
|
|
301
|
+
const documentTypeEntity = await this.#documentTypeRepository.load(document.typeId);
|
|
302
|
+
const result = await this.runAi(document.tenantId, DocumentWorkflowStep.Assignment, { document, properties: documentProperties, collectionIds }, {
|
|
303
|
+
targetId: documentTypeEntity.key ?? undefined,
|
|
304
|
+
defaultModel: this.#assignModel,
|
|
305
|
+
system: assignCollectionSystemInstructions,
|
|
306
|
+
user: assignCollectionUserInstructions,
|
|
307
|
+
data: { document: documentData, documentProperties: fromEntries(propertyEntries), collections },
|
|
308
|
+
schema: object({ collectionIds: array(string()) }),
|
|
309
|
+
config: { maxOutputTokens: 512 },
|
|
310
|
+
});
|
|
311
|
+
return result.collectionIds;
|
|
437
312
|
}
|
|
438
313
|
async findSuitableRequestForDocument(document, collectionIds) {
|
|
439
314
|
const session = this.#documentPropertyRepository.session;
|
|
@@ -470,30 +345,119 @@ ${assignCollectionUserPrompt}`,
|
|
|
470
345
|
tags: (documentTagLabels.length > 0) ? documentTagLabels : null,
|
|
471
346
|
summary: document.summary ?? null,
|
|
472
347
|
};
|
|
348
|
+
const documentTypeEntity = await this.#documentTypeRepository.load(document.typeId);
|
|
349
|
+
const result = await this.runAi(document.tenantId, DocumentWorkflowStep.Assignment, { document, properties: documentProperties, collectionIds: requestsCollectionIds }, {
|
|
350
|
+
targetId: documentTypeEntity.key ?? undefined,
|
|
351
|
+
defaultModel: this.#assignModel,
|
|
352
|
+
system: assignRequestSystemInstructions,
|
|
353
|
+
user: assignRequestUserInstructions,
|
|
354
|
+
data: { document: documentData, documentProperties: fromEntries(propertyEntries), requests },
|
|
355
|
+
schema: object({ requestId: nullable(string()) }),
|
|
356
|
+
config: { maxOutputTokens: 128 },
|
|
357
|
+
});
|
|
358
|
+
return result.requestId;
|
|
359
|
+
}
|
|
360
|
+
async runAi(tenantId, step, stepData, options) {
|
|
361
|
+
const config = options.aiConfig ?? await this.resolveAiConfiguration(tenantId, step, stepData);
|
|
362
|
+
const model = config.model ?? options.defaultModel;
|
|
363
|
+
const { systemPrompt, userPrompt } = buildPrompts({
|
|
364
|
+
baseSystemInstructions: options.system,
|
|
365
|
+
baseUserInstructions: options.user,
|
|
366
|
+
additionalSystemInstructions: config.prompt?.systemAddition,
|
|
367
|
+
additionalUserInstructions: config.prompt?.userAddition,
|
|
368
|
+
systemInstructionsOverride: config.prompt?.systemOverride,
|
|
369
|
+
userInstructionsOverride: config.prompt?.userOverride,
|
|
370
|
+
data: options.data,
|
|
371
|
+
media: isDefined(options.document) ? { mimeType: options.document.mimeType, content: await this.#documentFileService.getContent(options.document) } : undefined,
|
|
372
|
+
language: config.language,
|
|
373
|
+
});
|
|
473
374
|
const result = await this.#genkit.generate(genkitGenerationOptions({
|
|
474
|
-
model
|
|
475
|
-
config:
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
},
|
|
480
|
-
},
|
|
481
|
-
output: { schema: object({ requestId: nullable(string()) }) },
|
|
482
|
-
system: assignRequestSystemPrompt,
|
|
483
|
-
prompt: [{
|
|
484
|
-
text: `
|
|
485
|
-
${formatData({ document: documentData, documentProperties: fromEntries(propertyEntries) })}
|
|
486
|
-
|
|
487
|
-
${formatData({ requests })}
|
|
488
|
-
|
|
489
|
-
${assignRequestUserPrompt}
|
|
490
|
-
`.trim(),
|
|
491
|
-
}],
|
|
375
|
+
model,
|
|
376
|
+
config: options.config,
|
|
377
|
+
output: { schema: options.schema },
|
|
378
|
+
system: systemPrompt,
|
|
379
|
+
prompt: userPrompt,
|
|
492
380
|
}));
|
|
493
381
|
if (isNull(result.output)) {
|
|
494
|
-
throw new Error(`AI returned null output for
|
|
382
|
+
throw new Error(`AI returned null output for ${step} ${options.targetId ?? ''}`);
|
|
495
383
|
}
|
|
496
|
-
return result.output
|
|
384
|
+
return result.output;
|
|
385
|
+
}
|
|
386
|
+
async resolveAiConfiguration(tenantId, step, data) {
|
|
387
|
+
const globalConfig = await this.#aiProvider?.getGlobalConfiguration(tenantId);
|
|
388
|
+
const { typeKey, categoryKey } = await this.resolveContextKeys(tenantId, data);
|
|
389
|
+
const stepConfig = await match(step)
|
|
390
|
+
.with(DocumentWorkflowStep.ContentExtraction, async () => await this.#aiProvider?.getContentExtractionConfiguration(tenantId, data))
|
|
391
|
+
.with(DocumentWorkflowStep.Classification, async () => await this.#aiProvider?.getClassificationConfiguration(tenantId, data))
|
|
392
|
+
.with(DocumentWorkflowStep.DataExtraction, async () => await this.#aiProvider?.getDataExtractionConfiguration(tenantId, data))
|
|
393
|
+
.with(DocumentWorkflowStep.Assignment, async () => await this.#aiProvider?.getAssignmentConfiguration(tenantId, data))
|
|
394
|
+
.with(DocumentWorkflowStep.Validation, async () => await this.#aiProvider?.getValidationConfiguration(tenantId, data))
|
|
395
|
+
.exhaustive();
|
|
396
|
+
const configs = [
|
|
397
|
+
globalConfig?.defaults,
|
|
398
|
+
globalConfig?.steps?.[step],
|
|
399
|
+
(isNotNull(categoryKey) && isDefined(globalConfig?.categories)) ? globalConfig.categories[categoryKey] : undefined,
|
|
400
|
+
(isNotNull(typeKey) && isDefined(globalConfig?.documentTypes)) ? globalConfig.documentTypes[typeKey] : undefined,
|
|
401
|
+
stepConfig,
|
|
402
|
+
].filter(isDefined);
|
|
403
|
+
const model = mergeMostSpecific(configs, 'model');
|
|
404
|
+
const language = mergeMostSpecific(configs, 'language');
|
|
405
|
+
const systemAddition = resolveAdditions(configs.map((c) => c.prompt?.systemAddition));
|
|
406
|
+
const userAddition = resolveAdditions(configs.map((c) => c.prompt?.userAddition));
|
|
407
|
+
const systemOverride = mergeMostSpecific(configs.map((c) => c.prompt).filter(isDefined), 'systemOverride');
|
|
408
|
+
const userOverride = mergeMostSpecific(configs.map((c) => c.prompt).filter(isDefined), 'userOverride');
|
|
409
|
+
const extraction = this.mergeExtractionConfigs(configs.map((c) => c.extraction).filter(isDefined));
|
|
410
|
+
const classification = mergeMostSpecific(configs, 'classification');
|
|
411
|
+
return {
|
|
412
|
+
model,
|
|
413
|
+
language,
|
|
414
|
+
prompt: {
|
|
415
|
+
systemAddition,
|
|
416
|
+
userAddition,
|
|
417
|
+
systemOverride,
|
|
418
|
+
userOverride,
|
|
419
|
+
},
|
|
420
|
+
extraction,
|
|
421
|
+
classification,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
mergeExtractionConfigs(configs) {
|
|
425
|
+
if (configs.length == 0) {
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
const properties = {};
|
|
429
|
+
for (const config of configs) {
|
|
430
|
+
if (isDefined(config?.properties)) {
|
|
431
|
+
for (const [key, value] of objectEntries(config.properties)) {
|
|
432
|
+
properties[key] = value;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
title: mergeMostSpecific(configs, 'title'),
|
|
438
|
+
subtitle: mergeMostSpecific(configs, 'subtitle'),
|
|
439
|
+
date: mergeMostSpecific(configs, 'date'),
|
|
440
|
+
summary: mergeMostSpecific(configs, 'summary'),
|
|
441
|
+
tags: mergeMostSpecific(configs, 'tags'),
|
|
442
|
+
properties: (objectKeys(properties).length > 0) ? properties : undefined,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
async resolveContextKeys(tenantId, data) {
|
|
446
|
+
if (isUndefined(data)) {
|
|
447
|
+
return { typeKey: null, categoryKey: null };
|
|
448
|
+
}
|
|
449
|
+
let type = ('type' in data) ? data.type : undefined;
|
|
450
|
+
let category = ('category' in data) ? data.category : undefined;
|
|
451
|
+
if (isUndefined(type) && hasOwnProperty(data, 'document') && isNotNull(data.document.typeId)) {
|
|
452
|
+
type = await this.#documentTypeRepository.load(data.document.typeId);
|
|
453
|
+
}
|
|
454
|
+
if (isUndefined(category) && isNotNullOrUndefined(type)) {
|
|
455
|
+
category = await this.#documentCategoryTypeService.loadCategory(tenantId, type.categoryId);
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
typeKey: type?.key ?? null,
|
|
459
|
+
categoryKey: category?.key ?? null,
|
|
460
|
+
};
|
|
497
461
|
}
|
|
498
462
|
};
|
|
499
463
|
DocumentManagementAiService = DocumentManagementAiService_1 = __decorate([
|
|
@@ -514,3 +478,42 @@ function tryAiOutputDateObjectToNumericDate(dateObject) {
|
|
|
514
478
|
}
|
|
515
479
|
return date;
|
|
516
480
|
}
|
|
481
|
+
export function mergeFieldInstructions(instructionsKey, field, aiConfig) {
|
|
482
|
+
return mergeInstructions(dataExtractionInstructionsRaw['Field Specific Instructions'][instructionsKey], [aiConfig.extraction?.[field]], { formatTemplate: getFormatTemplate(field) });
|
|
483
|
+
}
|
|
484
|
+
export function mergeInstructions(base, overrides, options = {}) {
|
|
485
|
+
let result = base;
|
|
486
|
+
for (const override of overrides) {
|
|
487
|
+
if (isUndefined(override)) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (isString(override)) {
|
|
491
|
+
result = override;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (isDefined(override.format)) {
|
|
495
|
+
const template = options.formatTemplate ?? ((format) => `Use this format: ${format}`);
|
|
496
|
+
result = template(override.format);
|
|
497
|
+
}
|
|
498
|
+
if (isDefined(override.content)) {
|
|
499
|
+
const strategy = override.strategy ?? 'append';
|
|
500
|
+
if (strategy == 'replace') {
|
|
501
|
+
result = override.content;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
result = `${result}\n\n${override.content}`;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
510
|
+
function mergeMostSpecific(configs, key) {
|
|
511
|
+
return configs.reduceRight((acc, c) => acc ?? c?.[key], undefined);
|
|
512
|
+
}
|
|
513
|
+
function resolveAdditions(additions) {
|
|
514
|
+
const filtered = additions.filter(isDefined);
|
|
515
|
+
return (filtered.length == 0) ? undefined : (filtered.length == 1) ? filtered[0] : filtered;
|
|
516
|
+
}
|
|
517
|
+
function getFormatTemplate(fieldName) {
|
|
518
|
+
return (format) => `Format the ${fieldName} of the document exactly as follows: ${format}`;
|
|
519
|
+
}
|