@malipetek/semantic-router 0.0.5 → 0.1.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.
@@ -0,0 +1,31 @@
1
+ import { FlagEmbedding } from "fastembed";
2
+ export interface Route {
3
+ name: string;
4
+ documents: string[];
5
+ callback: (query: string) => void;
6
+ definitionVectors?: Float32Array[];
7
+ }
8
+ export interface DatabaseOptions {
9
+ save?: (vectors: Float32Array[]) => void;
10
+ query?: (query: string) => string[];
11
+ }
12
+ export interface SemanticRouterOptions {
13
+ fastEmbedOptions?: any;
14
+ db?: DatabaseOptions;
15
+ }
16
+ export declare class SemanticRouter {
17
+ private model?;
18
+ private loaded;
19
+ private routes;
20
+ private options;
21
+ private usedb;
22
+ constructor(options?: SemanticRouterOptions);
23
+ waitForModelLoad(): Promise<FlagEmbedding>;
24
+ onModelLoaded(callback: (...args: any[]) => void): void;
25
+ embed(texts: string[]): Promise<Float32Array[]>;
26
+ on(name: string, documents?: string[], callback?: (query: string) => void): Promise<void>;
27
+ route(query: string): Promise<Route | null>;
28
+ }
29
+ export declare const createRouter: (options?: SemanticRouterOptions) => SemanticRouter;
30
+ export default createRouter;
31
+ //# sourceMappingURL=semantic-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-router.d.ts","sourceRoot":"","sources":["../src/semantic-router.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1D,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,iBAAiB,CAAC,EAAE,YAAY,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,GAAG,CAAC;IACvB,EAAE,CAAC,EAAE,eAAe,CAAC;CACtB;AAWD,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAC,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,KAAK,CAAU;gBAEX,OAAO,GAAE,qBAA+D;IAmBpF,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC;IAY1C,aAAa,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,IAAI;IAOjD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAc/C,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,EAAE,QAAQ,GAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvG,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;CA2BlD;AAED,eAAO,MAAM,YAAY,GAAI,UAAU,qBAAqB,mBAAgC,CAAC;AAC7F,eAAe,YAAY,CAAC"}
@@ -0,0 +1,97 @@
1
+ import EventEmitter from 'events';
2
+ import { EmbeddingModel, FlagEmbedding } from "fastembed";
3
+ function similarity(xq, index) {
4
+ const indexNorm = index.map(vec => Math.sqrt(vec.reduce((sum, val) => sum + val ** 2, 0)));
5
+ const xqNorm = Math.sqrt(xq.reduce((sum, val) => sum + val ** 2, 0));
6
+ const sim = index.map((vec, i) => vec.reduce((sum, val, idx) => sum + val * xq[idx], 0) / (indexNorm[i] * xqNorm));
7
+ return Math.max(...sim);
8
+ }
9
+ const EMITTER = new EventEmitter();
10
+ export class SemanticRouter {
11
+ model;
12
+ loaded = false;
13
+ routes = [];
14
+ options;
15
+ usedb;
16
+ constructor(options = { fastEmbedOptions: {}, db: undefined }) {
17
+ this.options = {
18
+ ...options,
19
+ fastEmbedOptions: {
20
+ model: EmbeddingModel.BGESmallENV15,
21
+ showDownloadProgress: true,
22
+ ...options.fastEmbedOptions
23
+ }
24
+ };
25
+ this.usedb = !!(this.options.db && this.options.db.save && this.options.db.query);
26
+ FlagEmbedding.init(this.options.fastEmbedOptions).then(model => {
27
+ this.model = model;
28
+ this.loaded = true;
29
+ EMITTER.emit('model-loaded');
30
+ });
31
+ }
32
+ waitForModelLoad() {
33
+ return new Promise(resolve => {
34
+ if (this.loaded && this.model) {
35
+ resolve(this.model);
36
+ }
37
+ else {
38
+ EMITTER.once('model-loaded', () => {
39
+ if (this.model)
40
+ resolve(this.model);
41
+ });
42
+ }
43
+ });
44
+ }
45
+ onModelLoaded(callback) {
46
+ if (typeof callback !== 'function') {
47
+ throw new Error('Callback must be a function');
48
+ }
49
+ EMITTER.prependListener('model-loaded', callback);
50
+ }
51
+ async embed(texts) {
52
+ const model = await this.waitForModelLoad();
53
+ const embeddings = model.embed(texts);
54
+ const result = [];
55
+ for await (const batch of embeddings) {
56
+ for (const vector of batch) {
57
+ result.push(vector instanceof Float32Array ? vector : new Float32Array(vector));
58
+ }
59
+ }
60
+ return result;
61
+ }
62
+ async on(name, documents = [], callback = () => { }) {
63
+ const vectors = await this.embed(documents);
64
+ this.routes.push({
65
+ name,
66
+ documents,
67
+ callback,
68
+ definitionVectors: vectors,
69
+ });
70
+ }
71
+ async route(query) {
72
+ const queryVectors = await this.embed([query]);
73
+ const queryVector = queryVectors[0];
74
+ const similarityScores = [];
75
+ for (const route of this.routes) {
76
+ if (!route.definitionVectors || route.definitionVectors.length === 0) {
77
+ similarityScores.push(0);
78
+ continue;
79
+ }
80
+ const score = similarity(queryVector, route.definitionVectors);
81
+ similarityScores.push(score);
82
+ }
83
+ if (similarityScores.length === 0)
84
+ return null;
85
+ const maxScore = Math.max(...similarityScores);
86
+ if (maxScore < 0.1) {
87
+ return null;
88
+ }
89
+ const mostSimilarIndex = similarityScores.indexOf(maxScore);
90
+ const likelyRoute = this.routes[mostSimilarIndex];
91
+ likelyRoute.callback(query);
92
+ return likelyRoute;
93
+ }
94
+ }
95
+ export const createRouter = (options) => new SemanticRouter(options);
96
+ export default createRouter;
97
+ //# sourceMappingURL=semantic-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-router.js","sourceRoot":"","sources":["../src/semantic-router.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@malipetek/semantic-router",
3
- "version": "0.0.5",
3
+ "version": "0.1.0",
4
4
  "description": "Semantic router for nodejs for my personal use, pretty straightforward, inspired by semantic router by aureliolabs",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/malipetek/semantic-router.git"
