@yamo/memory-mesh 2.0.1
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/LICENSE +21 -0
- package/README.md +80 -0
- package/bin/memory_mesh.js +69 -0
- package/bin/scrubber.js +81 -0
- package/index.d.ts +111 -0
- package/lib/adapters/index.js +3 -0
- package/lib/embeddings/factory.js +150 -0
- package/lib/embeddings/index.js +2 -0
- package/lib/embeddings/service.js +586 -0
- package/lib/index.js +18 -0
- package/lib/lancedb/client.js +631 -0
- package/lib/lancedb/config.js +215 -0
- package/lib/lancedb/errors.js +144 -0
- package/lib/lancedb/index.js +4 -0
- package/lib/lancedb/schema.js +197 -0
- package/lib/memory/index.js +3 -0
- package/lib/memory/memory-context-manager.js +388 -0
- package/lib/memory/memory-mesh.js +910 -0
- package/lib/memory/memory-translator.js +130 -0
- package/lib/memory/migrate-memory.js +227 -0
- package/lib/memory/migrate-to-v2.js +120 -0
- package/lib/memory/scorer.js +85 -0
- package/lib/memory/vector-memory.js +364 -0
- package/lib/privacy/audit-logger.js +176 -0
- package/lib/privacy/dlp-redactor.js +72 -0
- package/lib/privacy/index.js +10 -0
- package/lib/reporting/skill-report-generator.js +283 -0
- package/lib/scrubber/.gitkeep +1 -0
- package/lib/scrubber/config/defaults.js +62 -0
- package/lib/scrubber/errors/scrubber-error.js +43 -0
- package/lib/scrubber/index.js +25 -0
- package/lib/scrubber/scrubber.js +130 -0
- package/lib/scrubber/stages/chunker.js +103 -0
- package/lib/scrubber/stages/metadata-annotator.js +74 -0
- package/lib/scrubber/stages/normalizer.js +59 -0
- package/lib/scrubber/stages/semantic-filter.js +61 -0
- package/lib/scrubber/stages/structural-cleaner.js +82 -0
- package/lib/scrubber/stages/validator.js +66 -0
- package/lib/scrubber/telemetry.js +66 -0
- package/lib/scrubber/utils/hash.js +39 -0
- package/lib/scrubber/utils/html-parser.js +45 -0
- package/lib/scrubber/utils/pattern-matcher.js +63 -0
- package/lib/scrubber/utils/token-counter.js +31 -0
- package/lib/search/filter.js +275 -0
- package/lib/search/hybrid.js +137 -0
- package/lib/search/index.js +3 -0
- package/lib/search/pattern-miner.js +160 -0
- package/lib/utils/error-sanitizer.js +84 -0
- package/lib/utils/handoff-validator.js +85 -0
- package/lib/utils/index.js +4 -0
- package/lib/utils/spinner.js +190 -0
- package/lib/utils/streaming-client.js +128 -0
- package/package.json +39 -0
- package/skills/SKILL.md +462 -0
- package/skills/skill-scrubber.yamo +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 YAMO Protocol
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# MemoryMesh
|
|
2
|
+
|
|
3
|
+
Portable, semantic memory system for AI agents with automatic Layer 0 sanitization.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Persistent Vector Storage**: Powered by LanceDB.
|
|
8
|
+
- **Layer 0 Scrubber**: Automatically sanitizes, deduplicates, and cleans content.
|
|
9
|
+
- **Local Embeddings**: Runs 100% locally using ONNX (no API keys required).
|
|
10
|
+
- **Portable CLI**: Simple JSON-based interface for any agent or language.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @yamo/memory-mesh
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### CLI
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Store a memory (automatically scrubbed & embedded)
|
|
24
|
+
memory-mesh store "My important memory" '{"tag":"test"}'
|
|
25
|
+
|
|
26
|
+
# Search memories
|
|
27
|
+
memory-mesh search "query" 5
|
|
28
|
+
|
|
29
|
+
# Scrub content only
|
|
30
|
+
scrubber scrub "Raw text content"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Node.js API
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import { MemoryMesh } from '@yamo/memory-mesh';
|
|
37
|
+
|
|
38
|
+
const mesh = new MemoryMesh();
|
|
39
|
+
await mesh.add('Content', { meta: 'data' });
|
|
40
|
+
const results = await mesh.search('query');
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Using in a Project
|
|
44
|
+
|
|
45
|
+
To use MemoryMesh with your Claude Code skills (like `yamo-super`) in a new project:
|
|
46
|
+
|
|
47
|
+
### 1. Install the Package
|
|
48
|
+
This installs the heavy dependencies (LanceDB, ONNX) and binaries.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install @yamo/memory-mesh
|
|
52
|
+
# Or install locally if developing:
|
|
53
|
+
# npm install /path/to/memory-mesh
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Setup the CLI Adapter
|
|
57
|
+
YAMO skills learn how to use the memory system by reading the source code of the CLI adapter. You must ensure `tools/memory_mesh.js` exists in your project so the agent can "see" the interface.
|
|
58
|
+
|
|
59
|
+
**Quick Setup:**
|
|
60
|
+
```bash
|
|
61
|
+
mkdir -p tools
|
|
62
|
+
cp node_modules/@yamo/memory-mesh/bin/memory_mesh.js tools/memory_mesh.js
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. Run Your Skill
|
|
66
|
+
Your `yamo-super` skill (or any skill referencing `memory_script;tools/memory_mesh.js`) will now work automatically.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
claude "Run yamo-super task='Setup CI pipeline'"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The agent will read `tools/memory_mesh.js`, understand how to call it, and execute memory operations which are handled by the installed `@yamo/memory-mesh` package.
|
|
73
|
+
|
|
74
|
+
## Docker
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
docker run -v $(pwd)/data:/app/runtime/data \
|
|
78
|
+
yamo/memory-mesh store "Content"
|
|
79
|
+
```
|
|
80
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MemoryMesh CLI Adapter
|
|
5
|
+
* Provides a portable interface for skills to interact with the MemoryMesh system.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node tools/memory_mesh.js store <content_string_or_json> [metadata_json]
|
|
9
|
+
* node tools/memory_mesh.js search <query_string> [limit]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { MemoryMesh } from '../lib/memory/memory-mesh.js';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
|
|
15
|
+
// Parse arguments
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const command = args[0];
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
try {
|
|
21
|
+
const mesh = new MemoryMesh();
|
|
22
|
+
// Wait for initialization if necessary (MemoryMesh constructor usually doesn't await,
|
|
23
|
+
// but operations might need internal init. We assume standard usage.)
|
|
24
|
+
|
|
25
|
+
if (command === 'store') {
|
|
26
|
+
const content = args[1];
|
|
27
|
+
let metadata = {};
|
|
28
|
+
if (args[2]) {
|
|
29
|
+
try {
|
|
30
|
+
metadata = JSON.parse(args[2]);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error(JSON.stringify({ error: "Invalid metadata JSON" }));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!content) {
|
|
38
|
+
console.error(JSON.stringify({ error: "Content required for store command" }));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await mesh.add(content, metadata);
|
|
43
|
+
console.log(JSON.stringify({ success: true, id: result.id, message: "Memory stored successfully" }));
|
|
44
|
+
|
|
45
|
+
} else if (command === 'search') {
|
|
46
|
+
const query = args[1];
|
|
47
|
+
const limit = parseInt(args[2]) || 5;
|
|
48
|
+
|
|
49
|
+
if (!query) {
|
|
50
|
+
console.error(JSON.stringify({ error: "Query required for search command" }));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const results = await mesh.search(query, { limit });
|
|
55
|
+
console.log(JSON.stringify({ success: true, results: results }));
|
|
56
|
+
|
|
57
|
+
} else {
|
|
58
|
+
console.error(JSON.stringify({ error: `Unknown command: ${command}` }));
|
|
59
|
+
console.error("Usage: node tools/memory_mesh.js [store|search] ...");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(JSON.stringify({ error: error.message }));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main();
|
package/bin/scrubber.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scrubber CLI Tool
|
|
5
|
+
*
|
|
6
|
+
* A portable CLI wrapper for the S-MORA Layer 0 Scrubber.
|
|
7
|
+
* Sanitizes, deduplicates, and cleans text content.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node tools/scrubber.js scrub "some text content"
|
|
11
|
+
* node tools/scrubber.js scrub-file path/to/file.md
|
|
12
|
+
*
|
|
13
|
+
* Output:
|
|
14
|
+
* JSON object with { success, cleaned_content, metadata }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Scrubber } from '../lib/smora/scrubber/scrubber.js';
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
const command = args[0];
|
|
23
|
+
const input = args[1];
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
// Initialize with default aggressive cleaning
|
|
27
|
+
const scrubber = new Scrubber({
|
|
28
|
+
enabled: true,
|
|
29
|
+
structural: { stripHTML: true, normalizeMarkdown: true },
|
|
30
|
+
semantic: { removeBoilerplate: true },
|
|
31
|
+
chunking: { minTokens: 1 } // Allow short inputs
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
let content = '';
|
|
36
|
+
let source = 'cli-input';
|
|
37
|
+
let type = 'txt';
|
|
38
|
+
|
|
39
|
+
if (command === 'scrub') {
|
|
40
|
+
if (!input) throw new Error('Content argument required');
|
|
41
|
+
content = input;
|
|
42
|
+
} else if (command === 'scrub-file') {
|
|
43
|
+
if (!input) throw new Error('File path argument required');
|
|
44
|
+
if (!fs.existsSync(input)) throw new Error(`File not found: ${input}`);
|
|
45
|
+
content = fs.readFileSync(input, 'utf8');
|
|
46
|
+
source = path.basename(input);
|
|
47
|
+
type = path.extname(input).replace('.', '') || 'txt';
|
|
48
|
+
} else {
|
|
49
|
+
console.error(JSON.stringify({ error: `Unknown command: ${command}` }));
|
|
50
|
+
console.error('Usage: node tools/scrubber.js [scrub|scrub-file] <input>');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = await scrubber.process({
|
|
55
|
+
content,
|
|
56
|
+
source,
|
|
57
|
+
type
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Reassemble chunks for simple return
|
|
61
|
+
const cleanedContent = result.chunks.map(c => c.text).join('\n\n');
|
|
62
|
+
|
|
63
|
+
console.log(JSON.stringify({
|
|
64
|
+
success: true,
|
|
65
|
+
original_length: content.length,
|
|
66
|
+
cleaned_length: cleanedContent.length,
|
|
67
|
+
cleaned_content: cleanedContent,
|
|
68
|
+
chunks: result.chunks.length,
|
|
69
|
+
metadata: result.metadata
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(JSON.stringify({
|
|
74
|
+
success: false,
|
|
75
|
+
error: error.message
|
|
76
|
+
}));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main();
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export declare class MemoryMesh {
|
|
2
|
+
constructor();
|
|
3
|
+
init(): Promise<void>;
|
|
4
|
+
add(content: string, metadata?: Record<string, any>): Promise<{
|
|
5
|
+
id: string;
|
|
6
|
+
content: string;
|
|
7
|
+
metadata: Record<string, any>;
|
|
8
|
+
created_at: string;
|
|
9
|
+
}>;
|
|
10
|
+
addBatch(entries: Array<{
|
|
11
|
+
content: string;
|
|
12
|
+
metadata?: Record<string, any>;
|
|
13
|
+
}>): Promise<{
|
|
14
|
+
count: number;
|
|
15
|
+
success: boolean;
|
|
16
|
+
ids: string[];
|
|
17
|
+
}>;
|
|
18
|
+
search(query: string, options?: {
|
|
19
|
+
limit?: number;
|
|
20
|
+
filter?: string;
|
|
21
|
+
useCache?: boolean;
|
|
22
|
+
}): Promise<Array<{
|
|
23
|
+
id: string;
|
|
24
|
+
content: string;
|
|
25
|
+
metadata: Record<string, any>;
|
|
26
|
+
score: number;
|
|
27
|
+
created_at: string;
|
|
28
|
+
}>>;
|
|
29
|
+
get(id: string): Promise<{
|
|
30
|
+
id: string;
|
|
31
|
+
content: string;
|
|
32
|
+
metadata: Record<string, any>;
|
|
33
|
+
created_at: string;
|
|
34
|
+
updated_at: string;
|
|
35
|
+
} | null>;
|
|
36
|
+
getAll(options?: {
|
|
37
|
+
limit?: number;
|
|
38
|
+
}): Promise<Array<any>>;
|
|
39
|
+
update(id: string, content: string, metadata?: Record<string, any>): Promise<{
|
|
40
|
+
id: string;
|
|
41
|
+
content: string;
|
|
42
|
+
success: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
delete(id: string): Promise<{
|
|
45
|
+
deleted: string;
|
|
46
|
+
success: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
stats(): Promise<{
|
|
49
|
+
count: number;
|
|
50
|
+
tableName: string;
|
|
51
|
+
uri: string;
|
|
52
|
+
isConnected: boolean;
|
|
53
|
+
embedding: any;
|
|
54
|
+
}>;
|
|
55
|
+
healthCheck(): Promise<{
|
|
56
|
+
status: string;
|
|
57
|
+
timestamp: string;
|
|
58
|
+
checks: Record<string, any>;
|
|
59
|
+
}>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export declare class Scrubber {
|
|
63
|
+
constructor(config?: ScrubberConfig);
|
|
64
|
+
process(document: {
|
|
65
|
+
content: string;
|
|
66
|
+
source: string;
|
|
67
|
+
type: 'html' | 'md' | 'txt';
|
|
68
|
+
}): Promise<ScrubberResult>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ScrubberConfig {
|
|
72
|
+
enabled?: boolean;
|
|
73
|
+
structural?: {
|
|
74
|
+
stripHTML?: boolean;
|
|
75
|
+
normalizeMarkdown?: boolean;
|
|
76
|
+
collapseWhitespace?: boolean;
|
|
77
|
+
removeScripts?: boolean;
|
|
78
|
+
removeStyles?: boolean;
|
|
79
|
+
};
|
|
80
|
+
semantic?: {
|
|
81
|
+
removeDuplicates?: boolean;
|
|
82
|
+
removeBoilerplate?: boolean;
|
|
83
|
+
minSignalRatio?: number;
|
|
84
|
+
};
|
|
85
|
+
chunking?: {
|
|
86
|
+
maxTokens?: number;
|
|
87
|
+
minTokens?: number;
|
|
88
|
+
splitOnHeadings?: boolean;
|
|
89
|
+
};
|
|
90
|
+
validation?: {
|
|
91
|
+
enforceMinLength?: boolean;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ScrubberResult {
|
|
96
|
+
chunks: Array<{
|
|
97
|
+
text: string;
|
|
98
|
+
metadata: Record<string, any>;
|
|
99
|
+
}>;
|
|
100
|
+
metadata: {
|
|
101
|
+
source: string;
|
|
102
|
+
type: string;
|
|
103
|
+
processingTimestamp: string;
|
|
104
|
+
};
|
|
105
|
+
telemetry: {
|
|
106
|
+
totalDuration: number;
|
|
107
|
+
stages: Record<string, any>;
|
|
108
|
+
};
|
|
109
|
+
success: boolean;
|
|
110
|
+
error?: string;
|
|
111
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmbeddingFactory - Multi-provider embedding with automatic fallback
|
|
3
|
+
* Manages primary and fallback embedding services
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import EmbeddingService from "./service.js";
|
|
7
|
+
import { ConfigurationError, EmbeddingError } from "../lancedb/errors.js";
|
|
8
|
+
|
|
9
|
+
class EmbeddingFactory {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.primaryService = null;
|
|
12
|
+
this.fallbackServices = [];
|
|
13
|
+
this.configured = false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configure embedding services with fallback chain
|
|
18
|
+
* @param {Array} configs - Array of { modelType, modelName, priority, apiKey }
|
|
19
|
+
* @returns {Object} Success status
|
|
20
|
+
*/
|
|
21
|
+
configure(configs) {
|
|
22
|
+
// Sort by priority (lower = higher priority)
|
|
23
|
+
configs.sort((a, b) => a.priority - b.priority);
|
|
24
|
+
|
|
25
|
+
this.primaryService = new EmbeddingService(configs[0]);
|
|
26
|
+
|
|
27
|
+
if (configs.length > 1) {
|
|
28
|
+
this.fallbackServices = configs.slice(1).map(c => new EmbeddingService(c));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.configured = true;
|
|
32
|
+
return { success: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Initialize all configured services
|
|
37
|
+
* @returns {Promise<Object>} Initialization status
|
|
38
|
+
*/
|
|
39
|
+
async init() {
|
|
40
|
+
if (!this.configured) {
|
|
41
|
+
throw new ConfigurationError('EmbeddingFactory not configured. Call configure() first.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Initialize primary service
|
|
45
|
+
if (this.primaryService && !this.primaryService.initialized) {
|
|
46
|
+
await this.primaryService.init();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Initialize fallback services lazily (on first use)
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
primary: this.primaryService ? this.primaryService.modelName : null,
|
|
53
|
+
fallbacks: this.fallbackServices.map(s => s.modelName)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate embedding with automatic fallback
|
|
59
|
+
* @param {string} text - Text to embed
|
|
60
|
+
* @param {Object} options - Options
|
|
61
|
+
* @returns {Promise<number[]>} Embedding vector
|
|
62
|
+
*/
|
|
63
|
+
async embed(text, options = {}) {
|
|
64
|
+
if (!this.configured || !this.primaryService) {
|
|
65
|
+
throw new ConfigurationError('EmbeddingFactory not configured');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Try primary service
|
|
69
|
+
try {
|
|
70
|
+
if (!this.primaryService.initialized) {
|
|
71
|
+
await this.primaryService.init();
|
|
72
|
+
}
|
|
73
|
+
return await this.primaryService.embed(text, options);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
76
|
+
console.warn(`[EmbeddingFactory] Primary service failed: ${errorMessage}`);
|
|
77
|
+
|
|
78
|
+
// Try fallback services in order
|
|
79
|
+
for (const fallback of this.fallbackServices) {
|
|
80
|
+
try {
|
|
81
|
+
if (!fallback.initialized) {
|
|
82
|
+
await fallback.init();
|
|
83
|
+
}
|
|
84
|
+
console.log(`[EmbeddingFactory] Using fallback: ${fallback.modelName}`);
|
|
85
|
+
return await fallback.embed(text, options);
|
|
86
|
+
} catch (fallbackError) {
|
|
87
|
+
const fallbackErrorMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
88
|
+
console.warn(`[EmbeddingFactory] Fallback ${fallback.modelName} failed: ${fallbackErrorMessage}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
throw new EmbeddingError('All embedding services failed', {
|
|
93
|
+
primaryError: errorMessage,
|
|
94
|
+
fallbackCount: this.fallbackServices.length
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate embeddings for batch of texts
|
|
101
|
+
* @param {string[]} texts - Texts to embed
|
|
102
|
+
* @param {Object} options - Options
|
|
103
|
+
* @returns {Promise<number[][]>} Array of embedding vectors
|
|
104
|
+
*/
|
|
105
|
+
async embedBatch(texts, options = {}) {
|
|
106
|
+
if (!this.configured || !this.primaryService) {
|
|
107
|
+
throw new ConfigurationError('EmbeddingFactory not configured');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Try primary service
|
|
111
|
+
try {
|
|
112
|
+
if (!this.primaryService.initialized) {
|
|
113
|
+
await this.primaryService.init();
|
|
114
|
+
}
|
|
115
|
+
return await this.primaryService.embedBatch(texts, options);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
118
|
+
console.warn(`[EmbeddingFactory] Primary batch failed: ${errorMessage}`);
|
|
119
|
+
// Fallback to individual embedding with fallback services
|
|
120
|
+
const results = [];
|
|
121
|
+
for (const text of texts) {
|
|
122
|
+
results.push(await this.embed(text, options));
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get factory statistics
|
|
130
|
+
* @returns {Object} Statistics
|
|
131
|
+
*/
|
|
132
|
+
getStats() {
|
|
133
|
+
const stats = {
|
|
134
|
+
configured: this.configured,
|
|
135
|
+
primary: this.primaryService?.getStats() || null,
|
|
136
|
+
fallbacks: this.fallbackServices.map(s => s.getStats())
|
|
137
|
+
};
|
|
138
|
+
return stats;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Clear all caches
|
|
143
|
+
*/
|
|
144
|
+
clearCache() {
|
|
145
|
+
this.primaryService?.clearCache();
|
|
146
|
+
this.fallbackServices.forEach(s => s.clearCache());
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export default EmbeddingFactory;
|