@tpsdev-ai/flair 0.3.1 → 0.3.4
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.
|
@@ -1,127 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* embeddings-provider.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Thin wrapper around harper-fabric-embeddings for Flair resources.
|
|
5
|
+
* harper-fabric-embeddings handles loading and running the embedding model
|
|
6
|
+
* at the Harper sub-component level — we just delegate to its exported API.
|
|
7
|
+
*
|
|
8
|
+
* On platforms where the native binary isn't available, getEmbedding()
|
|
9
|
+
* returns null and semantic search falls back to keyword matching.
|
|
7
10
|
*/
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (!process[SINGLETON_KEY]) {
|
|
14
|
-
process[SINGLETON_KEY] = { hfe: null, dims: 0, mode: "none", initPromise: null };
|
|
15
|
-
}
|
|
16
|
-
return process[SINGLETON_KEY];
|
|
17
|
-
}
|
|
18
|
-
function getQueue() {
|
|
19
|
-
if (!process[QUEUE_KEY]) {
|
|
20
|
-
process[QUEUE_KEY] = { queue: [], processing: false };
|
|
11
|
+
import * as hfe from "harper-fabric-embeddings";
|
|
12
|
+
const SINGLETON_KEY = "__flair_hfe_provider_v1__";
|
|
13
|
+
function getState() {
|
|
14
|
+
if (!globalThis[SINGLETON_KEY]) {
|
|
15
|
+
globalThis[SINGLETON_KEY] = { initialized: false, available: false };
|
|
21
16
|
}
|
|
22
|
-
return
|
|
17
|
+
return globalThis[SINGLETON_KEY];
|
|
23
18
|
}
|
|
24
|
-
// ─── Init ─────────────────────────────────────────────────────────────────────
|
|
25
19
|
export async function initEmbeddings() {
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
20
|
+
const state = getState();
|
|
21
|
+
if (state.initialized)
|
|
28
22
|
return;
|
|
29
|
-
if (s.initPromise) {
|
|
30
|
-
await s.initPromise;
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
s.initPromise = doInit(s);
|
|
34
|
-
await s.initPromise;
|
|
35
|
-
}
|
|
36
|
-
async function doInit(s) {
|
|
37
|
-
// Resolve path at build time relative to this file's location
|
|
38
|
-
const hfePath = new URL("../../node_modules/harper-fabric-embeddings/dist/index.js", import.meta.url).href;
|
|
39
23
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const importFn = new Function("url", "return import(url)");
|
|
44
|
-
const mod = await importFn(hfePath);
|
|
45
|
-
if (typeof mod.init !== "function") {
|
|
46
|
-
throw new Error(`Module has no init(). Keys: ${Object.keys(mod)}`);
|
|
47
|
-
}
|
|
48
|
-
const result = mod.init({ modelsDir: MODELS_DIR, gpuLayers: 99 });
|
|
49
|
-
if (result?.then)
|
|
50
|
-
await result;
|
|
51
|
-
s.hfe = mod;
|
|
52
|
-
s.dims = mod.dimensions();
|
|
53
|
-
s.mode = "native";
|
|
54
|
-
console.log(`[embeddings] Native in-process (v0.2.1): ${s.dims} dims`);
|
|
24
|
+
await hfe.init({});
|
|
25
|
+
state.available = true;
|
|
26
|
+
console.log(`[embeddings] harper-fabric-embeddings ready (${hfe.dimensions()} dims)`);
|
|
55
27
|
}
|
|
56
28
|
catch (err) {
|
|
57
|
-
console.
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
s.dims = 512;
|
|
61
|
-
s.mode = "hash";
|
|
62
|
-
console.log(`[embeddings] Fallback: 512 dims (hash-based)`);
|
|
63
|
-
}
|
|
64
|
-
catch (e2) {
|
|
65
|
-
console.error(`[embeddings] All embedding modes failed: ${e2.message}`);
|
|
66
|
-
}
|
|
29
|
+
console.log(`[embeddings] harper-fabric-embeddings unavailable: ${err.message}`);
|
|
30
|
+
state.available = false;
|
|
67
31
|
}
|
|
32
|
+
state.initialized = true;
|
|
68
33
|
}
|
|
69
|
-
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
70
|
-
export function getDimensions() { return getSingleton().dims; }
|
|
71
|
-
export function getMode() { return getSingleton().mode; }
|
|
72
34
|
export async function getEmbedding(text) {
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
35
|
+
const state = getState();
|
|
36
|
+
if (!state.initialized)
|
|
75
37
|
await initEmbeddings();
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
export function getQueueLength() { return getQueue().queue.length; }
|
|
83
|
-
async function processQueue(q) {
|
|
84
|
-
if (q.processing)
|
|
85
|
-
return;
|
|
86
|
-
q.processing = true;
|
|
87
|
-
while (q.queue.length > 0) {
|
|
88
|
-
const job = q.queue.shift();
|
|
89
|
-
try {
|
|
90
|
-
const s = getSingleton();
|
|
91
|
-
if (s.mode === "native" && s.hfe) {
|
|
92
|
-
job.resolve(await s.hfe.embed(job.text.slice(0, MAX_CHARS)));
|
|
93
|
-
}
|
|
94
|
-
else if (s.mode === "hash") {
|
|
95
|
-
// Hash fallback doesn't need the module
|
|
96
|
-
const text = job.text.slice(0, MAX_CHARS);
|
|
97
|
-
job.resolve(fallbackEmbed(text));
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
job.resolve(null);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch (err) {
|
|
104
|
-
console.error(`[embeddings] embed failed: ${err.message}`);
|
|
105
|
-
job.resolve(null);
|
|
106
|
-
}
|
|
38
|
+
if (!state.available)
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
return hfe.embed(text);
|
|
107
42
|
}
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function fallbackEmbed(text) {
|
|
112
|
-
const dims = 512;
|
|
113
|
-
const vec = new Array(dims).fill(0);
|
|
114
|
-
for (let i = 0; i < text.length; i++) {
|
|
115
|
-
const code = text.charCodeAt(i);
|
|
116
|
-
vec[i % dims] += code / 128;
|
|
117
|
-
vec[(i * 7 + 3) % dims] += Math.sin(code * 0.1) * 0.5;
|
|
43
|
+
catch (err) {
|
|
44
|
+
console.log(`[embeddings] embed failed: ${err.message}`);
|
|
45
|
+
return null;
|
|
118
46
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
mag = Math.sqrt(mag) || 1;
|
|
124
|
-
for (let i = 0; i < dims; i++)
|
|
125
|
-
vec[i] /= mag;
|
|
126
|
-
return vec;
|
|
47
|
+
}
|
|
48
|
+
export function getMode() {
|
|
49
|
+
const state = getState();
|
|
50
|
+
return state.available ? "local" : "none";
|
|
127
51
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality
|
|
3
|
+
"version": "0.3.4",
|
|
4
|
+
"description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@harperfast/harper": "5.0.0-beta.4",
|
|
53
|
-
"@node-llama-cpp/mac-arm64-metal": "^3.17.1",
|
|
54
53
|
"commander": "14.0.3",
|
|
55
54
|
"harper-fabric-embeddings": "^0.2.0",
|
|
56
55
|
"tweetnacl": "1.0.3"
|
|
@@ -66,5 +65,8 @@
|
|
|
66
65
|
"workspaces": [
|
|
67
66
|
"packages/*",
|
|
68
67
|
"plugins/*"
|
|
69
|
-
]
|
|
68
|
+
],
|
|
69
|
+
"optionalDependencies": {
|
|
70
|
+
"@node-llama-cpp/mac-arm64-metal": "^3.17.1"
|
|
71
|
+
}
|
|
70
72
|
}
|
|
@@ -1,144 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* embeddings-provider.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Thin wrapper around harper-fabric-embeddings for Flair resources.
|
|
5
|
+
* harper-fabric-embeddings handles loading and running the embedding model
|
|
6
|
+
* at the Harper sub-component level — we just delegate to its exported API.
|
|
7
|
+
*
|
|
8
|
+
* On platforms where the native binary isn't available, getEmbedding()
|
|
9
|
+
* returns null and semantic search falls back to keyword matching.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
const MODELS_DIR = process.env.FLAIR_MODELS_DIR || "/tmp/flair-models";
|
|
11
|
-
const SINGLETON_KEY = "__flair_hfe_021__";
|
|
12
|
-
const QUEUE_KEY = "__flair_embed_queue_021__";
|
|
13
|
-
|
|
14
|
-
interface HfeSingleton {
|
|
15
|
-
hfe: any;
|
|
16
|
-
dims: number;
|
|
17
|
-
mode: "native" | "hash" | "none";
|
|
18
|
-
initPromise: Promise<void> | null;
|
|
19
|
-
}
|
|
12
|
+
import * as hfe from "harper-fabric-embeddings";
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
if (!(process as any)[SINGLETON_KEY]) {
|
|
23
|
-
(process as any)[SINGLETON_KEY] = { hfe: null, dims: 0, mode: "none", initPromise: null };
|
|
24
|
-
}
|
|
25
|
-
return (process as any)[SINGLETON_KEY];
|
|
26
|
-
}
|
|
14
|
+
const SINGLETON_KEY = "__flair_hfe_provider_v1__";
|
|
27
15
|
|
|
28
|
-
interface
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
interface ProviderState {
|
|
17
|
+
initialized: boolean;
|
|
18
|
+
available: boolean;
|
|
31
19
|
}
|
|
32
20
|
|
|
33
|
-
function
|
|
34
|
-
if (!(
|
|
35
|
-
(
|
|
21
|
+
function getState(): ProviderState {
|
|
22
|
+
if (!(globalThis as any)[SINGLETON_KEY]) {
|
|
23
|
+
(globalThis as any)[SINGLETON_KEY] = { initialized: false, available: false };
|
|
36
24
|
}
|
|
37
|
-
return (
|
|
25
|
+
return (globalThis as any)[SINGLETON_KEY];
|
|
38
26
|
}
|
|
39
27
|
|
|
40
|
-
// ─── Init ─────────────────────────────────────────────────────────────────────
|
|
41
|
-
|
|
42
28
|
export async function initEmbeddings(): Promise<void> {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
if (s.initPromise) { await s.initPromise; return; }
|
|
46
|
-
s.initPromise = doInit(s);
|
|
47
|
-
await s.initPromise;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function doInit(s: HfeSingleton): Promise<void> {
|
|
51
|
-
// Resolve path at build time relative to this file's location
|
|
52
|
-
const hfePath = new URL(
|
|
53
|
-
"../../node_modules/harper-fabric-embeddings/dist/index.js",
|
|
54
|
-
import.meta.url
|
|
55
|
-
).href;
|
|
56
|
-
|
|
29
|
+
const state = getState();
|
|
30
|
+
if (state.initialized) return;
|
|
57
31
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const importFn = new Function("url", "return import(url)") as (url: string) => Promise<any>;
|
|
62
|
-
const mod = await importFn(hfePath);
|
|
63
|
-
|
|
64
|
-
if (typeof mod.init !== "function") {
|
|
65
|
-
throw new Error(`Module has no init(). Keys: ${Object.keys(mod)}`);
|
|
66
|
-
}
|
|
67
|
-
const result = mod.init({ modelsDir: MODELS_DIR, gpuLayers: 99 });
|
|
68
|
-
if (result?.then) await result;
|
|
69
|
-
|
|
70
|
-
s.hfe = mod;
|
|
71
|
-
s.dims = mod.dimensions();
|
|
72
|
-
s.mode = "native";
|
|
73
|
-
console.log(`[embeddings] Native in-process (v0.2.1): ${s.dims} dims`);
|
|
32
|
+
await hfe.init({});
|
|
33
|
+
state.available = true;
|
|
34
|
+
console.log(`[embeddings] harper-fabric-embeddings ready (${hfe.dimensions()} dims)`);
|
|
74
35
|
} catch (err: any) {
|
|
75
|
-
console.
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
s.dims = 512; s.mode = "hash";
|
|
79
|
-
console.log(`[embeddings] Fallback: 512 dims (hash-based)`);
|
|
80
|
-
} catch (e2: any) {
|
|
81
|
-
console.error(`[embeddings] All embedding modes failed: ${e2.message}`);
|
|
82
|
-
}
|
|
36
|
+
console.log(`[embeddings] harper-fabric-embeddings unavailable: ${err.message}`);
|
|
37
|
+
state.available = false;
|
|
83
38
|
}
|
|
39
|
+
state.initialized = true;
|
|
84
40
|
}
|
|
85
41
|
|
|
86
|
-
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
87
|
-
|
|
88
|
-
export function getDimensions(): number { return getSingleton().dims; }
|
|
89
|
-
export function getMode(): string { return getSingleton().mode; }
|
|
90
|
-
|
|
91
42
|
export async function getEmbedding(text: string): Promise<number[] | null> {
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function getQueueLength(): number { return getQueue().queue.length; }
|
|
103
|
-
|
|
104
|
-
async function processQueue(q: QueueState): Promise<void> {
|
|
105
|
-
if (q.processing) return;
|
|
106
|
-
q.processing = true;
|
|
107
|
-
while (q.queue.length > 0) {
|
|
108
|
-
const job = q.queue.shift()!;
|
|
109
|
-
try {
|
|
110
|
-
const s = getSingleton();
|
|
111
|
-
if (s.mode === "native" && s.hfe) {
|
|
112
|
-
job.resolve(await s.hfe.embed(job.text.slice(0, MAX_CHARS)));
|
|
113
|
-
} else if (s.mode === "hash") {
|
|
114
|
-
// Hash fallback doesn't need the module
|
|
115
|
-
const text = job.text.slice(0, MAX_CHARS);
|
|
116
|
-
job.resolve(fallbackEmbed(text));
|
|
117
|
-
} else {
|
|
118
|
-
job.resolve(null);
|
|
119
|
-
}
|
|
120
|
-
} catch (err: any) {
|
|
121
|
-
console.error(`[embeddings] embed failed: ${err.message}`);
|
|
122
|
-
job.resolve(null);
|
|
123
|
-
}
|
|
43
|
+
const state = getState();
|
|
44
|
+
if (!state.initialized) await initEmbeddings();
|
|
45
|
+
if (!state.available) return null;
|
|
46
|
+
try {
|
|
47
|
+
return hfe.embed(text);
|
|
48
|
+
} catch (err: any) {
|
|
49
|
+
console.log(`[embeddings] embed failed: ${err.message}`);
|
|
50
|
+
return null;
|
|
124
51
|
}
|
|
125
|
-
q.processing = false;
|
|
126
52
|
}
|
|
127
53
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const dims = 512;
|
|
132
|
-
const vec = new Array(dims).fill(0);
|
|
133
|
-
for (let i = 0; i < text.length; i++) {
|
|
134
|
-
const code = text.charCodeAt(i);
|
|
135
|
-
vec[i % dims] += code / 128;
|
|
136
|
-
vec[(i * 7 + 3) % dims] += Math.sin(code * 0.1) * 0.5;
|
|
137
|
-
}
|
|
138
|
-
// Normalize
|
|
139
|
-
let mag = 0;
|
|
140
|
-
for (let i = 0; i < dims; i++) mag += vec[i] * vec[i];
|
|
141
|
-
mag = Math.sqrt(mag) || 1;
|
|
142
|
-
for (let i = 0; i < dims; i++) vec[i] /= mag;
|
|
143
|
-
return vec;
|
|
54
|
+
export function getMode(): "local" | "none" {
|
|
55
|
+
const state = getState();
|
|
56
|
+
return state.available ? "local" : "none";
|
|
144
57
|
}
|