@carlonicora/nestjs-neo4jsonapi 2.7.2 → 2.9.0
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/dist/agents/graph.creator/services/graph.creator.service.d.ts +82 -0
- package/dist/agents/graph.creator/services/graph.creator.service.d.ts.map +1 -1
- package/dist/agents/graph.creator/services/graph.creator.service.js +43 -10
- package/dist/agents/graph.creator/services/graph.creator.service.js.map +1 -1
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +7 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/config/base.config.d.ts.map +1 -1
- package/dist/config/base.config.js +4 -0
- package/dist/config/base.config.js.map +1 -1
- package/dist/config/interfaces/config.ai.interface.d.ts +5 -0
- package/dist/config/interfaces/config.ai.interface.d.ts.map +1 -1
- package/dist/config/interfaces/config.prompts.interface.d.ts +15 -0
- package/dist/config/interfaces/config.prompts.interface.d.ts.map +1 -1
- package/dist/core/llm/index.d.ts +2 -0
- package/dist/core/llm/index.d.ts.map +1 -1
- package/dist/core/llm/index.js +2 -0
- package/dist/core/llm/index.js.map +1 -1
- package/dist/core/llm/interfaces/ai-candidate.interface.d.ts +32 -0
- package/dist/core/llm/interfaces/ai-candidate.interface.d.ts.map +1 -0
- package/dist/core/llm/interfaces/ai-candidate.interface.js +15 -0
- package/dist/core/llm/interfaces/ai-candidate.interface.js.map +1 -0
- package/dist/core/llm/llm.module.d.ts.map +1 -1
- package/dist/core/llm/llm.module.js +6 -1
- package/dist/core/llm/llm.module.js.map +1 -1
- package/dist/core/llm/services/ai-connection-resolver.service.d.ts +107 -0
- package/dist/core/llm/services/ai-connection-resolver.service.d.ts.map +1 -0
- package/dist/core/llm/services/ai-connection-resolver.service.js +329 -0
- package/dist/core/llm/services/ai-connection-resolver.service.js.map +1 -0
- package/dist/core/llm/services/embedder.service.d.ts +21 -0
- package/dist/core/llm/services/embedder.service.d.ts.map +1 -1
- package/dist/core/llm/services/embedder.service.js +52 -3
- package/dist/core/llm/services/embedder.service.js.map +1 -1
- package/dist/core/llm/services/llm.service.d.ts +65 -18
- package/dist/core/llm/services/llm.service.d.ts.map +1 -1
- package/dist/core/llm/services/llm.service.js +214 -70
- package/dist/core/llm/services/llm.service.js.map +1 -1
- package/dist/core/llm/services/model.service.d.ts +79 -2
- package/dist/core/llm/services/model.service.d.ts.map +1 -1
- package/dist/core/llm/services/model.service.js +289 -31
- package/dist/core/llm/services/model.service.js.map +1 -1
- package/dist/core/llm/services/vision.llm.service.d.ts +25 -1
- package/dist/core/llm/services/vision.llm.service.d.ts.map +1 -1
- package/dist/core/llm/services/vision.llm.service.js +74 -33
- package/dist/core/llm/services/vision.llm.service.js.map +1 -1
- package/dist/foundations/ai-connection/ai-connection.module.d.ts +5 -0
- package/dist/foundations/ai-connection/ai-connection.module.d.ts.map +1 -0
- package/dist/foundations/ai-connection/ai-connection.module.js +37 -0
- package/dist/foundations/ai-connection/ai-connection.module.js.map +1 -0
- package/dist/foundations/ai-connection/controllers/ai-connection.controller.d.ts +32 -0
- package/dist/foundations/ai-connection/controllers/ai-connection.controller.d.ts.map +1 -0
- package/dist/foundations/ai-connection/controllers/ai-connection.controller.js +162 -0
- package/dist/foundations/ai-connection/controllers/ai-connection.controller.js.map +1 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.post.dto.d.ts +41 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.post.dto.d.ts.map +1 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.post.dto.js +189 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.post.dto.js.map +1 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.put.dto.d.ts +42 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.put.dto.d.ts.map +1 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.put.dto.js +179 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.put.dto.js.map +1 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.reorder.dto.d.ts +8 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.reorder.dto.d.ts.map +1 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.reorder.dto.js +38 -0
- package/dist/foundations/ai-connection/dtos/ai-connection.reorder.dto.js.map +1 -0
- package/dist/foundations/ai-connection/entities/ai-connection.d.ts +57 -0
- package/dist/foundations/ai-connection/entities/ai-connection.d.ts.map +1 -0
- package/dist/foundations/ai-connection/entities/ai-connection.js +73 -0
- package/dist/foundations/ai-connection/entities/ai-connection.js.map +1 -0
- package/dist/foundations/ai-connection/entities/ai-connection.meta.d.ts +3 -0
- package/dist/foundations/ai-connection/entities/ai-connection.meta.d.ts.map +1 -0
- package/dist/foundations/ai-connection/entities/ai-connection.meta.js +10 -0
- package/dist/foundations/ai-connection/entities/ai-connection.meta.js.map +1 -0
- package/dist/foundations/ai-connection/index.d.ts +12 -0
- package/dist/foundations/ai-connection/index.d.ts.map +1 -0
- package/dist/foundations/ai-connection/index.js +35 -0
- package/dist/foundations/ai-connection/index.js.map +1 -0
- package/dist/foundations/ai-connection/registry/ai-provider.registry.d.ts +54 -0
- package/dist/foundations/ai-connection/registry/ai-provider.registry.d.ts.map +1 -0
- package/dist/foundations/ai-connection/registry/ai-provider.registry.js +255 -0
- package/dist/foundations/ai-connection/registry/ai-provider.registry.js.map +1 -0
- package/dist/foundations/ai-connection/repositories/ai-connection.repository.d.ts +52 -0
- package/dist/foundations/ai-connection/repositories/ai-connection.repository.d.ts.map +1 -0
- package/dist/foundations/ai-connection/repositories/ai-connection.repository.js +85 -0
- package/dist/foundations/ai-connection/repositories/ai-connection.repository.js.map +1 -0
- package/dist/foundations/ai-connection/services/ai-connection-encryption.service.d.ts +52 -0
- package/dist/foundations/ai-connection/services/ai-connection-encryption.service.d.ts.map +1 -0
- package/dist/foundations/ai-connection/services/ai-connection-encryption.service.js +148 -0
- package/dist/foundations/ai-connection/services/ai-connection-encryption.service.js.map +1 -0
- package/dist/foundations/ai-connection/services/ai-connection.service.d.ts +79 -0
- package/dist/foundations/ai-connection/services/ai-connection.service.d.ts.map +1 -0
- package/dist/foundations/ai-connection/services/ai-connection.service.js +184 -0
- package/dist/foundations/ai-connection/services/ai-connection.service.js.map +1 -0
- package/dist/foundations/foundations.modules.d.ts.map +1 -1
- package/dist/foundations/foundations.modules.js +2 -0
- package/dist/foundations/foundations.modules.js.map +1 -1
- package/dist/foundations/index.d.ts +1 -0
- package/dist/foundations/index.d.ts.map +1 -1
- package/dist/foundations/index.js +1 -0
- package/dist/foundations/index.js.map +1 -1
- package/dist/foundations/tokenusage/services/tokenusage.service.d.ts +29 -0
- package/dist/foundations/tokenusage/services/tokenusage.service.d.ts.map +1 -1
- package/dist/foundations/tokenusage/services/tokenusage.service.js +15 -3
- package/dist/foundations/tokenusage/services/tokenusage.service.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AI_CONNECTIONS_CHANGED_EVENT = exports.AI_CONNECTION_TYPES = void 0;
|
|
4
|
+
exports.AI_CONNECTION_TYPES = [
|
|
5
|
+
"ai",
|
|
6
|
+
"aiLite",
|
|
7
|
+
"aiLarge",
|
|
8
|
+
"vision",
|
|
9
|
+
"audio",
|
|
10
|
+
"embedder",
|
|
11
|
+
"transcriber",
|
|
12
|
+
"documentAi",
|
|
13
|
+
];
|
|
14
|
+
exports.AI_CONNECTIONS_CHANGED_EVENT = "ai-connections.changed";
|
|
15
|
+
//# sourceMappingURL=ai-candidate.interface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-candidate.interface.js","sourceRoot":"","sources":["../../../../src/core/llm/interfaces/ai-candidate.interface.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAAG;IACjC,IAAI;IACJ,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,OAAO;IACP,UAAU;IACV,aAAa;IACb,YAAY;CACJ,CAAC;AAGE,QAAA,4BAA4B,GAAG,wBAAwB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm.module.d.ts","sourceRoot":"","sources":["../../../src/core/llm/llm.module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"llm.module.d.ts","sourceRoot":"","sources":["../../../src/core/llm/llm.module.ts"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;GAaG;AACH,qBAUa,SAAS;CAAG"}
|
|
@@ -9,7 +9,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.LLMModule = void 0;
|
|
10
10
|
const common_1 = require("@nestjs/common");
|
|
11
11
|
const redis_module_1 = require("../redis/redis.module");
|
|
12
|
+
const ai_connection_module_1 = require("../../foundations/ai-connection/ai-connection.module");
|
|
12
13
|
const tokenusage_module_1 = require("../../foundations/tokenusage/tokenusage.module");
|
|
14
|
+
const ai_connection_resolver_service_1 = require("./services/ai-connection-resolver.service");
|
|
13
15
|
const audio_llm_service_1 = require("./services/audio.llm.service");
|
|
14
16
|
const embedder_service_1 = require("./services/embedder.service");
|
|
15
17
|
const embedder_token_bucket_service_1 = require("./services/embedder-token-bucket.service");
|
|
@@ -22,6 +24,7 @@ const vision_llm_service_1 = require("./services/vision.llm.service");
|
|
|
22
24
|
const LLM_SERVICES = [
|
|
23
25
|
llm_service_1.LLMService,
|
|
24
26
|
model_service_1.ModelService,
|
|
27
|
+
ai_connection_resolver_service_1.AiConnectionResolverService,
|
|
25
28
|
embedder_service_1.EmbedderService,
|
|
26
29
|
embedder_token_bucket_service_1.EmbedderTokenBucketService,
|
|
27
30
|
vision_llm_service_1.VisionLLMService,
|
|
@@ -52,7 +55,9 @@ exports.LLMModule = LLMModule = __decorate([
|
|
|
52
55
|
(0, common_1.Module)({
|
|
53
56
|
// RedisModule provides RedisClientStorageService for EmbedderTokenBucketService's
|
|
54
57
|
// distributed token bucket. RedisModule is NOT @Global, so it must be imported here.
|
|
55
|
-
|
|
58
|
+
// AiConnectionModule provides the repository + encryption service the
|
|
59
|
+
// AiConnectionResolverService reads its snapshot through.
|
|
60
|
+
imports: [ai_connection_module_1.AiConnectionModule, tokenusage_module_1.TokenUsageModule, redis_module_1.RedisModule],
|
|
56
61
|
providers: LLM_SERVICES,
|
|
57
62
|
exports: LLM_SERVICES,
|
|
58
63
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm.module.js","sourceRoot":"","sources":["../../../src/core/llm/llm.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,wDAAoD;AACpD,sFAAkF;AAClF,oEAA+D;AAC/D,kEAA8D;AAC9D,4FAAsF;AACtF,oEAA+D;AAC/D,wEAAmE;AACnE,gFAAmE;AACnE,wDAAoD;AACpD,4DAAwD;AACxD,sEAAiE;AAEjE,MAAM,YAAY,GAAG;IACnB,wBAAU;IACV,4BAAY;IACZ,kCAAe;IACf,0DAA0B;IAC1B,qCAAgB;IAChB,uCAAiB;IACjB,mCAAe;IACf,uCAAa;IACb,mCAAe;CAChB,CAAC;AAEF;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"llm.module.js","sourceRoot":"","sources":["../../../src/core/llm/llm.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,wDAAoD;AACpD,+FAA0F;AAC1F,sFAAkF;AAClF,8FAAwF;AACxF,oEAA+D;AAC/D,kEAA8D;AAC9D,4FAAsF;AACtF,oEAA+D;AAC/D,wEAAmE;AACnE,gFAAmE;AACnE,wDAAoD;AACpD,4DAAwD;AACxD,sEAAiE;AAEjE,MAAM,YAAY,GAAG;IACnB,wBAAU;IACV,4BAAY;IACZ,4DAA2B;IAC3B,kCAAe;IACf,0DAA0B;IAC1B,qCAAgB;IAChB,uCAAiB;IACjB,mCAAe;IACf,uCAAa;IACb,mCAAe;CAChB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AAWI,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IAVrB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,kFAAkF;QAClF,qFAAqF;QACrF,sEAAsE;QACtE,0DAA0D;QAC1D,OAAO,EAAE,CAAC,yCAAkB,EAAE,oCAAgB,EAAE,0BAAW,CAAC;QAC5D,SAAS,EAAE,YAAY;QACvB,OAAO,EAAE,YAAY;KACtB,CAAC;GACW,SAAS,CAAG"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { OnModuleInit } from "@nestjs/common";
|
|
2
|
+
import { ConfigService } from "@nestjs/config";
|
|
3
|
+
import { ClsService } from "nestjs-cls";
|
|
4
|
+
import { BaseConfigInterface } from "../../../config/interfaces";
|
|
5
|
+
import type { AiConnection } from "../../../foundations/ai-connection/entities/ai-connection";
|
|
6
|
+
import { AiConnectionRepository } from "../../../foundations/ai-connection/repositories/ai-connection.repository";
|
|
7
|
+
import { AiConnectionEncryptionService } from "../../../foundations/ai-connection/services/ai-connection-encryption.service";
|
|
8
|
+
import { AppLoggingService } from "../../logging/services/logging.service";
|
|
9
|
+
import { AiConnectionType, ResolvedAiCandidate } from "../interfaces/ai-candidate.interface";
|
|
10
|
+
/**
|
|
11
|
+
* Normalises one `AiConnection` node into the candidate shape `buildChatModel`
|
|
12
|
+
* (and its embedder/transcriber siblings) already consume.
|
|
13
|
+
*
|
|
14
|
+
* Pure on purpose: the decryptor is injected so the caller decides what a
|
|
15
|
+
* decryption failure means. It THROWS when a stored secret cannot be decrypted —
|
|
16
|
+
* {@link AiConnectionResolverService.refreshNow} catches that and skips the
|
|
17
|
+
* connection, so a single unreadable secret never takes the chain down
|
|
18
|
+
* (spec § 5 "Error handling").
|
|
19
|
+
*/
|
|
20
|
+
export declare function toCandidate(connection: AiConnection, decrypt: (value: string) => string): ResolvedAiCandidate;
|
|
21
|
+
/**
|
|
22
|
+
* Resolves the ordered fallback chain for an AI connection type.
|
|
23
|
+
*
|
|
24
|
+
* Holds an in-memory snapshot of every enabled `AiConnection` node (decrypted,
|
|
25
|
+
* grouped into chains by `(connectionType, companyId | null)`, ordered by
|
|
26
|
+
* `position`), refreshed at boot, every 60s, and immediately on any admin write
|
|
27
|
+
* in this process (`AI_CONNECTIONS_CHANGED_EVENT`).
|
|
28
|
+
*
|
|
29
|
+
* The snapshot exists because `ModelService.getLLM()` is synchronous and called
|
|
30
|
+
* directly by several library services: resolution must stay a pure sync lookup
|
|
31
|
+
* with no signature ripple (spec § 2).
|
|
32
|
+
*
|
|
33
|
+
* Failure ALWAYS degrades toward today's `.env` behaviour, never toward "no AI"
|
|
34
|
+
* (spec § 5): an empty table, a failed refresh, an undecryptable secret or an
|
|
35
|
+
* unknown connection type all leave the `.env` candidate in place, and nothing
|
|
36
|
+
* on the `resolve()` hot path throws.
|
|
37
|
+
*/
|
|
38
|
+
export declare class AiConnectionResolverService implements OnModuleInit {
|
|
39
|
+
private readonly repository;
|
|
40
|
+
private readonly encryption;
|
|
41
|
+
private readonly cls;
|
|
42
|
+
private readonly configService;
|
|
43
|
+
private readonly logger?;
|
|
44
|
+
/** `${connectionType}|${companyId ?? "global"}` → ordered candidates. */
|
|
45
|
+
private snapshot;
|
|
46
|
+
/** connectionId → epoch ms before which the candidate is skipped. */
|
|
47
|
+
private readonly cooldownUntil;
|
|
48
|
+
private refreshTimer?;
|
|
49
|
+
constructor(repository: AiConnectionRepository, encryption: AiConnectionEncryptionService, cls: ClsService, configService: ConfigService<BaseConfigInterface>, logger?: AppLoggingService);
|
|
50
|
+
/**
|
|
51
|
+
* Loads the first snapshot and starts the periodic refresh. Neither is
|
|
52
|
+
* awaited and neither can reject: boot must never depend on the database
|
|
53
|
+
* holding AI connection rows (spec § 5).
|
|
54
|
+
*/
|
|
55
|
+
onModuleInit(): void;
|
|
56
|
+
/**
|
|
57
|
+
* The ordered candidates to try for `type`, best first.
|
|
58
|
+
*
|
|
59
|
+
* Per-company chain when the request's company has one, otherwise the global
|
|
60
|
+
* chain; the `.env` block is ALWAYS appended last. Candidates inside their
|
|
61
|
+
* cooldown window are dropped — unless that empties the list, in which case
|
|
62
|
+
* the full chain is returned anyway (fail-open, spec § 2).
|
|
63
|
+
*
|
|
64
|
+
* Never throws.
|
|
65
|
+
*/
|
|
66
|
+
resolve(type: AiConnectionType): ResolvedAiCandidate[];
|
|
67
|
+
/**
|
|
68
|
+
* Parks a connection for `ai.connectionCooldownMinutes` after a transient
|
|
69
|
+
* failure (429 / 5xx / network). The `.env` candidate is tracked under
|
|
70
|
+
* `env:<type>` like any other, so a dead env fallback cannot block recovery.
|
|
71
|
+
*/
|
|
72
|
+
markFailure(connectionId: string): void;
|
|
73
|
+
/**
|
|
74
|
+
* Rebuilds the snapshot from the database. Also the handler for admin writes
|
|
75
|
+
* in this process; other processes catch up at the next scheduled refresh.
|
|
76
|
+
*
|
|
77
|
+
* A read failure keeps the PREVIOUS snapshot — a database blip must not wipe
|
|
78
|
+
* working configuration (spec § 5).
|
|
79
|
+
*/
|
|
80
|
+
refreshNow(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Reads every connection inside a CLS context: the refresh runs outside any
|
|
83
|
+
* HTTP request, and the query plumbing reads CLS (`companyId`) unconditionally.
|
|
84
|
+
*/
|
|
85
|
+
private readConnections;
|
|
86
|
+
/**
|
|
87
|
+
* The company of the current request, or undefined when there is none.
|
|
88
|
+
*
|
|
89
|
+
* Optional-call plus a silent catch: the resolver is also used by workers and
|
|
90
|
+
* boot-time code with no CLS context at all, which is an ordinary state and
|
|
91
|
+
* must not log or throw — it simply means "use the global chain".
|
|
92
|
+
*/
|
|
93
|
+
private currentCompanyId;
|
|
94
|
+
private chainKey;
|
|
95
|
+
private get aiConfig();
|
|
96
|
+
private get cooldownMinutes();
|
|
97
|
+
/**
|
|
98
|
+
* The `.env` block for a type, mapped field-for-field onto a candidate. This
|
|
99
|
+
* is the final link of every chain and the whole behaviour when the table is
|
|
100
|
+
* empty — with zero `AiConnection` nodes, resolution is byte-for-byte today's.
|
|
101
|
+
*/
|
|
102
|
+
private buildEnvCandidate;
|
|
103
|
+
/** Same block mapping the admin API uses for `meta.envDefaults`. */
|
|
104
|
+
private envBlockFor;
|
|
105
|
+
private warn;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=ai-connection-resolver.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-connection-resolver.service.d.ts","sourceRoot":"","sources":["../../../../src/core/llm/services/ai-connection-resolver.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,YAAY,EAAY,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAqB,MAAM,4BAA4B,CAAC;AACpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2DAA2D,CAAC;AAC9F,OAAO,EAAE,sBAAsB,EAAE,MAAM,0EAA0E,CAAC;AAClH,OAAO,EAAE,6BAA6B,EAAE,MAAM,8EAA8E,CAAC;AAC7H,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAGL,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,sCAAsC,CAAC;AA2B9C;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,mBAAmB,CA6B7G;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBACa,2BAA4B,YAAW,YAAY;IAU5D,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAGlB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAftC,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAA4C;IAE5D,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAE3D,OAAO,CAAC,YAAY,CAAC,CAAiC;gBAGnC,UAAU,EAAE,sBAAsB,EAClC,UAAU,EAAE,6BAA6B,EACzC,GAAG,EAAE,UAAU,EACf,aAAa,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAGrC,MAAM,CAAC,EAAE,iBAAiB;IAGzD;;;;OAIG;IACH,YAAY,IAAI,IAAI;IASpB;;;;;;;;;OASG;IACH,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,mBAAmB,EAAE;IA0BtD;;;;OAIG;IACH,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAKvC;;;;;;OAMG;IAEG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAuCjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAOvB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,QAAQ;IAIhB,OAAO,KAAK,QAAQ,GAMnB;IAED,OAAO,KAAK,eAAe,GAM1B;IAED;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IA8BzB,oEAAoE;IACpE,OAAO,CAAC,WAAW;IAuBnB,OAAO,CAAC,IAAI;CAOb"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var AiConnectionResolverService_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.AiConnectionResolverService = void 0;
|
|
17
|
+
exports.toCandidate = toCandidate;
|
|
18
|
+
const common_1 = require("@nestjs/common");
|
|
19
|
+
const config_1 = require("@nestjs/config");
|
|
20
|
+
const event_emitter_1 = require("@nestjs/event-emitter");
|
|
21
|
+
const nestjs_cls_1 = require("nestjs-cls");
|
|
22
|
+
const ai_connection_repository_1 = require("../../../foundations/ai-connection/repositories/ai-connection.repository");
|
|
23
|
+
const ai_connection_encryption_service_1 = require("../../../foundations/ai-connection/services/ai-connection-encryption.service");
|
|
24
|
+
const logging_service_1 = require("../../logging/services/logging.service");
|
|
25
|
+
const ai_candidate_interface_1 = require("../interfaces/ai-candidate.interface");
|
|
26
|
+
/** How often the in-memory snapshot is rebuilt from the database. */
|
|
27
|
+
const SNAPSHOT_REFRESH_INTERVAL_MS = 60_000;
|
|
28
|
+
/** Cooldown used when `ai.connectionCooldownMinutes` is missing from config. */
|
|
29
|
+
const DEFAULT_COOLDOWN_MINUTES = 5;
|
|
30
|
+
/** Snapshot key for the chain that applies to every company. */
|
|
31
|
+
const GLOBAL_SCOPE = "global";
|
|
32
|
+
const KNOWN_CONNECTION_TYPES = new Set(ai_candidate_interface_1.AI_CONNECTION_TYPES);
|
|
33
|
+
const asString = (value) => typeof value === "string" && value !== "" ? value : undefined;
|
|
34
|
+
const asNumber = (value) => (typeof value === "number" ? value : undefined);
|
|
35
|
+
const asBoolean = (value) => (typeof value === "boolean" ? value : undefined);
|
|
36
|
+
/**
|
|
37
|
+
* Normalises one `AiConnection` node into the candidate shape `buildChatModel`
|
|
38
|
+
* (and its embedder/transcriber siblings) already consume.
|
|
39
|
+
*
|
|
40
|
+
* Pure on purpose: the decryptor is injected so the caller decides what a
|
|
41
|
+
* decryption failure means. It THROWS when a stored secret cannot be decrypted —
|
|
42
|
+
* {@link AiConnectionResolverService.refreshNow} catches that and skips the
|
|
43
|
+
* connection, so a single unreadable secret never takes the chain down
|
|
44
|
+
* (spec § 5 "Error handling").
|
|
45
|
+
*/
|
|
46
|
+
function toCandidate(connection, decrypt) {
|
|
47
|
+
return {
|
|
48
|
+
source: "db",
|
|
49
|
+
connectionId: connection.id,
|
|
50
|
+
connectionType: connection.connectionType,
|
|
51
|
+
provider: connection.provider,
|
|
52
|
+
apiKey: connection.apiKey ? decrypt(connection.apiKey) : "",
|
|
53
|
+
model: connection.model ?? "",
|
|
54
|
+
url: connection.url ?? "",
|
|
55
|
+
region: connection.region,
|
|
56
|
+
instance: connection.instance,
|
|
57
|
+
apiVersion: connection.apiVersion,
|
|
58
|
+
googleCredentialsBase64: connection.googleCredentialsBase64
|
|
59
|
+
? decrypt(connection.googleCredentialsBase64)
|
|
60
|
+
: undefined,
|
|
61
|
+
allowFallbacks: connection.allowFallbacks,
|
|
62
|
+
reasoningEffort: connection.reasoningEffort,
|
|
63
|
+
maxOutputTokens: connection.maxOutputTokens,
|
|
64
|
+
dimensions: connection.dimensions,
|
|
65
|
+
inputCostPer1MTokens: connection.inputCostPer1MTokens,
|
|
66
|
+
outputCostPer1MTokens: connection.outputCostPer1MTokens,
|
|
67
|
+
cachedInputCostPer1MTokens: connection.cachedInputCostPer1MTokens,
|
|
68
|
+
costPerMinute: connection.costPerMinute,
|
|
69
|
+
costPerPage: connection.costPerPage,
|
|
70
|
+
directUrl: connection.directUrl,
|
|
71
|
+
language: connection.language,
|
|
72
|
+
directFormat: connection.directFormat,
|
|
73
|
+
directProvider: connection.directProvider,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resolves the ordered fallback chain for an AI connection type.
|
|
78
|
+
*
|
|
79
|
+
* Holds an in-memory snapshot of every enabled `AiConnection` node (decrypted,
|
|
80
|
+
* grouped into chains by `(connectionType, companyId | null)`, ordered by
|
|
81
|
+
* `position`), refreshed at boot, every 60s, and immediately on any admin write
|
|
82
|
+
* in this process (`AI_CONNECTIONS_CHANGED_EVENT`).
|
|
83
|
+
*
|
|
84
|
+
* The snapshot exists because `ModelService.getLLM()` is synchronous and called
|
|
85
|
+
* directly by several library services: resolution must stay a pure sync lookup
|
|
86
|
+
* with no signature ripple (spec § 2).
|
|
87
|
+
*
|
|
88
|
+
* Failure ALWAYS degrades toward today's `.env` behaviour, never toward "no AI"
|
|
89
|
+
* (spec § 5): an empty table, a failed refresh, an undecryptable secret or an
|
|
90
|
+
* unknown connection type all leave the `.env` candidate in place, and nothing
|
|
91
|
+
* on the `resolve()` hot path throws.
|
|
92
|
+
*/
|
|
93
|
+
let AiConnectionResolverService = AiConnectionResolverService_1 = class AiConnectionResolverService {
|
|
94
|
+
constructor(repository, encryption, cls, configService, logger) {
|
|
95
|
+
this.repository = repository;
|
|
96
|
+
this.encryption = encryption;
|
|
97
|
+
this.cls = cls;
|
|
98
|
+
this.configService = configService;
|
|
99
|
+
this.logger = logger;
|
|
100
|
+
/** `${connectionType}|${companyId ?? "global"}` → ordered candidates. */
|
|
101
|
+
this.snapshot = new Map();
|
|
102
|
+
/** connectionId → epoch ms before which the candidate is skipped. */
|
|
103
|
+
this.cooldownUntil = new Map();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Loads the first snapshot and starts the periodic refresh. Neither is
|
|
107
|
+
* awaited and neither can reject: boot must never depend on the database
|
|
108
|
+
* holding AI connection rows (spec § 5).
|
|
109
|
+
*/
|
|
110
|
+
onModuleInit() {
|
|
111
|
+
void this.refreshNow().catch((error) => this.warn("initial snapshot load failed", error));
|
|
112
|
+
this.refreshTimer = setInterval(() => {
|
|
113
|
+
void this.refreshNow().catch((error) => this.warn("scheduled snapshot refresh failed", error));
|
|
114
|
+
}, SNAPSHOT_REFRESH_INTERVAL_MS);
|
|
115
|
+
// Never hold the process open for a cache refresh.
|
|
116
|
+
this.refreshTimer.unref?.();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The ordered candidates to try for `type`, best first.
|
|
120
|
+
*
|
|
121
|
+
* Per-company chain when the request's company has one, otherwise the global
|
|
122
|
+
* chain; the `.env` block is ALWAYS appended last. Candidates inside their
|
|
123
|
+
* cooldown window are dropped — unless that empties the list, in which case
|
|
124
|
+
* the full chain is returned anyway (fail-open, spec § 2).
|
|
125
|
+
*
|
|
126
|
+
* Never throws.
|
|
127
|
+
*/
|
|
128
|
+
resolve(type) {
|
|
129
|
+
const candidates = [];
|
|
130
|
+
try {
|
|
131
|
+
const companyId = this.currentCompanyId();
|
|
132
|
+
if (companyId) {
|
|
133
|
+
const scoped = this.snapshot.get(this.chainKey(type, companyId));
|
|
134
|
+
if (scoped?.length)
|
|
135
|
+
candidates.push(...scoped);
|
|
136
|
+
}
|
|
137
|
+
if (candidates.length === 0) {
|
|
138
|
+
const global = this.snapshot.get(this.chainKey(type, undefined));
|
|
139
|
+
if (global?.length)
|
|
140
|
+
candidates.push(...global);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
// A broken CLS/snapshot read degrades to the env candidate alone.
|
|
145
|
+
this.warn(`chain lookup failed for "${type}"`, error);
|
|
146
|
+
candidates.length = 0;
|
|
147
|
+
}
|
|
148
|
+
candidates.push(this.buildEnvCandidate(type));
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const healthy = candidates.filter((candidate) => (this.cooldownUntil.get(candidate.connectionId) ?? 0) <= now);
|
|
151
|
+
return healthy.length > 0 ? healthy : candidates;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Parks a connection for `ai.connectionCooldownMinutes` after a transient
|
|
155
|
+
* failure (429 / 5xx / network). The `.env` candidate is tracked under
|
|
156
|
+
* `env:<type>` like any other, so a dead env fallback cannot block recovery.
|
|
157
|
+
*/
|
|
158
|
+
markFailure(connectionId) {
|
|
159
|
+
if (!connectionId)
|
|
160
|
+
return;
|
|
161
|
+
this.cooldownUntil.set(connectionId, Date.now() + this.cooldownMinutes * 60_000);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Rebuilds the snapshot from the database. Also the handler for admin writes
|
|
165
|
+
* in this process; other processes catch up at the next scheduled refresh.
|
|
166
|
+
*
|
|
167
|
+
* A read failure keeps the PREVIOUS snapshot — a database blip must not wipe
|
|
168
|
+
* working configuration (spec § 5).
|
|
169
|
+
*/
|
|
170
|
+
async refreshNow() {
|
|
171
|
+
let connections;
|
|
172
|
+
try {
|
|
173
|
+
connections = await this.readConnections();
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
this.warn("snapshot refresh failed — keeping the previous snapshot", error);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const next = new Map();
|
|
180
|
+
const ordered = [...(connections ?? [])].sort((a, b) => (a?.position ?? 0) - (b?.position ?? 0));
|
|
181
|
+
for (const connection of ordered) {
|
|
182
|
+
if (!connection || connection.enabled === false)
|
|
183
|
+
continue;
|
|
184
|
+
if (!KNOWN_CONNECTION_TYPES.has(connection.connectionType)) {
|
|
185
|
+
this.warn(`skipping connection ${connection.id}: unknown connectionType "${connection.connectionType}"`);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
let candidate;
|
|
189
|
+
try {
|
|
190
|
+
candidate = toCandidate(connection, (value) => this.encryption.decrypt(value));
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
this.warn(`skipping connection ${connection.id}: stored secret could not be decrypted`, error);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const key = this.chainKey(candidate.connectionType, connection.companyId);
|
|
197
|
+
const chain = next.get(key);
|
|
198
|
+
if (chain)
|
|
199
|
+
chain.push(candidate);
|
|
200
|
+
else
|
|
201
|
+
next.set(key, [candidate]);
|
|
202
|
+
}
|
|
203
|
+
this.snapshot = next;
|
|
204
|
+
}
|
|
205
|
+
// --- internals ----------------------------------------------------------
|
|
206
|
+
/**
|
|
207
|
+
* Reads every connection inside a CLS context: the refresh runs outside any
|
|
208
|
+
* HTTP request, and the query plumbing reads CLS (`companyId`) unconditionally.
|
|
209
|
+
*/
|
|
210
|
+
readConnections() {
|
|
211
|
+
if (typeof this.cls?.run === "function") {
|
|
212
|
+
return this.cls.run(async () => this.repository.findAllForResolver());
|
|
213
|
+
}
|
|
214
|
+
return this.repository.findAllForResolver();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The company of the current request, or undefined when there is none.
|
|
218
|
+
*
|
|
219
|
+
* Optional-call plus a silent catch: the resolver is also used by workers and
|
|
220
|
+
* boot-time code with no CLS context at all, which is an ordinary state and
|
|
221
|
+
* must not log or throw — it simply means "use the global chain".
|
|
222
|
+
*/
|
|
223
|
+
currentCompanyId() {
|
|
224
|
+
try {
|
|
225
|
+
const companyId = this.cls?.get?.("companyId");
|
|
226
|
+
return typeof companyId === "string" && companyId !== "" ? companyId : undefined;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
chainKey(type, companyId) {
|
|
233
|
+
return `${type}|${companyId ?? GLOBAL_SCOPE}`;
|
|
234
|
+
}
|
|
235
|
+
get aiConfig() {
|
|
236
|
+
try {
|
|
237
|
+
return this.configService?.get("ai");
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
get cooldownMinutes() {
|
|
244
|
+
// `connectionCooldownMinutes` is added to the `ai` block by the config task;
|
|
245
|
+
// default defensively so this works even when the read returns undefined.
|
|
246
|
+
const ai = this.aiConfig;
|
|
247
|
+
const configured = ai?.connectionCooldownMinutes;
|
|
248
|
+
return typeof configured === "number" && configured > 0 ? configured : DEFAULT_COOLDOWN_MINUTES;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The `.env` block for a type, mapped field-for-field onto a candidate. This
|
|
252
|
+
* is the final link of every chain and the whole behaviour when the table is
|
|
253
|
+
* empty — with zero `AiConnection` nodes, resolution is byte-for-byte today's.
|
|
254
|
+
*/
|
|
255
|
+
buildEnvCandidate(type) {
|
|
256
|
+
const block = this.envBlockFor(type);
|
|
257
|
+
return {
|
|
258
|
+
source: "env",
|
|
259
|
+
connectionId: `env:${type}`,
|
|
260
|
+
connectionType: type,
|
|
261
|
+
provider: asString(block.provider) ?? "",
|
|
262
|
+
apiKey: asString(block.apiKey) ?? "",
|
|
263
|
+
model: asString(block.model) ?? "",
|
|
264
|
+
url: asString(block.url) ?? "",
|
|
265
|
+
region: asString(block.region),
|
|
266
|
+
instance: asString(block.instance),
|
|
267
|
+
apiVersion: asString(block.apiVersion),
|
|
268
|
+
googleCredentialsBase64: asString(block.googleCredentialsBase64),
|
|
269
|
+
allowFallbacks: asBoolean(block.allowFallbacks),
|
|
270
|
+
reasoningEffort: asString(block.reasoningEffort),
|
|
271
|
+
maxOutputTokens: asNumber(block.maxOutputTokens),
|
|
272
|
+
dimensions: asNumber(block.dimensions),
|
|
273
|
+
inputCostPer1MTokens: asNumber(block.inputCostPer1MTokens),
|
|
274
|
+
outputCostPer1MTokens: asNumber(block.outputCostPer1MTokens),
|
|
275
|
+
cachedInputCostPer1MTokens: asNumber(block.cachedInputCostPer1MTokens),
|
|
276
|
+
costPerMinute: asNumber(block.costPerMinute),
|
|
277
|
+
costPerPage: asNumber(block.costPerPage),
|
|
278
|
+
directUrl: asString(block.directUrl),
|
|
279
|
+
language: asString(block.language),
|
|
280
|
+
directFormat: asString(block.directFormat),
|
|
281
|
+
directProvider: asString(block.directProvider),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/** Same block mapping the admin API uses for `meta.envDefaults`. */
|
|
285
|
+
envBlockFor(type) {
|
|
286
|
+
const ai = this.aiConfig;
|
|
287
|
+
if (!ai)
|
|
288
|
+
return {};
|
|
289
|
+
switch (type) {
|
|
290
|
+
case "aiLite":
|
|
291
|
+
return (ai.aiLite ?? {});
|
|
292
|
+
case "aiLarge":
|
|
293
|
+
return (ai.aiLarge ?? {});
|
|
294
|
+
case "vision":
|
|
295
|
+
return (ai.vision ?? {});
|
|
296
|
+
case "audio":
|
|
297
|
+
return (ai.audio ?? {});
|
|
298
|
+
case "embedder":
|
|
299
|
+
return (ai.embedder ?? {});
|
|
300
|
+
case "transcriber":
|
|
301
|
+
return (ai.transcriber ?? {});
|
|
302
|
+
case "documentAi":
|
|
303
|
+
return (ai.documentAi ?? {});
|
|
304
|
+
default:
|
|
305
|
+
return (ai.ai ?? {});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
warn(message, error) {
|
|
309
|
+
const detail = error instanceof Error ? error.message : error !== undefined ? String(error) : undefined;
|
|
310
|
+
this.logger?.warn(detail ? `[AiConnectionResolver] ${message}: ${detail}` : `[AiConnectionResolver] ${message}`, AiConnectionResolverService_1.name);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
exports.AiConnectionResolverService = AiConnectionResolverService;
|
|
314
|
+
__decorate([
|
|
315
|
+
(0, event_emitter_1.OnEvent)(ai_candidate_interface_1.AI_CONNECTIONS_CHANGED_EVENT),
|
|
316
|
+
__metadata("design:type", Function),
|
|
317
|
+
__metadata("design:paramtypes", []),
|
|
318
|
+
__metadata("design:returntype", Promise)
|
|
319
|
+
], AiConnectionResolverService.prototype, "refreshNow", null);
|
|
320
|
+
exports.AiConnectionResolverService = AiConnectionResolverService = AiConnectionResolverService_1 = __decorate([
|
|
321
|
+
(0, common_1.Injectable)(),
|
|
322
|
+
__param(4, (0, common_1.Optional)()),
|
|
323
|
+
__metadata("design:paramtypes", [ai_connection_repository_1.AiConnectionRepository,
|
|
324
|
+
ai_connection_encryption_service_1.AiConnectionEncryptionService,
|
|
325
|
+
nestjs_cls_1.ClsService,
|
|
326
|
+
config_1.ConfigService,
|
|
327
|
+
logging_service_1.AppLoggingService])
|
|
328
|
+
], AiConnectionResolverService);
|
|
329
|
+
//# sourceMappingURL=ai-connection-resolver.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-connection-resolver.service.js","sourceRoot":"","sources":["../../../../src/core/llm/services/ai-connection-resolver.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,6 +21,27 @@ export declare class EmbedderService {
|
|
|
21
21
|
private readonly tokenUsageRecorder?;
|
|
22
22
|
private readonly logger;
|
|
23
23
|
constructor(modelService: ModelService, config: ConfigService<BaseConfigInterface>, tokenUsageService?: TokenUsageService, tokenUsageRecorder?: TokenUsageRecorderInterface);
|
|
24
|
+
/**
|
|
25
|
+
* Runs ONE embedding invocation, with a single failover retry on a transient
|
|
26
|
+
* provider failure (429/5xx/socket-level — classified by exactly the rule
|
|
27
|
+
* `LLMService` uses).
|
|
28
|
+
*
|
|
29
|
+
* ONE retry only, and only when there is somewhere to go: the failed
|
|
30
|
+
* connection is put into cooldown and the embedder is rebuilt, so
|
|
31
|
+
* `getEmbedder()` resolves the NEXT healthy link of the chain. A chain of one
|
|
32
|
+
* (no DB-backed embedder connection — just the `.env` block) has nowhere to
|
|
33
|
+
* fail over to, so the rejection propagates immediately, exactly as it does
|
|
34
|
+
* today. Everything beyond that single re-resolution belongs to the BullMQ
|
|
35
|
+
* job retry that owns these calls (spec § 2, "Other modalities").
|
|
36
|
+
*/
|
|
37
|
+
private withCandidateFailover;
|
|
38
|
+
/**
|
|
39
|
+
* Puts the embedder connection currently in service into its cooldown window
|
|
40
|
+
* and returns it — or undefined when there is nothing to fail over to: no
|
|
41
|
+
* candidate-aware `ModelService`, a chain of one (`.env` only), or a registry
|
|
42
|
+
* that threw. Undefined means "behave exactly as before failover existed".
|
|
43
|
+
*/
|
|
44
|
+
private failCurrentEmbedderCandidate;
|
|
24
45
|
vectoriseText(params: {
|
|
25
46
|
text: string;
|
|
26
47
|
attribution?: EmbedderAttribution;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedder.service.d.ts","sourceRoot":"","sources":["../../../../src/core/llm/services/embedder.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAwB,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAqB,MAAM,4BAA4B,CAAC;AAEpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,6DAA6D,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"embedder.service.d.ts","sourceRoot":"","sources":["../../../../src/core/llm/services/embedder.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAwB,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAqB,MAAM,4BAA4B,CAAC;AAEpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,6DAA6D,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAehE;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAcD,qBACa,eAAe;IAIxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAIX,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IACL,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAThF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;gBAGxC,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAI9B,iBAAiB,CAAC,EAAE,iBAAiB,EACP,kBAAkB,CAAC,EAAE,2BAA2B;IAG7G;;;;;;;;;;;;OAYG;YACW,qBAAqB;IAgBnC;;;;;OAKG;IACH,OAAO,CAAC,4BAA4B;IAc9B,aAAa,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,mBAAmB,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAWxF,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IA6B5F;;;;;OAKG;YACW,YAAY;IAO1B;;;;OAIG;YACW,WAAW;IAOzB,OAAO,CAAC,kBAAkB;IAW1B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAuB1B,qEAAqE;IACrE,OAAO,CAAC,gBAAgB;IAaxB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IAYhB,0FAA0F;IAC1F,OAAO,CAAC,SAAS,CAA8B;IAE/C;;;;;;;;;OASG;YACW,YAAY;IA4B1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;YACW,qBAAqB;IASnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;;OAGG;IACH,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;CAsBpB"}
|
|
@@ -21,6 +21,7 @@ const tokens_1 = require("../../../common/tokens");
|
|
|
21
21
|
const tokenusage_type_1 = require("../../../foundations/tokenusage/enums/tokenusage.type");
|
|
22
22
|
const tokenusage_service_1 = require("../../../foundations/tokenusage/services/tokenusage.service");
|
|
23
23
|
const model_service_1 = require("../../llm/services/model.service");
|
|
24
|
+
const llm_service_1 = require("./llm.service");
|
|
24
25
|
/**
|
|
25
26
|
* Ceiling for ONE embedding input. The provider hard cap is 8192 tokens
|
|
26
27
|
* (`400 Invalid 'input[0]': maximum input length is 8192 tokens` from
|
|
@@ -39,6 +40,54 @@ let EmbedderService = EmbedderService_1 = class EmbedderService {
|
|
|
39
40
|
this.tokenUsageRecorder = tokenUsageRecorder;
|
|
40
41
|
this.logger = new common_1.Logger(EmbedderService_1.name);
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Runs ONE embedding invocation, with a single failover retry on a transient
|
|
45
|
+
* provider failure (429/5xx/socket-level — classified by exactly the rule
|
|
46
|
+
* `LLMService` uses).
|
|
47
|
+
*
|
|
48
|
+
* ONE retry only, and only when there is somewhere to go: the failed
|
|
49
|
+
* connection is put into cooldown and the embedder is rebuilt, so
|
|
50
|
+
* `getEmbedder()` resolves the NEXT healthy link of the chain. A chain of one
|
|
51
|
+
* (no DB-backed embedder connection — just the `.env` block) has nowhere to
|
|
52
|
+
* fail over to, so the rejection propagates immediately, exactly as it does
|
|
53
|
+
* today. Everything beyond that single re-resolution belongs to the BullMQ
|
|
54
|
+
* job retry that owns these calls (spec § 2, "Other modalities").
|
|
55
|
+
*/
|
|
56
|
+
async withCandidateFailover(work) {
|
|
57
|
+
try {
|
|
58
|
+
return await work(this.modelService.getEmbedder());
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (!(0, llm_service_1.isTransientNetworkError)(error))
|
|
62
|
+
throw error;
|
|
63
|
+
const failed = this.failCurrentEmbedderCandidate();
|
|
64
|
+
if (!failed)
|
|
65
|
+
throw error;
|
|
66
|
+
this.logger.warn(`Embedder connection ${failed.connectionId} failed transiently — retrying once on the next candidate: ${String(error)}`);
|
|
67
|
+
return work(this.modelService.getEmbedder());
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Puts the embedder connection currently in service into its cooldown window
|
|
72
|
+
* and returns it — or undefined when there is nothing to fail over to: no
|
|
73
|
+
* candidate-aware `ModelService`, a chain of one (`.env` only), or a registry
|
|
74
|
+
* that threw. Undefined means "behave exactly as before failover existed".
|
|
75
|
+
*/
|
|
76
|
+
failCurrentEmbedderCandidate() {
|
|
77
|
+
const resolver = this.modelService;
|
|
78
|
+
if (typeof resolver.getCandidatesForType !== "function" || typeof resolver.notifyCandidateFailure !== "function")
|
|
79
|
+
return undefined;
|
|
80
|
+
try {
|
|
81
|
+
const candidates = resolver.getCandidatesForType("embedder") ?? [];
|
|
82
|
+
if (candidates.length < 2)
|
|
83
|
+
return undefined;
|
|
84
|
+
resolver.notifyCandidateFailure(candidates[0]);
|
|
85
|
+
return candidates[0];
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
42
91
|
async vectoriseText(params) {
|
|
43
92
|
try {
|
|
44
93
|
const result = await this.embedGuarded(params.text, params.attribution);
|
|
@@ -60,7 +109,7 @@ let EmbedderService = EmbedderService_1 = class EmbedderService {
|
|
|
60
109
|
// The texts that fit still travel as ONE provider batch, in input order —
|
|
61
110
|
// batching is why vectoriseTextBatch exists (chunk.repository.ts:405).
|
|
62
111
|
if (withinBudget.length > 0) {
|
|
63
|
-
const vectors = await this.
|
|
112
|
+
const vectors = await this.withCandidateFailover((embedder) => embedder.embedDocuments(withinBudget.map((index) => texts[index])));
|
|
64
113
|
withinBudget.forEach((index, position) => (result[index] = vectors[position]));
|
|
65
114
|
}
|
|
66
115
|
for (const index of oversized) {
|
|
@@ -84,7 +133,7 @@ let EmbedderService = EmbedderService_1 = class EmbedderService {
|
|
|
84
133
|
async embedGuarded(text, attribution) {
|
|
85
134
|
const slices = this.splitByTokenBudget(text);
|
|
86
135
|
if (slices.length === 1)
|
|
87
|
-
return this.
|
|
136
|
+
return this.withCandidateFailover((embedder) => embedder.embedQuery(text));
|
|
88
137
|
this.warnOversizedInput(text, slices.length, attribution);
|
|
89
138
|
return this.meanPool(await this.embedSlices(slices));
|
|
90
139
|
}
|
|
@@ -96,7 +145,7 @@ let EmbedderService = EmbedderService_1 = class EmbedderService {
|
|
|
96
145
|
async embedSlices(slices) {
|
|
97
146
|
const vectors = [];
|
|
98
147
|
for (const slice of slices)
|
|
99
|
-
vectors.push(await this.
|
|
148
|
+
vectors.push(await this.withCandidateFailover((embedder) => embedder.embedQuery(slice)));
|
|
100
149
|
return vectors;
|
|
101
150
|
}
|
|
102
151
|
warnOversizedInput(text, sliceCount, attribution) {
|