n2-qln 3.4.2 → 4.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.
Files changed (57) hide show
  1. package/README.ko.md +459 -470
  2. package/README.md +459 -490
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +87 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/config.d.ts +9 -0
  7. package/{lib → dist/lib}/config.js +23 -27
  8. package/dist/lib/config.js.map +1 -0
  9. package/dist/lib/embedding.d.ts +27 -0
  10. package/{lib → dist/lib}/embedding.js +39 -47
  11. package/dist/lib/embedding.js.map +1 -0
  12. package/dist/lib/executor.d.ts +57 -0
  13. package/dist/lib/executor.js +175 -0
  14. package/dist/lib/executor.js.map +1 -0
  15. package/dist/lib/mcp-discovery.d.ts +83 -0
  16. package/dist/lib/mcp-discovery.js +203 -0
  17. package/dist/lib/mcp-discovery.js.map +1 -0
  18. package/dist/lib/provider-loader.d.ts +13 -0
  19. package/dist/lib/provider-loader.js +146 -0
  20. package/dist/lib/provider-loader.js.map +1 -0
  21. package/dist/lib/registry.d.ts +38 -0
  22. package/{lib → dist/lib}/registry.js +82 -92
  23. package/dist/lib/registry.js.map +1 -0
  24. package/dist/lib/router.d.ts +63 -0
  25. package/{lib → dist/lib}/router.js +75 -117
  26. package/dist/lib/router.js.map +1 -0
  27. package/dist/lib/schema.d.ts +20 -0
  28. package/{lib → dist/lib}/schema.js +38 -30
  29. package/dist/lib/schema.js.map +1 -0
  30. package/dist/lib/store.d.ts +37 -0
  31. package/dist/lib/store.js +207 -0
  32. package/dist/lib/store.js.map +1 -0
  33. package/dist/lib/validator.d.ts +37 -0
  34. package/dist/lib/validator.js +114 -0
  35. package/dist/lib/validator.js.map +1 -0
  36. package/dist/lib/vector-index.d.ts +37 -0
  37. package/{lib → dist/lib}/vector-index.js +19 -36
  38. package/dist/lib/vector-index.js.map +1 -0
  39. package/dist/tools/qln-call.d.ts +41 -0
  40. package/dist/tools/qln-call.js +353 -0
  41. package/dist/tools/qln-call.js.map +1 -0
  42. package/dist/tools/qln-helpers.d.ts +55 -0
  43. package/dist/tools/qln-helpers.js +88 -0
  44. package/dist/tools/qln-helpers.js.map +1 -0
  45. package/dist/types.d.ts +243 -0
  46. package/dist/types.js +4 -0
  47. package/dist/types.js.map +1 -0
  48. package/index.js +3 -79
  49. package/package.json +11 -4
  50. package/.github/FUNDING.yml +0 -3
  51. package/docs/README.md +0 -2
  52. package/docs/architecture.png +0 -0
  53. package/lib/executor.js +0 -104
  54. package/lib/provider-loader.js +0 -126
  55. package/lib/store.js +0 -217
  56. package/lib/validator.js +0 -171
  57. package/tools/qln-call.js +0 -257
