@deepsweet/mdn 0.1.3 → 0.3.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/README.md +6 -4
- package/dist/index.js +42 -33
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ See [dataset repo](https://huggingface.co/datasets/deepsweet/mdn) on HuggigFace
|
|
|
22
22
|
### 1. Download dataset and embedding model
|
|
23
23
|
|
|
24
24
|
```sh
|
|
25
|
-
npx -y @deepsweet/mdn download
|
|
25
|
+
npx -y @deepsweet/mdn@latest download
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Both [dataset](https://huggingface.co/datasets/deepsweet/mdn) (\~260 MB) and the [embedding model GGUF file](https://huggingface.co/deepsweet/bge-m3-GGUF-Q4_K_M) (\~438 MB) will be downloaded directly from HugginFace and stored in its default cache location (typically `~/.cache/huggingface/`), just like the `hf download` command does.
|
|
@@ -35,7 +35,8 @@ Both [dataset](https://huggingface.co/datasets/deepsweet/mdn) (\~260 MB) and the
|
|
|
35
35
|
"mdn": {
|
|
36
36
|
"command": "npx",
|
|
37
37
|
"args": [
|
|
38
|
-
"
|
|
38
|
+
"-y",
|
|
39
|
+
"@deepsweet/mdn@latest",
|
|
39
40
|
"server"
|
|
40
41
|
],
|
|
41
42
|
"env": {}
|
|
@@ -44,6 +45,9 @@ Both [dataset](https://huggingface.co/datasets/deepsweet/mdn) (\~260 MB) and the
|
|
|
44
45
|
}
|
|
45
46
|
```
|
|
46
47
|
|
|
48
|
+
> [!TIP]
|
|
49
|
+
> Remove `@latest` for a full offline experience, but keep in mind that this will cache a fixed version without auto-updating.
|
|
50
|
+
|
|
47
51
|
The `stdio` server will spawn [llama.cpp](https://github.com/ggml-org/llama.cpp) under the hood, load the embedding model (~655 MB RAM/VRAM), and query the dataset – all on demand.
|
|
48
52
|
|
|
49
53
|
## Settings
|
|
@@ -51,7 +55,6 @@ The `stdio` server will spawn [llama.cpp](https://github.com/ggml-org/llama.cpp)
|
|
|
51
55
|
| Env variable | Default value | Description |
|
|
52
56
|
|----------------------------|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|
|
|
53
57
|
| `MDN_DATASET_PATH` | HuggingFace cache | Custom dataset directory path |
|
|
54
|
-
| `MDN_DATASET_LOCALE` | `en-us` | Dataset language, currently `en-us` only |
|
|
55
58
|
| `MDN_MODEL_PATH` | HuggingFace cache | Custom model file path |
|
|
56
59
|
| `MDN_MODEL_TTL` | `1800` | For how long llama.cpp with embedding model should be kept loaded in memory, in seconds; `0` to prevent unloading |
|
|
57
60
|
| `MDN_QUERY_DESCRIPTION` | `Natural language query for hybrid vector and full-text search` | Custom search query description in case your LLM does a poor job asking the MCP tool |
|
|
@@ -60,7 +63,6 @@ The `stdio` server will spawn [llama.cpp](https://github.com/ggml-org/llama.cpp)
|
|
|
60
63
|
## To do
|
|
61
64
|
|
|
62
65
|
- [ ] figure out a better query description so that LLM doesn't over-generate keywords
|
|
63
|
-
- [ ] add more dataset [translations](https://github.com/mdn/translated-content/tree/main/files/)
|
|
64
66
|
- [ ] automatically update and upload the dataset artifacts monthly with GitHub Actions
|
|
65
67
|
|
|
66
68
|
## License
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/huggingface.ts
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import {
|
|
7
|
+
downloadFileToCacheDir,
|
|
8
|
+
getHFHubCachePath,
|
|
9
|
+
getRepoFolderName,
|
|
10
|
+
scanCachedRepo,
|
|
11
|
+
snapshotDownload
|
|
12
|
+
} from "@huggingface/hub";
|
|
13
|
+
|
|
14
|
+
// src/const.ts
|
|
15
|
+
var DATASET_REPO = "deepsweet/mdn";
|
|
16
|
+
var MODEL_REPO = "deepsweet/bge-m3-GGUF-Q4_K_M";
|
|
17
|
+
var MODEL_FILE = "bge-m3-GGUF-Q4_K_M.gguf";
|
|
18
|
+
var MODEL_MAX_TOKENS = 8192;
|
|
19
|
+
var TABLE_NAME = "mdn";
|
|
20
|
+
var TABLE_FILENAME = `${TABLE_NAME}.lance`;
|
|
21
|
+
|
|
3
22
|
// src/env.ts
|
|
4
23
|
import { z } from "zod";
|
|
5
24
|
var env = z.object({
|
|
6
25
|
MDN_DATASET_PATH: z.string().optional(),
|
|
7
|
-
MDN_DATASET_LOCALE: z.enum(["en-us"]).default("en-us"),
|
|
8
26
|
MDN_MODEL_PATH: z.string().optional(),
|
|
9
27
|
MDN_MODEL_TTL: z.number().default(1800),
|
|
10
28
|
MDN_QUERY_DESCRIPTION: z.string().default("Natural language query for hybrid vector and full-text search"),
|
|
@@ -12,19 +30,6 @@ var env = z.object({
|
|
|
12
30
|
}).parse(process.env);
|
|
13
31
|
|
|
14
32
|
// src/huggingface.ts
|
|
15
|
-
import fs from "node:fs/promises";
|
|
16
|
-
import path from "path";
|
|
17
|
-
import { getHFHubCachePath, getRepoFolderName, scanCachedRepo, snapshotDownload } from "@huggingface/hub";
|
|
18
|
-
|
|
19
|
-
// src/utils.ts
|
|
20
|
-
var getTableName = (locale) => {
|
|
21
|
-
return `mdn-${locale}`;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// src/huggingface.ts
|
|
25
|
-
var DATASET_REPO = "deepsweet/mdn";
|
|
26
|
-
var MODEL_REPO = "deepsweet/bge-m3-GGUF-Q4_K_M";
|
|
27
|
-
var MODEL_FILE = "bge-m3-GGUF-Q4_K_M.gguf";
|
|
28
33
|
var replaceSymlinksWithHardlinks = async (dir) => {
|
|
29
34
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
30
35
|
for (const entry of entries) {
|
|
@@ -57,18 +62,17 @@ var getLatestCachedRepoRevision = async (name, type) => {
|
|
|
57
62
|
});
|
|
58
63
|
return latestRevision.path;
|
|
59
64
|
};
|
|
60
|
-
var downloadDataset = async (
|
|
61
|
-
const tableName = getTableName(locale);
|
|
65
|
+
var downloadDataset = async () => {
|
|
62
66
|
const dirPath = await snapshotDownload({
|
|
63
67
|
repo: `datasets/${DATASET_REPO}`,
|
|
64
|
-
path: `data/${
|
|
68
|
+
path: `data/${TABLE_FILENAME}`
|
|
65
69
|
});
|
|
66
70
|
const dataPath = path.join(dirPath, "data");
|
|
67
71
|
await replaceSymlinksWithHardlinks(dataPath);
|
|
68
72
|
};
|
|
69
73
|
var getDatasetPath = async () => {
|
|
70
|
-
if (
|
|
71
|
-
return
|
|
74
|
+
if (env.MDN_DATASET_PATH != null) {
|
|
75
|
+
return env.MDN_DATASET_PATH;
|
|
72
76
|
}
|
|
73
77
|
const latestRevisionPath = await getLatestCachedRepoRevision(DATASET_REPO, "dataset");
|
|
74
78
|
const datasetPath = path.join(latestRevisionPath, "data");
|
|
@@ -80,8 +84,8 @@ var downloadModel = async () => {
|
|
|
80
84
|
});
|
|
81
85
|
};
|
|
82
86
|
var getModelPath = async () => {
|
|
83
|
-
if (
|
|
84
|
-
return
|
|
87
|
+
if (env.MDN_MODEL_PATH != null) {
|
|
88
|
+
return env.MDN_MODEL_PATH;
|
|
85
89
|
}
|
|
86
90
|
const latestRevisionPath = await getLatestCachedRepoRevision(MODEL_REPO, "model");
|
|
87
91
|
const modelPath = path.join(latestRevisionPath, MODEL_FILE);
|
|
@@ -95,15 +99,16 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
95
99
|
import { z as z2 } from "zod";
|
|
96
100
|
|
|
97
101
|
// src/llama.ts
|
|
102
|
+
import os from "node:os";
|
|
98
103
|
import { getLlama, LlamaLogLevel } from "node-llama-cpp";
|
|
99
|
-
var MAX_TOKENS = 8192;
|
|
100
104
|
var getLlamaContext = async (modelPath) => {
|
|
105
|
+
const threads = Math.floor(os.availableParallelism() / 2);
|
|
101
106
|
const llama = await getLlama({ logLevel: LlamaLogLevel.error });
|
|
102
107
|
const model = await llama.loadModel({ modelPath });
|
|
103
108
|
const context = await model.createEmbeddingContext({
|
|
104
|
-
contextSize:
|
|
105
|
-
batchSize:
|
|
106
|
-
threads
|
|
109
|
+
contextSize: MODEL_MAX_TOKENS,
|
|
110
|
+
batchSize: MODEL_MAX_TOKENS,
|
|
111
|
+
threads
|
|
107
112
|
});
|
|
108
113
|
context.onDispose.createOnceListener(() => {
|
|
109
114
|
model.dispose().then(() => llama.dispose()).catch(console.error);
|
|
@@ -124,7 +129,7 @@ var vectorize = async (context, text) => {
|
|
|
124
129
|
// src/query.ts
|
|
125
130
|
var queryHybrid = async (llamaContext, table, reranker, text) => {
|
|
126
131
|
const vector = await vectorize(llamaContext, text);
|
|
127
|
-
const results = await table.query().nearestTo(vector).fullTextSearch(text).rerank(reranker).limit(env.MDN_SEARCH_RESULTS_LIMIT).toArray();
|
|
132
|
+
const results = await table.query().nearestTo(vector).column("vector").fullTextSearch(text, { columns: "text" }).rerank(reranker).limit(env.MDN_SEARCH_RESULTS_LIMIT).toArray();
|
|
128
133
|
return results;
|
|
129
134
|
};
|
|
130
135
|
var createReranker = async () => {
|
|
@@ -134,7 +139,7 @@ var createReranker = async () => {
|
|
|
134
139
|
|
|
135
140
|
// package.json
|
|
136
141
|
var name = "@deepsweet/mdn";
|
|
137
|
-
var version = "0.
|
|
142
|
+
var version = "0.3.0";
|
|
138
143
|
|
|
139
144
|
// src/server.ts
|
|
140
145
|
var startMcpServer = async () => {
|
|
@@ -142,8 +147,7 @@ var startMcpServer = async () => {
|
|
|
142
147
|
const db = await lancedb2.connect(datasetPath);
|
|
143
148
|
const reranker = await createReranker();
|
|
144
149
|
const server = new McpServer({ name, version });
|
|
145
|
-
const
|
|
146
|
-
const table = await db.openTable(tableName);
|
|
150
|
+
const table = await db.openTable(TABLE_NAME);
|
|
147
151
|
const modelPath = await getModelPath();
|
|
148
152
|
const llamaTtl = env.MDN_MODEL_TTL * 1000;
|
|
149
153
|
let llamaContext = null;
|
|
@@ -152,6 +156,9 @@ var startMcpServer = async () => {
|
|
|
152
156
|
description: "Reference documentation for Web API, JavaScript, HTML, CSS, SVG and HTTP",
|
|
153
157
|
inputSchema: z2.object({
|
|
154
158
|
query: z2.string().describe(env.MDN_QUERY_DESCRIPTION)
|
|
159
|
+
}),
|
|
160
|
+
outputSchema: z2.object({
|
|
161
|
+
results: z2.array(z2.string())
|
|
155
162
|
})
|
|
156
163
|
}, async ({ query }) => {
|
|
157
164
|
llamaContext ??= await getLlamaContext(modelPath);
|
|
@@ -170,7 +177,10 @@ var startMcpServer = async () => {
|
|
|
170
177
|
content: results.map((result) => ({
|
|
171
178
|
type: "text",
|
|
172
179
|
text: result.text
|
|
173
|
-
}))
|
|
180
|
+
})),
|
|
181
|
+
structuredContent: {
|
|
182
|
+
results: results.map((result) => result.text)
|
|
183
|
+
}
|
|
174
184
|
};
|
|
175
185
|
});
|
|
176
186
|
const transport = new StdioServerTransport;
|
|
@@ -196,8 +206,7 @@ var startMcpServer = async () => {
|
|
|
196
206
|
// src/index.ts
|
|
197
207
|
switch (process.argv[2]) {
|
|
198
208
|
case "download": {
|
|
199
|
-
|
|
200
|
-
await downloadDataset(locale);
|
|
209
|
+
await downloadDataset();
|
|
201
210
|
await downloadModel();
|
|
202
211
|
break;
|
|
203
212
|
}
|
|
@@ -206,7 +215,7 @@ switch (process.argv[2]) {
|
|
|
206
215
|
break;
|
|
207
216
|
}
|
|
208
217
|
default: {
|
|
209
|
-
console.error('Unknown
|
|
218
|
+
console.error('Unknown command, use "download" or "server"');
|
|
210
219
|
process.exit(1);
|
|
211
220
|
}
|
|
212
221
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepsweet/mdn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -27,13 +27,18 @@
|
|
|
27
27
|
"eslint": "^10.1.0",
|
|
28
28
|
"gray-matter": "^4.0.3",
|
|
29
29
|
"marked": "^17.0.5",
|
|
30
|
+
"p-all": "^5.0.1",
|
|
30
31
|
"rimraf": "^6.1.3",
|
|
31
32
|
"typescript": "^6.0.2"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
35
|
+
"download": "bun scripts/download.ts",
|
|
34
36
|
"chunk": "bun scripts/chunk.ts",
|
|
35
37
|
"ingest": "bun scripts/ingest.ts",
|
|
38
|
+
"update": "bun scripts/update.ts",
|
|
36
39
|
"query": "bun scripts/query.ts",
|
|
40
|
+
"upload": "bun scripts/upload.ts",
|
|
41
|
+
"test": "bun scripts/test.ts",
|
|
37
42
|
"check": "tsc --noEmit && eslint --cache scripts/ src/",
|
|
38
43
|
"dist": "bun build --format esm --target node --packages external --outdir dist/ src/index.ts"
|
|
39
44
|
},
|