@futdevpro/nts-dynamo 1.15.57 → 1.15.58

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.
Files changed (134) hide show
  1. package/.dynamo/logs/cicd-pipeline/output.log +1757 -3604
  2. package/.dynamo/logs/cicd-pipeline/status.json +43 -345
  3. package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.d.ts +110 -0
  4. package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.d.ts.map +1 -0
  5. package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.js +419 -0
  6. package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.js.map +1 -0
  7. package/build/_modules/ai/_modules/document-ai/_models/interfaces/dai-code-chunk.interface.d.ts +50 -0
  8. package/build/_modules/ai/_modules/document-ai/_models/interfaces/dai-code-chunk.interface.d.ts.map +1 -0
  9. package/build/_modules/ai/_modules/document-ai/_models/interfaces/dai-code-chunk.interface.js +3 -0
  10. package/build/_modules/ai/_modules/document-ai/_models/interfaces/dai-code-chunk.interface.js.map +1 -0
  11. package/build/_modules/ai/_modules/document-ai/index.d.ts +2 -0
  12. package/build/_modules/ai/_modules/document-ai/index.d.ts.map +1 -1
  13. package/build/_modules/ai/_modules/document-ai/index.js +2 -0
  14. package/build/_modules/ai/_modules/document-ai/index.js.map +1 -1
  15. package/build/_modules/ai/_services/ai-embedding-mock.service.d.ts +81 -0
  16. package/build/_modules/ai/_services/ai-embedding-mock.service.d.ts.map +1 -0
  17. package/build/_modules/ai/_services/ai-embedding-mock.service.js +167 -0
  18. package/build/_modules/ai/_services/ai-embedding-mock.service.js.map +1 -0
  19. package/build/_modules/ai/_services/ai-embedding-provider.registry.d.ts +52 -0
  20. package/build/_modules/ai/_services/ai-embedding-provider.registry.d.ts.map +1 -0
  21. package/build/_modules/ai/_services/ai-embedding-provider.registry.js +79 -0
  22. package/build/_modules/ai/_services/ai-embedding-provider.registry.js.map +1 -0
  23. package/build/_modules/ai/_services/lmstudio-embedding.control-service.d.ts +111 -0
  24. package/build/_modules/ai/_services/lmstudio-embedding.control-service.d.ts.map +1 -0
  25. package/build/_modules/ai/_services/lmstudio-embedding.control-service.js +298 -0
  26. package/build/_modules/ai/_services/lmstudio-embedding.control-service.js.map +1 -0
  27. package/build/_modules/ai/index.d.ts +3 -0
  28. package/build/_modules/ai/index.d.ts.map +1 -1
  29. package/build/_modules/ai/index.js +4 -0
  30. package/build/_modules/ai/index.js.map +1 -1
  31. package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.d.ts +59 -0
  32. package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.d.ts.map +1 -0
  33. package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.js +169 -0
  34. package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.js.map +1 -0
  35. package/build/_modules/data-readers/_models/interfaces/dynts-sqlite-reader.interface.d.ts +32 -0
  36. package/build/_modules/data-readers/_models/interfaces/dynts-sqlite-reader.interface.d.ts.map +1 -0
  37. package/build/_modules/data-readers/_models/interfaces/dynts-sqlite-reader.interface.js +8 -0
  38. package/build/_modules/data-readers/_models/interfaces/dynts-sqlite-reader.interface.js.map +1 -0
  39. package/build/_modules/data-readers/index.d.ts +3 -0
  40. package/build/_modules/data-readers/index.d.ts.map +1 -0
  41. package/build/_modules/data-readers/index.js +11 -0
  42. package/build/_modules/data-readers/index.js.map +1 -0
  43. package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.d.ts +36 -0
  44. package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.d.ts.map +1 -0
  45. package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.js +54 -0
  46. package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.js.map +1 -0
  47. package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.d.ts +70 -0
  48. package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.d.ts.map +1 -0
  49. package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.js +123 -0
  50. package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.js.map +1 -0
  51. package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.d.ts +43 -0
  52. package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.d.ts.map +1 -0
  53. package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.js +72 -0
  54. package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.js.map +1 -0
  55. package/build/_modules/local-vector-search/index.d.ts +3 -0
  56. package/build/_modules/local-vector-search/index.d.ts.map +1 -1
  57. package/build/_modules/local-vector-search/index.js +4 -0
  58. package/build/_modules/local-vector-search/index.js.map +1 -1
  59. package/build/_modules/mcp/_models/interfaces/dynts-mcp.interface.d.ts +109 -0
  60. package/build/_modules/mcp/_models/interfaces/dynts-mcp.interface.d.ts.map +1 -0
  61. package/build/_modules/mcp/_models/interfaces/dynts-mcp.interface.js +14 -0
  62. package/build/_modules/mcp/_models/interfaces/dynts-mcp.interface.js.map +1 -0
  63. package/build/_modules/mcp/_services/dynts-mcp-server.service-base.d.ts +71 -0
  64. package/build/_modules/mcp/_services/dynts-mcp-server.service-base.d.ts.map +1 -0
  65. package/build/_modules/mcp/_services/dynts-mcp-server.service-base.js +99 -0
  66. package/build/_modules/mcp/_services/dynts-mcp-server.service-base.js.map +1 -0
  67. package/build/_modules/mcp/_services/dynts-mcp.adapter.d.ts +57 -0
  68. package/build/_modules/mcp/_services/dynts-mcp.adapter.d.ts.map +1 -0
  69. package/build/_modules/mcp/_services/dynts-mcp.adapter.js +134 -0
  70. package/build/_modules/mcp/_services/dynts-mcp.adapter.js.map +1 -0
  71. package/build/_modules/mcp/index.d.ts +4 -0
  72. package/build/_modules/mcp/index.d.ts.map +1 -0
  73. package/build/_modules/mcp/index.js +13 -0
  74. package/build/_modules/mcp/index.js.map +1 -0
  75. package/build/_modules/scoped-config/_enums/dynts-scoped-config-level.enum.d.ts +19 -0
  76. package/build/_modules/scoped-config/_enums/dynts-scoped-config-level.enum.d.ts.map +1 -0
  77. package/build/_modules/scoped-config/_enums/dynts-scoped-config-level.enum.js +23 -0
  78. package/build/_modules/scoped-config/_enums/dynts-scoped-config-level.enum.js.map +1 -0
  79. package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.d.ts +44 -0
  80. package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.d.ts.map +1 -0
  81. package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.js +68 -0
  82. package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.js.map +1 -0
  83. package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.d.ts +89 -0
  84. package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.d.ts.map +1 -0
  85. package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.js +3 -0
  86. package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.js.map +1 -0
  87. package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.d.ts +84 -0
  88. package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.d.ts.map +1 -0
  89. package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.js +220 -0
  90. package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.js.map +1 -0
  91. package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.d.ts +54 -0
  92. package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.d.ts.map +1 -0
  93. package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.js +76 -0
  94. package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.js.map +1 -0
  95. package/build/_modules/scoped-config/index.d.ts +6 -0
  96. package/build/_modules/scoped-config/index.d.ts.map +1 -0
  97. package/build/_modules/scoped-config/index.js +15 -0
  98. package/build/_modules/scoped-config/index.js.map +1 -0
  99. package/package.json +58 -2
  100. package/pnpm-workspace.yaml +1 -0
  101. package/src/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.spec.ts +295 -0
  102. package/src/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.ts +520 -0
  103. package/src/_modules/ai/_modules/document-ai/_models/interfaces/dai-code-chunk.interface.ts +68 -0
  104. package/src/_modules/ai/_modules/document-ai/index.ts +2 -0
  105. package/src/_modules/ai/_services/ai-embedding-mock.service.spec.ts +115 -0
  106. package/src/_modules/ai/_services/ai-embedding-mock.service.ts +219 -0
  107. package/src/_modules/ai/_services/ai-embedding-provider.registry.spec.ts +110 -0
  108. package/src/_modules/ai/_services/ai-embedding-provider.registry.ts +110 -0
  109. package/src/_modules/ai/_services/lmstudio-embedding.control-service.spec.ts +197 -0
  110. package/src/_modules/ai/_services/lmstudio-embedding.control-service.ts +378 -0
  111. package/src/_modules/ai/index.ts +5 -0
  112. package/src/_modules/data-readers/_collections/dynts-sqlite-reader.util.spec.ts +161 -0
  113. package/src/_modules/data-readers/_collections/dynts-sqlite-reader.util.ts +192 -0
  114. package/src/_modules/data-readers/_models/interfaces/dynts-sqlite-reader.interface.ts +33 -0
  115. package/src/_modules/data-readers/index.ts +11 -0
  116. package/src/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.ts +59 -0
  117. package/src/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.spec.ts +198 -0
  118. package/src/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.ts +146 -0
  119. package/src/_modules/local-vector-search/_services/lvs-vector-persist.data-service.spec.ts +167 -0
  120. package/src/_modules/local-vector-search/_services/lvs-vector-persist.data-service.ts +106 -0
  121. package/src/_modules/local-vector-search/index.ts +6 -1
  122. package/src/_modules/mcp/_models/interfaces/dynts-mcp.interface.ts +111 -0
  123. package/src/_modules/mcp/_services/dynts-mcp-server.service-base.spec.ts +142 -0
  124. package/src/_modules/mcp/_services/dynts-mcp-server.service-base.ts +120 -0
  125. package/src/_modules/mcp/_services/dynts-mcp.adapter.ts +157 -0
  126. package/src/_modules/mcp/index.ts +13 -0
  127. package/src/_modules/scoped-config/_enums/dynts-scoped-config-level.enum.ts +22 -0
  128. package/src/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.ts +81 -0
  129. package/src/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.ts +109 -0
  130. package/src/_modules/scoped-config/_services/dynts-scoped-config.control-service.spec.ts +306 -0
  131. package/src/_modules/scoped-config/_services/dynts-scoped-config.control-service.ts +295 -0
  132. package/src/_modules/scoped-config/_services/dynts-scoped-config.data-service.spec.ts +118 -0
  133. package/src/_modules/scoped-config/_services/dynts-scoped-config.data-service.ts +105 -0
  134. package/src/_modules/scoped-config/index.ts +17 -0
