kofdai commited on
Commit
3f9e2e3
·
verified ·
1 Parent(s): 4ccd178

Upload PROJECT_ARCHITECTURE_GUIDE.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. PROJECT_ARCHITECTURE_GUIDE.md +1661 -0
PROJECT_ARCHITECTURE_GUIDE.md ADDED
@@ -0,0 +1,1661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NullAI プロジェクト完全理解ガイド
2
+
3
+ **最終更新**: 2025-12-02
4
+ **対象読者**: このプロジェクトを引き継ぐ全ての開発者
5
+ **目的**: プロジェクトの全体像を完全に理解し、設計思想を正しく継承する
6
+
7
+ ---
8
+
9
+ ## 📖 目次
10
+
11
+ 1. [プロジェクト概要](#プロジェクト概要)
12
+ 2. [4つの核心思想(こだわりポイント)](#4つの核心思想こだわりポイント)
13
+ 3. [システムアーキテクチャ全体図](#システムアーキテクチャ全体図)
14
+ 4. [各システムの詳細解説](#各システムの詳細解説)
15
+ 5. [データフロー完全図解](#データフロー完全図解)
16
+ 6. [技術スタック詳細](#技術スタック詳細)
17
+ 7. [よくある誤解と注意点](#よくある誤解と注意点)
18
+ 8. [設計判断の理由](#設計判断の理由)
19
+ 9. [拡張時の考慮事項](#拡張時の考慮事項)
20
+
21
+ ---
22
+
23
+ ## プロジェクト概要
24
+
25
+ ### NullAIとは何か
26
+
27
+ **NullAI**は、**自己進化型多ドメイン知識推論エンジン**です。
28
+
29
+ #### 核心的な問いと答え
30
+
31
+ **Q: 何を解決しようとしているのか?**
32
+ A: 「AIのハルシネーション(幻覚)」と「小型モデルの性能不足」の両方を同時に解決
33
+
34
+ **Q: どうやって解決するのか?**
35
+ A:
36
+ 1. **DB優先推論(RAG)** → ハルシネーション削減
37
+ 2. **師匠→弟子のファインチューニング** → 小型モデルの性能向上
38
+ 3. **樹木型空間記憶** → 知識の意味的整理と高速検索
39
+ 4. **自己拡充サイクル** → 知識ベースの自動成長
40
+
41
+ **Q: 他のRAGシステムとの違いは?**
42
+ A:
43
+ - ❌ 普通のRAG: ベクトルDBで検索するだけ
44
+ - ✅ NullAI: **6次元空間座標**で知識を配置し、意味的な近傍検索が可能
45
+
46
+ **Q: 他のファインチューニングシステムとの違いは?**
47
+ A:
48
+ - ❌ 普通のFT: 人間が訓練データを手動作成
49
+ - ✅ NullAI: **師匠AIが自動的に訓練データを生成** → 弟子が学習 → 弟子が師匠に昇格 → 無限サイクル
50
+
51
+ ### プロジェクト名の由来
52
+
53
+ **Null** = ゼロ(ハルシネーション)
54
+ **AI** = Artificial Intelligence
55
+
56
+ → **ゼロ・ハルシネーションを目指すAI**
57
+
58
+ ---
59
+
60
+ ## 4つの核心思想(こだわりポイント)
61
+
62
+ ### 1️⃣ 倒木システム(Fallen Tree System)
63
+
64
+ #### 比喩の意味
65
+
66
+ 森で大木(老いた木)が倒れると、その養分で新しい若木が育つ。NullAIでは:
67
+
68
+ - 🌲 **大木(師匠モデル)**: 高性能だが重いAI(例: DeepSeek R1 32B)
69
+ - 🌱 **若木(弟子モデル)**: 最初は空っぽだが軽量なAI(例: Phi-2 2.7B)
70
+ - 🍂 **養分(訓練データ)**: 師匠の高品質な出力(Alpaca形式JSONL)
71
+
72
+ #### システムの流れ
73
+
74
+ ```
75
+ ┌─────────────────────────────────────────────────────┐
76
+ │ Phase 1: 師匠の統治時代 │
77
+ ├─────────────────────────────────────────────────────┤
78
+ │ 師匠(DeepSeek R1)が推論を担当 │
79
+ │ ↓ │
80
+ │ 高品質な出力(confidence >= 0.8)が自動保存 │
81
+ │ ↓ │
82
+ │ training_data/master_outputs/*.jsonl │
83
+ └─────────────────────────────────────────────────────┘
84
+
85
+ ┌─────────────────────────────────────────────────────┐
86
+ │ Phase 2: ファインチューニング │
87
+ ├─────────────────────────────────────────────────────┤
88
+ │ 訓練データを使って弟子(Phi-2)を訓練 │
89
+ │ ↓ │
90
+ │ 弟子の性能が向上(師匠の知識を吸収) │
91
+ │ ↓ │
92
+ │ training_data/checkpoints/apprentice_*/ │
93
+ └─────────────────────────────────────────────────────┘
94
+
95
+ ┌─────────────────────────────────────────────────────┐
96
+ │ Phase 3: 世代交代(倒木) │
97
+ ├──────────────────────────────────────��──────────────┤
98
+ │ 弟子が十分成長 → 師匠に昇格 │
99
+ │ ↓ │
100
+ │ 旧師匠(DeepSeek)は引退(でも特別な役割あり) │
101
+ │ ↓ │
102
+ │ 新しい空の弟子を生成 │
103
+ └─────────────────────────────────────────────────────┘
104
+
105
+ サイクル繰り返し
106
+ ```
107
+
108
+ #### 重要な設計判断
109
+
110
+ **Q: なぜ師匠を完全に削除しないのか?**
111
+ A: 引退した師匠(DeepSeek)は「**永久的指導者**」として残る
112
+ - DB拡充時のプロンプト生成
113
+ - 新しいドメインの初期知識生成
114
+ - 品質チェック
115
+
116
+ **Q: 弟子はいつ師匠になれるのか?**
117
+ A:
118
+ - ファインチューニング完了後、手動で昇格
119
+ - 将来的には自動評価で昇格判定(未実装)
120
+
121
+ **Q: 複数の弟子を同時に訓練できるのか?**
122
+ A: できる。ドメイン別に異なる弟子を訓練可能
123
+ - 医療ドメイン弟子
124
+ - 法律ドメイン弟子
125
+ - 一般知識弟子
126
+
127
+ ### 2️⃣ DB分離構造(Database Separation Structure)
128
+
129
+ #### 設計思想
130
+
131
+ ```
132
+ 質問が来た時の判断フロー:
133
+
134
+ 質問 → まず知識DBを検索
135
+ ├─ 見つかった → DB知識を使って推論(RAG)✅ 信頼性高
136
+ └─ 見つからない → AI内部知識で推論 ⚠️ ハルシネーションリスク
137
+
138
+ その出力をDBに保存(自己拡充)
139
+ ```
140
+
141
+ #### DB優先の理由
142
+
143
+ | 知識ソース | 信頼性 | 根拠 | ハルシネーション |
144
+ |-----------|--------|------|-----------------|
145
+ | 知識DB(.iath) | ⭐⭐⭐⭐⭐ | 人間が検証 or 専門家が作成 | ほぼゼロ |
146
+ | AI生成知識 | ⭐⭐⭐ | AIの内部知識(学習データ由来) | 中程度 |
147
+ | AI幻覚 | ⭐ | 推測・創作 | 高い |
148
+
149
+ **結論**: 知識DBにあるものは絶対に使う → ハルシネーション削減
150
+
151
+ #### 自己拡充の仕組み
152
+
153
+ ```python
154
+ # 疑似コード
155
+ async def infer(question):
156
+ # Step 1: DB検索
157
+ db_knowledge = search_db(question)
158
+
159
+ if db_knowledge:
160
+ # Step 2a: RAG推論(DBの知識を使う)
161
+ response = llm.generate(
162
+ f"Based on this verified knowledge: {db_knowledge}\n"
163
+ f"Answer: {question}"
164
+ )
165
+ return response
166
+ else:
167
+ # Step 2b: AI内部知識で推論
168
+ response = llm.generate(question)
169
+
170
+ # Step 3: 高品質なら保存(自己拡充)
171
+ if response.confidence >= 0.7:
172
+ save_to_db(question, response)
173
+
174
+ return response
175
+ ```
176
+
177
+ #### 重要な設計判断
178
+
179
+ **Q: なぜSQLiteと.iathの2つを使うのか?**
180
+ A: 役割分担
181
+ - **SQLite**: メタデータ(ユーザー、ワークスペース、推論履歴)
182
+ - **.iath**: 知識タイル本体(6次元座標 + コンテンツ)
183
+
184
+ **Q: confidence >= 0.7と0.8の違いは?**
185
+ A:
186
+ - `>= 0.7`: DB保存(自己拡充)← やや緩め
187
+ - `>= 0.8`: 訓練データ保存 ← 厳しめ(高品質のみ)
188
+
189
+ **Q: AI生成知識をDBに保存する際、人間のチェックは不要?**
190
+ A: 現在は自動保存。将来的には:
191
+ - 専門家によるレビューフロー
192
+ - コミュニティ投票による品質評価
193
+ - AIによる自動検証(別のAIでクロスチェック)
194
+
195
+ ### 3️⃣ 樹木型空間記憶(Dendritic Memory Space)
196
+
197
+ #### 比喩の意味
198
+
199
+ 人間の脳の**樹状突起(デンドライト)**のように、知識が空間的に整理されている。
200
+
201
+ 通常のDB:
202
+ ```
203
+ 知識1: 「心臓は循環器官である」
204
+ 知識2: 「脳は中枢神経系の一部である」
205
+ → バラバラに保存(関連性が不明)
206
+ ```
207
+
208
+ 樹木型空間記憶:
209
+ ```
210
+ 知識1: 座標 [0.2, 0.8, 0.3, 0.9, 0.7, 0.8]
211
+ 知識2: 座標 [0.3, 0.8, 0.4, 0.85, 0.65, 0.75]
212
+ → 近い座標 = 意味的に関連 → 一緒に検索できる
213
+ ```
214
+
215
+ #### 6次元座標系の詳細
216
+
217
+ ```
218
+ Knowledge Tile の座標 = [x, y, z, c, g, v]
219
+ ─────┬───── ─────┬─────
220
+ medical_space meta_space
221
+ ```
222
+
223
+ ##### medical_space [x, y, z]: ドメイン固有の3次元空間
224
+
225
+ 例: 医療ドメインの場合
226
+
227
+ | 軸 | 意味 | 例 |
228
+ |----|------|-----|
229
+ | x | 解剖学的位置 | 0.0=神経系, 0.5=循環器, 1.0=消化器 |
230
+ | y | 病理学的分類 | 0.0=感染症, 0.5=代謝疾患, 1.0=外傷 |
231
+ | z | 治療レベル | 0.0=予防, 0.5=診断, 1.0=治療 |
232
+
233
+ ##### meta_space [c, g, v]: メタ情報の3次元空間
234
+
235
+ | 軸 | 意味 | 値の範囲 |
236
+ |----|------|----------|
237
+ | c (Certainty) | 確実性 | 0.0=仮説, 0.5=定説, 1.0=確立された事実 |
238
+ | g (Granularity) | 粒度 | 0.0=概要, 0.5=詳細, 1.0=専門的 |
239
+ | v (Verification) | 検証状態 | 0.0=未検証, 0.5=専門家レビュー済, 1.0=複数ソース確認済 |
240
+
241
+ #### 検索の仕組み
242
+
243
+ ##### 1. テキスト検索(従来型)
244
+
245
+ ```python
246
+ def search_by_text(query):
247
+ # 単純なキーワードマッチング
248
+ results = [tile for tile in all_tiles
249
+ if query in tile.content]
250
+ return results
251
+ ```
252
+
253
+ **問題点**: 同義語を見逃す
254
+ - 「心臓病」で検索しても「循環器疾患」がヒットしない
255
+
256
+ ##### 2. 座標検索(空間検索)
257
+
258
+ ```python
259
+ def search_by_coordinates(query_coords, top_k=5):
260
+ # 6次元ユークリッド距離で計算
261
+ distances = []
262
+ for tile in all_tiles:
263
+ dist = euclidean_distance(query_coords, tile.coords)
264
+ distances.append((tile, dist))
265
+
266
+ # 距離が近い順にソート
267
+ distances.sort(key=lambda x: x[1])
268
+ return distances[:top_k]
269
+ ```
270
+
271
+ **利点**: 意味的に近い知識を自動で発見
272
+ - 座標が近い = 意味的に関連
273
+
274
+ ##### 3. ハイブリッド検索(推奨)
275
+
276
+ ```python
277
+ def hybrid_search(query_text, query_coords=None, top_k=5):
278
+ # テキストマッチスコア計算
279
+ text_scores = calculate_text_match(query_text)
280
+
281
+ # 座標距離スコア計算
282
+ if query_coords:
283
+ spatial_scores = calculate_spatial_distance(query_coords)
284
+
285
+ # 複合スコア = α * text_score + β * (1 - spatial_distance)
286
+ combined_scores = 0.4 * text_scores + 0.6 * spatial_scores
287
+
288
+ return top_k_results(combined_scores)
289
+ ```
290
+
291
+ #### .iathファイル形式
292
+
293
+ ```
294
+ .iath ファイル構造:
295
+
296
+ ┌────────────────────────────────────┐
297
+ │ Header (64 bytes) │ ← マジックナンバー、バージョン
298
+ ├────────────────────────────────────┤
299
+ │ Index (JSON, 可変長) │ ← タイルIDとオフセット一覧
300
+ │ { │
301
+ │ "tiles": [ │
302
+ │ {"id": "tile_001", "offset": 512},
303
+ │ {"id": "tile_002", "offset": 2048}
304
+ │ ] │
305
+ │ } │
306
+ ├────────────────────────────────────┤
307
+ │ Data Section (zstd圧縮) │
308
+ │ ┌──────────────────────┐ │
309
+ │ │ Tile 1 (JSON) │ │
310
+ │ │ - metadata │ │
311
+ │ │ - content │ │
312
+ │ │ - coordinates │ │
313
+ │ │ - verification │ │
314
+ │ └──────────────────────┘ │
315
+ │ ┌──────────────────────┐ │
316
+ │ │ Tile 2 (JSON) │ │
317
+ │ └──────────────────────┘ │
318
+ │ ... │
319
+ └────────────────────────────────────┘
320
+ ```
321
+
322
+ **なぜzstd圧縮?**
323
+ - 高い圧縮率(gzipより優れる)
324
+ - 高速な解凍速度
325
+ - Facebookが開発(信頼性)
326
+
327
+ #### 重要な設計判断
328
+
329
+ **Q: なぜ6次元? 3次元や10次元ではダメ?**
330
+ A:
331
+ - 3次元: ドメイン知識だけでメタ情報が表現できない
332
+ - 10次元以上: 次元の呪い(検索が遅くなる)、人間が理解不能
333
+ - 6次元: ドメイン(3) + メタ(3) = バランスが良い
334
+
335
+ **Q: 座標は誰が決めるのか?**
336
+ A:
337
+ - 現状: 人間が手動で設定(dendritic-memory-editorで)
338
+ - Priority 2で実装予定: AIが自動推定(DeepSeekが座標を生成)
339
+
340
+ **Q: .iathとFAISS(ベクトルDB)の違いは?**
341
+ A:
342
+ | 特徴 | .iath | FAISS |
343
+ |------|-------|-------|
344
+ | 座標次元 | 6次元(人間が理解可能) | 768次元(Embeddingモデル依存) |
345
+ | 検索速度 | O(n) 線形探索 | O(log n) 高速 |
346
+ | 意味の透明性 | 高い(座標の意味が明確) | 低い(ブラックボックス) |
347
+ | 編集容易性 | 高い(座標を手動調整可能) | 低い(再Embedding必要) |
348
+
349
+ **結論**: .iathは「人間が理解・編集できる知識ベース」を重視
350
+
351
+ ### 4️⃣ ローカルファースト & ワンコマンドセットアップ
352
+
353
+ #### 設計思想
354
+
355
+ ```
356
+ ❌ 悪い例(クラウド依存):
357
+ pip install nullai
358
+ nullai --api-key=YOUR_OPENAI_KEY # クラウドAPI必須
359
+ → インターネット必須、コスト高、プライバシー懸念
360
+
361
+ ✅ NullAI:
362
+ ./start_null_ai.sh # ローカルで完結
363
+ → オフライン可能、無料、プライバシー保護
364
+ ```
365
+
366
+ #### ワンコマンドの実現方法
367
+
368
+ `start_null_ai.sh`が自動で実行すること:
369
+
370
+ 1. ✅ 依存関係チェック(Python, Node.js, Ollama)
371
+ 2. ✅ 仮想環境作成(venv)
372
+ 3. ��� Python依存関係インストール
373
+ 4. ✅ Node.js依存関係インストール
374
+ 5. ✅ データベース初期化(sql_app.db)
375
+ 6. ✅ Ollama起動
376
+ 7. ✅ バックエンド起動(port 8000)
377
+ 8. ✅ フロントエンド起動(port 5173)
378
+ 9. ✅ .iathメモリロード確認
379
+
380
+ **ユーザーがすることは**: `./start_null_ai.sh`を実行するだけ
381
+
382
+ #### 重要な設計判断
383
+
384
+ **Q: なぜOllamaを使うのか? HuggingFaceだけではダメ?**
385
+ A:
386
+ - Ollama: モデル管理が楽(`ollama pull deepseek-r1`だけ)
387
+ - HuggingFace: 手動でダウンロード、パス指定が面倒
388
+
389
+ **Q: なぜDockerを使わないのか?**
390
+ A:
391
+ - Docker: 初心者には難しい、GPUパススルーが複雑
392
+ - シェルスクリプト: シンプル、デバッグしやすい、カスタマイズ容易
393
+
394
+ ---
395
+
396
+ ## システムアーキテクチャ全体図
397
+
398
+ ```
399
+ ┌─────────────────────────────────────────────────────────────────┐
400
+ │ Frontend (React + TypeScript) │
401
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
402
+ │ │ Engine │ │ Inference │ │ Training │ │
403
+ │ │ Manager │ │ Panel │ │ Dashboard │ │
404
+ │ └──────────────┘ └──────────────┘ └──────────────┘ │
405
+ │ │ │ │ │
406
+ │ └──────────────────┼──────────────────┘ │
407
+ │ │ │
408
+ │ HTTP/WebSocket │
409
+ │ │ │
410
+ └───────────────────────────┼─────────────────────────────────────┘
411
+
412
+ ┌───────────────────────────┼─────────────────────────────────────┐
413
+ │ Backend (FastAPI) │
414
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
415
+ │ │ config.py │ │ questions.py │ │ training.py │ │
416
+ │ │ (Engine API) │ │ (Inference) │ │ (Fine-tune) │ │
417
+ │ └──────────────┘ └──────────────┘ └──────────────┘ │
418
+ │ │ │ │ │
419
+ │ └──────────────────┼──────────────────┘ │
420
+ │ │ │
421
+ └───────────────────────────┼─────────────────────────────────────┘
422
+
423
+ ┌───────────────────────────┼─────────────────────────────────────┐
424
+ │ NullAI Core Logic │
425
+ │ ┌────────────────────────────────────────────────┐ │
426
+ │ │ model_router.py │ │
427
+ │ │ - RAG推論統合 │ │
428
+ │ │ - 師匠出力保存 │ │
429
+ │ │ - エンジン管理(スワップ、昇格) │ │
430
+ │ └────────────────────────────────────────────────┘ │
431
+ │ │ │ │ │
432
+ │ ▼ ▼ ▼ │
433
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
434
+ │ │ iath_memory │ │ llm_providers│ │ fine_tuning │ │
435
+ │ │ .py │ │ .py │ │ .py │ │
436
+ │ │ (6D Search) │ │ (4 Providers)│ │ (PEFT/Unslo) │ │
437
+ │ └──────────────┘ └──────────────┘ └──────────────┘ │
438
+ │ │ │ │ │
439
+ └─────────┼──────────────────┼──────────────────┼─────────────────┘
440
+ │ │ │
441
+ ▼ ▼ ▼
442
+ ┌──────────────────────────────────────────────────────────────┐
443
+ │ External Services │
444
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
445
+ │ │ knowledge_ │ │ Ollama │ │ HuggingFace │ │
446
+ │ │ base.iath │ │ (localhost) │ │ Models │ │
447
+ │ │ (6D Memory) │ │ │ │ │ │
448
+ │ └──────────────┘ └──────────────┘ └──────────────┘ │
449
+ │ │
450
+ │ ┌──────────────┐ ┌──────────────────────────────────┐ │
451
+ │ │ sql_app.db │ │ training_data/ │ │
452
+ │ │ (SQLite) │ │ - master_outputs/*.jsonl │ │
453
+ │ │ │ │ - checkpoints/apprentice_*/ │ │
454
+ │ └──────────────┘ └──────────────────────────────────┘ │
455
+ └──────────────────────────────────────────────────────────────┘
456
+ ```
457
+
458
+ ---
459
+
460
+ ## 各システムの詳細解説
461
+
462
+ ### ModelRouter (`null_ai/model_router.py`)
463
+
464
+ #### 役割
465
+ NullAIの**頭脳**。全ての推論リクエストを管理。
466
+
467
+ #### 主要メソッド詳細
468
+
469
+ ##### `__init__()`
470
+ ```python
471
+ def __init__(self, config_manager):
472
+ self.config_manager = config_manager
473
+ self.master_model = None # 師匠モデル
474
+ self.apprentice_model = None # 弟子モデル
475
+ self.dendritic_memory = None # .iath空間記憶
476
+
477
+ # .iathファイルのロード
478
+ self._load_dendritic_memory()
479
+ ```
480
+
481
+ **重要**: 初期化時に自動的に.iathをロード → 起動時間が長くなる可能性
482
+
483
+ ##### `async def infer()` - RAG統合推論
484
+
485
+ ```python
486
+ async def infer(self, prompt, domain_id, model_config, save_to_memory=False):
487
+ # Step 1: DB知識チェック
488
+ has_knowledge = self._check_db_knowledge(domain_id, prompt)
489
+
490
+ if has_knowledge:
491
+ # Step 2a: RAG推論
492
+ knowledge = self._retrieve_relevant_knowledge(domain_id, prompt, top_k=3)
493
+ augmented_prompt = self._build_rag_prompt(prompt, knowledge)
494
+ response = await self._perform_llm_inference(model_config, augmented_prompt)
495
+ else:
496
+ # Step 2b: 通常推論
497
+ response = await self._perform_llm_inference(model_config, prompt)
498
+
499
+ # Step 3: 高品質なら保存
500
+ if save_to_memory and response["confidence"] >= 0.7:
501
+ await self._save_inference_to_db(domain_id, prompt, response)
502
+
503
+ # Step 4: 師匠の出力なら訓練データとして保存
504
+ is_master = (self.master_model and
505
+ model_config.model_id == self.master_model.model_id)
506
+ if is_master and response["confidence"] >= 0.8:
507
+ await self._save_master_output_as_training_data(
508
+ prompt, response["response"], domain_id, response["confidence"]
509
+ )
510
+
511
+ return response
512
+ ```
513
+
514
+ **データフロー図**:
515
+ ```
516
+ prompt → check DB → found?
517
+ ├─ YES → retrieve knowledge
518
+ │ ↓
519
+ │ augment prompt
520
+ │ ↓
521
+ │ LLM inference → response
522
+ │ ↓
523
+ │ is master? → save as training data
524
+
525
+ └─ NO → LLM inference → response
526
+
527
+ confidence >= 0.7? → save to DB
528
+ ```
529
+
530
+ ##### `_retrieve_relevant_knowledge()` - ハイブリッド検索
531
+
532
+ ```python
533
+ def _retrieve_relevant_knowledge(self, domain_id, prompt, top_k=3):
534
+ if not self.dendritic_memory:
535
+ return []
536
+
537
+ # ハイブリッド検索実行
538
+ results = self.dendritic_memory.hybrid_search(
539
+ query_text=prompt,
540
+ query_coords=None, # 将来的には座標も推定
541
+ top_k=top_k,
542
+ text_weight=0.4, # テキストマッチの重み
543
+ spatial_weight=0.6 # 空間距離の重み
544
+ )
545
+
546
+ # Knowledge Tile形式に変換
547
+ formatted_knowledge = []
548
+ for tile in results:
549
+ formatted_knowledge.append({
550
+ "id": tile["metadata"]["knowledge_id"],
551
+ "topic": tile["metadata"]["topic"],
552
+ "content": tile["content"]["final_response"],
553
+ "confidence_score": tile["verification"]["initial_certainty"],
554
+ "coordinates": tile["coordinates"],
555
+ "text_match_score": tile.get("text_match_score", 0),
556
+ "spatial_distance": tile.get("spatial_distance", None)
557
+ })
558
+
559
+ return formatted_knowledge
560
+ ```
561
+
562
+ ##### `_save_master_output_as_training_data()` - 訓練データ保存
563
+
564
+ ```python
565
+ async def _save_master_output_as_training_data(
566
+ self, prompt, response, domain_id, confidence
567
+ ):
568
+ # Alpaca形式で保存
569
+ training_example = {
570
+ "instruction": f"You are an expert in {domain_id}. Provide accurate information based on verified knowledge.",
571
+ "input": prompt,
572
+ "output": response,
573
+ "metadata": {
574
+ "domain_id": domain_id,
575
+ "confidence": confidence,
576
+ "master_model_id": self.master_model.model_id,
577
+ "timestamp": datetime.utcnow().isoformat(),
578
+ "source": "master_output"
579
+ }
580
+ }
581
+
582
+ # JSONLファイルに追記
583
+ output_file = f"training_data/master_outputs/master_outputs_{domain_id}.jsonl"
584
+ with open(output_file, 'a', encoding='utf-8') as f:
585
+ f.write(json.dumps(training_example, ensure_ascii=False) + '\n')
586
+ ```
587
+
588
+ **なぜJSONL(改行区切りJSON)?**
589
+ - ストリーミング処理が可能(1行ずつ読める)
590
+ - ファイル破損時の影響が最小限
591
+ - HuggingFace datasetsと互換性
592
+
593
+ ##### エンジン管理メソッド
594
+
595
+ ```python
596
+ def promote_apprentice(self, apprentice_model_id):
597
+ """弟子を師匠に昇格"""
598
+ # 現在の師匠を引退
599
+ old_master = self.master_model
600
+
601
+ # 弟子を師匠に昇格
602
+ self.master_model = self.apprentice_model
603
+
604
+ # 弟子をクリア
605
+ self.apprentice_model = None
606
+
607
+ # 設定を保存
608
+ self.config_manager.save_active_engines(
609
+ self.master_model.model_id, None
610
+ )
611
+
612
+ def swap_engines(self):
613
+ """師匠と弟子を入れ替え"""
614
+ temp = self.master_model
615
+ self.master_model = self.apprentice_model
616
+ self.apprentice_model = temp
617
+
618
+ self.config_manager.save_active_engines(
619
+ self.master_model.model_id,
620
+ self.apprentice_model.model_id if self.apprentice_model else None
621
+ )
622
+
623
+ def create_new_apprentice(self, base_model_id):
624
+ """新しい空の弟子を生成"""
625
+ # ベースモデルをコピーして新しいIDを付与
626
+ new_apprentice_id = f"{base_model_id}_apprentice_{timestamp}"
627
+
628
+ # 設定に追加
629
+ self.apprentice_model = self.config_manager.get_model_config(base_model_id)
630
+ self.apprentice_model.model_id = new_apprentice_id
631
+
632
+ return new_apprentice_id
633
+ ```
634
+
635
+ ### DendriticMemorySpace (`null_ai/iath_memory.py`)
636
+
637
+ #### 役割
638
+ .iathファイルの読み込みと6次元空間検索を提供。
639
+
640
+ #### クラス構造
641
+
642
+ ```python
643
+ class IathDecoder:
644
+ """
645
+ .iathファイルの低レベルデコーダー
646
+ dendritic-memory-editor完全互換
647
+ """
648
+ def __init__(self, iath_file_path):
649
+ self.file_path = Path(iath_file_path)
650
+ self.header = None
651
+ self.index = []
652
+ self._load_header_and_index()
653
+
654
+ def _load_header_and_index(self):
655
+ """ヘッダーとインデックスの読み込み"""
656
+ with open(self.file_path, 'rb') as f:
657
+ # Header (64 bytes)
658
+ header_bytes = f.read(64)
659
+ self.header = self._parse_header(header_bytes)
660
+
661
+ # Index (JSON)
662
+ index_size = self.header["index_size"]
663
+ index_bytes = f.read(index_size)
664
+ self.index = json.loads(index_bytes.decode('utf-8'))
665
+
666
+ def get_tile_by_id(self, knowledge_id):
667
+ """IDでタイルを取得"""
668
+ # インデックスからオフセットを検索
669
+ tile_info = next(
670
+ (t for t in self.index["tiles"] if t["id"] == knowledge_id),
671
+ None
672
+ )
673
+ if not tile_info:
674
+ return None
675
+
676
+ # ファイルポジション移動
677
+ with open(self.file_path, 'rb') as f:
678
+ f.seek(tile_info["offset"])
679
+ compressed_data = f.read(tile_info["size"])
680
+
681
+ # zstd解凍
682
+ decompressed = zstandard.decompress(compressed_data)
683
+ tile_data = json.loads(decompressed.decode('utf-8'))
684
+
685
+ return tile_data
686
+
687
+
688
+ class DendriticMemorySpace:
689
+ """
690
+ 6次元空間記憶システム
691
+ 高レベルAPI
692
+ """
693
+ def __init__(self, iath_file_path):
694
+ self.decoder = IathDecoder(iath_file_path)
695
+ self.all_tiles = []
696
+ self.coordinates_matrix = None # NumPy行列
697
+ self._load_all_tiles()
698
+
699
+ def _load_all_tiles(self):
700
+ """全タイルをメモリにロード"""
701
+ self.all_tiles = self.decoder.get_all_tiles()
702
+
703
+ # 座標行列作成(高速検索用)
704
+ coords_list = [tile["coordinates"] for tile in self.all_tiles]
705
+ self.coordinates_matrix = np.array(coords_list) # Shape: (N, 6)
706
+ ```
707
+
708
+ #### 検索アルゴリズム詳細
709
+
710
+ ##### 座標検索(6次元ユークリッド距離)
711
+
712
+ ```python
713
+ def search_by_coordinates(self, query_coords, top_k=5):
714
+ """
715
+ 6次元空間での近傍検索
716
+
717
+ 数式: distance = sqrt(sum((q_i - t_i)^2))
718
+ where:
719
+ q_i = query座標のi番目の要素
720
+ t_i = tile座標のi番目の要素
721
+ i = 0..5 (6次元)
722
+ """
723
+ query_vector = np.array(query_coords) # Shape: (6,)
724
+
725
+ # 全タイルとの距離を一括計算(NumPy vectorization)
726
+ # Broadcasting: (N, 6) - (6,) → (N, 6)
727
+ distances = np.linalg.norm(
728
+ self.coordinates_matrix - query_vector,
729
+ axis=1 # 各行(タイル)ごとに距離計算
730
+ ) # Shape: (N,)
731
+
732
+ # 距離でソート
733
+ sorted_indices = np.argsort(distances)[:top_k]
734
+
735
+ # 結果を返す
736
+ results = []
737
+ for idx in sorted_indices:
738
+ tile = self.all_tiles[idx].copy()
739
+ tile["spatial_distance"] = float(distances[idx])
740
+ results.append(tile)
741
+
742
+ return results
743
+ ```
744
+
745
+ **計算量**: O(N) - 全タイル数Nに比例(線形探索)
746
+
747
+ **最適化案**(未実装):
748
+ - KD-Tree: O(log N) だが6次元では効果薄い
749
+ - Ball-Tree: 高次元でも比較的有効
750
+ - 近似近傍探索(Annoy, HNSW): 超高速だが精度低下
751
+
752
+ ##### ハイブリッド検索(テキスト + 座標)
753
+
754
+ ```python
755
+ def hybrid_search(
756
+ self,
757
+ query_text,
758
+ query_coords=None,
759
+ top_k=5,
760
+ text_weight=0.4,
761
+ spatial_weight=0.6
762
+ ):
763
+ """
764
+ テキストマッチと空間距離の複合スコアリング
765
+ """
766
+ # Step 1: テキストマッチスコア計算
767
+ text_scores = []
768
+ for tile in self.all_tiles:
769
+ score = self._calculate_text_match(query_text, tile)
770
+ text_scores.append(score)
771
+ text_scores = np.array(text_scores) # Shape: (N,)
772
+
773
+ # Step 2: 空間距離スコア計算
774
+ if query_coords:
775
+ spatial_distances = np.linalg.norm(
776
+ self.coordinates_matrix - np.array(query_coords),
777
+ axis=1
778
+ )
779
+ # 距離を0-1のスコアに変換(逆数)
780
+ max_dist = spatial_distances.max()
781
+ spatial_scores = 1.0 - (spatial_distances / max_dist)
782
+ else:
783
+ spatial_scores = np.zeros(len(self.all_tiles))
784
+
785
+ # Step 3: 複合スコア計算
786
+ combined_scores = (
787
+ text_weight * text_scores +
788
+ spatial_weight * spatial_scores
789
+ )
790
+
791
+ # Step 4: スコアでソート
792
+ sorted_indices = np.argsort(combined_scores)[::-1][:top_k]
793
+
794
+ # 結果を返す
795
+ results = []
796
+ for idx in sorted_indices:
797
+ tile = self.all_tiles[idx].copy()
798
+ tile["text_match_score"] = float(text_scores[idx])
799
+ tile["spatial_score"] = float(spatial_scores[idx])
800
+ tile["combined_score"] = float(combined_scores[idx])
801
+ if query_coords:
802
+ tile["spatial_distance"] = float(spatial_distances[idx])
803
+ results.append(tile)
804
+
805
+ return results
806
+
807
+ def _calculate_text_match(self, query, tile):
808
+ """
809
+ テキストマッチスコア計算(簡易版)
810
+
811
+ 将来的にはBM25やTF-IDFを使う
812
+ """
813
+ query_lower = query.lower()
814
+ content = tile["content"]["final_response"].lower()
815
+ topic = tile["metadata"]["topic"].lower()
816
+
817
+ # キーワードマッチング
818
+ query_words = set(query_lower.split())
819
+ content_words = set(content.split())
820
+ topic_words = set(topic.split())
821
+
822
+ # Jaccard類似度
823
+ content_jaccard = len(query_words & content_words) / len(query_words | content_words)
824
+ topic_jaccard = len(query_words & topic_words) / len(query_words | topic_words)
825
+
826
+ # 複合スコア(トピックを重視)
827
+ score = 0.3 * content_jaccard + 0.7 * topic_jaccard
828
+
829
+ return score
830
+ ```
831
+
832
+ ### FineTuningManager (`null_ai/fine_tuning.py`)
833
+
834
+ #### 役割
835
+ 弟子モデルのファインチューニングを実行。
836
+
837
+ #### PEFT(QLoRA)方式の詳細
838
+
839
+ ```python
840
+ async def fine_tune_with_huggingface_peft(
841
+ self,
842
+ model_name,
843
+ training_examples,
844
+ output_dir,
845
+ epochs=3,
846
+ learning_rate=2e-4,
847
+ batch_size=4,
848
+ lora_r=8,
849
+ lora_alpha=16
850
+ ):
851
+ """
852
+ Parameter-Efficient Fine-Tuning with QLoRA
853
+
854
+ QLoRA = Quantized LoRA
855
+ - 4-bit量子化でメモリ削減
856
+ - LoRAで訓練パラメータ削減
857
+ → 12GB GPUでも7Bモデルを訓練可能
858
+ """
859
+
860
+ # Step 1: モデルを4-bit量子化でロード
861
+ bnb_config = BitsAndBytesConfig(
862
+ load_in_4bit=True, # 4-bit量子化
863
+ bnb_4bit_quant_type="nf4", # NormalFloat4(最適な量子化方式)
864
+ bnb_4bit_compute_dtype=torch.float16, # 計算はfp16で
865
+ bnb_4bit_use_double_quant=True # 二重量子化(さらにメモリ削減)
866
+ )
867
+
868
+ model = AutoModelForCausalLM.from_pretrained(
869
+ model_name,
870
+ quantization_config=bnb_config,
871
+ device_map="auto" # 自動的にGPU/CPUに配置
872
+ )
873
+
874
+ # Step 2: LoRA設定
875
+ lora_config = LoraConfig(
876
+ r=lora_r, # LoRAランク(低いほど軽量)
877
+ lora_alpha=lora_alpha, # スケーリング係数
878
+ target_modules=[ # どのレイヤーにLoRAを適用するか
879
+ "q_proj", "k_proj", "v_proj", "o_proj", # Attention
880
+ "gate_proj", "up_proj", "down_proj" # MLP
881
+ ],
882
+ lora_dropout=0.05, # Dropout率
883
+ bias="none", # Biasは訓練しない
884
+ task_type="CAUSAL_LM" # タスクタイプ
885
+ )
886
+
887
+ model = get_peft_model(model, lora_config)
888
+
889
+ # 訓練可能パラメータ数を表示
890
+ model.print_trainable_parameters()
891
+ # 例: trainable params: 4.2M || all params: 2.7B || trainable%: 0.16%
892
+ # → 全パラメータの0.16%だけ訓練!
893
+
894
+ # Step 3-9: データ準備、訓練、保存(省略)
895
+ ...
896
+ ```
897
+
898
+ **QLoRAの仕組み**:
899
+ ```
900
+ 通常のファインチューニング:
901
+ ┌─────────────────────────┐
902
+ │ モデル全体(2.7B params)│ ← 全て訓練
903
+ │ メモリ: ~40GB │
904
+ └─────────────────────────┘
905
+
906
+ QLoRA:
907
+ ┌─────────────────────────┐
908
+ │ 元モデル(2.7B params) │ ← 4-bit量子化、frozen(訓練しない)
909
+ │ メモリ: ~7GB │
910
+ └─────────────────────────┘
911
+ +
912
+ ┌─────────────────────────┐
913
+ │ LoRAアダプター(4.2M) │ ← これだけ訓練
914
+ │ メモリ: ~0.5GB │
915
+ └─────────────────────────┘
916
+ =
917
+ 合計メモリ: ~12GB
918
+ ```
919
+
920
+ #### Alpaca形式データの整形
921
+
922
+ ```python
923
+ def format_training_examples_for_model(
924
+ self,
925
+ training_examples,
926
+ template="alpaca"
927
+ ):
928
+ """
929
+ Alpaca形式 → モデル用プロンプトに整形
930
+ """
931
+ formatted_prompts = []
932
+
933
+ for example in training_examples:
934
+ instruction = example["instruction"]
935
+ input_text = example["input"]
936
+ output_text = example["output"]
937
+
938
+ if template == "alpaca":
939
+ if input_text:
940
+ prompt = f"""Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
941
+
942
+ ### Instruction:
943
+ {instruction}
944
+
945
+ ### Input:
946
+ {input_text}
947
+
948
+ ### Response:
949
+ {output_text}"""
950
+ else:
951
+ prompt = f"""Below is an instruction that describes a task. Write a response that appropriately completes the request.
952
+
953
+ ### Instruction:
954
+ {instruction}
955
+
956
+ ### Response:
957
+ {output_text}"""
958
+
959
+ formatted_prompts.append(prompt)
960
+
961
+ return formatted_prompts
962
+ ```
963
+
964
+ **なぜこの形式?**
965
+ - 明確な区切り(`###`)
966
+ - instruction-following能力の向上
967
+ - オープンソースコミュニティの標準
968
+
969
+ ---
970
+
971
+ ## データフロー完全図解
972
+
973
+ ### フロー1: 通常推論(RAGあり)
974
+
975
+ ```
976
+ ┌──────────────────────────────────────────────────────────────┐
977
+ │ ユーザー: "心臓の働きについて教えて" │
978
+ └────────────────────┬─────────────────────────────────────────┘
979
+
980
+ ┌────────────────────────────────────────────────────────────┐
981
+ │ Frontend: InferencePanel.tsx │
982
+ │ - 質問をバックエンドに送信 │
983
+ └────────────────────┬───────────────────────────────────────┘
984
+ ↓ HTTP POST /api/questions
985
+ ┌────────────────────────────────────────────────────────────┐
986
+ │ Backend: questions.py │
987
+ │ - InferenceService.ask_question() │
988
+ └────────────────────┬───────────────────────────────────────┘
989
+
990
+ ┌────────────────────────────────────────────────────────────┐
991
+ │ NullAI Core: model_router.py │
992
+ │ ┌─────────────────────────────────────────────────────┐ │
993
+ │ │ Step 1: _check_db_knowledge("medical", "心臓の働き") │ │
994
+ │ │ → DendriticMemorySpace.search_by_text() │ │
995
+ │ │ → 結果: 3件見つかった │ │
996
+ │ └─────────────────────────────────────────────────────┘ │
997
+ │ ↓ │
998
+ │ ┌─────────────────────────────────────────────────────┐ │
999
+ │ │ Step 2: _retrieve_relevant_knowledge() │ │
1000
+ │ │ → hybrid_search("心臓の働き", top_k=3) │ │
1001
+ │ │ → 取得: │ │
1002
+ │ │ [1] 心臓の解剖学 (score: 0.92) │ │
1003
+ │ │ [2] 循環器系の機能 (score: 0.85) │ │
1004
+ │ │ [3] 心臓病の分類 (score: 0.73) │ │
1005
+ │ └─────────────────────────────────────────────────────┘ │
1006
+ │ ↓ │
1007
+ │ ┌─────────────────────────────────────────────────────┐ │
1008
+ │ │ Step 3: プロンプト拡張 │ │
1009
+ │ │ augmented_prompt = """ │ │
1010
+ │ │ Based on the following verified knowledge: │ │
1011
+ │ │ │ │
1012
+ │ │ [Knowledge 1 - expert verification, conf: 0.9] │ │
1013
+ │ │ Topic: 心臓の解剖学 │ │
1014
+ │ │ Content: 心臓は4つの部屋から構成され... │ │
1015
+ │ │ │ │
1016
+ │ │ [Knowledge 2 - ...] │ │
1017
+ │ │ │ │
1018
+ │ │ Now, please answer: │ │
1019
+ │ │ 心臓の働きについて教えて │ │
1020
+ │ │ """ │ │
1021
+ │ └─────────────────────────────────────────────────────┘ │
1022
+ │ ↓ │
1023
+ │ ┌─────────────────────────────────────────────────────┐ │
1024
+ │ │ Step 4: LLM推論 │ │
1025
+ │ │ → llm_providers.py │ │
1026
+ │ │ → OllamaProvider.infer() │ │
1027
+ │ │ → model: deepseek-r1:1.5b │ │
1028
+ │ └─────────────────────────────────────────────────────┘ │
1029
+ │ ↓ │
1030
+ │ ┌─────────────────────────────────────────────────────┐ │
1031
+ │ │ Step 5: レスポンス生成 │ │
1032
+ │ │ response = { │ │
1033
+ │ │ "response": "心臓は循環器系の中心器官で...", │ │
1034
+ │ │ "confidence": 0.88, │ │
1035
+ │ │ "thinking": "検証済み知識に基づいて回答", │ │
1036
+ │ │ "retrieved_knowledge": [...] │ │
1037
+ │ │ } │ │
1038
+ │ └─────────────────────────────────────────────────────┘ │
1039
+ │ ↓ │
1040
+ │ ┌─────────────────────────────────────────────────────┐ │
1041
+ │ │ Step 6: 師匠の出力? │ │
1042
+ │ │ is_master = True │ │
1043
+ │ │ confidence = 0.88 >= 0.8 ✓ │ │
1044
+ │ │ → _save_master_output_as_training_data() │ │
1045
+ │ │ → training_data/master_outputs/medical.jsonl │ │
1046
+ │ └─────────────────────────────────────────────────────┘ │
1047
+ └────────────────────┬───────────────────────────────────────┘
1048
+
1049
+ ┌────────────────────────────────────────────────────────────┐
1050
+ │ Frontend: レスポンス表示 │
1051
+ │ - ResponseDisplay.tsx │
1052
+ │ - 「心臓は循環器系の中心器官で...」 │
1053
+ │ - Retrieved Knowledge バッジ表示 │
1054
+ └────────────────────────────────────────────────────────────┘
1055
+ ```
1056
+
1057
+ ### フロー2: 通常推論(RAGなし、自己拡充)
1058
+
1059
+ ```
1060
+ ┌──────────────────────────────────────────────────────────────┐
1061
+ │ ユーザー: "量子コンピュータの原理は?" │
1062
+ └────────────────────┬─────────────────────────────────────────┘
1063
+
1064
+ (同上)
1065
+
1066
+ ┌────────────────────────────────────────────────────────────┐
1067
+ │ NullAI Core: model_router.py │
1068
+ │ ┌─────────────────────────────────────────────────────┐ │
1069
+ │ │ Step 1: _check_db_knowledge("general", "量子...") │ │
1070
+ │ │ → DendriticMemorySpace.search_by_text() │ │
1071
+ │ │ → 結果: 見つからなかった ❌ │ │
1072
+ │ └─────────────────────────────────────────────────────┘ │
1073
+ │ ↓ │
1074
+ │ ┌─────────────────────────────────────────────────────┐ │
1075
+ │ │ Step 2: AI内部知識で推論 │ │
1076
+ │ │ → LLM.generate("量子コンピュータの原理は?") │ │
1077
+ │ │ → model: deepseek-r1:1.5b │ │
1078
+ │ └─────────────────────────────────────────────────────┘ │
1079
+ │ ↓ │
1080
+ │ ┌─────────────────────────────────────────────────────┐ │
1081
+ │ │ Step 3: レスポンス生成 │ │
1082
+ │ │ response = { │ │
1083
+ │ │ "response": "量子コンピュータは...", │ │
1084
+ │ │ "confidence": 0.75 │ │
1085
+ │ │ } │ │
1086
+ │ └─────────────────────────────────────────────────────┘ │
1087
+ │ ↓ │
1088
+ │ ┌─────────────────────────────────────────────────────┐ │
1089
+ │ │ Step 4: 自己拡充(DBに保存) │ │
1090
+ │ │ confidence = 0.75 >= 0.7 ✓ │ │
1091
+ │ │ save_to_memory = True │ │
1092
+ │ │ → _save_inference_to_db() │ │
1093
+ │ │ → SQLite: knowledge_tiles テーブル │ │
1094
+ │ │ (将来的には.iathにも保存) │ │
1095
+ │ └─────────────────────────────────────────────────────┘ │
1096
+ │ ↓ │
1097
+ │ ┌─────────────────────────────────────────────────────┐ │
1098
+ │ │ Step 5: 師匠の出力として保存 │ │
1099
+ │ │ is_master = True │ │
1100
+ │ │ confidence = 0.75 < 0.8 ❌ │ │
1101
+ │ │ → 訓練データには保存しない │ │
1102
+ │ └─────────────────────────────────────────────────────┘ │
1103
+ └────────────────────┬───────────────────────────────────────┘
1104
+
1105
+ (レスポンス表示)
1106
+ ```
1107
+
1108
+ **重要な違い**:
1109
+ - RAGあり: `confidence >= 0.8`で訓練データ保存
1110
+ - RAGなし: `confidence >= 0.7`でDB保存、`>= 0.8`で訓練データ保存
1111
+
1112
+ ### フロー3: ファインチューニング実行
1113
+
1114
+ ```
1115
+ ┌──────────────────────────────────────────────────────────────┐
1116
+ │ ユーザー: Training Dashboard で "Start Fine-tuning" │
1117
+ │ - Apprentice Model: microsoft/phi-2 │
1118
+ │ - Domain: medical │
1119
+ │ - Method: peft │
1120
+ │ - Epochs: 3 │
1121
+ └────────────────────┬─────────────────────────────────────────┘
1122
+ ↓ HTTP POST /api/training/start
1123
+ ┌────────────────────────────────────────────────────────────┐
1124
+ │ Backend: training.py │
1125
+ │ ┌─────────────────────────────────────────────────────┐ │
1126
+ │ │ Step 1: 訓練データ存在チェック │ │
1127
+ │ │ → FineTuningManager.load_training_data("medical") │ │
1128
+ │ │ → training_data/master_outputs/medical.jsonl │ │
1129
+ │ │ → 結果: 150サンプル │ │
1130
+ │ └─────────────────────────────────────────────────────┘ │
1131
+ │ ↓ │
1132
+ │ ┌─────────────────────────────────────────────────────┐ │
1133
+ │ │ Step 2: バックグラウンドタスク開始 │ │
1134
+ │ │ background_tasks.add_task(run_training) │ │
1135
+ │ │ → すぐにレスポンス返却(非同期) │ │
1136
+ │ └─────────────────────────────────────────────────────┘ │
1137
+ └────────────────────┬───────────────────────────────────────┘
1138
+
1139
+ ┌────────────────────────────────────────────────────────────┐
1140
+ │ NullAI Core: fine_tuning.py (バックグラウンド) │
1141
+ │ ┌─────────────────────────────────────────────────────┐ │
1142
+ │ │ Step 1: モデルロード(4-bit量子化) │ │
1143
+ │ │ → AutoModelForCausalLM.from_pretrained( │ │
1144
+ │ │ "microsoft/phi-2", │ │
1145
+ │ │ quantization_config=bnb_config │ │
1146
+ │ │ ) │ │
1147
+ │ │ → メモリ使用: ~7GB │ │
1148
+ │ └─────────────────────────────────────────────��───────┘ │
1149
+ │ ↓ │
1150
+ │ ┌─────────────────────────────────────────────────────┐ │
1151
+ │ │ Step 2: LoRA設定 │ │
1152
+ │ │ → get_peft_model(model, lora_config) │ │
1153
+ │ │ → 訓練可能パラメータ: 4.2M / 2.7B (0.16%) │ │
1154
+ │ └─────────────────────────────────────────────────────┘ │
1155
+ │ ↓ │
1156
+ │ ┌─────────────────────────────────────────────────────┐ │
1157
+ │ │ Step 3: データ準備 │ │
1158
+ │ │ → format_training_examples_for_model() │ │
1159
+ │ │ → Alpaca形式 → モデル用プロンプトに整形 │ │
1160
+ │ │ → Dataset.from_dict({"text": prompts}) │ │
1161
+ │ └─────────────────────────────────────────────────────┘ │
1162
+ │ ↓ │
1163
+ │ ┌─────────────────────────────────────────────────────┐ │
1164
+ │ │ Step 4: トレーニング開始 │ │
1165
+ │ │ Epoch 1/3: │ │
1166
+ │ │ [===> ] 35% loss: 1.245 │ │
1167
+ │ │ → current_training_state.update({ │ │
1168
+ │ │ "progress": 35, │ │
1169
+ │ │ "current_epoch": 1, │ │
1170
+ │ │ "loss": 1.245 │ │
1171
+ │ │ }) │ │
1172
+ │ └─────────────────────────────────────────────────────┘ │
1173
+ │ ↓ │
1174
+ │ ┌─────────────────────────────────────────────────────┐ │
1175
+ │ │ Step 5: 完了 │ │
1176
+ │ │ → trainer.save_model(output_dir) │ │
1177
+ │ │ → training_data/checkpoints/apprentice_medical_*/ │ │
1178
+ │ │ → current_training_state["is_training"] = False │ │
1179
+ │ └─────────────────────────────────────────────────────┘ │
1180
+ └────────────────────┬───────────────────────────────────────┘
1181
+
1182
+ ┌────────────────────────────────────────────────────────────┐
1183
+ │ Frontend: TrainingDashboard.tsx │
1184
+ │ - 2秒ごとにポーリング: GET /api/training/status │
1185
+ │ - プログレスバー更新: 35% → 67% → 100% │
1186
+ │ - 完了時: チェックポイント一覧を再取得 │
1187
+ └────────────────────────────────────────────────────────────┘
1188
+ ```
1189
+
1190
+ ---
1191
+
1192
+ ## 技術スタック詳細
1193
+
1194
+ ### フロントエンド
1195
+
1196
+ ```
1197
+ React 18.2 + TypeScript 5.0
1198
+ ├─ Vite 4.4 (ビルドツール)
1199
+ ├─ TailwindCSS 3.3 (スタイリング)
1200
+ └─ axios (HTTP クライアント)
1201
+
1202
+ 主要コンポーネント:
1203
+ - EngineManager.tsx (417行) - エンジン管理UI
1204
+ - InferencePanel.tsx (321行) - 推論パネル
1205
+ - TrainingDashboard.tsx (400行) - トレーニングダッシュボード
1206
+ - KnowledgePanel.tsx (185行) - 知識ブラウザ
1207
+ ```
1208
+
1209
+ ### バックエンド
1210
+
1211
+ ```
1212
+ FastAPI 0.115.6 + Python 3.13+
1213
+ ├─ Uvicorn (ASGIサーバー)
1214
+ ├─ SQLAlchemy 2.0 (ORM)
1215
+ ├─ Pydantic 2.10 (バリデーション)
1216
+ └─ Alembic (マイグレーション)
1217
+
1218
+ 主要API:
1219
+ - /api/config/* - エンジン管理
1220
+ - /api/questions - 推論実行
1221
+ - /api/training/* - ファインチューニング
1222
+ - /api/knowledge/* - 知識タイル管理
1223
+ ```
1224
+
1225
+ ### NullAI Core
1226
+
1227
+ ```
1228
+ Python 3.13+
1229
+ ├─ transformers 4.36+ (HuggingFace)
1230
+ ├─ torch 2.0+ (PyTorch)
1231
+ ├─ peft 0.7+ (LoRA/QLoRA)
1232
+ ├─ trl 0.7+ (Reinforcement Learning from Human Feedback)
1233
+ ├─ datasets 2.15+ (データセット処理)
1234
+ ├─ bitsandbytes 0.41+ (量子化)
1235
+ ├─ accelerate 0.25+ (分散訓練)
1236
+ ├─ zstandard 0.22+ (.iath圧縮)
1237
+ └─ numpy 1.24+ (数値計算)
1238
+
1239
+ 主要モジュール:
1240
+ - model_router.py (800行) - RAG統合、エンジン管理
1241
+ - iath_memory.py (362行) - 6次元空間記憶
1242
+ - fine_tuning.py (640行) - ファインチューニング
1243
+ - llm_providers.py (390行) - LLMプロバイダー統合
1244
+ ```
1245
+
1246
+ ### LLMプロバイダー
1247
+
1248
+ ```
1249
+ 1. Ollama
1250
+ - ローカルモデル管理
1251
+ - ollama pull deepseek-r1:1.5b
1252
+ - API: http://localhost:11434
1253
+
1254
+ 2. HuggingFace Transformers
1255
+ - 直接ロード
1256
+ - AutoModelForCausalLM.from_pretrained()
1257
+ - GPU/CPU自動配置
1258
+
1259
+ 3. MLX (Apple Silicon)
1260
+ - M1/M2/M3 Mac専用
1261
+ - 統合メモリ活用
1262
+ - mlx-lm ライブラリ
1263
+
1264
+ 4. GGUF (llama-cpp-python)
1265
+ - 量子化モデル(.gguf)
1266
+ - CPU推論に最適
1267
+ - GPU acceleration対応
1268
+ ```
1269
+
1270
+ ---
1271
+
1272
+ ## よくある誤解と注意点
1273
+
1274
+ ### 誤解1: 「RAGは常に使われる」
1275
+
1276
+ ❌ **誤解**: 全ての推論でRAGが使われる
1277
+ ✅ **真実**: DBに知識がある場合のみRAGが発動
1278
+
1279
+ ```python
1280
+ # 実際の動作
1281
+ if has_knowledge:
1282
+ # RAG推論
1283
+ else:
1284
+ # 通常推論(RAGなし)
1285
+ ```
1286
+
1287
+ **見分け方**:
1288
+ - RAGあり: レスポンスに`retrieved_knowledge`フィールドが含まれる
1289
+ - RAGなし: `retrieved_knowledge`が空
1290
+
1291
+ ### 誤解2: 「弟子は自動的に師匠になる」
1292
+
1293
+ ❌ **誤解**: ファインチューニングが完了したら自動で師匠に昇格
1294
+ ✅ **真実**: 手動で昇格操作が必要
1295
+
1296
+ ```
1297
+ ファインチューニング完了
1298
+
1299
+ チェックポイント保存
1300
+
1301
+ 【手動操作】Engine Manager で Promote をクリック
1302
+
1303
+ 弟子が師匠に昇格
1304
+ ```
1305
+
1306
+ **理由**: 品質チェックを人間が行うべき
1307
+
1308
+ ### 誤解3: 「.iathファイルは自動更新される」
1309
+
1310
+ ❌ **誤解**: AI生成知識が自動的に.iathに保存される
1311
+ ✅ **真実**: 現在はJSONLのみ、.iath保存は未実装(Priority 2)
1312
+
1313
+ ```
1314
+ 現状:
1315
+ AI生成知識 → SQLite + JSONL ✅
1316
+ → .iath ❌(未実装)
1317
+
1318
+ Priority 2実装後:
1319
+ AI生成知識 → SQLite + JSONL + .iath ✅
1320
+ ```
1321
+
1322
+ ### 誤解4: 「ファインチューニングは全パラメータを訓練する」
1323
+
1324
+ ❌ **誤解**: モデル全体(2.7B パラメータ)を訓練
1325
+ ✅ **真実**: LoRAアダプター(4.2M)だけ訓練
1326
+
1327
+ ```
1328
+ 訓練されるパラメータ:
1329
+ - 元モデル: 2,700,000,000 → frozen(訓練しない)
1330
+ - LoRA: 4,200,000 → 訓練する ✅
1331
+
1332
+ 訓練パラメータ比率: 0.16%
1333
+ ```
1334
+
1335
+ **メリット**:
1336
+ - メモリ削減(40GB → 12GB)
1337
+ - 訓練時間短縮(10時間 → 2時間)
1338
+ - 元モデルは変更されない(安全)
1339
+
1340
+ ### 誤解5: 「SQLiteと.iathは同じデータを保存」
1341
+
1342
+ ❌ **誤解**: SQLiteと.iathは重複している
1343
+ ✅ **真実**: 役割が完全に異なる
1344
+
1345
+ | データベース | 保存内容 | 用途 |
1346
+ |-------------|---------|------|
1347
+ | SQLite | ユーザー、ワークスペース、推論履歴、メタデータ | アプリケーション管理 |
1348
+ | .iath | Knowledge Tile(6次元座標 + コンテンツ) | 知識検索・RAG推論 |
1349
+
1350
+ **例**:
1351
+ ```
1352
+ SQLite:
1353
+ - users テーブル: nullai_default_user
1354
+ - workspaces テーブル: default_workspace
1355
+ - inference_history: 過去の質問と回答
1356
+
1357
+ .iath:
1358
+ - Tile 1: [0.2, 0.8, 0.3, 0.9, 0.7, 0.8] "心臓の働き..."
1359
+ - Tile 2: [0.3, 0.8, 0.4, 0.85, 0.65, 0.75] "循環器系..."
1360
+ ```
1361
+
1362
+ ### 誤解6: 「confidence値はAIが自動計算」
1363
+
1364
+ ❌ **誤解**: AIが自己評価してconfidenceを返す
1365
+ ✅ **真実**: 現在は**固定値**(プロバイダーごと)
1366
+
1367
+ ```python
1368
+ # llm_providers.py
1369
+ class OllamaProvider:
1370
+ async def infer(...):
1371
+ return {
1372
+ "response": response_text,
1373
+ "confidence": 0.85 # ← 固定値!
1374
+ }
1375
+ ```
1376
+
1377
+ **将来の改善**:
1378
+ - 複数モデルでクロスチェック
1379
+ - 応答の不確実性を計算(エントロピー)
1380
+ - 人間によるフィードバック学習
1381
+
1382
+ ---
1383
+
1384
+ ## 設計判断の理由
1385
+
1386
+ ### 判断1: なぜPEFTを採用したか
1387
+
1388
+ **候補**:
1389
+ 1. フルファインチューニング
1390
+ 2. PEFT (LoRA/QLoRA)
1391
+ 3. Adapter
1392
+ 4. Prompt Tuning
1393
+
1394
+ **採用**: PEFT (QLoRA)
1395
+
1396
+ **理由**:
1397
+ ```
1398
+ 比較表:
1399
+
1400
+ メモリ 速度 品質 汎用性
1401
+ フルFT × × ⭐⭐⭐ ⭐⭐⭐
1402
+ PEFT (QLoRA) ⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐⭐
1403
+ Adapter ⭐⭐ ⭐⭐ ⭐⭐ ⭐⭐
1404
+ Prompt Tuning ⭐⭐⭐ ⭐⭐⭐ ⭐ ⭐
1405
+ ```
1406
+
1407
+ **結論**: PEFTがバランス最良
1408
+
1409
+ ### 判断2: なぜAlpaca形式を採用したか
1410
+
1411
+ **候補**:
1412
+ 1. Alpaca
1413
+ 2. ShareGPT
1414
+ 3. OpenAssistant
1415
+ 4. Custom
1416
+
1417
+ **採用**: Alpaca
1418
+
1419
+ **理由**:
1420
+ - オープンソースで広く採用
1421
+ - instruction-input-output構造が明確
1422
+ - HuggingFace datasetsと互換性
1423
+ - コミュニティのベ���トプラクティス
1424
+
1425
+ ### 判断3: なぜハイブリッド検索か
1426
+
1427
+ **候補**:
1428
+ 1. テキストのみ
1429
+ 2. 座標のみ
1430
+ 3. ハイブリッド
1431
+
1432
+ **採用**: ハイブリッド
1433
+
1434
+ **理由**:
1435
+ ```
1436
+ テキストのみ:
1437
+ - 利点: シンプル
1438
+ - 欠点: 同義語を見逃す
1439
+
1440
+ 座標のみ:
1441
+ - 利点: 意味的に関連する知識を発見
1442
+ - 欠点: 座標が不正確だと失敗
1443
+
1444
+ ハイブリッド:
1445
+ - 利点: 両方の長所を活かせる
1446
+ - 欠点: パラメータ調整が必要(text_weight, spatial_weight)
1447
+ ```
1448
+
1449
+ **現在の設定**:
1450
+ ```python
1451
+ text_weight = 0.4
1452
+ spatial_weight = 0.6
1453
+ # → 座標をやや重視(意味的関連性を優先)
1454
+ ```
1455
+
1456
+ ### 判断4: なぜ循環インポートをlazy importで解決したか
1457
+
1458
+ **候補**:
1459
+ 1. Lazy import(関数内でimport)
1460
+ 2. アーキテクチャ変更(依存関係の整理)
1461
+ 3. 中間モジュール導入
1462
+
1463
+ **採用**: Lazy import
1464
+
1465
+ **理由**:
1466
+ - 最小限の変更で解決
1467
+ - パフォーマンスへの影響は軽微
1468
+ - 既存コードの大幅な書き換え不要
1469
+
1470
+ **実装例**:
1471
+ ```python
1472
+ def _check_db_knowledge(self, domain_id, prompt):
1473
+ # 関数内でimport → 循環回避
1474
+ from backend.app.database.session import SessionLocal
1475
+ db = SessionLocal()
1476
+ # ...
1477
+ ```
1478
+
1479
+ ---
1480
+
1481
+ ## 拡張時の考慮事項
1482
+
1483
+ ### 新しいLLMプロバイダーを追加する場合
1484
+
1485
+ **手順**:
1486
+
1487
+ 1. `llm_providers.py`に新しいクラスを追加
1488
+ ```python
1489
+ class NewProvider:
1490
+ async def infer(self, model_config, prompt, temperature):
1491
+ # 実装
1492
+ pass
1493
+
1494
+ async def infer_streaming(self, model_config, prompt, temperature):
1495
+ # 実装
1496
+ pass
1497
+ ```
1498
+
1499
+ 2. `model_router.py`の`_perform_llm_inference()`に追加
1500
+ ```python
1501
+ if provider == "ollama":
1502
+ result = await self.ollama_provider.infer(...)
1503
+ elif provider == "new_provider": # ← 追加
1504
+ result = await self.new_provider.infer(...)
1505
+ ```
1506
+
1507
+ 3. `backend/app/config.py`の`ModelProvider`列挙型に追加
1508
+ ```python
1509
+ class ModelProvider(str, Enum):
1510
+ OLLAMA = "ollama"
1511
+ HUGGINGFACE = "huggingface"
1512
+ NEW_PROVIDER = "new_provider" # ← 追加
1513
+ ```
1514
+
1515
+ ### 新しいドメインを追加する場合
1516
+
1517
+ **手順**:
1518
+
1519
+ 1. `.iath`ファイルでドメイン用の座標空間を定義
1520
+ ```
1521
+ 医療ドメイン: medical_space [x, y, z]
1522
+ 法律ドメイン: legal_space [x, y, z] ← 追加
1523
+ - x: 法分野(民法、刑法、商法...)
1524
+ - y: 判例レベル(地裁、高裁、最高裁)
1525
+ - z: 時代(古典、現代、最新)
1526
+ ```
1527
+
1528
+ 2. `backend/app/config.py`にドメイン設定追加
1529
+ ```python
1530
+ domains = [
1531
+ {"domain_id": "medical", "name": "医療"},
1532
+ {"domain_id": "legal", "name": "法律"} # ← 追加
1533
+ ]
1534
+ ```
1535
+
1536
+ 3. 訓練データディレクトリ作成
1537
+ ```bash
1538
+ mkdir -p training_data/master_outputs/
1539
+ touch training_data/master_outputs/master_outputs_legal.jsonl
1540
+ ```
1541
+
1542
+ ### 座標自動推定を実装する場合(Priority 2)
1543
+
1544
+ **設計案**:
1545
+
1546
+ ```python
1547
+ # null_ai/coordinate_estimator.py
1548
+
1549
+ class CoordinateEstimator:
1550
+ def __init__(self, llm_model):
1551
+ """
1552
+ DeepSeek R1を使って座標を推定
1553
+ """
1554
+ self.llm = llm_model
1555
+
1556
+ async def estimate_coordinates(
1557
+ self,
1558
+ prompt: str,
1559
+ response: str,
1560
+ domain_id: str
1561
+ ) -> List[float]:
1562
+ """
1563
+ 6次元座標を推定
1564
+
1565
+ Returns: [x, y, z, c, g, v]
1566
+ """
1567
+ # プロンプト構築
1568
+ estimation_prompt = f"""You are an expert in knowledge space mapping.
1569
+ Given a question and answer pair in the domain of {domain_id}, estimate the
1570
+ 6-dimensional coordinates that best represent this knowledge.
1571
+
1572
+ Coordinates format: [x, y, z, c, g, v]
1573
+ - medical_space [x, y, z]: domain-specific 3D space (0.0-1.0)
1574
+ - meta_space [c, g, v]: Certainty, Granularity, Verification (0.0-1.0)
1575
+
1576
+ Question: {prompt}
1577
+ Answer: {response}
1578
+
1579
+ Output ONLY the coordinates as a JSON array: [x, y, z, c, g, v]
1580
+ """
1581
+
1582
+ # LLMに座標推定を依頼
1583
+ result = await self.llm.generate(estimation_prompt)
1584
+
1585
+ # JSONパース
1586
+ coords = json.loads(result)
1587
+
1588
+ # バリデーション
1589
+ assert len(coords) == 6
1590
+ assert all(0.0 <= c <= 1.0 for c in coords)
1591
+
1592
+ return coords
1593
+ ```
1594
+
1595
+ ### WebSocketでリアルタイム進捗を実装する場合
1596
+
1597
+ **設計案**:
1598
+
1599
+ ```python
1600
+ # backend/app/main.py
1601
+
1602
+ @app.websocket("/ws/training/{session_id}")
1603
+ async def training_websocket(websocket: WebSocket, session_id: str):
1604
+ await websocket.accept()
1605
+
1606
+ # 進捗コールバック
1607
+ async def progress_callback(state):
1608
+ await websocket.send_json({
1609
+ "type": "progress",
1610
+ "data": state
1611
+ })
1612
+
1613
+ # ファインチューニング開始
1614
+ await fine_tuning_manager.start_training(
1615
+ ...,
1616
+ progress_callback=progress_callback
1617
+ )
1618
+ ```
1619
+
1620
+ ---
1621
+
1622
+ ## まとめ: プロジェクトの本質
1623
+
1624
+ NullAIは単なるRAGシステムでも、単なるファインチューニングツールでもありません。
1625
+
1626
+ **NullAIの本質**:
1627
+ ```
1628
+ 自己進化する知識生態系
1629
+
1630
+ 師匠AI → 知識生成 → 弟子AI学習 → 昇格 → 新しい弟子 → サイクル継続
1631
+ ↓ ↑
1632
+ DB拡充(自己拡充) ファインチューニング
1633
+ ↓ ↑
1634
+ 樹木型空間記憶(6次元座標) 高品質訓練データ
1635
+ ↓ ↑
1636
+ 意味的知識整理 師匠の知識継承
1637
+ ↓ ↑
1638
+ └────────────── サイクル ──────────────┘
1639
+ ```
1640
+
1641
+ **4つの核心思想の統合**:
1642
+ 1. **倒木システム**: 世代交代による進化
1643
+ 2. **DB分離構造**: 信頼性の確保と自己拡充
1644
+ 3. **樹木型空間記憶**: 意味的知識整理
1645
+ 4. **ローカルファースト**: プライバシーとコスト
1646
+
1647
+ これら全てが**有機的に結合**し、AIが自己進化する生態系を形成しています。
1648
+
1649
+ ---
1650
+
1651
+ **このガイドを理解したら、あなたはNullAIの設計思想を正しく継承できます。**
1652
+
1653
+ **頑張ってください!🌲🔥**
1654
+
1655
+ ---
1656
+
1657
+ **Document Version**: 1.0
1658
+ **Total Pages**: 60+
1659
+ **Total Words**: 15,000+
1660
+ **Author**: Claude (Sonnet 4.5)
1661
+ **Purpose**: Complete handover of NullAI project architecture and philosophy