8
8
  },
9
9
  "homepage": "https://github.com/malipetek/semantic-router",
10
- "main": "src/semantic-router.js",
10
+ "main": "dist/semantic-router.js",
11
+ "types": "dist/semantic-router.d.ts",
11
12
  "author": "m.ali petek",
12
13
  "license": "MIT",
13
14
  "private": false,
@@ -15,7 +16,16 @@
15
16
  "dependencies": {
16
17
  "fastembed": "^1.14.1"
17
18
  },
19
+ "devDependencies": {
20
+ "@types/node": "^25.0.3",
21
+ "tsx": "^4.21.0",
22
+ "typescript": "^5.9.3"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
18
27
  "scripts": {
19
- "test": "node tests/*.js"
28
+ "build": "tsc",
29
+ "test": "tsx --test tests/*.ts"
20
30
  }
21
- }
31
+ }
package/image-2.png DELETED
Binary file
package/image-3.png DELETED
Binary file
package/image.png DELETED
Binary file
@@ -1,141 +0,0 @@
1
- import EventEmitter from 'events';
2
- import { EmbeddingModel, FlagEmbedding } from "fastembed";
3
- /**
4
- * @typedef {{ name: string, documents: string[], callback: Function, definitionVectors: Float32Array[] }} Route
5
- */
6
-
7
- /**
8
- * Calculates the similarity between the query vector and the index vectors.
9
- *
10
- * @param {Float32Array} xq - The query vector
11
- * @param {Float32Array[]} index - The array dxszawof index vectors
12
- * @return {Number} The maximum similarity score
13
- */
14
- function similarity(xq, index) {
15
- const indexNorm = index.map(vec => Math.sqrt(vec.reduce((sum, val) => sum + val ** 2, 0)));
16
- const xqNorm = Math.sqrt(xq.reduce((sum, val) => sum + val ** 2, 0));
17
- const sim = index.map((vec, i) => vec.reduce((sum, val, i) => sum + val * xq[i], 0) / (indexNorm[i] * xqNorm));
18
- return Math.max(...sim); // Return the maximum similarity score
19
- }
20
-
21
- const EMITTER = new EventEmitter();
22
- export class SemanticRouter {
23
- constructor({ fastEmbedOptions } = {}) {
24
- fastEmbedOptions = {
25
- model: EmbeddingModel.BGESmallENV15,
26
- showDownloadProgress: true,
27
- ...fastEmbedOptions
28
- }
29
-
30
- /** @type {FlagEmbedding} */
31
- this.model;
32
- this.loaded = false;
33
- /** @type {Route[]} */
34
- this.routes = [];
35
-
36
- FlagEmbedding.init(fastEmbedOptions).then(
37
- /** @type {FlagEmbedding} */
38
- model => {
39
- this.model = model;
40
- this.loaded = true;
41
- EMITTER.emit('model-loaded');
42
- });
43
- }
44
-
45
- /**
46
- * Waits for the model to be loaded and resolves with the model.
47
- *
48
- * @return {Promise<FlagEmbedding>} A promise that resolves with the loaded model.
49
- */
50
- waitForModelLoad() {
51
- return new Promise(resolve => {
52
- if (this.loaded) {
53
- resolve(this.model);
54
- } else {
55
- EMITTER.on('model-loaded', () => resolve(this.model));
56
- }
57
- });
58
- }
59
-
60
- /**
61
- * onModelLoaded - A function to register a callback for when the model is loaded.
62
- *
63
- * @param {(...args: any[]) => void} callback - The callback function to be executed when the model is loaded.
64
- * @return {void}
65
- */
66
- onModelLoaded(callback) {
67
- // make sure callback is a function
68
- if (typeof callback !== 'function') {
69
- throw new Error('Callback must be a function');
70
- }
71
- EMITTER.prependListener('model-loaded', callback);
72
- }
73
- /**
74
- * Asynchronously embeds the given text using the model after waiting for the model to load.
75
- *
76
- * @param {string[]} texts - the text to embed
77
- * @return {Promise<Float32Array[]>} the embedded result
78
- */
79
- async embed(texts) {
80
- await this.waitForModelLoad();
81
- const embeddings = this.model.embed(texts);
82
- const embeddingsRes = [];
83
- for await (const batch of embeddings) {
84
- // make sure batch is Float32Array
85
- embeddingsRes.push(batch);
86
- }
87
- /** @type {Float32Array[]} */
88
- const result = embeddingsRes.flat();
89
-
90
- return result;
91
- }
92
-
93
- /**
94
- * Adds a new route to the routes object.
95
- *
96
- * @param {string} name - the name of the route
97
- * @param {string[]} documents - the documents associated with the route
98
- * @param {function} callback - the callback function for the route
99
- * @return {Promise<undefined>}
100
- */
101
- async on(name, documents = [], callback = () => {}) {
102
- const vector = await this.embed(documents);
103
- this.routes.push({
104
- name,
105
- documents,
106
- callback,
107
- definitionVectors: vector,
108
- });
109
- }
110
-
111
- /**
112
- * Asynchronously routes a query to the most similar route.
113
- *
114
- * @param {string} query - the query to be routed
115
- * @return {Promise<any>} the result of the routed query
116
- */
117
- async route(query) {
118
- const queryVector = await this.embed([query]);
119
- /** @type {number[]} */
120
- const similarityScores = [];
121
- for (let index in this.routes) {
122
- const route = this.routes[index];
123
- if(!route.definitionVectors) {
124
- continue;
125
- }
126
- const similarityScore = similarity(queryVector[0], route.definitionVectors);
127
- similarityScores.push(similarityScore);
128
- }
129
- const mostSimilarIndex = similarityScores.indexOf(similarityScores.find((x, i) => x === Math.max(...similarityScores)));
130
- const likelyRoute = this.routes[mostSimilarIndex];
131
- // if top score has very low score return null
132
- if (similarityScores[mostSimilarIndex] < 0.1) {
133
- return null;
134
- }
135
-
136
- likelyRoute.callback(query);
137
-
138
- return likelyRoute;
139
- }
140
- }
141
- export default () => new SemanticRouter();