@@ -0,0 +1,115 @@
1
+ import { CreateEmbeddingResponse } from 'openai/resources';
2
+ import { DyFM_AI_Provider } from '@futdevpro/fsm-dynamo/ai';
3
+
4
+ import {
5
+ DyNTS_AI_Embedding_MockService,
6
+ DyNTS_AI_MOCK_EMBEDDING_DIMS,
7
+ } from './ai-embedding-mock.service';
8
+ import { DyNTS_AI_CostEvent } from '../_models/interfaces/dynts-ai-cost-event.interface';
9
+
10
+ describe('| DyNTS_AI_Embedding_MockService', () => {
11
+
12
+ const issuer: string = 'test-issuer';
13
+
14
+ describe('| determinism', () => {
15
+ it('| should return the same vector for the same (model, text)', async () => {
16
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService();
17
+ const first: number[][] = (await service.createEmbeddings({ texts: ['hello'], model: 'm', issuer: issuer })) as number[][];
18
+ const second: number[][] = (await service.createEmbeddings({ texts: ['hello'], model: 'm', issuer: issuer })) as number[][];
19
+ expect(first[0]).toEqual(second[0]);
20
+ });
21
+
22
+ it('| should return different vectors for different texts', async () => {
23
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService();
24
+ const result: number[][] = (await service.createEmbeddings({ texts: ['hello', 'world'], model: 'm', issuer: issuer })) as number[][];
25
+ expect(result[0]).not.toEqual(result[1]);
26
+ });
27
+
28
+ it('| should return different vectors for different models (re-embed scenario)', async () => {
29
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService();
30
+ const a: number[][] = (await service.createEmbeddings({ texts: ['hello'], model: 'model-a', issuer: issuer })) as number[][];
31
+ const b: number[][] = (await service.createEmbeddings({ texts: ['hello'], model: 'model-b', issuer: issuer })) as number[][];
32
+ expect(a[0]).not.toEqual(b[0]);
33
+ });
34
+ });
35
+
36
+ describe('| dimensions', () => {
37
+ it('| should default to DyNTS_AI_MOCK_EMBEDDING_DIMS', async () => {
38
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService();
39
+ const result: number[][] = (await service.createEmbeddings({ texts: ['x'], model: 'm', issuer: issuer })) as number[][];
40
+ expect(result[0].length).toBe(DyNTS_AI_MOCK_EMBEDDING_DIMS);
41
+ });
42
+
43
+ it('| should honour a configured dims', async () => {
44
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService({ dims: 16 });
45
+ const result: number[][] = (await service.createEmbeddings({ texts: ['x'], model: 'm', issuer: issuer })) as number[][];
46
+ expect(result[0].length).toBe(16);
47
+ });
48
+
49
+ it('| should produce an l2-normalized vector (unit length)', async () => {
50
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService({ dims: 32 });
51
+ const result: number[][] = (await service.createEmbeddings({ texts: ['vector'], model: 'm', issuer: issuer })) as number[][];
52
+ const norm: number = Math.sqrt(result[0].reduce((sum: number, v: number) => sum + v * v, 0));
53
+ expect(norm).toBeCloseTo(1, 6);
54
+ });
55
+ });
56
+
57
+ describe('| createEmbedding (single)', () => {
58
+ it('| should match the batch vector for the same text', async () => {
59
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService();
60
+ const single: number[] = (await service.createEmbedding({ text: 'hi', model: 'm', issuer: issuer })) as number[];
61
+ const batch: number[][] = (await service.createEmbeddings({ texts: ['hi'], model: 'm', issuer: issuer })) as number[][];
62
+ expect(single).toEqual(batch[0]);
63
+ });
64
+
65
+ it('| should return a full OpenAI-shaped response when fullResponse is true', async () => {
66
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService({ dims: 8 });
67
+ const response: CreateEmbeddingResponse = (await service.createEmbedding({
68
+ text: 'hi',
69
+ model: 'm',
70
+ fullResponse: true,
71
+ issuer: issuer,
72
+ })) as CreateEmbeddingResponse;
73
+ expect(response.object).toBe('list');
74
+ expect(response.data[0].embedding.length).toBe(8);
75
+ expect(response.usage.total_tokens).toBeGreaterThan(0);
76
+ });
77
+ });
78
+
79
+ describe('| cost-event', () => {
80
+ it('| should emit an embedding-batch cost-event with provider mock', async () => {
81
+ const events: DyNTS_AI_CostEvent[] = [];
82
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService({
83
+ onCostEvent: (event: DyNTS_AI_CostEvent) => events.push(event),
84
+ });
85
+ await service.createEmbeddings({ texts: ['abcd', 'efgh'], model: 'm', issuer: issuer });
86
+ expect(events.length).toBe(1);
87
+ expect(events[0].callType).toBe('embedding-batch');
88
+ expect(events[0].provider).toBe('mock');
89
+ expect(events[0].estimatedCostUsd).toBe(0);
90
+ expect(events[0].tokensUsed.input).toBeGreaterThan(0);
91
+ });
92
+
93
+ it('| should emit an embedding-single cost-event', async () => {
94
+ const events: DyNTS_AI_CostEvent[] = [];
95
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService({
96
+ onCostEvent: (event: DyNTS_AI_CostEvent) => events.push(event),
97
+ });
98
+ await service.createEmbedding({ text: 'hi', model: 'm', issuer: issuer });
99
+ expect(events[0].callType).toBe('embedding-single');
100
+ });
101
+ });
102
+
103
+ describe('| provider info + availability', () => {
104
+ it('| should report the LocalAI provider', () => {
105
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService();
106
+ expect(service.aiProvider).toBe(DyFM_AI_Provider.LocalAI);
107
+ expect(service.getEmbeddingInfo('m')).toEqual({ provider: DyFM_AI_Provider.LocalAI, model: 'm' });
108
+ });
109
+
110
+ it('| should always be available', async () => {
111
+ const service: DyNTS_AI_Embedding_MockService = new DyNTS_AI_Embedding_MockService();
112
+ expect(await service.testConnection(issuer)).toBe(true);
113
+ });
114
+ });
115
+ });
@@ -0,0 +1,219 @@
1
+ import * as crypto from 'crypto';
2
+
3
+ import { CreateEmbeddingResponse } from 'openai/resources';
4
+ import { DyFM_AI_Provider, DyFM_AI_ProviderCapabilities, DyFM_AI_Config } from '@futdevpro/fsm-dynamo/ai';
5
+ import { DyFM_DAI_EmbeddingInfo } from '@futdevpro/fsm-dynamo/ai/document-ai';
6
+
7
+ import { DyNTS_AI_Embedding_ServiceBase } from './ai-embedding.service-base';
8
+ import { DyNTS_AI_CostEventCallback } from '../_models/interfaces/dynts-ai-cost-event-callback.interface';
9
+
10
+ /** A mock-embedding default dimenziója (kicsi, hogy a teszt-pool gyors legyen, de elég a koszinuszhoz). */
11
+ export const DyNTS_AI_MOCK_EMBEDDING_DIMS: number = 64;
12
+
13
+ /**
14
+ * A mock embedding-service config-set-je. A `dims` a generált vektor dimenziója (default
15
+ * `DyNTS_AI_MOCK_EMBEDDING_DIMS`), az `onCostEvent` a per-call cost-event sink (FR-002 / BFR-AM-007).
16
+ */
17
+ export interface DyNTS_AI_Embedding_MockSettings {
18
+ /** A generált vektor dimenziója (default `DyNTS_AI_MOCK_EMBEDDING_DIMS`). */
19
+ dims?: number;
20
+ /** Per-call cost-event callback (FR-002 / BFR-AM-007). Non-breaking: ha undefined, nincs emit. */
21
+ onCostEvent?: DyNTS_AI_CostEventCallback;
22
+ }
23
+
24
+ /**
25
+ * `DyNTS_AI_Embedding_MockService` (BFR-AM-008) — determinisztikus, hash-alapú, fix-dimenziós,
26
+ * l2-normalizált pszeudo-vektor a szövegből. Provider- és hálózat-mentes → a tesztek + a tsc-/offline-
27
+ * smoke valódi API-kulcs nélkül futnak. Azonos `(model, text)` → azonos vektor (idempotens), így a
28
+ * koszinusz-keresés determinisztikusan tesztelhető. A FAM `FAM_Mock_EmbeddingProvider` workaround-ját
29
+ * emeli bedrock-szintre, mint újrahasználható test-double.
30
+ *
31
+ * **Cost-event (BFR-AM-007):** minden call után `emitCostEvent`-tel jelez (fake token-fogyasztás,
32
+ * 4 char ≈ 1 token), provider `'mock'`, callType `embedding-single` / `embedding-batch`. Így a
33
+ * cost-aggregáló pipeline mock-providerrel is end-to-end tesztelhető.
34
+ */
35
+ export class DyNTS_AI_Embedding_MockService extends DyNTS_AI_Embedding_ServiceBase {
36
+
37
+ /** A mock a `LocalAI` provider-enum alá tartozik (nincs külső hívás). */
38
+ readonly aiProvider: DyFM_AI_Provider = DyFM_AI_Provider.LocalAI;
39
+
40
+ /** A cost-event provider-string (a `DyNTS_AI_CostEvent.provider` szabad-string mezőjéhez). */
41
+ protected readonly costProvider: string = 'mock';
42
+
43
+ /** Mock capability-k: csak embedding (determinisztikus). */
44
+ readonly capabilities: DyFM_AI_ProviderCapabilities = {
45
+ chat: false,
46
+ embeddings: true,
47
+ imageGeneration: false,
48
+ vision: false,
49
+ audioGeneration: false,
50
+ audioAnalysis: false,
51
+ functionCalling: false,
52
+ streaming: false,
53
+ batchOperations: true,
54
+ supportedModelTypes: [],
55
+ };
56
+
57
+ /** A generált vektor dimenziója. */
58
+ protected dims: number;
59
+
60
+ /** @param set opcionális dims + onCostEvent. */
61
+ constructor(set?: DyNTS_AI_Embedding_MockSettings) {
62
+ super();
63
+ this.dims = set?.dims && set.dims > 0 ? set.dims : DyNTS_AI_MOCK_EMBEDDING_DIMS;
64
+ if (set?.onCostEvent) {
65
+ this.onCostEvent = set.onCostEvent;
66
+ }
67
+ }
68
+
69
+ /** A mock-nak nincs külső config-ja; a `setup` a dimenzió override-jára szolgál (config.dimensions). */
70
+ setup(config: DyFM_AI_Config): void {
71
+ const dims: unknown = Reflect.get(config ?? {}, 'dimensions');
72
+ if (typeof dims === 'number' && dims > 0) {
73
+ this.dims = dims;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Egy determinisztikus embedding. A `model`-t beleszövi a seed-be, hogy modell-váltás más vektort
79
+ * adjon (re-embed teszteléshez). `fullResponse=true` → OpenAI-alakú válasz-objektum.
80
+ */
81
+ async createEmbedding(
82
+ set: {
83
+ text: string;
84
+ model: string;
85
+ fullResponse?: boolean;
86
+ issuer: string;
87
+ }
88
+ ): Promise<number[] | CreateEmbeddingResponse> {
89
+ const start: number = Date.now();
90
+ const vector: number[] = this.deterministicVector(`${set.model}::${set.text ?? ''}`);
91
+ const durationMs: number = Date.now() - start;
92
+
93
+ this.emitMockCostEvent('embedding-single', set.model, [set.text ?? ''], durationMs, set.issuer);
94
+
95
+ if (set.fullResponse) {
96
+ return this.toResponse([vector], set.model, [set.text ?? '']);
97
+ }
98
+ return vector;
99
+ }
100
+
101
+ /**
102
+ * Determinisztikus batch-embedding: minden szöveghez a `(model, text)` seed-ből generál egy fix-
103
+ * dimenziós, l2-normalizált vektort. A `texts` sorrendje == a visszaadott `number[][]` sorrendje.
104
+ */
105
+ async createEmbeddings(
106
+ set: {
107
+ texts: string[];
108
+ model: string;
109
+ fullResponse?: boolean;
110
+ issuer: string;
111
+ }
112
+ ): Promise<number[][] | CreateEmbeddingResponse> {
113
+ const texts: string[] = set.texts ?? [];
114
+ const start: number = Date.now();
115
+ const vectors: number[][] = texts.map((text) => this.deterministicVector(`${set.model}::${text}`));
116
+ const durationMs: number = Date.now() - start;
117
+
118
+ this.emitMockCostEvent('embedding-batch', set.model, texts, durationMs, set.issuer);
119
+
120
+ if (set.fullResponse) {
121
+ return this.toResponse(vectors, set.model, texts);
122
+ }
123
+ return vectors;
124
+ }
125
+
126
+ /** Embedding model-info: provider `LocalAI`, model a megadott azonosító. */
127
+ getEmbeddingInfo(model: string): DyFM_DAI_EmbeddingInfo {
128
+ return {
129
+ provider: DyFM_AI_Provider.LocalAI,
130
+ model: model,
131
+ };
132
+ }
133
+
134
+ /** A mock mindig elérhető (nincs külső függőség). */
135
+ async testConnection(_issuer: string): Promise<boolean> {
136
+ return true;
137
+ }
138
+
139
+ // =========================================================================
140
+ // belső: determinisztikus vektor + cost-event + response-alak
141
+ // =========================================================================
142
+
143
+ /**
144
+ * Egy determinisztikus, l2-normalizált vektor a seed-ből. A SHA-256 hash bájtjait körkörösen
145
+ * használja a `dims` kitöltésére, [-1, 1) tartományra képzi, majd l2-normalizál (egységhossz a
146
+ * koszinuszhoz). Egy második hash-bájttal kever, hogy a hosszabb dimenziókon ne ismétlődjön a minta.
147
+ */
148
+ protected deterministicVector(seed: string): number[] {
149
+ const hash: Buffer = crypto.createHash('sha256').update(seed).digest();
150
+ const vector: number[] = [];
151
+ for (let i = 0; i < this.dims; i++) {
152
+ const byte: number = hash[i % hash.length];
153
+ const mix: number = hash[(i * 7 + 3) % hash.length];
154
+ const combined: number = (byte ^ mix) & 0xff;
155
+ vector.push((combined / 255) * 2 - 1);
156
+ }
157
+ return this.l2Normalize(vector);
158
+ }
159
+
160
+ /** L2-normalizálás (egységhossz). Nulla-vektor → változatlan (elkerüli a 0-osztást). */
161
+ protected l2Normalize(vector: number[]): number[] {
162
+ let sumSq: number = 0;
163
+ for (const value of vector) {
164
+ sumSq += value * value;
165
+ }
166
+ const norm: number = Math.sqrt(sumSq);
167
+ if (!norm) {
168
+ return vector;
169
+ }
170
+ return vector.map((value) => value / norm);
171
+ }
172
+
173
+ /** OpenAI-alakú válasz-objektum a vektorokból (a `fullResponse=true` ághoz). */
174
+ protected toResponse(vectors: number[][], model: string, texts: string[]): CreateEmbeddingResponse {
175
+ const tokens: number = this.estimateTokens(texts);
176
+ return {
177
+ object: 'list',
178
+ model: model,
179
+ data: vectors.map((embedding, index) => ({ object: 'embedding', embedding: embedding, index: index })),
180
+ usage: {
181
+ prompt_tokens: tokens,
182
+ total_tokens: tokens,
183
+ },
184
+ };
185
+ }
186
+
187
+ /** Cost-event emit (BFR-AM-007) fake tokennel (4 char ≈ 1 token), provider `'mock'`. */
188
+ protected emitMockCostEvent(
189
+ callType: 'embedding-single' | 'embedding-batch',
190
+ model: string,
191
+ texts: string[],
192
+ durationMs: number,
193
+ issuer: string,
194
+ ): void {
195
+ const tokens: number = this.estimateTokens(texts);
196
+ this.emitCostEvent({
197
+ callType: callType,
198
+ provider: this.costProvider,
199
+ model: model,
200
+ tokensUsed: {
201
+ input: tokens,
202
+ total: tokens,
203
+ },
204
+ durationMs: durationMs,
205
+ issuer: issuer,
206
+ timestamp: new Date(),
207
+ estimatedCostUsd: 0,
208
+ });
209
+ }
210
+
211
+ /** Becsült (fake) token-szám: 4 char ≈ 1 token. */
212
+ protected estimateTokens(texts: string[]): number {
213
+ let chars: number = 0;
214
+ for (const text of texts) {
215
+ chars += (text ?? '').length;
216
+ }
217
+ return Math.max(1, Math.ceil(chars / 4));
218
+ }
219
+ }
@@ -0,0 +1,110 @@
1
+ import { DyNTS_AI_EmbeddingProvider_Registry } from './ai-embedding-provider.registry';
2
+ import { DyNTS_LMStudio_Embedding_ControlService } from './lmstudio-embedding.control-service';
3
+ import { DyNTS_AI_Embedding_MockService } from './ai-embedding-mock.service';
4
+ import { DyNTS_OAI_Embedding_ControlService } from '../_modules/open-ai/_services/oai-embedding.control-service';
5
+
6
+ describe('| DyNTS_AI_EmbeddingProvider_Registry', () => {
7
+
8
+ /** Az env-mező visszaállításához (a normalizeKey env-feloldás-ágához). */
9
+ let savedProviderEnv: string | undefined;
10
+ let savedBaseUrlEnv: string | undefined;
11
+
12
+ beforeEach(() => {
13
+ savedProviderEnv = process.env.AI_EMBEDDING_PROVIDER;
14
+ savedBaseUrlEnv = process.env.LMSTUDIO_BASE_URL;
15
+ delete process.env.AI_EMBEDDING_PROVIDER;
16
+ delete process.env.LMSTUDIO_BASE_URL;
17
+ });
18
+
19
+ afterEach(() => {
20
+ if (savedProviderEnv === undefined) {
21
+ delete process.env.AI_EMBEDDING_PROVIDER;
22
+ } else {
23
+ process.env.AI_EMBEDDING_PROVIDER = savedProviderEnv;
24
+ }
25
+ if (savedBaseUrlEnv === undefined) {
26
+ delete process.env.LMSTUDIO_BASE_URL;
27
+ } else {
28
+ process.env.LMSTUDIO_BASE_URL = savedBaseUrlEnv;
29
+ }
30
+ });
31
+
32
+ describe('| normalizeKey', () => {
33
+ it('| should resolve openai aliases', () => {
34
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey('openai')).toBe('openai');
35
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey('OpenAI')).toBe('openai');
36
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey('open-ai')).toBe('openai');
37
+ });
38
+
39
+ it('| should resolve lmstudio aliases', () => {
40
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey('lmstudio')).toBe('lmstudio');
41
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey('lm-studio')).toBe('lmstudio');
42
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey('local-ai')).toBe('lmstudio');
43
+ });
44
+
45
+ it('| should resolve mock aliases', () => {
46
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey('mock')).toBe('mock');
47
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey('test')).toBe('mock');
48
+ });
49
+
50
+ it('| should default to openai when no provider and no env', () => {
51
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey()).toBe('openai');
52
+ });
53
+
54
+ it('| should fall back to the AI_EMBEDDING_PROVIDER env var', () => {
55
+ process.env.AI_EMBEDDING_PROVIDER = 'mock';
56
+ expect(DyNTS_AI_EmbeddingProvider_Registry.normalizeKey()).toBe('mock');
57
+ });
58
+
59
+ it('| should throw on an unknown provider', () => {
60
+ expect(() => DyNTS_AI_EmbeddingProvider_Registry.normalizeKey('gemini')).toThrow();
61
+ });
62
+ });
63
+
64
+ describe('| resolve', () => {
65
+ it('| should resolve the openai provider to DyNTS_OAI_Embedding_ControlService', () => {
66
+ const service = DyNTS_AI_EmbeddingProvider_Registry.resolve({
67
+ provider: 'openai',
68
+ openAiSettings: { config: { apiKey: 'test-key' } },
69
+ });
70
+ expect(service).toBeInstanceOf(DyNTS_OAI_Embedding_ControlService);
71
+ });
72
+
73
+ it('| should resolve the lmstudio provider to DyNTS_LMStudio_Embedding_ControlService', () => {
74
+ const service = DyNTS_AI_EmbeddingProvider_Registry.resolve({
75
+ provider: 'lmstudio',
76
+ lmStudioSettings: { baseUrl: 'http://localhost:1234/v1' },
77
+ });
78
+ expect(service).toBeInstanceOf(DyNTS_LMStudio_Embedding_ControlService);
79
+ });
80
+
81
+ it('| should resolve the lmstudio baseUrl from the LMSTUDIO_BASE_URL env var', () => {
82
+ process.env.LMSTUDIO_BASE_URL = 'http://localhost:5678/v1';
83
+ const service = DyNTS_AI_EmbeddingProvider_Registry.resolve({ provider: 'lmstudio' });
84
+ expect(service).toBeInstanceOf(DyNTS_LMStudio_Embedding_ControlService);
85
+ });
86
+
87
+ it('| should throw if lmstudio is selected without a baseUrl', () => {
88
+ expect(() => DyNTS_AI_EmbeddingProvider_Registry.resolve({ provider: 'lmstudio' })).toThrow();
89
+ });
90
+
91
+ it('| should resolve the mock provider to DyNTS_AI_Embedding_MockService', () => {
92
+ const service = DyNTS_AI_EmbeddingProvider_Registry.resolve({ provider: 'mock' });
93
+ expect(service).toBeInstanceOf(DyNTS_AI_Embedding_MockService);
94
+ });
95
+
96
+ it('| should pass the cost-event sink through to the mock provider', async () => {
97
+ const events: string[] = [];
98
+ const service = DyNTS_AI_EmbeddingProvider_Registry.resolve({
99
+ provider: 'mock',
100
+ onCostEvent: (event) => events.push(event.provider),
101
+ });
102
+ await service.createEmbeddings({ texts: ['x'], model: 'm', issuer: 'test' });
103
+ expect(events).toEqual(['mock']);
104
+ });
105
+
106
+ it('| should throw on an unknown provider in resolve', () => {
107
+ expect(() => DyNTS_AI_EmbeddingProvider_Registry.resolve({ provider: 'cohere' })).toThrow();
108
+ });
109
+ });
110
+ });
@@ -0,0 +1,110 @@
1
+ import { DyFM_OAI_Settings } from '@futdevpro/fsm-dynamo/ai/open-ai';
2
+
3
+ import { DyNTS_AI_Embedding_ServiceBase } from './ai-embedding.service-base';
4
+ import { DyNTS_OAI_Embedding_ControlService } from '../_modules/open-ai/_services/oai-embedding.control-service';
5
+ import { DyNTS_LMStudio_Embedding_ControlService, DyNTS_LMStudio_Embedding_Settings } from './lmstudio-embedding.control-service';
6
+ import { DyNTS_AI_Embedding_MockService, DyNTS_AI_Embedding_MockSettings } from './ai-embedding-mock.service';
7
+ import { DyNTS_AI_CostEventCallback } from '../_models/interfaces/dynts-ai-cost-event-callback.interface';
8
+
9
+ /**
10
+ * Az embedding-provider kulcsa (BFR-AM-002). String-union, NEM enum (a cost-event provider-stringekkel
11
+ * konzisztens): `'openai'` (SDK reuse), `'lmstudio'` (lokális OpenAI-kompatibilis HTTP), `'mock'`
12
+ * (determinisztikus test-double). Az env-feloldás kis-/nagybetűt és a `lm-studio` alias-t is elfogad.
13
+ */
14
+ export type DyNTS_AI_EmbeddingProviderKey = 'openai' | 'lmstudio' | 'mock';
15
+
16
+ /**
17
+ * A provider-feloldás opciói. A `provider` explicit kulcsot ad; ha hiányzik, az env-ből oldjuk fel
18
+ * (`AI_EMBEDDING_PROVIDER`, default `openai`). A többi mező a megfelelő provider config-ját adja át.
19
+ */
20
+ export interface DyNTS_AI_EmbeddingResolveOptions {
21
+ /** Explicit provider-kulcs; ha hiányzik → env-feloldás. */
22
+ provider?: DyNTS_AI_EmbeddingProviderKey | string;
23
+ /** Per-call cost-event sink (FR-002 / BFR-AM-007) — mind a 3 providernek átadva. */
24
+ onCostEvent?: DyNTS_AI_CostEventCallback;
25
+ /** OpenAI-specifikus config (apiKey/org/project/defaultSettings) — `openai` providerhez. */
26
+ openAiSettings?: DyFM_OAI_Settings;
27
+ /** LM Studio config (baseUrl/apiKey) — `lmstudio` providerhez; baseUrl hiány → env (`LMSTUDIO_BASE_URL`). */
28
+ lmStudioSettings?: Partial<DyNTS_LMStudio_Embedding_Settings>;
29
+ /** Mock config (dims) — `mock` providerhez. */
30
+ mockSettings?: DyNTS_AI_Embedding_MockSettings;
31
+ }
32
+
33
+ /**
34
+ * `DyNTS_AI_EmbeddingProvider_Registry` (BFR-AM-002) — provider-dispatch resolver. A provider-kulcs
35
+ * (`openai` | `lmstudio` | `mock`) alapján a megfelelő `DyNTS_AI_Embedding_ServiceBase` példányt adja
36
+ * vissza, így a hívók egységes embedding-felületet látnak a konkrét provider-impl mögött (a FAM
37
+ * `FAM_Embedding_ControlService.buildProvider` dispatch-ét emeli bedrock-szintre, generikusan).
38
+ *
39
+ * Static util (nincs állapota): minden hívás friss példányt épít (env/secret a hívás-időpontban
40
+ * érvényes — memory: `dynts_dataservice_eager_resolve`-konzisztens, nincs élő singleton-állapot).
41
+ */
42
+ export class DyNTS_AI_EmbeddingProvider_Registry {
43
+
44
+ /**
45
+ * A megadott (vagy env-ből feloldott) provider-kulcs alapján egy embedding-service példányt épít.
46
+ * - `openai` → `DyNTS_OAI_Embedding_ControlService` (SDK reuse, cost-event sink-kel),
47
+ * - `lmstudio` → `DyNTS_LMStudio_Embedding_ControlService` (HTTP; baseUrl a config vagy env),
48
+ * - `mock` → `DyNTS_AI_Embedding_MockService` (determinisztikus),
49
+ * - ismeretlen → `Error` (provider nincs konfigurálva — nem néma).
50
+ */
51
+ static resolve(options?: DyNTS_AI_EmbeddingResolveOptions): DyNTS_AI_Embedding_ServiceBase {
52
+ const key: DyNTS_AI_EmbeddingProviderKey = DyNTS_AI_EmbeddingProvider_Registry.normalizeKey(options?.provider);
53
+ const onCostEvent: DyNTS_AI_CostEventCallback | undefined = options?.onCostEvent;
54
+
55
+ switch (key) {
56
+ case 'openai':
57
+ return new DyNTS_OAI_Embedding_ControlService({
58
+ ...(options?.openAiSettings ?? {}),
59
+ onCostEvent: onCostEvent,
60
+ });
61
+ case 'lmstudio': {
62
+ const baseUrl: string | undefined = options?.lmStudioSettings?.baseUrl ?? process.env.LMSTUDIO_BASE_URL;
63
+ if (!baseUrl) {
64
+ throw new Error(
65
+ 'DyNTS_AI_EmbeddingProvider_Registry: the lmstudio provider was selected but no baseUrl '
66
+ + 'was provided (set lmStudioSettings.baseUrl or the LMSTUDIO_BASE_URL env var).',
67
+ );
68
+ }
69
+ return new DyNTS_LMStudio_Embedding_ControlService({
70
+ baseUrl: baseUrl,
71
+ apiKey: options?.lmStudioSettings?.apiKey ?? process.env.LMSTUDIO_API_KEY,
72
+ onCostEvent: onCostEvent,
73
+ });
74
+ }
75
+ case 'mock':
76
+ return new DyNTS_AI_Embedding_MockService({
77
+ ...(options?.mockSettings ?? {}),
78
+ onCostEvent: onCostEvent,
79
+ });
80
+ }
81
+ }
82
+
83
+ /**
84
+ * A provider-kulcs normalizálása: kis-/nagybetű-független, a `lm-studio` / `local-ai` alias-t
85
+ * `lmstudio`-ra képzi. Hiány → env (`AI_EMBEDDING_PROVIDER`) → default `openai`. Ismeretlen → `Error`.
86
+ */
87
+ static normalizeKey(provider?: string): DyNTS_AI_EmbeddingProviderKey {
88
+ const raw: string = (provider ?? process.env.AI_EMBEDDING_PROVIDER ?? 'openai').trim().toLowerCase();
89
+ switch (raw) {
90
+ case 'openai':
91
+ case 'open-ai':
92
+ case 'oai':
93
+ return 'openai';
94
+ case 'lmstudio':
95
+ case 'lm-studio':
96
+ case 'local-ai':
97
+ case 'localai':
98
+ return 'lmstudio';
99
+ case 'mock':
100
+ case 'fake':
101
+ case 'test':
102
+ return 'mock';
103
+ default:
104
+ throw new Error(
105
+ `DyNTS_AI_EmbeddingProvider_Registry: unknown embedding provider '${raw}'. `
106
+ + `Available: 'openai' | 'lmstudio' | 'mock'.`,
107
+ );
108
+ }
109
+ }
110
+ }