package/README.ko.md CHANGED
@@ -1,470 +1,459 @@
1
- 🇺🇸 [English](README.md)
2
-
3
- # n2-qln
4
-
5
- [![npm](https://img.shields.io/npm/v/n2-qln?color=brightgreen)](https://www.npmjs.com/package/n2-qln) [![license](https://img.shields.io/npm/l/n2-qln)](LICENSE) [![node](https://img.shields.io/node/v/n2-qln?color=brightgreen)](https://nodejs.org) [![downloads](https://img.shields.io/npm/dm/n2-qln?color=blue)](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
- ![QLN Architecture — Without vs With](docs/architecture.png)
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단계 검색 엔진 (트리거 + BM25 키워드 + 시맨틱)이 1,000개 이상의 도구에서도 5ms 이내에 최적 도구를 찾습니다.
33
-
34
- **BM25 키워드 랭킹** *(v3.4)* — Stage 2에 [Okapi BM25](https://en.wikipedia.org/wiki/Okapi_BM25) 알고리즘 적용. 희귀한 단어일수록 높은 점수, 문서 길이 정규화. Google, Elasticsearch, Wikipedia 검색의 핵심 알고리즘.
35
-
36
- **자동 학습 랭킹** — 많이 사용되고 성공률이 높은 도구는 자동으로 상위에 랭크됩니다. 수동 튜닝 불필요.
37
-
38
- **런타임 동적 관리** — 서버 재시작 없이 도구를 추가, 수정, 삭제할 수 있습니다. Provider 단위 일괄 관리 지원.
39
-
40
- **강제 품질 검증** — 도구 등록 시 엄격한 검증: `verb_target` 네이밍, 최소 설명 길이, 카테고리 제약. 잘못된 도구는 거부됩니다.
41
-
42
- **시맨틱 검색 (선택)** — [Ollama](https://ollama.ai) 추가 시 벡터 유사도 검색 활성화. 없어도 Stage 1 + 2만으로 충분한 결과. Ollama가 다운되어도 검색은 정상 작동합니다.
43
-
44
- **네이티브 의존성 제로** [sql.js](https://github.com/sql-js/sql.js) (WASM) 기반. `node-gyp` 빌드 없음, 플랫폼별 바이너리 없음. `npm install`이면 끝.
45
-
46
- **이중 실행** 도구를 로컬 함수 또는 HTTP 엔드포인트로 실행. 핸들러를 직접 등록하거나 원격 서비스를 연결. 혼합도 가능.
47
-
48
- **Provider 자동 인덱싱** *(v3.3)* `providers/`에 JSON 매니페스트를 넣으면 부팅 시 자동 등록. 코드 수정 불필요, 수동 `create` 호출 불필요. 멱등성 보장 및 에러 격리.
49
-
50
- **10,000개 이상 확장** 카테고리별 centroid hierarchy 파티셔닝. 100개 ~1ms, 1,000개 ~3ms, 10,000개 ~5ms.
51
-
52
- **범용 MCP** Claude Desktop, Cursor, n2-soul 또는 모든 MCP 호환 클라이언트에서 동작. 표준 stdio 전송.
53
-
54
- ## 문제점
55
-
56
- MCP 도구를 등록할 때마다 AI 컨텍스트 토큰을 소모합니다. 10개는 괜찮습니다. 100개면 느려집니다. **1,000개면 불가능합니다** 대화가 시작되기도 전에 컨텍스트 윈도우가 가득 찹니다.
57
-
58
- QLN은 **시맨틱 검색 라우터**로 이 문제를 해결합니다:
59
-
60
- 1. 모든 도구를 QLN의 SQLite 인덱스에 등록
61
- 2. AI는 **하나의 도구**만 봅니다: `n2_qln_call` (~200 토큰)
62
- 3. AI가 도구가 필요하면 **검색** → **최적 매칭** → **실행**
63
-
64
- **결과: ~50,000 토큰 대신 ~200 토큰. 99.6% 절감.**
65
-
66
- ---
67
-
68
- ## 설치
69
-
70
- ```bash
71
- npm install n2-qln
72
- ```
73
-
74
- **요구사항:** Node.js ≥ 18
75
-
76
- **선택사항:** 시맨틱 벡터 검색(Stage 3)을 위해 [Ollama](https://ollama.ai) 설치. [시맨틱 검색 설정](#시맨틱-검색-설정-선택사항) 참조.
77
-
78
- ---
79
-
80
- ## 설정
81
-
82
- QLN은 MCP 서버입니다. 모든 MCP 호환 AI 클라이언트에 연결할 수 있습니다.
83
-
84
- ### Claude Desktop
85
-
86
- Claude Desktop 설정 파일을 편집합니다:
87
-
88
- - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
89
- - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
90
-
91
- ```json
92
- {
93
- "mcpServers": {
94
- "n2-qln": {
95
- "command": "npx",
96
- "args": ["-y", "n2-qln"]
97
- }
98
- }
99
- }
100
- ```
101
-
102
- Claude Desktop을 재시작하면 `n2_qln_call` 도구가 목록에 나타납니다.
103
-
104
- ### Cursor
105
-
106
- **Settings → MCP Servers → Add Server**에서 설정:
107
-
108
- ```json
109
- {
110
- "name": "n2-qln",
111
- "command": "npx",
112
- "args": ["-y", "n2-qln"]
113
- }
114
- ```
115
-
116
- ### n2-soul
117
-
118
- Soul `config.local.js`에 추가:
119
-
120
- ```javascript
121
- module.exports = {
122
- mcpServers: {
123
- 'n2-qln': {
124
- command: 'node',
125
- args: ['<path-to-qln>/index.js'],
126
- }
127
- }
128
- };
129
- ```
130
-
131
- npm으로 설치한 경우:
132
-
133
- ```javascript
134
- module.exports = {
135
- mcpServers: {
136
- 'n2-qln': {
137
- command: 'npx',
138
- args: ['-y', 'n2-qln'],
139
- }
140
- }
141
- };
142
- ```
143
-
144
- ### 기타 MCP 클라이언트
145
-
146
- QLN은 **stdio 전송** — 표준 MCP 통신 방식을 사용합니다. 모든 MCP 호환 클라이언트에서 연결 가능합니다:
147
-
148
- ```
149
- command: npx
150
- args: ["-y", "n2-qln"]
151
- ```
152
-
153
- 소스를 클론한 경우:
154
-
155
- ```
156
- command: node
157
- args: ["/absolute/path/to/n2-qln/index.js"]
158
- ```
159
-
160
- > ** 팁:** 가장 쉬운 설정 방법? **그냥 AI 에이전트에게 부탁하세요.** *"n2-qln을 내 MCP 설정에 추가해줘"* — 에이전트가 알아서 설정합니다.
161
-
162
- ---
163
-
164
- ## 작동 방식
165
-
166
- ### 단계별 예시
167
-
168
- ```
169
- 사용자: "이 페이지 스크린샷 찍어"
170
-
171
- Step 1 AI 호출: n2_qln_call(action: "search", query: "screenshot page")
172
- QLN이 1,000개 이상의 도구를 <5ms에 검색
173
- 응답: take_screenshot (score: 8.0)
174
-
175
- Step 2 AI 호출: n2_qln_call(action: "exec", tool: "take_screenshot", args: {fullPage: true})
176
- QLN이 실제 도구로 라우팅 실행
177
- 응답: 스크린샷 저장됨
178
- ```
179
-
180
- AI는 `n2_qln_call`만 사용했습니다. 나머지 999개 도구는 전혀 보지 않았습니다.
181
-
182
- ### 3단계 검색 엔진
183
-
184
- QLN은 세 단계의 검색으로 적합한 도구를 찾습니다:
185
-
186
- | 단계 | 방식 | 속도 | 작동 원리 |
187
- |:---:|--------|:---:|---------|
188
- | **1** | 트리거 매칭 | <1ms | 도구 이름과 트리거 키워드 정확 매칭 |
189
- | **2** | BM25 키워드 | 1-3ms | [Okapi BM25](https://en.wikipedia.org/wiki/Okapi_BM25) 랭킹 검색 — IDF 가중치 + 문서 길이 정규화 *(v3.4)* |
190
- | **3** | 시맨틱 검색 | 5-15ms | 임베딩 벡터 유사도 검색 *(선택, Ollama 필요)* |
191
-
192
- 모든 단계의 결과를 병합 랭킹:
193
-
194
- ```
195
- final_score = trigger_score × 3.0
196
- + bm25_keyword_score × 1.0
197
- + semantic_score × 2.0
198
- + log2(usage_count + 1) × 0.5
199
- + success_rate × 1.0
200
- ```
201
-
202
- 많이 사용되고 성공률이 높은 도구가 시간이 지날수록 상위에 랭크됩니다.
203
-
204
- ---
205
-
206
- ## API 레퍼런스
207
-
208
- QLN은 **하나의 MCP 도구** — `n2_qln_call` — 5개의 액션을 제공합니다.
209
-
210
- ### search — 자연어로 도구 검색
211
-
212
- ```javascript
213
- n2_qln_call({
214
- action: "search",
215
- query: "take a screenshot", // 자연어 쿼리 (필수)
216
- category: "capture", // 카테고리 필터 (선택)
217
- topK: 5 // 최대 결과 수, 기본: 5 (선택)
218
- })
219
- ```
220
-
221
- ### exec — 이름으로 도구 실행
222
-
223
- ```javascript
224
- n2_qln_call({
225
- action: "exec",
226
- tool: "take_screenshot", // 도구 이름 (필수)
227
- args: { // 도구 인수 (선택)
228
- fullPage: true,
229
- format: "png"
230
- }
231
- })
232
- ```
233
-
234
- ### create 도구 등록
235
-
236
- ```javascript
237
- n2_qln_call({
238
- action: "create",
239
- name: "read_pdf", // 필수, verb_target 형식
240
- description: "Read and extract text from PDF files", // 필수, 최소 10자
241
- category: "data", // 필수, 아래 카테고리 참조
242
- provider: "pdf-tools", // 선택, 소스별 도구 그룹화
243
- tags: ["pdf", "read", "extract", "document"], // 선택, 검색 개선
244
- examples: [ // 선택, 키워드 검색에 색인
245
- "read this PDF file",
246
- "extract text from PDF",
247
- "open the PDF"
248
- ],
249
- endpoint: "http://127.0.0.1:3100", // 선택, HTTP 기반 도구용
250
- toolSchema: { filePath: { type: "string" } } // 선택, 입력 스키마
251
- })
252
- ```
253
-
254
- **검증 규칙 (강제 — 위반 시 거부):**
255
-
256
- | 규칙 | 요구사항 | 예시 |
257
- |------|---------|------|
258
- | **이름** | `verb_target` 형식 (소문자 + 밑줄) | `read_pdf`, `take_screenshot` |
259
- | **설명** | 최소 10자 | `"Read and extract text from PDF files"` |
260
- | **카테고리** | 유효한 카테고리 중 하나 | `"data"` |
261
- | **고유성** | 중복 이름 불가 | — |
262
-
263
- **유효 카테고리:** `web` · `data` · `file` · `dev` · `ai` · `capture` · `misc`
264
-
265
- ### update — 기존 도구 수정
266
-
267
- ```javascript
268
- n2_qln_call({
269
- action: "update",
270
- tool: "read_pdf", // 수정할 도구 (필수)
271
- description: "Enhanced PDF text extractor", // 변경할 필드만 제공
272
- examples: ["read this PDF", "parse PDF"],
273
- tags: ["pdf", "read", "parse"]
274
- })
275
- ```
276
-
277
- 변경된 필드만 제공하면 됩니다. 미변경 필드는 기존 값 유지. 동일한 검증 규칙 적용.
278
-
279
- ### delete — 도구 삭제
280
-
281
- ```javascript
282
- // 이름으로 단일 도구 삭제
283
- n2_qln_call({
284
- action: "delete",
285
- tool: "read_pdf"
286
- })
287
-
288
- // Provider의 모든 도구 일괄 삭제
289
- n2_qln_call({
290
- action: "delete",
291
- provider: "pdf-tools"
292
- })
293
- // Deleted 3 tools from provider: pdf-tools
294
- ```
295
-
296
- ---
297
-
298
- ## 설정 파일
299
-
300
- QLN은 설정 없이 바로 동작합니다. 커스터마이즈하려면 QLN 디렉토리에 `config.local.js`를 생성하세요:
301
-
302
- ```javascript
303
- module.exports = {
304
- dataDir: './data', // SQLite DB 저장 위치
305
- embedding: {
306
- enabled: true, // Stage 3 시맨틱 검색 활성화
307
- provider: 'ollama',
308
- model: 'nomic-embed-text',
309
- baseUrl: 'http://127.0.0.1:11434',
310
- },
311
- };
312
- ```
313
-
314
- > **참고:** `config.local.js`는 gitignore 처리됩니다. 로컬 설정은 커밋되지 않습니다.
315
-
316
- ---
317
-
318
- ## 시맨틱 검색 설정 (선택사항)
319
-
320
- Ollama 없이도 QLN은 Stage 1 (트리거) + Stage 2 (키워드) 매칭을 사용하며, 대부분의 경우 충분한 결과를 제공합니다.
321
-
322
- 최대 정확도를 원한다면 시맨틱 벡터 검색(Stage 3)을 추가하세요:
323
-
324
- ### 1. Ollama 설치
325
-
326
- [ollama.ai](https://ollama.ai)에서 다운로드 후 설치.
327
-
328
- ### 2. 임베딩 모델 다운로드
329
-
330
- ```bash
331
- ollama pull nomic-embed-text
332
- ```
333
-
334
- ### 3. 설정 활성화
335
-
336
- `config.local.js` 생성:
337
-
338
- ```javascript
339
- module.exports = {
340
- embedding: {
341
- enabled: true,
342
- provider: 'ollama',
343
- model: 'nomic-embed-text',
344
- baseUrl: 'http://127.0.0.1:11434',
345
- },
346
- };
347
- ```
348
-
349
- ### 비교
350
-
351
- | 설정 | 검색 단계 | 정확도 | 의존성 |
352
- |:------|:---:|:---:|:---:|
353
- | **기본** (Ollama 없음) | Stage 1 + 2 | 훌륭 | 없음 |
354
- | **Ollama 포함** | Stage 1 + 2 + 3 | 완벽 | Ollama 실행 필요 |
355
-
356
- ### 다국어 사용자
357
-
358
- `nomic-embed-text`는 영어에 최적화되어 있습니다. **한국어, 일본어, 중국어** 등 다른 언어를 사용한다면 다국어 모델로 교체하세요:
359
-
360
- ```bash
361
- ollama pull bge-m3
362
- ```
363
-
364
- ```javascript
365
- // config.local.js
366
- module.exports = {
367
- embedding: {
368
- enabled: true,
369
- model: 'bge-m3', // 다국어 지원 (100개 이상 언어)
370
- },
371
- };
372
- ```
373
-
374
- 코드 수정 없이 config의 모델명만 바꾸면 됩니다.
375
-
376
- ### 클라우드 동기화
377
-
378
- 도구 인덱스를 여러 기기에서 동기화하고 싶다면 `dataDir`을 클라우드 폴더로 지정하세요:
379
-
380
- ```javascript
381
- // config.local.js
382
- module.exports = {
383
- dataDir: 'G:/My Drive/n2-qln', // Google Drive, OneDrive, Dropbox, NAS...
384
- };
385
- ```
386
-
387
- [n2-soul 클라우드 스토리지](https://github.com/choihyunsus/soul#%EF%B8%8F-cloud-storage--store-your-ai-memory-anywhere)와 동일한 방식입니다. SQLite 파일이 해당 폴더에 저장되고, 동기화 서비스가 나머지를 처리합니다.
388
-
389
- ---
390
-
391
- ## 프로젝트 구조
392
-
393
- ```
394
- n2-qln/
395
- ├── index.js # MCP 서버 진입점
396
- ├── lib/
397
- ├── config.js # 설정 로더 (기본 + 로컬 병합)
398
- ├── store.js # SQLite 스토리지 엔진 (sql.js WASM)
399
- ├── schema.js # 도구 스키마 정규화 + 검색 텍스트 빌더
400
- ├── validator.js # 강제 검증 (이름, 설명, 카테고리)
401
- ├── registry.js # 도구 CRUD + 사용량 추적 + 임베딩 캐시
402
- ├── router.js # 3단계 검색 엔진 (BM25 v3.4)
403
- ├── vector-index.js # Float32 벡터 인덱스 (centroid hierarchy)
404
- │ ├── embedding.js # Ollama 임베딩 클라이언트 (nomic-embed-text)
405
- │ ├── executor.js # HTTP/함수 도구 실행기
406
- └── provider-loader.js # 부팅 시 providers/*.json 자동 인덱싱
407
- ├── tools/
408
- └── qln-call.js # 통합 MCP 도구 (search/exec/create/update/delete)
409
- ├── providers/ # 도구 provider 매니페스트 (일괄 등록용)
410
- ├── config.local.js # 로컬 설정 오버라이드 (gitignored)
411
- └── data/ # SQLite 데이터베이스 (gitignored, 자동 생성)
412
- ```
413
-
414
- ## 기술 스택
415
-
416
- | 컴포넌트 | 기술 | 이유 |
417
- |-----------|-----------|------|
418
- | 런타임 | Node.js ≥ 18 | MCP SDK 호환성 |
419
- | 데이터베이스 | SQLite via [sql.js](https://github.com/sql-js/sql.js) (WASM) | 네이티브 의존성 제로, 크로스 플랫폼, 빌드 불필요 |
420
- | 임베딩 | [Ollama](https://ollama.ai) + nomic-embed-text | 로컬, 빠름, 무료, 선택사항 |
421
- | 프로토콜 | [MCP](https://modelcontextprotocol.io) (Model Context Protocol) | 표준 AI 도구 프로토콜 |
422
- | 검증 | [Zod](https://zod.dev) | 런타임 타입 안전 스키마 검증 |
423
-
424
- ## 관련 프로젝트
425
-
426
- | 프로젝트 | 관계 |
427
- |---------|------|
428
- | [n2-soul](https://github.com/choihyunsus/soul) | AI 에이전트 오케스트레이터 — QLN은 Soul의 "도구 브레인" 역할 |
429
-
430
- ## 실전 검증 완료
431
-
432
- 주말 프로토타입이 아닙니다. QLN은 **2개월 이상 운영 환경에서 검증**되었으며, [n2-soul](https://github.com/choihyunsus/soul)의 핵심 도구 라우터로 매일 실사용되고 있습니다.
433
-
434
- **Rose** 제작 N2의 첫 번째 AI 에이전트. 하루에 수백 번 QLN을 통해 라우팅합니다.
435
-
436
- 문제가 있거나 아이디어가 있다면 이슈를 열어주세요. 여러분의 활용 사례를 듣고 싶습니다.
437
-
438
- ## FAQ
439
-
440
- **"왜 프로젝트를 이렇게 자주 올리나요?"**
441
-
442
- N2 생태계는 4개월 이상 활발히 개발되어 왔습니다. Soul, QLN, Ark — 보이는 모든 프로젝트는 공개 전에 실제 업무에서 충분히 테스트되고 검증되었습니다. 앞으로 더 나올 예정이지만, 도배가 아니라 이미 만들어져서 검증 완료된 것들이 많기 때문입니다.
443
-
444
- 혼자서 개발하고 배포하는 프로젝트입니다. 빌드, 테스트, 문서화를 혼자 하다 보니 시간이 많이 걸렸습니다. 관심과 인내에 감사드립니다
445
-
446
- ## 기여하기
447
-
448
- 기여를 환영합니다! 시작하는 방법:
449
-
450
- 1. 저장소를 Fork합니다
451
- 2. 기능 브랜치를 생성합니다 (`git checkout -b feature/amazing-feature`)
452
- 3. 변경사항을 커밋합니다 (`git commit -m 'feat: add amazing feature'`)
453
- 4. 브랜치에 Push합니다 (`git push origin feature/amazing-feature`)
454
- 5. Pull Request를 엽니다
455
-
456
- ## Star History
457
-
458
- QLN이 도움이 되었다면 Star를 눌러주세요!
459
-
460
- ## 라이선스
461
-
462
- Apache-2.0
463
-
464
- ---
465
-
466
- > *"1,000개 도구를 200 토큰으로. 이건 최적화가 아니라 패러다임 전환이다."*
467
-
468
- [nton2.com](https://nton2.com) · [npm](https://www.npmjs.com/package/n2-qln) · lagi0730@gmail.com
469
-
470
- <sub> Rose가 만들었습니다 — N2의 첫 번째 AI 에이전트. 하루에 수백 번 QLN으로 검색하고, 이 README도 직접 작성했습니다.</sub>
1
+ 🇺🇸 [English](README.md)
2
+
3
+ # n2-qln
4
+
5
+ [![npm](https://img.shields.io/npm/v/n2-qln?color=brightgreen)](https://www.npmjs.com/package/n2-qln) [![license](https://img.shields.io/npm/l/n2-qln)](LICENSE) [![node](https://img.shields.io/node/v/n2-qln?color=brightgreen)](https://nodejs.org) [![downloads](https://img.shields.io/npm/dm/n2-qln?color=blue)](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
+ ![QLN Architecture — Without vs With](docs/architecture.png)
12
+
13
+ ## 목차
14
+
15
+ - [왜 QLN인가](#왜-qln인가)
16
+ - [v4.1 주요 변경사항](#v41-주요-변경사항)
17
+ - [빠른 시작](#빠른-시작)
18
+ - [작동 방식](#작동-방식)
19
+ - [API 레퍼런스](#api-레퍼런스)
20
+ - [MCP 자동 디스커버리](#mcp-자동-디스커버리)
21
+ - [Provider 매니페스트](#provider-매니페스트)
22
+ - [설정](#설정)
23
+ - [프로젝트 구조](#프로젝트-구조)
24
+ - [FAQ](#faq)
25
+ - [기여하기](#기여하기)
26
+
27
+ ## 왜 QLN인가
28
+
29
+ MCP 도구를 등록할 때마다 컨텍스트 토큰을 소모합니다. 10개? 괜찮습니다. 100개? 느려집니다. **1,000개? 불가능합니다** — 대화 시작 전에 컨텍스트가 가득 찹니다.
30
+
31
+ QLN이 해결합니다:
32
+
33
+ 1. 모든 도구를 QLN의 SQLite 엔진에 인덱싱
34
+ 2. AI는 **도구 1개**만 봅니다: `n2_qln_call` (~200 토큰)
35
+ 3. AI가 검색 → 최적 매칭 → 자동 폴백과 함께 실행
36
+
37
+ **결과: ~50,000 토큰 대신 ~200 토큰. 99.6% 절감.**
38
+
39
+ ## 기능
40
+
41
+ | 기능 | 설명 |
42
+ |------|------|
43
+ | **1 도구 = 1,000 도구** | AI는 `n2_qln_call` (~200 토큰)만 보고, QLN이 올바른 도구로 라우팅 |
44
+ | **5ms 이하 검색** | 3단계 엔진: 트리거 매칭 BM25 키워드 시맨틱 벡터 |
45
+ | **Auto 모드** | 검색 + 실행 원샷. 신뢰도 게이팅 + 폴백 체인 |
46
+ | **서킷 브레이커** | 실패하는 도구 자동 비활성화, 타임아웃 자동 복구 |
47
+ | **MCP 자동 디스커버리** | 외부 MCP 서버 스캔 → 도구 자동 인덱싱 |
48
+ | **부스트 키워드** | BM25 가중치 적용 검색어로 정밀 검색 |
49
+ | **자동 학습 랭킹** | 사용 횟수 + 성공률이 점수에 반영 |
50
+ | **소스 가중치** | 출처별 도구 우선순위 (mcp > plugin > local) |
51
+ | **핫 리로드** | `providers/` 매니페스트 런타임 수정 → 자동 재인덱싱 |
52
+ | **벌크 인젝트** | 번의 호출로 수백 도구 등록 |
53
+ | **강제 검증** | `verb_target` 네이밍, 최소 설명 길이, 카테고리 제약 |
54
+ | **시맨틱 검색** | 선택적 Ollama 임베딩으로 자연어 매칭 |
55
+ | **네이티브 의존성 제로** | [sql.js](https://github.com/sql-js/sql.js) WASM 기반 — `npm install`이면 끝 |
56
+ | **이중 실행** | 로컬 함수 핸들러 또는 HTTP 프록시혼합 가능 |
57
+ | **TypeScript strict** | v4.0부터 완전 strict 모드 코드베이스 |
58
+
59
+ ## v4.1 주요 변경사항
60
+
61
+ ### 🔍 MCP 자동 디스커버리
62
+
63
+ 연결된 MCP 서버를 스캔하고 도구를 자동 인덱싱 — QLN이 **범용 MCP 허브**가 됩니다.
64
+
65
+ ```javascript
66
+ n2_qln_call({
67
+ action: "discover",
68
+ servers: [
69
+ { name: "my-server", command: "node", args: ["server.js"] }
70
+ ]
71
+ })
72
+ // → my-server에서 47개 도구 발견 (320ms)
73
+ ```
74
+
75
+ ### ⚡ 서킷 브레이커
76
+
77
+ 3회 연속 실패 시 도구 자동 비활성화. 60초 후 복구 시도. 연쇄 장애 방지.
78
+
79
+ ```
80
+ closed → 3회 실패 → open (즉시 거부) → 60초 → half-open (재시도) → 성공 → closed
81
+ ```
82
+
83
+ ### 🔄 폴백 체인
84
+
85
+ `auto` 모드에서 최대 3개 후보를 순차 시도. 1순위가 실패하면 자동으로 다음 후보 실행.
86
+
87
+ ```
88
+ auto "알림 보내기" → push_notification 시도 ❌ → send_email 시도 ✅
89
+ ```
90
+
91
+ ### 🎯 부스트 키워드
92
+
93
+ `boostKeywords` 필드로 검색 최적화. BM25 랭킹에서 2배 가중치 적용.
94
+
95
+ ```json
96
+ {
97
+ "name": "send_email",
98
+ "description": "이메일을 수신자에게 전송",
99
+ "boostKeywords": "smtp outbound notification mail"
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## 빠른 시작
106
+
107
+ ```bash
108
+ npm install n2-qln
109
+ ```
110
+
111
+ **요구사항:** Node.js ≥ 18
112
+
113
+ ### MCP 클라이언트 연결
114
+
115
+ <details>
116
+ <summary><strong>Claude Desktop</strong></summary>
117
+
118
+ `claude_desktop_config.json` 편집:
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "n2-qln": {
124
+ "command": "npx",
125
+ "args": ["-y", "n2-qln"]
126
+ }
127
+ }
128
+ }
129
+ ```
130
+ </details>
131
+
132
+ <details>
133
+ <summary><strong>Cursor</strong></summary>
134
+
135
+ **Settings MCP Servers → Add Server**:
136
+
137
+ ```json
138
+ {
139
+ "name": "n2-qln",
140
+ "command": "npx",
141
+ "args": ["-y", "n2-qln"]
142
+ }
143
+ ```
144
+ </details>
145
+
146
+ <details>
147
+ <summary><strong>기타 MCP 클라이언트</strong></summary>
148
+
149
+ QLN은 **stdio 전송** — MCP 표준 방식을 사용합니다.
150
+
151
+ ```
152
+ command: npx
153
+ args: ["-y", "n2-qln"]
154
+ ```
155
+
156
+ > **팁:** AI 에이전트에게 *"n2-qln을 MCP 설정에 추가해줘"*라고 말하면 됩니다.
157
+ </details>
158
+
159
+ ---
160
+
161
+ ## 작동 방식
162
+
163
+ ```
164
+ 사용자: "이 페이지 스크린샷 찍어"
165
+
166
+ AI n2_qln_call(action: "auto", query: "screenshot page")
167
+ QLN → 3단계 검색 (< 5ms) → take_screenshot (score: 8.0)
168
+ → 실행 → 필요시 폴백 → 결과 반환
169
+ ```
170
+
171
+ ### 3단계 검색 엔진
172
+
173
+ | 단계 | 방식 | 속도 | 상세 |
174
+ |:---:|--------|:---:|------|
175
+ | **1** | 트리거 매칭 | <1ms | 도구 이름과 트리거의 정확 키워드 매칭 |
176
+ | **2** | BM25 키워드 | 1-3ms | [Okapi BM25](https://en.wikipedia.org/wiki/Okapi_BM25) — IDF 가중치, 길이 정규화, `boostKeywords` 2× 부스트 |
177
+ | **3** | 시맨틱 검색 | 5-15ms | [Ollama](https://ollama.ai) 임베딩 벡터 유사도 *(선택)* |
178
+
179
+ 결과 병합 후 랭킹:
180
+
181
+ ```
182
+ final_score = trigger × 3.0 + bm25 × 1.0 + semantic × 2.0
183
+ + log₂(usage + 1) × 0.5 + success_rate × 1.0
184
+ ```
185
+
186
+ ---
187
+
188
+ ## API 레퍼런스
189
+
190
+ QLN은 **하나의 MCP 도구** `n2_qln_call` 9개 액션을 제공합니다.
191
+
192
+ ### auto 검색 + 실행 (원샷)
193
+
194
+ 권장 액션. 검색 → 최적 매칭 → 폴백 체인으로 실행.
195
+
196
+ ```javascript
197
+ n2_qln_call({
198
+ action: "auto",
199
+ query: "스크린샷 찍어", // 자연어 (필수)
200
+ args: { fullPage: true } // 매칭된 도구에 전달 (선택)
201
+ })
202
+ // [auto] "스크린샷 찍어" take_screenshot (score: 8.0, 2ms 검색 + 150ms 실행)
203
+ ```
204
+
205
+ **신뢰도 게이트:** 최고 점수가 2.0 미만이면 실행 대신 검색 결과만 반환 — 잘못된 실행 방지.
206
+
207
+ **폴백 체인:** 1순위가 실패하면 자동으로 2, 3순위까지 시도 후 포기.
208
+
209
+ ### search — 도구 검색
210
+
211
+ ```javascript
212
+ n2_qln_call({
213
+ action: "search",
214
+ query: "이메일 보내기",
215
+ topK: 5 // 최대 결과 (기본: 5, 최대: 20)
216
+ })
217
+ ```
218
+
219
+ ### exec — 특정 도구 실행
220
+
221
+ ```javascript
222
+ n2_qln_call({
223
+ action: "exec",
224
+ tool: "take_screenshot",
225
+ args: { fullPage: true, format: "png" }
226
+ })
227
+ ```
228
+
229
+ ### create — 도구 등록
230
+
231
+ ```javascript
232
+ n2_qln_call({
233
+ action: "create",
234
+ name: "read_pdf", // verb_target 형식 (필수)
235
+ description: "PDF에서 텍스트를 추출합니다", // 최소 10자 (필수)
236
+ category: "data", // web|data|file|dev|ai|capture|misc
237
+ boostKeywords: "pdf extract parse document", // BM25 부스트 검색어
238
+ tags: ["pdf", "read", "extract"],
239
+ endpoint: "http://127.0.0.1:3100" // HTTP 기반 도구용
240
+ })
241
+ ```
242
+
243
+ ### inject 벌크 등록
244
+
245
+ ```javascript
246
+ n2_qln_call({
247
+ action: "inject",
248
+ source: "my-plugin",
249
+ tools: [
250
+ { name: "tool_a", description: "A를 수행합니다", category: "misc" },
251
+ { name: "tool_b", description: "B를 수행합니다", category: "dev" }
252
+ ]
253
+ })
254
+ ```
255
+
256
+ ### discover MCP 서버 스캔
257
+
258
+ [MCP 자동 디스커버리](#mcp-자동-디스커버리) 참조.
259
+
260
+ ### update / delete / stats
261
+
262
+ ```javascript
263
+ // 필드 수정
264
+ n2_qln_call({ action: "update", tool: "read_pdf", description: "향상된 PDF 리더" })
265
+
266
+ // 이름 또는 provider로 삭제
267
+ n2_qln_call({ action: "delete", tool: "read_pdf" })
268
+ n2_qln_call({ action: "delete", provider: "pdf-tools" })
269
+
270
+ // 시스템 통계 (서킷 브레이커 상태 포함)
271
+ n2_qln_call({ action: "stats" })
272
+ ```
273
+
274
+ ---
275
+
276
+ ## MCP 자동 디스커버리
277
+
278
+ v4.1의 킬러 피처. MCP 서버를 연결하면 QLN이 모든 도구를 자동으로 인덱싱합니다.
279
+
280
+ ```javascript
281
+ n2_qln_call({
282
+ action: "discover",
283
+ servers: [
284
+ { name: "n2-soul", command: "node", args: ["path/to/soul/index.js"] },
285
+ { name: "github", command: "npx", args: ["-y", "@modelcontextprotocol/server-github"] }
286
+ ]
287
+ })
288
+ ```
289
+
290
+ **처리 과정:**
291
+ 1. QLN이 stdio로 각 서버에 연결
292
+ 2. `tools/list`로 모든 도구 조회
293
+ 3. `mcp__서버명__도구명` 형식으로 QLN 인덱스에 등록
294
+ 4. 도구 이름과 설명에서 `boostKeywords` 자동 생성
295
+ 5. 실행을 위해 연결 유지
296
+
297
+ **재디스커버리는 멱등** — 다시 실행하면 기존 항목 삭제 후 재등록.
298
+
299
+ ---
300
+
301
+ ## Provider 매니페스트
302
+
303
+ `providers/`에 JSON 파일을 넣으면 부팅 시 자동 인덱싱. 코드 수정 불필요.
304
+
305
+ ```json
306
+ {
307
+ "provider": "my-tools",
308
+ "version": "1.0.0",
309
+ "tools": [
310
+ {
311
+ "name": "send_email",
312
+ "description": "수신자에게 이메일을 전송합니다",
313
+ "category": "communication",
314
+ "triggers": ["email", "send", "mail"],
315
+ "boostKeywords": "smtp outbound notification"
316
+ }
317
+ ]
318
+ }
319
+ ```
320
+
321
+ 핫 리로드: QLN 실행 중 매니페스트 수정 → 자동 반영.
322
+
323
+ ---
324
+
325
+ ## 설정
326
+
327
+ 설정 없이 바로 동작합니다. 커스터마이즈하려면 `config.local.js` 생성:
328
+
329
+ ```javascript
330
+ module.exports = {
331
+ dataDir: './data',
332
+
333
+ // Stage 3 시맨틱 검색 (선택 — Stage 1+2는 이것 없이 작동)
334
+ embedding: {
335
+ enabled: true,
336
+ provider: 'ollama',
337
+ model: 'nomic-embed-text', // 다국어는 'bge-m3'
338
+ baseUrl: 'http://127.0.0.1:11434',
339
+ },
340
+
341
+ // 도구 실행
342
+ executor: {
343
+ timeout: 20000, // 실행 타임아웃 (ms)
344
+ circuitBreaker: {
345
+ failureThreshold: 3, // 연속 실패 횟수 → 비활성화
346
+ recoveryTimeout: 60000, // 복구 시도까지 대기 시간 (ms)
347
+ },
348
+ },
349
+
350
+ // 소스 가중치 (v4.0)
351
+ // 높은 가중치 = 검색 결과에서 높은 우선순위
352
+ search: {
353
+ sourceWeights: {
354
+ mcp: 1.5, // MCP 디스커버리 도구 최우선
355
+ provider: 1.2, // Provider 매니페스트 도구
356
+ local: 1.0, // 수동 생성 도구 (기본)
357
+ },
358
+ },
359
+
360
+ // Provider 자동 인덱싱
361
+ providers: {
362
+ enabled: true, // 부팅 시 providers/*.json 자동 로드
363
+ dir: './providers', // 매니페스트 디렉토리
364
+ },
365
+ };
366
+ ```
367
+
368
+ > `config.local.js`는 gitignore 처리. 클라우드 동기화: `dataDir`을 Google Drive / OneDrive / NAS로 지정.
369
+
370
+ ### 시맨틱 검색 (선택)
371
+
372
+ Ollama 없이도 Stage 1 + 2로 충분한 결과를 제공합니다.
373
+
374
+ ```bash
375
+ ollama pull nomic-embed-text # 영어 최적화
376
+ # 또는
377
+ ollama pull bge-m3 # 다국어 (100개 이상 언어)
378
+ ```
379
+
380
+ ---
381
+
382
+ ## 프로젝트 구조
383
+
384
+ ```
385
+ n2-qln/
386
+ ├── src/
387
+ │ ├── index.ts # MCP 서버 진입점
388
+ │ ├── types.ts # 공유 타입 정의
389
+ │ └── lib/
390
+ │ ├── config.ts # 설정 로더
391
+ │ ├── store.ts # SQLite 엔진 (sql.js WASM)
392
+ │ ├── schema.ts # 도구 정규화 + boostKeywords 빌더
393
+ │ ├── validator.ts # 강제 검증 (이름, 설명, 카테고리)
394
+ │ ├── registry.ts # 도구 CRUD + 사용량 추적 + 서킷 브레이커 통계
395
+ ├── router.ts # 3단계 병렬 검색 (BM25)
396
+ ├── vector-index.ts # Float32 centroid hierarchy
397
+ ├── embedding.ts # Ollama 임베딩 클라이언트
398
+ ├── executor.ts # HTTP/함수 실행기 + 서킷 브레이커
399
+ ├── mcp-discovery.ts # MCP 자동 디스커버리 엔진
400
+ └── provider-loader.ts
401
+ ├── providers/ # 도구 매니페스트 (부팅 자동 인덱싱)
402
+ ├── config.local.js # 로컬 오버라이드 (gitignored)
403
+ └── data/ # SQLite 데이터베이스 (gitignored)
404
+ ```
405
+
406
+ ## 기술 스택
407
+
408
+ | 컴포넌트 | 기술 | 이유 |
409
+ |-----------|-----------|------|
410
+ | 런타임 | Node.js 18 | MCP SDK 호환성 |
411
+ | 데이터베이스 | SQLite via [sql.js](https://github.com/sql-js/sql.js) (WASM) | 네이티브 의존성 제로, 크로스 플랫폼 |
412
+ | 임베딩 | [Ollama](https://ollama.ai) | 로컬, 빠름, 무료, 선택사항 |
413
+ | 프로토콜 | [MCP](https://modelcontextprotocol.io) | 표준 AI 도구 프로토콜 |
414
+ | 언어 | TypeScript (strict) | 타입 안전, 유지보수성 |
415
+
416
+ ## 관련 프로젝트
417
+
418
+ | 프로젝트 | 관계 |
419
+ |---------|------|
420
+ | [n2-soul](https://github.com/choihyunsus/soul) | AI 에이전트 오케스트레이터 QLN은 Soul의 도구 브레인 |
421
+
422
+ ## 실전 검증 완료
423
+
424
+ QLN은 [n2-soul](https://github.com/choihyunsus/soul)의 핵심 도구 라우터로 **2개월 이상 운영 환경에서 검증**되었습니다. 프로토타입이 아니라 매일 사용하는 실전 도구입니다.
425
+
426
+ **Rose** 제작 N2의 첫 번째 AI 에이전트.
427
+
428
+ ## FAQ
429
+
430
+ **"왜 도구 1개만 쓰나요?"**
431
+
432
+ 컨텍스트 토큰 때문입니다. 도구 정의 하나당 50~200 토큰. 100개 = 대화 시작 10,000 토큰 소모. QLN으로 1,000개 이상의 도구를 ~200 토큰으로 사용합니다.
433
+
434
+ **"검색이 잘못된 도구를 고르면?"**
435
+
436
+ 폴백 체인 (v4.1)이 자동으로 다음 후보를 시도합니다. 게다가 자동 학습 — 자주 사용되고 성공률 높은 도구가 상위에 오릅니다.
437
+
438
+ **"Ollama가 꼭 필요한가요?"**
439
+
440
+ 아닙니다. Stage 1 (트리거) + Stage 2 (BM25)가 대부분의 경우를 처리합니다. Ollama는 엣지 케이스에 시맨틱 이해를 추가 — 있으면 좋지만 필수는 아닙니다.
441
+
442
+ ## 기여하기
443
+
444
+ 1. 저장소를 Fork합니다
445
+ 2. 기능 브랜치 생성 (`git checkout -b feature/amazing-feature`)
446
+ 3. 커밋 (`git commit -m 'feat: add amazing feature'`)
447
+ 4. Push 후 PR을 엽니다
448
+
449
+ ## 라이선스
450
+
451
+ Apache-2.0
452
+
453
+ ---
454
+
455
+ > *"1,000개 도구를 200 토큰으로. 이건 최적화가 아니라 패러다임 전환이다."*
456
+
457
+ 🔗 [nton2.com](https://nton2.com) · [npm](https://www.npmjs.com/package/n2-qln) · lagi0730@gmail.com
458
+
459
+ <sub>Rose가 만들었습니다 — N2의 첫 번째 AI 에이전트. 하루에 수백 번 QLN으로 검색하고, 이 README도 직접 작성했습니다.</sub>