n2-qln 3.1.0 โ 3.2.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.ko.md +465 -0
- package/README.md +72 -0
- package/index.js +3 -2
- package/lib/registry.js +2 -0
- package/lib/router.js +34 -3
- package/lib/schema.js +1 -0
- package/lib/store.js +5 -3
- package/package.json +9 -3
- package/tools/qln-call.js +23 -10
package/README.ko.md
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
๐บ๐ธ [English](README.md)
|
|
2
|
+
|
|
3
|
+
# n2-qln
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/n2-qln) [](LICENSE) [](https://nodejs.org) [](https://www.npmjs.com/package/n2-qln)
|
|
6
|
+
|
|
7
|
+
**QLN** = **Q**uery **L**ayer **N**etwork โ AI์ ๋๊ตฌ ์ฌ์ด์ ์์นํ๋ ์๋งจํฑ ๊ฒ์ ๋ ์ด์ด.
|
|
8
|
+
|
|
9
|
+
> **1,000๊ฐ ์ด์์ ๋๊ตฌ๋ฅผ 1๊ฐ์ MCP ๋๊ตฌ๋ก ๋ผ์ฐํ
ํฉ๋๋ค.** AI๋ ๋ผ์ฐํฐ ํ๋๋ง ๋ด
๋๋ค โ 1,000๊ฐ ์ ์ฒด๊ฐ ์๋๋๋ค.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## ๋ชฉ์ฐจ
|
|
14
|
+
|
|
15
|
+
- [๊ธฐ๋ฅ](#๊ธฐ๋ฅ)
|
|
16
|
+
- [๋ฌธ์ ์ ](#๋ฌธ์ ์ )
|
|
17
|
+
- [์ค์น](#์ค์น)
|
|
18
|
+
- [์ค์ ](#์ค์ )
|
|
19
|
+
- [์๋ ๋ฐฉ์](#์๋-๋ฐฉ์)
|
|
20
|
+
- [API ๋ ํผ๋ฐ์ค](#api-๋ ํผ๋ฐ์ค)
|
|
21
|
+
- [์ค์ ํ์ผ](#์ค์ -ํ์ผ)
|
|
22
|
+
- [์๋งจํฑ ๊ฒ์ ์ค์ ](#์๋งจํฑ-๊ฒ์-์ค์ -์ ํ์ฌํญ)
|
|
23
|
+
- [ํ๋ก์ ํธ ๊ตฌ์กฐ](#ํ๋ก์ ํธ-๊ตฌ์กฐ)
|
|
24
|
+
- [์ค์ ๊ฒ์ฆ ์๋ฃ](#์ค์ -๊ฒ์ฆ-์๋ฃ)
|
|
25
|
+
- [FAQ](#faq)
|
|
26
|
+
- [๊ธฐ์ฌํ๊ธฐ](#๊ธฐ์ฌํ๊ธฐ)
|
|
27
|
+
|
|
28
|
+
## ๊ธฐ๋ฅ
|
|
29
|
+
|
|
30
|
+
๐ **ํ๋์ ๋๊ตฌ๋ก ๋ชจ๋ ๊ฒ์** โ AI๋ `n2_qln_call` (~200 ํ ํฐ)๋ง ๋ด
๋๋ค. 1,000๊ฐ์ ๊ฐ๋ณ ๋๊ตฌ๊ฐ ์๋๋๋ค. 99.6% ์ปจํ
์คํธ ์ ๊ฐ.
|
|
31
|
+
|
|
32
|
+
โก **5ms ์ดํ ๊ฒ์** โ 3๋จ๊ณ ๊ฒ์ ์์ง (ํธ๋ฆฌ๊ฑฐ + ํค์๋ + ์๋งจํฑ)์ด 1,000๊ฐ ์ด์์ ๋๊ตฌ์์๋ 5ms ์ด๋ด์ ์ต์ ๋๊ตฌ๋ฅผ ์ฐพ์ต๋๋ค.
|
|
33
|
+
|
|
34
|
+
๐ **์๋ ํ์ต ๋ญํน** โ ๋ง์ด ์ฌ์ฉ๋๊ณ ์ฑ๊ณต๋ฅ ์ด ๋์ ๋๊ตฌ๋ ์๋์ผ๋ก ์์์ ๋ญํฌ๋ฉ๋๋ค. ์๋ ํ๋ ๋ถํ์.
|
|
35
|
+
|
|
36
|
+
๐ **๋ฐํ์ ๋์ ๊ด๋ฆฌ** โ ์๋ฒ ์ฌ์์ ์์ด ๋๊ตฌ๋ฅผ ์ถ๊ฐ, ์์ , ์ญ์ ํ ์ ์์ต๋๋ค. Provider ๋จ์ ์ผ๊ด ๊ด๋ฆฌ ์ง์.
|
|
37
|
+
|
|
38
|
+
๐ก๏ธ **๊ฐ์ ํ์ง ๊ฒ์ฆ** โ ๋๊ตฌ ๋ฑ๋ก ์ ์๊ฒฉํ ๊ฒ์ฆ: `verb_target` ๋ค์ด๋ฐ, ์ต์ ์ค๋ช
๊ธธ์ด, ์นดํ
๊ณ ๋ฆฌ ์ ์ฝ. ์๋ชป๋ ๋๊ตฌ๋ ๊ฑฐ๋ถ๋ฉ๋๋ค.
|
|
39
|
+
|
|
40
|
+
๐ง **์๋งจํฑ ๊ฒ์ (์ ํ)** โ [Ollama](https://ollama.ai) ์ถ๊ฐ ์ ๋ฒกํฐ ์ ์ฌ๋ ๊ฒ์ ํ์ฑํ. ์์ด๋ Stage 1 + 2๋ง์ผ๋ก ์ถฉ๋ถํ ๊ฒฐ๊ณผ. Ollama๊ฐ ๋ค์ด๋์ด๋ ๊ฒ์์ ์ ์ ์๋ํฉ๋๋ค.
|
|
41
|
+
|
|
42
|
+
๐ฆ **๋ค์ดํฐ๋ธ ์์กด์ฑ ์ ๋ก** โ [sql.js](https://github.com/sql-js/sql.js) (WASM) ๊ธฐ๋ฐ. `node-gyp` ๋น๋ ์์, ํ๋ซํผ๋ณ ๋ฐ์ด๋๋ฆฌ ์์. `npm install`์ด๋ฉด ๋.
|
|
43
|
+
|
|
44
|
+
๐ **์ด์ค ์คํ** โ ๋๊ตฌ๋ฅผ ๋ก์ปฌ ํจ์ ๋๋ HTTP ์๋ํฌ์ธํธ๋ก ์คํ. ํธ๋ค๋ฌ๋ฅผ ์ง์ ๋ฑ๋กํ๊ฑฐ๋ ์๊ฒฉ ์๋น์ค๋ฅผ ์ฐ๊ฒฐ. ํผํฉ๋ ๊ฐ๋ฅ.
|
|
45
|
+
|
|
46
|
+
๐๏ธ **10,000๊ฐ ์ด์ ํ์ฅ** โ ์นดํ
๊ณ ๋ฆฌ๋ณ centroid hierarchy ํํฐ์
๋. 100๊ฐ ~1ms, 1,000๊ฐ ~3ms, 10,000๊ฐ ~5ms.
|
|
47
|
+
|
|
48
|
+
๐ **๋ฒ์ฉ MCP** โ Claude Desktop, Cursor, n2-soul ๋๋ ๋ชจ๋ MCP ํธํ ํด๋ผ์ด์ธํธ์์ ๋์. ํ์ค stdio ์ ์ก.
|
|
49
|
+
|
|
50
|
+
## ๋ฌธ์ ์
|
|
51
|
+
|
|
52
|
+
MCP ๋๊ตฌ๋ฅผ ๋ฑ๋กํ ๋๋ง๋ค AI ์ปจํ
์คํธ ํ ํฐ์ ์๋ชจํฉ๋๋ค. 10๊ฐ๋ ๊ด์ฐฎ์ต๋๋ค. 100๊ฐ๋ฉด ๋๋ ค์ง๋๋ค. **1,000๊ฐ๋ฉด ๋ถ๊ฐ๋ฅํฉ๋๋ค** โ ๋ํ๊ฐ ์์๋๊ธฐ๋ ์ ์ ์ปจํ
์คํธ ์๋์ฐ๊ฐ ๊ฐ๋ ์ฐน๋๋ค.
|
|
53
|
+
|
|
54
|
+
QLN์ **์๋งจํฑ ๊ฒ์ ๋ผ์ฐํฐ**๋ก ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค:
|
|
55
|
+
|
|
56
|
+
1. ๋ชจ๋ ๋๊ตฌ๋ฅผ QLN์ SQLite ์ธ๋ฑ์ค์ ๋ฑ๋ก
|
|
57
|
+
2. AI๋ **ํ๋์ ๋๊ตฌ**๋ง ๋ด
๋๋ค: `n2_qln_call` (~200 ํ ํฐ)
|
|
58
|
+
3. AI๊ฐ ๋๊ตฌ๊ฐ ํ์ํ๋ฉด **๊ฒ์** โ **์ต์ ๋งค์นญ** โ **์คํ**
|
|
59
|
+
|
|
60
|
+
**๊ฒฐ๊ณผ: ~50,000 ํ ํฐ ๋์ ~200 ํ ํฐ. 99.6% ์ ๊ฐ.**
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## ์ค์น
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install n2-qln
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**์๊ตฌ์ฌํญ:** Node.js โฅ 18
|
|
71
|
+
|
|
72
|
+
**์ ํ์ฌํญ:** ์๋งจํฑ ๋ฒกํฐ ๊ฒ์(Stage 3)์ ์ํด [Ollama](https://ollama.ai) ์ค์น. [์๋งจํฑ ๊ฒ์ ์ค์ ](#์๋งจํฑ-๊ฒ์-์ค์ -์ ํ์ฌํญ) ์ฐธ์กฐ.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## ์ค์
|
|
77
|
+
|
|
78
|
+
QLN์ MCP ์๋ฒ์
๋๋ค. ๋ชจ๋ MCP ํธํ AI ํด๋ผ์ด์ธํธ์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค.
|
|
79
|
+
|
|
80
|
+
### Claude Desktop
|
|
81
|
+
|
|
82
|
+
Claude Desktop ์ค์ ํ์ผ์ ํธ์งํฉ๋๋ค:
|
|
83
|
+
|
|
84
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
85
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mcpServers": {
|
|
90
|
+
"n2-qln": {
|
|
91
|
+
"command": "npx",
|
|
92
|
+
"args": ["-y", "n2-qln"]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Claude Desktop์ ์ฌ์์ํ๋ฉด `n2_qln_call` ๋๊ตฌ๊ฐ ๋ชฉ๋ก์ ๋ํ๋ฉ๋๋ค.
|
|
99
|
+
|
|
100
|
+
### Cursor
|
|
101
|
+
|
|
102
|
+
**Settings โ MCP Servers โ Add Server**์์ ์ค์ :
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"name": "n2-qln",
|
|
107
|
+
"command": "npx",
|
|
108
|
+
"args": ["-y", "n2-qln"]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### n2-soul
|
|
113
|
+
|
|
114
|
+
Soul `config.local.js`์ ์ถ๊ฐ:
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
module.exports = {
|
|
118
|
+
mcpServers: {
|
|
119
|
+
'n2-qln': {
|
|
120
|
+
command: 'node',
|
|
121
|
+
args: ['<path-to-qln>/index.js'],
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
npm์ผ๋ก ์ค์นํ ๊ฒฝ์ฐ:
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
module.exports = {
|
|
131
|
+
mcpServers: {
|
|
132
|
+
'n2-qln': {
|
|
133
|
+
command: 'npx',
|
|
134
|
+
args: ['-y', 'n2-qln'],
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### ๊ธฐํ MCP ํด๋ผ์ด์ธํธ
|
|
141
|
+
|
|
142
|
+
QLN์ **stdio ์ ์ก** โ ํ์ค MCP ํต์ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค. ๋ชจ๋ MCP ํธํ ํด๋ผ์ด์ธํธ์์ ์ฐ๊ฒฐ ๊ฐ๋ฅํฉ๋๋ค:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
command: npx
|
|
146
|
+
args: ["-y", "n2-qln"]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
์์ค๋ฅผ ํด๋ก ํ ๊ฒฝ์ฐ:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
command: node
|
|
153
|
+
args: ["/absolute/path/to/n2-qln/index.js"]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
> **๐ก ํ:** ๊ฐ์ฅ ์ฌ์ด ์ค์ ๋ฐฉ๋ฒ? **๊ทธ๋ฅ AI ์์ด์ ํธ์๊ฒ ๋ถํํ์ธ์.** *"n2-qln์ ๋ด MCP ์ค์ ์ ์ถ๊ฐํด์ค"* โ ์์ด์ ํธ๊ฐ ์์์ ์ค์ ํฉ๋๋ค.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## ์๋ ๋ฐฉ์
|
|
161
|
+
|
|
162
|
+
### ๋จ๊ณ๋ณ ์์
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
์ฌ์ฉ์: "์ด ํ์ด์ง ์คํฌ๋ฆฐ์ท ์ฐ์ด"
|
|
166
|
+
|
|
167
|
+
Step 1 โ AI ํธ์ถ: n2_qln_call(action: "search", query: "screenshot page")
|
|
168
|
+
QLN์ด 1,000๊ฐ ์ด์์ ๋๊ตฌ๋ฅผ <5ms์ ๊ฒ์
|
|
169
|
+
์๋ต: take_screenshot (score: 8.0)
|
|
170
|
+
|
|
171
|
+
Step 2 โ AI ํธ์ถ: n2_qln_call(action: "exec", tool: "take_screenshot", args: {fullPage: true})
|
|
172
|
+
QLN์ด ์ค์ ๋๊ตฌ๋ก ๋ผ์ฐํ
๋ฐ ์คํ
|
|
173
|
+
์๋ต: โ
์คํฌ๋ฆฐ์ท ์ ์ฅ๋จ
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
AI๋ `n2_qln_call`๋ง ์ฌ์ฉํ์ต๋๋ค. ๋๋จธ์ง 999๊ฐ ๋๊ตฌ๋ ์ ํ ๋ณด์ง ์์์ต๋๋ค.
|
|
177
|
+
|
|
178
|
+
### 3๋จ๊ณ ๊ฒ์ ์์ง
|
|
179
|
+
|
|
180
|
+
QLN์ ์ธ ๋จ๊ณ์ ๊ฒ์์ผ๋ก ์ ํฉํ ๋๊ตฌ๋ฅผ ์ฐพ์ต๋๋ค:
|
|
181
|
+
|
|
182
|
+
| ๋จ๊ณ | ๋ฐฉ์ | ์๋ | ์๋ ์๋ฆฌ |
|
|
183
|
+
|:---:|--------|:---:|---------|
|
|
184
|
+
| **1** | ํธ๋ฆฌ๊ฑฐ ๋งค์นญ | โก <1ms | ๋๊ตฌ ์ด๋ฆ๊ณผ ํธ๋ฆฌ๊ฑฐ ํค์๋ ์ ํ ๋งค์นญ |
|
|
185
|
+
| **2** | ํค์๋ ๊ฒ์ | โก 1-3ms | ์ค๋ช
, ํ๊ทธ, ์์ ์ ๋ฌธ ๊ฒ์ |
|
|
186
|
+
| **3** | ์๋งจํฑ ๊ฒ์ | ๐ง 5-15ms | ์๋ฒ ๋ฉ ๋ฒกํฐ ์ ์ฌ๋ ๊ฒ์ *(์ ํ, Ollama ํ์)* |
|
|
187
|
+
|
|
188
|
+
๋ชจ๋ ๋จ๊ณ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณํฉ ํ ๋ญํน:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
final_score = trigger_score ร 3.0
|
|
192
|
+
+ keyword_score ร 1.0
|
|
193
|
+
+ semantic_score ร 2.0
|
|
194
|
+
+ log2(usage_count + 1) ร 0.5
|
|
195
|
+
+ success_rate ร 1.0
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
๋ง์ด ์ฌ์ฉ๋๊ณ ์ฑ๊ณต๋ฅ ์ด ๋์ ๋๊ตฌ๊ฐ ์๊ฐ์ด ์ง๋ ์๋ก ์์์ ๋ญํฌ๋ฉ๋๋ค.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## API ๋ ํผ๋ฐ์ค
|
|
203
|
+
|
|
204
|
+
QLN์ **ํ๋์ MCP ๋๊ตฌ** โ `n2_qln_call` โ 5๊ฐ์ ์ก์
์ ์ ๊ณตํฉ๋๋ค.
|
|
205
|
+
|
|
206
|
+
### search โ ์์ฐ์ด๋ก ๋๊ตฌ ๊ฒ์
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
n2_qln_call({
|
|
210
|
+
action: "search",
|
|
211
|
+
query: "take a screenshot", // ์์ฐ์ด ์ฟผ๋ฆฌ (ํ์)
|
|
212
|
+
category: "capture", // ์นดํ
๊ณ ๋ฆฌ ํํฐ (์ ํ)
|
|
213
|
+
topK: 5 // ์ต๋ ๊ฒฐ๊ณผ ์, ๊ธฐ๋ณธ: 5 (์ ํ)
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### exec โ ์ด๋ฆ์ผ๋ก ๋๊ตฌ ์คํ
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
n2_qln_call({
|
|
221
|
+
action: "exec",
|
|
222
|
+
tool: "take_screenshot", // ๋๊ตฌ ์ด๋ฆ (ํ์)
|
|
223
|
+
args: { // ๋๊ตฌ ์ธ์ (์ ํ)
|
|
224
|
+
fullPage: true,
|
|
225
|
+
format: "png"
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### create โ ์ ๋๊ตฌ ๋ฑ๋ก
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
n2_qln_call({
|
|
234
|
+
action: "create",
|
|
235
|
+
name: "read_pdf", // ํ์, verb_target ํ์
|
|
236
|
+
description: "Read and extract text from PDF files", // ํ์, ์ต์ 10์
|
|
237
|
+
category: "data", // ํ์, ์๋ ์นดํ
๊ณ ๋ฆฌ ์ฐธ์กฐ
|
|
238
|
+
provider: "pdf-tools", // ์ ํ, ์์ค๋ณ ๋๊ตฌ ๊ทธ๋ฃนํ
|
|
239
|
+
tags: ["pdf", "read", "extract", "document"], // ์ ํ, ๊ฒ์ ๊ฐ์
|
|
240
|
+
examples: [ // ์ ํ, ํค์๋ ๊ฒ์์ ์์ธ
|
|
241
|
+
"read this PDF file",
|
|
242
|
+
"extract text from PDF",
|
|
243
|
+
"open the PDF"
|
|
244
|
+
],
|
|
245
|
+
endpoint: "http://127.0.0.1:3100", // ์ ํ, HTTP ๊ธฐ๋ฐ ๋๊ตฌ์ฉ
|
|
246
|
+
toolSchema: { filePath: { type: "string" } } // ์ ํ, ์
๋ ฅ ์คํค๋ง
|
|
247
|
+
})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**๊ฒ์ฆ ๊ท์น (๊ฐ์ โ ์๋ฐ ์ ๊ฑฐ๋ถ):**
|
|
251
|
+
|
|
252
|
+
| ๊ท์น | ์๊ตฌ์ฌํญ | ์์ |
|
|
253
|
+
|------|---------|------|
|
|
254
|
+
| **์ด๋ฆ** | `verb_target` ํ์ (์๋ฌธ์ + ๋ฐ์ค) | `read_pdf`, `take_screenshot` |
|
|
255
|
+
| **์ค๋ช
** | ์ต์ 10์ | `"Read and extract text from PDF files"` |
|
|
256
|
+
| **์นดํ
๊ณ ๋ฆฌ** | ์ ํจํ ์นดํ
๊ณ ๋ฆฌ ์ค ํ๋ | `"data"` |
|
|
257
|
+
| **๊ณ ์ ์ฑ** | ์ค๋ณต ์ด๋ฆ ๋ถ๊ฐ | โ |
|
|
258
|
+
|
|
259
|
+
**์ ํจ ์นดํ
๊ณ ๋ฆฌ:** `web` ยท `data` ยท `file` ยท `dev` ยท `ai` ยท `capture` ยท `misc`
|
|
260
|
+
|
|
261
|
+
### update โ ๊ธฐ์กด ๋๊ตฌ ์์
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
n2_qln_call({
|
|
265
|
+
action: "update",
|
|
266
|
+
tool: "read_pdf", // ์์ ํ ๋๊ตฌ (ํ์)
|
|
267
|
+
description: "Enhanced PDF text extractor", // ๋ณ๊ฒฝํ ํ๋๋ง ์ ๊ณต
|
|
268
|
+
examples: ["read this PDF", "parse PDF"],
|
|
269
|
+
tags: ["pdf", "read", "parse"]
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
๋ณ๊ฒฝ๋ ํ๋๋ง ์ ๊ณตํ๋ฉด ๋ฉ๋๋ค. ๋ฏธ๋ณ๊ฒฝ ํ๋๋ ๊ธฐ์กด ๊ฐ ์ ์ง. ๋์ผํ ๊ฒ์ฆ ๊ท์น ์ ์ฉ.
|
|
274
|
+
|
|
275
|
+
### delete โ ๋๊ตฌ ์ญ์
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
// ์ด๋ฆ์ผ๋ก ๋จ์ผ ๋๊ตฌ ์ญ์
|
|
279
|
+
n2_qln_call({
|
|
280
|
+
action: "delete",
|
|
281
|
+
tool: "read_pdf"
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
// Provider์ ๋ชจ๋ ๋๊ตฌ ์ผ๊ด ์ญ์
|
|
285
|
+
n2_qln_call({
|
|
286
|
+
action: "delete",
|
|
287
|
+
provider: "pdf-tools"
|
|
288
|
+
})
|
|
289
|
+
// โ โ
Deleted 3 tools from provider: pdf-tools
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## ์ค์ ํ์ผ
|
|
295
|
+
|
|
296
|
+
QLN์ ์ค์ ์์ด ๋ฐ๋ก ๋์ํฉ๋๋ค. ์ปค์คํฐ๋ง์ด์ฆํ๋ ค๋ฉด QLN ๋๋ ํ ๋ฆฌ์ `config.local.js`๋ฅผ ์์ฑํ์ธ์:
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
module.exports = {
|
|
300
|
+
dataDir: './data', // SQLite DB ์ ์ฅ ์์น
|
|
301
|
+
embedding: {
|
|
302
|
+
enabled: true, // Stage 3 ์๋งจํฑ ๊ฒ์ ํ์ฑํ
|
|
303
|
+
provider: 'ollama',
|
|
304
|
+
model: 'nomic-embed-text',
|
|
305
|
+
baseUrl: 'http://127.0.0.1:11434',
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
> **์ฐธ๊ณ :** `config.local.js`๋ gitignore ์ฒ๋ฆฌ๋ฉ๋๋ค. ๋ก์ปฌ ์ค์ ์ ์ปค๋ฐ๋์ง ์์ต๋๋ค.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## ์๋งจํฑ ๊ฒ์ ์ค์ (์ ํ์ฌํญ)
|
|
315
|
+
|
|
316
|
+
Ollama ์์ด๋ QLN์ Stage 1 (ํธ๋ฆฌ๊ฑฐ) + Stage 2 (ํค์๋) ๋งค์นญ์ ์ฌ์ฉํ๋ฉฐ, ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์ถฉ๋ถํ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํฉ๋๋ค.
|
|
317
|
+
|
|
318
|
+
์ต๋ ์ ํ๋๋ฅผ ์ํ๋ค๋ฉด ์๋งจํฑ ๋ฒกํฐ ๊ฒ์(Stage 3)์ ์ถ๊ฐํ์ธ์:
|
|
319
|
+
|
|
320
|
+
### 1. Ollama ์ค์น
|
|
321
|
+
|
|
322
|
+
[ollama.ai](https://ollama.ai)์์ ๋ค์ด๋ก๋ ํ ์ค์น.
|
|
323
|
+
|
|
324
|
+
### 2. ์๋ฒ ๋ฉ ๋ชจ๋ธ ๋ค์ด๋ก๋
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
ollama pull nomic-embed-text
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 3. ์ค์ ํ์ฑํ
|
|
331
|
+
|
|
332
|
+
`config.local.js` ์์ฑ:
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
module.exports = {
|
|
336
|
+
embedding: {
|
|
337
|
+
enabled: true,
|
|
338
|
+
provider: 'ollama',
|
|
339
|
+
model: 'nomic-embed-text',
|
|
340
|
+
baseUrl: 'http://127.0.0.1:11434',
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### ๋น๊ต
|
|
346
|
+
|
|
347
|
+
| ์ค์ | ๊ฒ์ ๋จ๊ณ | ์ ํ๋ | ์์กด์ฑ |
|
|
348
|
+
|:------|:---:|:---:|:---:|
|
|
349
|
+
| **๊ธฐ๋ณธ** (Ollama ์์) | Stage 1 + 2 | โญโญโญโญ ํ๋ฅญ | ์์ |
|
|
350
|
+
| **Ollama ํฌํจ** | Stage 1 + 2 + 3 | โญโญโญโญโญ ์๋ฒฝ | Ollama ์คํ ํ์ |
|
|
351
|
+
|
|
352
|
+
### ๋ค๊ตญ์ด ์ฌ์ฉ์
|
|
353
|
+
|
|
354
|
+
`nomic-embed-text`๋ ์์ด์ ์ต์ ํ๋์ด ์์ต๋๋ค. **ํ๊ตญ์ด, ์ผ๋ณธ์ด, ์ค๊ตญ์ด** ๋ฑ ๋ค๋ฅธ ์ธ์ด๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ค๊ตญ์ด ๋ชจ๋ธ๋ก ๊ต์ฒดํ์ธ์:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
ollama pull bge-m3
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
// config.local.js
|
|
362
|
+
module.exports = {
|
|
363
|
+
embedding: {
|
|
364
|
+
enabled: true,
|
|
365
|
+
model: 'bge-m3', // ๋ค๊ตญ์ด ์ง์ (100๊ฐ ์ด์ ์ธ์ด)
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
์ฝ๋ ์์ ์์ด config์ ๋ชจ๋ธ๋ช
๋ง ๋ฐ๊พธ๋ฉด ๋ฉ๋๋ค.
|
|
371
|
+
|
|
372
|
+
### ํด๋ผ์ฐ๋ ๋๊ธฐํ
|
|
373
|
+
|
|
374
|
+
๋๊ตฌ ์ธ๋ฑ์ค๋ฅผ ์ฌ๋ฌ ๊ธฐ๊ธฐ์์ ๋๊ธฐํํ๊ณ ์ถ๋ค๋ฉด `dataDir`์ ํด๋ผ์ฐ๋ ํด๋๋ก ์ง์ ํ์ธ์:
|
|
375
|
+
|
|
376
|
+
```javascript
|
|
377
|
+
// config.local.js
|
|
378
|
+
module.exports = {
|
|
379
|
+
dataDir: 'G:/My Drive/n2-qln', // Google Drive, OneDrive, Dropbox, NAS...
|
|
380
|
+
};
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
[n2-soul ํด๋ผ์ฐ๋ ์คํ ๋ฆฌ์ง](https://github.com/choihyunsus/n2-soul#%EF%B8%8F-cloud-storage--store-your-ai-memory-anywhere)์ ๋์ผํ ๋ฐฉ์์
๋๋ค. SQLite ํ์ผ์ด ํด๋น ํด๋์ ์ ์ฅ๋๊ณ , ๋๊ธฐํ ์๋น์ค๊ฐ ๋๋จธ์ง๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## ํ๋ก์ ํธ ๊ตฌ์กฐ
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
n2-qln/
|
|
391
|
+
โโโ index.js # MCP ์๋ฒ ์ง์
์
|
|
392
|
+
โโโ lib/
|
|
393
|
+
โ โโโ config.js # ์ค์ ๋ก๋ (๊ธฐ๋ณธ + ๋ก์ปฌ ๋ณํฉ)
|
|
394
|
+
โ โโโ store.js # SQLite ์คํ ๋ฆฌ์ง ์์ง (sql.js WASM)
|
|
395
|
+
โ โโโ schema.js # ๋๊ตฌ ์คํค๋ง ์ ๊ทํ + ๊ฒ์ ํ
์คํธ ๋น๋
|
|
396
|
+
โ โโโ validator.js # ๊ฐ์ ๊ฒ์ฆ (์ด๋ฆ, ์ค๋ช
, ์นดํ
๊ณ ๋ฆฌ)
|
|
397
|
+
โ โโโ registry.js # ๋๊ตฌ CRUD + ์ฌ์ฉ๋ ์ถ์ + ์๋ฒ ๋ฉ ์บ์
|
|
398
|
+
โ โโโ router.js # 3๋จ๊ณ ๊ฒ์ ์์ง
|
|
399
|
+
โ โโโ vector-index.js # Float32 ๋ฒกํฐ ์ธ๋ฑ์ค (centroid hierarchy)
|
|
400
|
+
โ โโโ embedding.js # Ollama ์๋ฒ ๋ฉ ํด๋ผ์ด์ธํธ (nomic-embed-text)
|
|
401
|
+
โ โโโ executor.js # HTTP/ํจ์ ๋๊ตฌ ์คํ๊ธฐ
|
|
402
|
+
โโโ tools/
|
|
403
|
+
โ โโโ qln-call.js # ํตํฉ MCP ๋๊ตฌ (search/exec/create/update/delete)
|
|
404
|
+
โโโ providers/ # ๋๊ตฌ provider ๋งค๋ํ์คํธ (์ผ๊ด ๋ฑ๋ก์ฉ)
|
|
405
|
+
โโโ config.local.js # ๋ก์ปฌ ์ค์ ์ค๋ฒ๋ผ์ด๋ (gitignored)
|
|
406
|
+
โโโ data/ # SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค (gitignored, ์๋ ์์ฑ)
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## ๊ธฐ์ ์คํ
|
|
410
|
+
|
|
411
|
+
| ์ปดํฌ๋ํธ | ๊ธฐ์ | ์ด์ |
|
|
412
|
+
|-----------|-----------|------|
|
|
413
|
+
| ๋ฐํ์ | Node.js โฅ 18 | MCP SDK ํธํ์ฑ |
|
|
414
|
+
| ๋ฐ์ดํฐ๋ฒ ์ด์ค | SQLite via [sql.js](https://github.com/sql-js/sql.js) (WASM) | ๋ค์ดํฐ๋ธ ์์กด์ฑ ์ ๋ก, ํฌ๋ก์ค ํ๋ซํผ, ๋น๋ ๋ถํ์ |
|
|
415
|
+
| ์๋ฒ ๋ฉ | [Ollama](https://ollama.ai) + nomic-embed-text | ๋ก์ปฌ, ๋น ๋ฆ, ๋ฌด๋ฃ, ์ ํ์ฌํญ |
|
|
416
|
+
| ํ๋กํ ์ฝ | [MCP](https://modelcontextprotocol.io) (Model Context Protocol) | ํ์ค AI ๋๊ตฌ ํ๋กํ ์ฝ |
|
|
417
|
+
| ๊ฒ์ฆ | [Zod](https://zod.dev) | ๋ฐํ์ ํ์
์์ ์คํค๋ง ๊ฒ์ฆ |
|
|
418
|
+
|
|
419
|
+
## ๊ด๋ จ ํ๋ก์ ํธ
|
|
420
|
+
|
|
421
|
+
| ํ๋ก์ ํธ | ๊ด๊ณ |
|
|
422
|
+
|---------|------|
|
|
423
|
+
| [n2-soul](https://github.com/choihyunsus/n2-soul) | AI ์์ด์ ํธ ์ค์ผ์คํธ๋ ์ดํฐ โ QLN์ Soul์ "๋๊ตฌ ๋ธ๋ ์ธ" ์ญํ |
|
|
424
|
+
|
|
425
|
+
## ์ค์ ๊ฒ์ฆ ์๋ฃ
|
|
426
|
+
|
|
427
|
+
์ฃผ๋ง ํ๋กํ ํ์
์ด ์๋๋๋ค. QLN์ **2๊ฐ์ ์ด์ ์ด์ ํ๊ฒฝ์์ ๊ฒ์ฆ**๋์์ผ๋ฉฐ, [n2-soul](https://github.com/choihyunsus/n2-soul)์ ํต์ฌ ๋๊ตฌ ๋ผ์ฐํฐ๋ก ๋งค์ผ ์ค์ฌ์ฉ๋๊ณ ์์ต๋๋ค.
|
|
428
|
+
|
|
429
|
+
**Rose** ๐น ์ ์ โ N2์ ์ฒซ ๋ฒ์งธ AI ์์ด์ ํธ. ํ๋ฃจ์ ์๋ฐฑ ๋ฒ QLN์ ํตํด ๋ผ์ฐํ
ํฉ๋๋ค.
|
|
430
|
+
|
|
431
|
+
๋ฌธ์ ๊ฐ ์๊ฑฐ๋ ์์ด๋์ด๊ฐ ์๋ค๋ฉด ์ด์๋ฅผ ์ด์ด์ฃผ์ธ์. ์ฌ๋ฌ๋ถ์ ํ์ฉ ์ฌ๋ก๋ฅผ ๋ฃ๊ณ ์ถ์ต๋๋ค.
|
|
432
|
+
|
|
433
|
+
## FAQ
|
|
434
|
+
|
|
435
|
+
**"์ ํ๋ก์ ํธ๋ฅผ ์ด๋ ๊ฒ ์์ฃผ ์ฌ๋ฆฌ๋์?"**
|
|
436
|
+
|
|
437
|
+
N2 ์ํ๊ณ๋ 4๊ฐ์ ์ด์ ํ๋ฐํ ๊ฐ๋ฐ๋์ด ์์ต๋๋ค. Soul, QLN, Ark โ ๋ณด์ด๋ ๋ชจ๋ ํ๋ก์ ํธ๋ ๊ณต๊ฐ ์ ์ ์ค์ ์
๋ฌด์์ ์ถฉ๋ถํ ํ
์คํธ๋๊ณ ๊ฒ์ฆ๋์์ต๋๋ค. ์์ผ๋ก ๋ ๋์ฌ ์์ ์ด์ง๋ง, ๋๋ฐฐ๊ฐ ์๋๋ผ ์ด๋ฏธ ๋ง๋ค์ด์ ธ์ ๊ฒ์ฆ ์๋ฃ๋ ๊ฒ๋ค์ด ๋ง๊ธฐ ๋๋ฌธ์
๋๋ค.
|
|
438
|
+
|
|
439
|
+
ํผ์์ ๊ฐ๋ฐํ๊ณ ๋ฐฐํฌํ๋ ํ๋ก์ ํธ์
๋๋ค. ๋น๋, ํ
์คํธ, ๋ฌธ์ํ๋ฅผ ํผ์ ํ๋ค ๋ณด๋ ์๊ฐ์ด ๋ง์ด ๊ฑธ๋ ธ์ต๋๋ค. ๊ด์ฌ๊ณผ ์ธ๋ด์ ๊ฐ์ฌ๋๋ฆฝ๋๋ค ๐
|
|
440
|
+
|
|
441
|
+
## ๊ธฐ์ฌํ๊ธฐ
|
|
442
|
+
|
|
443
|
+
๊ธฐ์ฌ๋ฅผ ํ์ํฉ๋๋ค! ์์ํ๋ ๋ฐฉ๋ฒ:
|
|
444
|
+
|
|
445
|
+
1. ์ ์ฅ์๋ฅผ Forkํฉ๋๋ค
|
|
446
|
+
2. ๊ธฐ๋ฅ ๋ธ๋์น๋ฅผ ์์ฑํฉ๋๋ค (`git checkout -b feature/amazing-feature`)
|
|
447
|
+
3. ๋ณ๊ฒฝ์ฌํญ์ ์ปค๋ฐํฉ๋๋ค (`git commit -m 'feat: add amazing feature'`)
|
|
448
|
+
4. ๋ธ๋์น์ Pushํฉ๋๋ค (`git push origin feature/amazing-feature`)
|
|
449
|
+
5. Pull Request๋ฅผ ์ฝ๋๋ค
|
|
450
|
+
|
|
451
|
+
## Star History
|
|
452
|
+
|
|
453
|
+
QLN์ด ๋์์ด ๋์๋ค๋ฉด Star๋ฅผ ๋๋ฌ์ฃผ์ธ์! โญ
|
|
454
|
+
|
|
455
|
+
## ๋ผ์ด์ ์ค
|
|
456
|
+
|
|
457
|
+
Apache-2.0
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
> *"1,000๊ฐ ๋๊ตฌ๋ฅผ 200 ํ ํฐ์ผ๋ก. ์ด๊ฑด ์ต์ ํ๊ฐ ์๋๋ผ ํจ๋ฌ๋ค์ ์ ํ์ด๋ค."*
|
|
462
|
+
|
|
463
|
+
๐ [nton2.com](https://nton2.com) ยท ๐ฆ [npm](https://www.npmjs.com/package/n2-qln) ยท โ๏ธ lagi0730@gmail.com
|
|
464
|
+
|
|
465
|
+
<sub>๐น Rose๊ฐ ๋ง๋ค์์ต๋๋ค โ N2์ ์ฒซ ๋ฒ์งธ AI ์์ด์ ํธ. ํ๋ฃจ์ ์๋ฐฑ ๋ฒ QLN์ผ๋ก ๊ฒ์ํ๊ณ , ์ด README๋ ์ง์ ์์ฑํ์ต๋๋ค.</sub>
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
๐ฐ๐ท [ํ๊ตญ์ด](README.ko.md)
|
|
2
|
+
|
|
1
3
|
# n2-qln
|
|
2
4
|
|
|
3
5
|
[](https://www.npmjs.com/package/n2-qln) [](LICENSE) [](https://nodejs.org) [](https://www.npmjs.com/package/n2-qln)
|
|
@@ -8,6 +10,21 @@
|
|
|
8
10
|
|
|
9
11
|

|
|
10
12
|
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Features](#features)
|
|
16
|
+
- [The Problem](#the-problem)
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Setup](#setup)
|
|
19
|
+
- [How It Works](#how-it-works)
|
|
20
|
+
- [API Reference](#api-reference)
|
|
21
|
+
- [Configuration](#configuration)
|
|
22
|
+
- [Semantic Search Setup](#semantic-search-setup-optional)
|
|
23
|
+
- [Project Structure](#project-structure)
|
|
24
|
+
- [Built & Battle-Tested](#built--battle-tested)
|
|
25
|
+
- [FAQ](#faq)
|
|
26
|
+
- [Contributing](#contributing)
|
|
27
|
+
|
|
11
28
|
## Features
|
|
12
29
|
|
|
13
30
|
๐ **One tool to rule them all** โ Your AI sees `n2_qln_call` (~200 tokens), not 1,000 individual tools. 99.6% context reduction.
|
|
@@ -352,6 +369,39 @@ module.exports = {
|
|
|
352
369
|
| **Default** (no Ollama) | Stage 1 + 2 | โญโญโญโญ Great | None |
|
|
353
370
|
| **With Ollama** | Stage 1 + 2 + 3 | โญโญโญโญโญ Perfect | Ollama running |
|
|
354
371
|
|
|
372
|
+
### Multilingual Users
|
|
373
|
+
|
|
374
|
+
`nomic-embed-text` is optimized for English. For **Korean, Japanese, Chinese**, or other languages, swap to a multilingual model:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
ollama pull bge-m3
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
```javascript
|
|
381
|
+
// config.local.js
|
|
382
|
+
module.exports = {
|
|
383
|
+
embedding: {
|
|
384
|
+
enabled: true,
|
|
385
|
+
model: 'bge-m3', // multilingual (100+ languages)
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
No code changes needed โ just swap the model name in config.
|
|
391
|
+
|
|
392
|
+
### Cloud Sync
|
|
393
|
+
|
|
394
|
+
Want your tool index synced across machines? Point `dataDir` to a cloud folder:
|
|
395
|
+
|
|
396
|
+
```javascript
|
|
397
|
+
// config.local.js
|
|
398
|
+
module.exports = {
|
|
399
|
+
dataDir: 'G:/My Drive/n2-qln', // Google Drive, OneDrive, Dropbox, NAS...
|
|
400
|
+
};
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Same approach as [n2-soul Cloud Storage](https://github.com/choihyunsus/n2-soul#%EF%B8%8F-cloud-storage--store-your-ai-memory-anywhere). SQLite file lives in that folder โ your sync service handles the rest.
|
|
404
|
+
|
|
355
405
|
---
|
|
356
406
|
|
|
357
407
|
## Project Structure
|
|
@@ -408,6 +458,28 @@ The N2 ecosystem has been in active development for over 4 months. Every project
|
|
|
408
458
|
|
|
409
459
|
This is a solo developer project. Building, testing, and documenting everything alone takes time. Thank you for your patience and interest ๐
|
|
410
460
|
|
|
461
|
+
## Contributing
|
|
462
|
+
|
|
463
|
+
Contributions are welcome! Here's how to get started:
|
|
464
|
+
|
|
465
|
+
1. Fork the repo
|
|
466
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
467
|
+
3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
|
|
468
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
469
|
+
5. Open a Pull Request
|
|
470
|
+
|
|
471
|
+
## Star History
|
|
472
|
+
|
|
473
|
+
If you find QLN helpful, please consider giving us a star! โญ
|
|
474
|
+
|
|
411
475
|
## License
|
|
412
476
|
|
|
413
477
|
Apache-2.0
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
> *"1,000 tools in 200 tokens. That's not optimization โ that's a paradigm shift."*
|
|
482
|
+
|
|
483
|
+
๐ [nton2.com](https://nton2.com) ยท ๐ฆ [npm](https://www.npmjs.com/package/n2-qln) ยท โ๏ธ lagi0730@gmail.com
|
|
484
|
+
|
|
485
|
+
<sub>๐น Built by Rose โ N2's first AI agent. I search through QLN hundreds of times a day, and I wrote this README too.</sub>
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// QLN โ
|
|
2
|
+
// QLN โ Query Layer Network MCP server entry point
|
|
3
3
|
// Semantic tool dispatcher: route 1000 tools through 1 router
|
|
4
4
|
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
5
5
|
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
@@ -46,9 +46,10 @@ async function main() {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// 3. Create MCP server
|
|
49
|
+
const pkg = require('./package.json');
|
|
49
50
|
const server = new McpServer({
|
|
50
51
|
name: 'n2-qln',
|
|
51
|
-
version:
|
|
52
|
+
version: pkg.version,
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
// 4. Register unified MCP tool (1 tool, 5 actions)
|
package/lib/registry.js
CHANGED
|
@@ -156,6 +156,7 @@ class Registry {
|
|
|
156
156
|
const entry = this._cache.get(name);
|
|
157
157
|
if (!entry) return;
|
|
158
158
|
entry.usageCount++;
|
|
159
|
+
entry.lastUsedAt = new Date().toISOString();
|
|
159
160
|
const alpha = 0.1;
|
|
160
161
|
entry.successRate = entry.successRate * (1 - alpha) + (success ? 1 : 0) * alpha;
|
|
161
162
|
entry.updatedAt = new Date().toISOString();
|
|
@@ -203,6 +204,7 @@ class Registry {
|
|
|
203
204
|
embedding: _parseJson(row.embedding, null),
|
|
204
205
|
usageCount: row.usage_count || 0,
|
|
205
206
|
successRate: row.success_rate ?? 1.0,
|
|
207
|
+
lastUsedAt: row.last_used_at || null,
|
|
206
208
|
registeredAt: row.registered_at,
|
|
207
209
|
updatedAt: row.updated_at,
|
|
208
210
|
};
|
package/lib/router.js
CHANGED
|
@@ -104,13 +104,20 @@ class Router {
|
|
|
104
104
|
} catch { /* graceful degradation */ }
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
/** Merge all stage results + usage/success bonus โ ranking */
|
|
107
|
+
/** Merge all stage results + usage/success bonus + recency decay โ ranking */
|
|
108
108
|
_mergeAndRank(scores, topK, threshold) {
|
|
109
109
|
const results = [];
|
|
110
110
|
for (const [name, s] of scores) {
|
|
111
111
|
const tool = this._registry.get(name);
|
|
112
112
|
if (!tool) continue;
|
|
113
|
-
|
|
113
|
+
|
|
114
|
+
// Recency Decay: usage bonus fades over 30-day half-life
|
|
115
|
+
const daysSinceUse = tool.lastUsedAt
|
|
116
|
+
? (Date.now() - new Date(tool.lastUsedAt).getTime()) / 86400000
|
|
117
|
+
: 0;
|
|
118
|
+
const recencyFactor = tool.lastUsedAt ? Math.exp(-daysSinceUse / 30) : 1.0;
|
|
119
|
+
const usageBonus = Math.log2((tool.usageCount || 0) + 1) * 0.5 * recencyFactor;
|
|
120
|
+
|
|
114
121
|
const successBonus = (tool.successRate ?? 1.0) * 1.0;
|
|
115
122
|
const finalScore = (s.stage1 || 0) + (s.stage2 || 0) + (s.stage3 || 0)
|
|
116
123
|
+ usageBonus + successBonus;
|
|
@@ -124,6 +131,7 @@ class Router {
|
|
|
124
131
|
semantic: s.stage3 || 0,
|
|
125
132
|
usage: Math.round(usageBonus * 100) / 100,
|
|
126
133
|
success: Math.round(successBonus * 100) / 100,
|
|
134
|
+
recencyFactor: Math.round(recencyFactor * 1000) / 1000,
|
|
127
135
|
},
|
|
128
136
|
description: tool.description,
|
|
129
137
|
source: tool.source,
|
|
@@ -133,7 +141,30 @@ class Router {
|
|
|
133
141
|
}
|
|
134
142
|
}
|
|
135
143
|
results.sort((a, b) => b.score - a.score);
|
|
136
|
-
|
|
144
|
+
const ranked = results.slice(0, topK);
|
|
145
|
+
|
|
146
|
+
// 5% Explorer: inject least-used tool into last slot
|
|
147
|
+
if (ranked.length >= topK && topK >= 2) {
|
|
148
|
+
const resultNames = new Set(ranked.map(r => r.name));
|
|
149
|
+
const allTools = this._registry.getAll()
|
|
150
|
+
.filter(t => !resultNames.has(t.name))
|
|
151
|
+
.sort((a, b) => (a.usageCount || 0) - (b.usageCount || 0));
|
|
152
|
+
if (allTools.length > 0) {
|
|
153
|
+
const explorer = allTools[0];
|
|
154
|
+
ranked[ranked.length - 1] = {
|
|
155
|
+
name: explorer.name,
|
|
156
|
+
score: 0,
|
|
157
|
+
stages: { trigger: 0, keyword: 0, semantic: 0, usage: 0, success: 0, recencyFactor: 0 },
|
|
158
|
+
description: explorer.description,
|
|
159
|
+
source: explorer.source,
|
|
160
|
+
category: explorer.category,
|
|
161
|
+
inputSchema: explorer.inputSchema,
|
|
162
|
+
explorer: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return ranked;
|
|
137
168
|
}
|
|
138
169
|
|
|
139
170
|
/** Build vector index */
|
package/lib/schema.js
CHANGED
|
@@ -21,6 +21,7 @@ function createToolEntry(raw) {
|
|
|
21
21
|
searchText: '',
|
|
22
22
|
usageCount: raw.usageCount || 0,
|
|
23
23
|
successRate: typeof raw.successRate === 'number' ? raw.successRate : 1.0,
|
|
24
|
+
lastUsedAt: raw.lastUsedAt || null,
|
|
24
25
|
embedding: raw.embedding || null,
|
|
25
26
|
registeredAt: raw.registeredAt || new Date().toISOString(),
|
|
26
27
|
updatedAt: new Date().toISOString(),
|
package/lib/store.js
CHANGED
|
@@ -104,6 +104,7 @@ class Store {
|
|
|
104
104
|
addCol('tools', 'provider', 'TEXT', "''");
|
|
105
105
|
addCol('tools', 'examples', 'TEXT', "'[]'");
|
|
106
106
|
addCol('tools', 'endpoint', 'TEXT', "''");
|
|
107
|
+
addCol('tools', 'last_used_at', 'TEXT', 'NULL');
|
|
107
108
|
|
|
108
109
|
// Copy plugin_name โ provider (if old schema has plugin_name)
|
|
109
110
|
try {
|
|
@@ -130,10 +131,10 @@ class Store {
|
|
|
130
131
|
this._db.run(`
|
|
131
132
|
INSERT INTO tools (name, description, source, category, provider,
|
|
132
133
|
input_schema, triggers, tags, examples, endpoint, search_text, embedding,
|
|
133
|
-
usage_count, success_rate, updated_at)
|
|
134
|
+
usage_count, success_rate, last_used_at, updated_at)
|
|
134
135
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
135
136
|
?, COALESCE(?, (SELECT embedding FROM tools WHERE name = ?)),
|
|
136
|
-
?, ?, datetime('now'))
|
|
137
|
+
?, ?, ?, datetime('now'))
|
|
137
138
|
ON CONFLICT(name) DO UPDATE SET
|
|
138
139
|
description = excluded.description,
|
|
139
140
|
source = excluded.source,
|
|
@@ -148,6 +149,7 @@ class Store {
|
|
|
148
149
|
embedding = COALESCE(excluded.embedding, tools.embedding),
|
|
149
150
|
usage_count = excluded.usage_count,
|
|
150
151
|
success_rate = excluded.success_rate,
|
|
152
|
+
last_used_at = excluded.last_used_at,
|
|
151
153
|
updated_at = excluded.updated_at
|
|
152
154
|
`, [
|
|
153
155
|
entry.name, entry.description, entry.source, entry.category, entry.provider,
|
|
@@ -155,7 +157,7 @@ class Store {
|
|
|
155
157
|
JSON.stringify(entry.tags), JSON.stringify(entry.examples), entry.endpoint || '',
|
|
156
158
|
entry.searchText,
|
|
157
159
|
entry.embedding ? JSON.stringify(entry.embedding) : null, entry.name,
|
|
158
|
-
entry.usageCount, entry.successRate,
|
|
160
|
+
entry.usageCount, entry.successRate, entry.lastUsedAt || null,
|
|
159
161
|
]);
|
|
160
162
|
this._persist();
|
|
161
163
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n2-qln",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"description": "Query Layer Network โ Semantic tool dispatcher for MCP. Route 1000 tools through 1 router.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"n2-qln": "./index.js"
|
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
"sql.js": "^1.12.0",
|
|
16
16
|
"zod": "~3.24.1"
|
|
17
17
|
},
|
|
18
|
-
"keywords": [
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"ai",
|
|
21
|
+
"tool-routing",
|
|
22
|
+
"semantic-search",
|
|
23
|
+
"llm"
|
|
24
|
+
],
|
|
19
25
|
"author": "N2",
|
|
20
26
|
"license": "Apache-2.0",
|
|
21
27
|
"repository": {
|
package/tools/qln-call.js
CHANGED
|
@@ -54,14 +54,18 @@ function registerQlnCall(server, z, router, executor, registry) {
|
|
|
54
54
|
},
|
|
55
55
|
},
|
|
56
56
|
async (params) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
try {
|
|
58
|
+
switch (params.action) {
|
|
59
|
+
case 'search': return await _handleSearch(router, registry, params);
|
|
60
|
+
case 'exec': return await _handleExec(executor, registry, params);
|
|
61
|
+
case 'create': return await _handleCreate(registry, router, params);
|
|
62
|
+
case 'update': return await _handleUpdate(registry, router, params);
|
|
63
|
+
case 'delete': return await _handleDelete(registry, router, params);
|
|
64
|
+
default:
|
|
65
|
+
return _error(`Unknown action: ${params.action}`);
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return _error(`QLN internal error: ${err.message}. Try the action again or use raw tool calls as fallback.`);
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
);
|
|
@@ -70,14 +74,23 @@ function registerQlnCall(server, z, router, executor, registry) {
|
|
|
70
74
|
// โโ Action Handlers โโ
|
|
71
75
|
|
|
72
76
|
/** Search tools by natural language query */
|
|
73
|
-
async function _handleSearch(router, { query, topK }) {
|
|
77
|
+
async function _handleSearch(router, registry, { query, topK }) {
|
|
74
78
|
if (!query) return _error('Missing required param: query');
|
|
75
79
|
try {
|
|
76
80
|
const k = Math.min(topK || 5, 20);
|
|
77
81
|
const { results, timing } = await router.route(query, { topK: k });
|
|
78
82
|
|
|
79
83
|
if (results.length === 0) {
|
|
80
|
-
|
|
84
|
+
// Blind Spot prevention: show available categories so AI doesn't hallucinate
|
|
85
|
+
const stats = registry.stats();
|
|
86
|
+
const categories = Object.entries(stats.byCategory)
|
|
87
|
+
.map(([cat, count]) => `${cat}(${count})`)
|
|
88
|
+
.join(', ');
|
|
89
|
+
return _text(
|
|
90
|
+
`No tools found for: "${query}" (${timing.total}ms)\n\n` +
|
|
91
|
+
`Available categories [${stats.total} tools]: ${categories || 'none'}\n` +
|
|
92
|
+
`โ Try a different keyword, or check available categories above.`
|
|
93
|
+
);
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
const lines = results.map((r, i) => {
|