@jungtz/wiki-router 1.3.0 → 1.4.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 +208 -225
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
# @jungtz/wiki-router
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
把結構化知識(JSON / Markdown)丟給 LLM 拆成 wiki,再依使用者問題自動挑相關 .md 當上下文回傳。
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/js/@jungtz%2Fwiki-router)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
+
```
|
|
9
|
+
知識來源 → build() → LLM 拆成多個 .md → getContext(prompt) → LLM 路由 → 相關 .md
|
|
10
|
+
```
|
|
11
|
+
|
|
8
12
|
## 安裝
|
|
9
13
|
|
|
10
14
|
```bash
|
|
11
|
-
npm install @jungtz/wiki-router
|
|
15
|
+
npm install @jungtz/wiki-router @jungtz/ai-router
|
|
16
|
+
# 多租戶 + DB 儲存(範本 B)需要:
|
|
17
|
+
npm install better-sqlite3
|
|
12
18
|
```
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
知識來源 -> build() -> LLM 生成 Wiki -> getContext(prompt) -> LLM 路由 -> 相關上下文
|
|
18
|
-
```
|
|
20
|
+
---
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
2. **getContext(prompt)**: 根據使用者問題,由 LLM 從 Index.md 中選擇相關檔案,回傳合併後的上下文
|
|
22
|
+
## 整合範本(複製即用)
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
### A. 單一 wiki(檔案系統,最小整合)
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
`server.js`:
|
|
26
27
|
|
|
27
28
|
```js
|
|
28
29
|
const { createWikiRouter } = require('@jungtz/wiki-router')
|
|
@@ -36,317 +37,299 @@ const router = createRouter({
|
|
|
36
37
|
|
|
37
38
|
const wiki = createWikiRouter({
|
|
38
39
|
router,
|
|
39
|
-
knowledgeDir: './knowledge',
|
|
40
|
-
outputDir: './wiki-output',
|
|
40
|
+
knowledgeDir: './knowledge', // 放 .json / .md 知識來源
|
|
41
|
+
outputDir: './wiki-output', // 生成的 .md 維基
|
|
41
42
|
modelId: 'ollama-local/gemma4:31b',
|
|
42
43
|
})
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
;(async () => {
|
|
46
|
+
await wiki.build() // 啟動時建構(指紋未變動會自動跳過)
|
|
47
|
+
const ctx = await wiki.getContext('住宿規定?')
|
|
48
|
+
console.log(ctx)
|
|
49
|
+
})()
|
|
46
50
|
```
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
modelId: 'ollama-local/gemma4:31b',
|
|
59
|
-
source: {
|
|
60
|
-
async list() { return ['base.json'] },
|
|
61
|
-
async read() {
|
|
62
|
-
const res = await fetch(`/api/hotels/${hotelId}/knowledge`)
|
|
63
|
-
return { type: 'json', content: await res.text() }
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
store: {
|
|
67
|
-
async list() { return await db.wiki.list(hotelId) },
|
|
68
|
-
async read(filename) { return await db.wiki.read(hotelId, filename) },
|
|
69
|
-
async write(name, body) { await db.wiki.upsert(hotelId, name, body) },
|
|
70
|
-
},
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const wikiA = wikiOf(4) // 旅館 A
|
|
75
|
-
const wikiB = wikiOf(7) // 旅館 B
|
|
52
|
+
目錄結構:
|
|
53
|
+
```
|
|
54
|
+
your-project/
|
|
55
|
+
├── server.js
|
|
56
|
+
├── knowledge/
|
|
57
|
+
│ └── base.json ← 知識來源
|
|
58
|
+
└── wiki-output/ ← 自動產生
|
|
59
|
+
├── Index.md
|
|
60
|
+
├── About.md
|
|
61
|
+
└── .manifest.json ← 來源指紋(自動寫入)
|
|
76
62
|
```
|
|
77
63
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
### `createWikiRouter(config)`
|
|
81
|
-
|
|
82
|
-
| 參數 | 類型 | 必要 | 說明 |
|
|
83
|
-
|------|------|------|------|
|
|
84
|
-
| `router` | `AIProviderRouter` | ✅ | AI Router 實例 |
|
|
85
|
-
| `source` | `Source` | ✱ | Knowledge 來源 adapter(與 `knowledgeDir` 二選一) |
|
|
86
|
-
| `store` | `Store` | ✱ | Wiki 儲存 adapter(與 `outputDir` 二選一) |
|
|
87
|
-
| `knowledgeDir` | `string` | ✱ | 簡寫:等同 `source: fsSource(dir)` |
|
|
88
|
-
| `outputDir` | `string` | ✱ | 簡寫:等同 `store: fsStore(dir)` |
|
|
89
|
-
| `modelId` | `string` | | Wiki 生成模型,格式 `provider/model` |
|
|
90
|
-
| `routerModelId` | `string` | | 路由選擇模型,預設同 `modelId` |
|
|
91
|
-
| `timeout` | `number` | | LLM 對話逾時毫秒數,預設 `300000` (5 分鐘) |
|
|
92
|
-
| `splitPrompt` | `string` | | 自訂 Split prompt |
|
|
93
|
-
| `mergePrompt` | `string` | | 自訂 Merge prompt |
|
|
94
|
-
| `routerPrompt` | `string` | | 自訂 Router prompt |
|
|
64
|
+
### B. 多租戶 + SQLite(推薦正式環境)
|
|
95
65
|
|
|
96
|
-
|
|
66
|
+
`server.js`:
|
|
97
67
|
|
|
98
|
-
|
|
68
|
+
```js
|
|
69
|
+
const fs = require('fs')
|
|
70
|
+
const Database = require('better-sqlite3')
|
|
71
|
+
const {
|
|
72
|
+
createTenantManager,
|
|
73
|
+
fsSource,
|
|
74
|
+
sqliteStore,
|
|
75
|
+
ensureWikiTables,
|
|
76
|
+
} = require('@jungtz/wiki-router')
|
|
77
|
+
const { createRouter } = require('@jungtz/ai-router')
|
|
99
78
|
|
|
100
|
-
|
|
79
|
+
const router = createRouter({
|
|
80
|
+
providers: {
|
|
81
|
+
'ollama-local': { type: 'local', baseURL: 'http://localhost:11434' },
|
|
82
|
+
},
|
|
83
|
+
})
|
|
101
84
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
85
|
+
// 開 DB(每個 tenant 透過 tenantId 隔離,全部存同一個 .db 檔)
|
|
86
|
+
const db = new Database('./data/wiki.db')
|
|
87
|
+
db.pragma('journal_mode = WAL')
|
|
88
|
+
ensureWikiTables(db)
|
|
105
89
|
|
|
106
|
-
|
|
107
|
-
- 後續:使用 merge prompt,將新來源合併到既有檔案
|
|
108
|
-
- **快取**:若 source 提供 `getFingerprint()` 且 store 提供 `readManifest()` / `writeManifest()`,build 會比對來源指紋,未變動時直接跳過 LLM(詳見「來源變更偵測」)
|
|
90
|
+
const KNOWLEDGE_DIR = './knowledge'
|
|
109
91
|
|
|
110
|
-
|
|
92
|
+
const wiki = createTenantManager({
|
|
93
|
+
router,
|
|
94
|
+
modelId: 'ollama-local/gemma4:31b',
|
|
111
95
|
|
|
112
|
-
|
|
96
|
+
// 每個 tenantId 各自建立 source / store
|
|
97
|
+
source: tid => fsSource(`${KNOWLEDGE_DIR}/${tid}`),
|
|
98
|
+
store: tid => sqliteStore({ db, tenantId: tid }),
|
|
113
99
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
4. 不等 → 走原本流程,build 成功後呼叫 `store.writeManifest()` 寫入新指紋
|
|
100
|
+
// buildAll() 才需要:列出所有 tenant
|
|
101
|
+
listTenants: () => fs.readdirSync(KNOWLEDGE_DIR),
|
|
102
|
+
})
|
|
118
103
|
|
|
119
|
-
|
|
104
|
+
;(async () => {
|
|
105
|
+
await wiki.buildAll() // 並行 build 所有 tenant
|
|
106
|
+
const ctx = await wiki.getContext('問題', 'hotel-001') // 依 tenantId 取上下文
|
|
107
|
+
})()
|
|
108
|
+
```
|
|
120
109
|
|
|
121
|
-
|
|
110
|
+
目錄結構:
|
|
111
|
+
```
|
|
112
|
+
your-project/
|
|
113
|
+
├── server.js
|
|
114
|
+
├── data/
|
|
115
|
+
│ └── wiki.db ← 自動建立(含 wiki_files / wiki_manifests 兩張表)
|
|
116
|
+
└── knowledge/
|
|
117
|
+
├── hotel-001/
|
|
118
|
+
│ └── base.json
|
|
119
|
+
└── hotel-002/
|
|
120
|
+
└── base.json
|
|
121
|
+
```
|
|
122
122
|
|
|
123
|
-
###
|
|
123
|
+
### C. 串進 Express server
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
接續範本 B 的 `wiki`:
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
```js
|
|
128
|
+
const express = require('express')
|
|
129
|
+
const app = express()
|
|
130
|
+
app.use(express.json())
|
|
130
131
|
|
|
131
|
-
|
|
132
|
+
// 啟動時背景 build(不阻塞 listen)
|
|
133
|
+
app.listen(3000, () => {
|
|
134
|
+
wiki.buildAll().catch(err => console.error('[Wiki] init failed', err))
|
|
135
|
+
})
|
|
132
136
|
|
|
133
|
-
|
|
137
|
+
// 強制重建某 tenant
|
|
138
|
+
app.post('/api/wiki/rebuild', async (req, res) => {
|
|
139
|
+
const ok = await wiki.build(req.body.tenantId, { force: true })
|
|
140
|
+
res.json({ ok })
|
|
141
|
+
})
|
|
134
142
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
// 對話:用 wiki context 增強 prompt
|
|
144
|
+
app.post('/api/chat', async (req, res) => {
|
|
145
|
+
const { prompt, tenantId } = req.body
|
|
146
|
+
const ctx = await wiki.getContext(prompt, tenantId)
|
|
147
|
+
const enhanced = ctx ? `${ctx}\n\n問題:${prompt}` : prompt
|
|
148
|
+
// ... 把 enhanced 餵給你的 LLM
|
|
149
|
+
})
|
|
141
150
|
```
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
interface Store {
|
|
147
|
-
list(): Promise<string[]> // 已生成的 wiki 檔名(含 .md)
|
|
148
|
-
read(filename: string): Promise<string | null> // 不存在回 null
|
|
149
|
-
write(filename: string, content: string): Promise<void>
|
|
150
|
-
readManifest?(): Promise<Record<string, string> | null> // 選用:讀上次 build 指紋
|
|
151
|
-
writeManifest?(m: Record<string, string>): Promise<void> // 選用:寫本次 build 指紋
|
|
152
|
-
}
|
|
153
|
-
```
|
|
152
|
+
---
|
|
154
153
|
|
|
155
|
-
|
|
154
|
+
## 換成自訂 source / store
|
|
156
155
|
|
|
157
|
-
|
|
156
|
+
當知識不在檔案系統(例如打 API)或 wiki 不存 SQLite(例如改 PostgreSQL)時,自訂 adapter:
|
|
158
157
|
|
|
159
158
|
```js
|
|
160
|
-
|
|
161
|
-
async list()
|
|
162
|
-
async read()
|
|
159
|
+
const customSource = (tenantId) => ({
|
|
160
|
+
async list() { return ['base.json'] },
|
|
161
|
+
async read() {
|
|
162
|
+
const res = await fetch(`https://api.example.com/hotels/${tenantId}/knowledge`)
|
|
163
|
+
return { type: 'json', content: await res.text() }
|
|
164
|
+
},
|
|
165
|
+
// 選用:用 ETag / updated_at 當指紋,比 sha256 整檔輕量
|
|
163
166
|
async getFingerprint() {
|
|
164
|
-
const res = await fetch(
|
|
165
|
-
return { 'base.json': await res.text() }
|
|
167
|
+
const res = await fetch(`https://api.example.com/hotels/${tenantId}/etag`)
|
|
168
|
+
return { 'base.json': await res.text() }
|
|
166
169
|
},
|
|
167
|
-
},
|
|
168
|
-
store: {
|
|
169
|
-
async list() { /* DB query */ },
|
|
170
|
-
async read(filename) { /* ... */ },
|
|
171
|
-
async write(filename, body) { /* ... */ },
|
|
172
|
-
async readManifest() { return await db.wikiManifest.get(hotelId) },
|
|
173
|
-
async writeManifest(manifest) { await db.wikiManifest.upsert(hotelId, manifest) },
|
|
174
|
-
},
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### 內建 adapter
|
|
178
|
-
|
|
179
|
-
```js
|
|
180
|
-
import { fsSource, fsStore, sqliteStore, ensureWikiTables } from '@jungtz/wiki-router'
|
|
181
|
-
|
|
182
|
-
// 檔案系統
|
|
183
|
-
createWikiRouter({
|
|
184
|
-
router,
|
|
185
|
-
source: fsSource('./knowledge'),
|
|
186
|
-
store: fsStore('./wiki-output'),
|
|
187
170
|
})
|
|
188
171
|
|
|
189
|
-
|
|
190
|
-
ensureWikiTables(db) // 一次性建表(idempotent)
|
|
191
|
-
createWikiRouter({
|
|
172
|
+
createTenantManager({
|
|
192
173
|
router,
|
|
193
|
-
source:
|
|
194
|
-
store: sqliteStore({ db, tenantId:
|
|
174
|
+
source: customSource,
|
|
175
|
+
store: tid => sqliteStore({ db, tenantId: tid }), // 仍用 SQLite 儲 wiki
|
|
176
|
+
listTenants: async () => {
|
|
177
|
+
const res = await fetch('https://api.example.com/hotels')
|
|
178
|
+
return (await res.json()).map(h => String(h.id))
|
|
179
|
+
},
|
|
195
180
|
})
|
|
196
181
|
```
|
|
197
182
|
|
|
198
|
-
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## API 參考
|
|
199
186
|
|
|
200
|
-
|
|
187
|
+
### `createWikiRouter(config)` — 單一 wiki 工廠
|
|
201
188
|
|
|
202
189
|
| 參數 | 類型 | 必要 | 說明 |
|
|
203
190
|
|------|------|------|------|
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
|
|
209
|
-
`
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
把「快取 wiki 實例 / 並行 build dedup / buildAll / 已 build 狀態追蹤 / Index.md fallback」統一封裝。其他專案串多 wiki 時只要設定 config 即可。
|
|
214
|
-
|
|
215
|
-
```js
|
|
216
|
-
import { createTenantManager, fsSource, sqliteStore } from '@jungtz/wiki-router'
|
|
217
|
-
import Database from 'better-sqlite3'
|
|
218
|
-
|
|
219
|
-
const db = new Database('./wiki.db')
|
|
191
|
+
| `router` | `AIProviderRouter` | ✅ | AI Router 實例 |
|
|
192
|
+
| `source` | `Source` | ✱ | Knowledge 來源 adapter(與 `knowledgeDir` 二選一) |
|
|
193
|
+
| `store` | `Store` | ✱ | Wiki 儲存 adapter(與 `outputDir` 二選一) |
|
|
194
|
+
| `knowledgeDir` | `string` | ✱ | 簡寫:等同 `source: fsSource(dir)` |
|
|
195
|
+
| `outputDir` | `string` | ✱ | 簡寫:等同 `store: fsStore(dir)` |
|
|
196
|
+
| `modelId` | `string` | | Wiki 生成模型,格式 `provider/model` |
|
|
197
|
+
| `routerModelId` | `string` | | 路由選擇模型,預設同 `modelId` |
|
|
198
|
+
| `timeout` | `number` | | LLM 對話逾時毫秒數,預設 `300000` (5 分鐘) |
|
|
199
|
+
| `splitPrompt` / `mergePrompt` / `routerPrompt` | `string` | | 自訂三種內建 prompt |
|
|
220
200
|
|
|
221
|
-
|
|
222
|
-
router,
|
|
223
|
-
modelId: 'ollama-local/gemma4:31b',
|
|
201
|
+
✱ `source`/`knowledgeDir` 至少擇一;`store`/`outputDir` 至少擇一。
|
|
224
202
|
|
|
225
|
-
|
|
226
|
-
source: tid => fsSource(`./knowledge/${tid}`),
|
|
227
|
-
store: tid => sqliteStore({ db, tenantId: tid }),
|
|
203
|
+
回傳:
|
|
228
204
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
})
|
|
205
|
+
| 方法 | 說明 |
|
|
206
|
+
|------|------|
|
|
207
|
+
| `build({ force })` | 建構或增量更新;`force: true` 跳過指紋比對 |
|
|
208
|
+
| `getContext(prompt)` | 依問題回傳相關 .md 合併內容;無相關回空字串 |
|
|
232
209
|
|
|
233
|
-
|
|
234
|
-
await manager.build('hotel-001', { force: true }) // 單一租戶強制重建
|
|
235
|
-
const ctx = await manager.getContext('問題', 'hotel-001')
|
|
236
|
-
```
|
|
210
|
+
### `createTenantManager(config)` — 多租戶協調器
|
|
237
211
|
|
|
238
|
-
|
|
212
|
+
封裝多 wiki 的快取、並行 build dedup、`buildAll`、Index.md fallback 等樣板。
|
|
239
213
|
|
|
240
214
|
| 參數 | 類型 | 必要 | 說明 |
|
|
241
215
|
|------|------|------|------|
|
|
242
|
-
| `router` | `AIProviderRouter` | ✅ |
|
|
216
|
+
| `router` | `AIProviderRouter` | ✅ | 同上 |
|
|
243
217
|
| `source` | `(tenantId) => Source` | ✅ | Source factory |
|
|
244
|
-
| `store`
|
|
218
|
+
| `store` | `(tenantId) => Store` | ✅ | Store factory |
|
|
245
219
|
| `listTenants` | `() => string[] \| Promise<string[]>` | | `buildAll()` 才需要 |
|
|
246
|
-
| `autoIndex` | `boolean` | | 預設 `true`;build
|
|
220
|
+
| `autoIndex` | `boolean` | | 預設 `true`;build 後若 store 沒 Index.md,從現有 .md 合成 |
|
|
247
221
|
| `autoIndexHeader` | `string` | | 預設 `# 知識庫目錄` |
|
|
248
222
|
| `logger` | `{ log, warn, error }` | | 預設 `console` |
|
|
249
|
-
| `modelId` / `routerModelId` / `timeout` / `*Prompt` | | | 同 `createWikiRouter
|
|
223
|
+
| `modelId` / `routerModelId` / `timeout` / `*Prompt` | | | 同 `createWikiRouter`,傳給每個 wiki |
|
|
250
224
|
|
|
251
|
-
|
|
225
|
+
回傳:
|
|
252
226
|
|
|
253
227
|
| 方法 | 說明 |
|
|
254
228
|
|------|------|
|
|
255
|
-
| `build(tenantId, { force })` |
|
|
256
|
-
| `buildAll()` | 並行 build
|
|
257
|
-
| `getContext(prompt, tenantId)` |
|
|
258
|
-
| `isBuilt(tenantId)` |
|
|
259
|
-
| `listBuilt()` | 已 build 過的租戶清單 |
|
|
260
|
-
|
|
261
|
-
## 子模組
|
|
229
|
+
| `build(tenantId, { force })` | 為單一 tenant build;同 tenant 並行呼叫共用 promise |
|
|
230
|
+
| `buildAll()` | 並行 build 所有 tenant,使用 `allSettled` |
|
|
231
|
+
| `getContext(prompt, tenantId)` | 取得指定 tenant 的 wiki 上下文;尚未 build 完成時回空字串 |
|
|
232
|
+
| `isBuilt(tenantId)` / `listBuilt()` | build 狀態查詢 |
|
|
262
233
|
|
|
263
|
-
###
|
|
234
|
+
### 內建 adapter
|
|
264
235
|
|
|
265
236
|
```js
|
|
266
|
-
|
|
237
|
+
const { fsSource, fsStore, sqliteStore, ensureWikiTables } = require('@jungtz/wiki-router')
|
|
267
238
|
|
|
268
|
-
|
|
269
|
-
//
|
|
239
|
+
fsSource('./knowledge') // 讀 .json/.md
|
|
240
|
+
fsStore('./wiki-output') // 寫 .md + .manifest.json
|
|
241
|
+
sqliteStore({ db, tenantId: 'x' }) // SQLite Store(多租戶用同一 db)
|
|
242
|
+
ensureWikiTables(db, { filesTable, manifestsTable }) // 一次性建表(idempotent)
|
|
270
243
|
```
|
|
271
244
|
|
|
272
|
-
|
|
245
|
+
`sqliteStore` 內部會自動 `ensureWikiTables`,但建議啟動時先呼叫一次以避免每個 tenant 都跑 CREATE TABLE。
|
|
273
246
|
|
|
274
|
-
|
|
247
|
+
### Adapter 介面
|
|
275
248
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
249
|
+
```ts
|
|
250
|
+
interface Source {
|
|
251
|
+
list(): Promise<string[]>
|
|
252
|
+
read(key: string): Promise<{ type: 'json' | 'markdown', content: string }>
|
|
253
|
+
getFingerprint?(): Promise<Record<string, string>> // 選用:啟用快取
|
|
254
|
+
}
|
|
281
255
|
|
|
282
|
-
|
|
283
|
-
|
|
256
|
+
interface Store {
|
|
257
|
+
list(): Promise<string[]>
|
|
258
|
+
read(filename: string): Promise<string | null>
|
|
259
|
+
write(filename: string, content: string): Promise<void>
|
|
260
|
+
readManifest?(): Promise<Record<string, string> | null> // 選用:啟用快取
|
|
261
|
+
writeManifest?(m: Record<string, string>): Promise<void>// 選用:啟用快取
|
|
262
|
+
}
|
|
284
263
|
```
|
|
285
264
|
|
|
286
|
-
|
|
265
|
+
`getFingerprint` + `readManifest` + `writeManifest` **三者同時實作才會啟用快取**;缺其一則每次 build 都重跑 LLM(向下相容)。
|
|
287
266
|
|
|
288
|
-
|
|
267
|
+
---
|
|
289
268
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
269
|
+
## 來源變更偵測(Fingerprint Cache)
|
|
270
|
+
|
|
271
|
+
`build()` 流程:
|
|
272
|
+
1. 從 `source.getFingerprint()` 算當前指紋
|
|
273
|
+
2. 從 `store.readManifest()` 讀上次指紋
|
|
274
|
+
3. 兩者相等且 store 已有檔案 → 跳過 LLM,回傳 `true`
|
|
275
|
+
4. 不等 → 走原本流程;成功後 `store.writeManifest()` 更新指紋
|
|
276
|
+
|
|
277
|
+
`build({ force: true })` 跳過比對直接重建。
|
|
278
|
+
|
|
279
|
+
`fsStore` 把 manifest 存於 store 目錄下的 `.manifest.json`;`sqliteStore` 存於 `wiki_manifests` 表。
|
|
280
|
+
|
|
281
|
+
---
|
|
298
282
|
|
|
299
283
|
## 範例
|
|
300
284
|
|
|
301
|
-
- `examples/basic.js` —
|
|
302
|
-
- `examples/multi-tenant.js` —
|
|
285
|
+
- [`examples/basic.js`](./examples/basic.js) — 範本 A 完整可跑檔
|
|
286
|
+
- [`examples/multi-tenant.js`](./examples/multi-tenant.js) — 範本 B 完整可跑檔
|
|
303
287
|
|
|
304
288
|
## 互動預覽
|
|
305
289
|
|
|
306
|
-
|
|
290
|
+
單一 wiki:
|
|
307
291
|
|
|
308
292
|
```bash
|
|
309
293
|
npm run preview
|
|
310
|
-
|
|
311
|
-
# 自訂路徑
|
|
312
294
|
npm run preview -- --knowledge ./my-knowledge --output ./my-wiki
|
|
313
295
|
```
|
|
314
296
|
|
|
315
|
-
|
|
297
|
+
多租戶(createTenantManager + sqliteStore):
|
|
316
298
|
|
|
317
|
-
|
|
299
|
+
```bash
|
|
300
|
+
npm run preview:multi
|
|
301
|
+
npm run preview:multi -- --knowledge ./knowledge --db ./data/wiki-preview.db
|
|
302
|
+
```
|
|
318
303
|
|
|
319
|
-
|
|
304
|
+
`preview:multi` 額外支援 `:tenants`、`:tenant <id>` 切換、`:buildall` 全租戶重建。其餘指令:`:list / :build / :files / :help / :quit`。
|
|
320
305
|
|
|
321
|
-
|
|
306
|
+
## 子模組匯出
|
|
322
307
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
| 其他 (`:bug:` / `:hammer:` / ...) | patch | 1.0.5 → 1.0.6 |
|
|
308
|
+
```js
|
|
309
|
+
const { parseWikiOutput } = require('@jungtz/wiki-router/parser')
|
|
310
|
+
const { SPLIT_PROMPT, MERGE_PROMPT, ROUTER_PROMPT } = require('@jungtz/wiki-router/prompts')
|
|
311
|
+
```
|
|
328
312
|
|
|
329
|
-
|
|
313
|
+
## 發布(自動)
|
|
330
314
|
|
|
331
|
-
|
|
315
|
+
push 到 master 後 CI 掃 commit message 決定 bump:
|
|
332
316
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
npm run release # bump:patch + push:version
|
|
339
|
-
```
|
|
317
|
+
| Commit 訊息 | Bump |
|
|
318
|
+
|---|---|
|
|
319
|
+
| `:boom:` 💥 / `BREAKING CHANGE` | major |
|
|
320
|
+
| `:sparkles:` ✨ | minor |
|
|
321
|
+
| 其他 (`:bug:` / `:hammer:` / ...) | patch |
|
|
340
322
|
|
|
341
323
|
## 錯誤處理
|
|
342
324
|
|
|
343
|
-
|
|
325
|
+
採靜默失敗:模型連不上時只 warn 不 throw,`getContext` 回空字串、`build` 回 `false`,主流程不中斷。
|
|
344
326
|
|
|
345
|
-
##
|
|
327
|
+
## 相依
|
|
346
328
|
|
|
347
329
|
| 套件 | 關係 |
|
|
348
330
|
|------|------|
|
|
349
331
|
| `@jungtz/ai-router` | Peer (optional) — LLM 調度 |
|
|
332
|
+
| `better-sqlite3` 等 SQLite 驅動 | 由使用者提供(傳入 `sqliteStore`),套件不直接相依 |
|
|
350
333
|
|
|
351
334
|
## 授權
|
|
352
335
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungtz/wiki-router",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "LLM Wiki 知識庫路由引擎 - 將結構化知識轉為 Markdown 維基,智慧路由查詢",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"dev": "rollup -c -w",
|
|
35
35
|
"test": "node --test",
|
|
36
36
|
"preview": "node scripts/preview.js",
|
|
37
|
+
"preview:multi": "node scripts/preview-multi.js",
|
|
37
38
|
"lint": "eslint src/",
|
|
38
39
|
"lint:fix": "eslint src/ --fix",
|
|
39
40
|
"format": "prettier --write src/",
|