Upload PROJECT_ARCHITECTURE_GUIDE.md with huggingface_hub
Browse files- 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
|