ltcai 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ltcai-0.1.0/LICENSE +21 -0
- ltcai-0.1.0/PKG-INFO +235 -0
- ltcai-0.1.0/README.md +199 -0
- ltcai-0.1.0/codex_telegram_bot.py +191 -0
- ltcai-0.1.0/llm_router.py +537 -0
- ltcai-0.1.0/ltcai.egg-info/PKG-INFO +235 -0
- ltcai-0.1.0/ltcai.egg-info/SOURCES.txt +20 -0
- ltcai-0.1.0/ltcai.egg-info/dependency_links.txt +1 -0
- ltcai-0.1.0/ltcai.egg-info/entry_points.txt +2 -0
- ltcai-0.1.0/ltcai.egg-info/requires.txt +27 -0
- ltcai-0.1.0/ltcai.egg-info/top_level.txt +7 -0
- ltcai-0.1.0/ltcai_cli.py +74 -0
- ltcai-0.1.0/p_reinforce.py +148 -0
- ltcai-0.1.0/pyproject.toml +51 -0
- ltcai-0.1.0/server.py +3215 -0
- ltcai-0.1.0/setup.cfg +4 -0
- ltcai-0.1.0/setup.py +399 -0
- ltcai-0.1.0/static/admin.html +1013 -0
- ltcai-0.1.0/static/index.html +270 -0
- ltcai-0.1.0/static/indexd.html +5664 -0
- ltcai-0.1.0/telegram_bot.py +430 -0
- ltcai-0.1.0/tools.py +1136 -0
ltcai-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TaeSoo Park
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ltcai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ltcai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lattice AI local MLX/cloud LLM workspace server
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/TaeSooPark-PTS/LatticeAI
|
|
7
|
+
Project-URL: Repository, https://github.com/TaeSooPark-PTS/LatticeAI
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: fastapi
|
|
12
|
+
Requires-Dist: uvicorn
|
|
13
|
+
Requires-Dist: pydantic
|
|
14
|
+
Requires-Dist: httpx
|
|
15
|
+
Requires-Dist: pillow
|
|
16
|
+
Requires-Dist: openai
|
|
17
|
+
Requires-Dist: python-docx
|
|
18
|
+
Requires-Dist: openpyxl
|
|
19
|
+
Requires-Dist: python-pptx
|
|
20
|
+
Requires-Dist: python-multipart
|
|
21
|
+
Requires-Dist: keyring
|
|
22
|
+
Provides-Extra: local
|
|
23
|
+
Requires-Dist: mlx-lm; extra == "local"
|
|
24
|
+
Requires-Dist: mlx-vlm; extra == "local"
|
|
25
|
+
Provides-Extra: voice
|
|
26
|
+
Requires-Dist: openai-whisper; extra == "voice"
|
|
27
|
+
Requires-Dist: SpeechRecognition; extra == "voice"
|
|
28
|
+
Requires-Dist: pydub; extra == "voice"
|
|
29
|
+
Provides-Extra: all
|
|
30
|
+
Requires-Dist: mlx-lm; extra == "all"
|
|
31
|
+
Requires-Dist: mlx-vlm; extra == "all"
|
|
32
|
+
Requires-Dist: openai-whisper; extra == "all"
|
|
33
|
+
Requires-Dist: SpeechRecognition; extra == "all"
|
|
34
|
+
Requires-Dist: pydub; extra == "all"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# Lattice AI
|
|
38
|
+
|
|
39
|
+
Local/cloud LLM workspace server with MLX, Ollama, vLLM, OpenAI-compatible providers,
|
|
40
|
+
BYOK API keys, MCP recommendations, and editor extensions for VS Code, Cursor, and Antigravity.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 아키텍처
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Lattice AI/
|
|
48
|
+
├── server.py # FastAPI bridge server (port 4825)
|
|
49
|
+
├── llm_router.py # local/cloud model router
|
|
50
|
+
├── tools.py # local workspace tools
|
|
51
|
+
├── static/ # web UI
|
|
52
|
+
├── bin/ltcai.js # npm CLI entrypoint
|
|
53
|
+
├── pyproject.toml # PyPI metadata
|
|
54
|
+
└── vscode-extension/ # VS Code/Cursor/Antigravity extension
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 빠른 시작
|
|
60
|
+
|
|
61
|
+
### 1. 서버 설치 & 실행
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# PyPI
|
|
65
|
+
pip install ltcai
|
|
66
|
+
|
|
67
|
+
# 로컬 MLX까지 함께 쓰려면
|
|
68
|
+
pip install "ltcai[local]"
|
|
69
|
+
|
|
70
|
+
# npm
|
|
71
|
+
npm install -g ltcai
|
|
72
|
+
|
|
73
|
+
# 서버 실행
|
|
74
|
+
LTCAI
|
|
75
|
+
# → http://localhost:4825 에서 실행됨
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
개발 중에는 설치 없이도 실행할 수 있습니다.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
python ltcai_cli.py
|
|
82
|
+
python ltcai_cli.py --reload
|
|
83
|
+
LTCAI doctor
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`npm install -g ltcai`로 설치한 경우 첫 실행 시 `~/.ltcai/npm-python`에 Python 가상환경을 만들고
|
|
87
|
+
`requirements.txt`를 설치합니다. 자동 설치를 끄려면 `LTCAI_SKIP_NPM_BOOTSTRAP=1`을 설정하세요.
|
|
88
|
+
|
|
89
|
+
Lattice AI stores runtime data in `~/.ltcai/` by default. Override it with
|
|
90
|
+
`LATTICEAI_DATA_DIR=/path/to/data` when running `LTCAI`.
|
|
91
|
+
|
|
92
|
+
### 2. 첫 모델 로드 (터미널 or 확장 프로그램에서)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 터미널에서 직접
|
|
96
|
+
curl -X POST http://localhost:4825/models/load \
|
|
97
|
+
-H "Content-Type: application/json" \
|
|
98
|
+
-d '{"model_id": "mlx-community/Qwen2.5-Coder-7B-Instruct-4bit"}'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
또는 확장 프로그램에서 `Cmd+Shift+M` → 모델 선택
|
|
102
|
+
|
|
103
|
+
### 3. 확장 프로그램 설치
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cd vscode-extension
|
|
107
|
+
npm install
|
|
108
|
+
npm run build
|
|
109
|
+
npm run package:vsix
|
|
110
|
+
|
|
111
|
+
# VS Code / Cursor / Antigravity에서:
|
|
112
|
+
# 1. Extensions 패널 → "..." → "Install from VSIX" 또는
|
|
113
|
+
# 2. 로컬 CLI가 있으면:
|
|
114
|
+
npm run install:all
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 모델/비용 구조
|
|
120
|
+
|
|
121
|
+
- Local LLM: MLX, Ollama, vLLM, LM Studio, llama.cpp
|
|
122
|
+
- Cloud LLM: OpenAI, OpenRouter, Groq, Together, xAI 등 OpenAI-compatible provider
|
|
123
|
+
- API 비용: 사용자가 본인 API key를 입력하는 BYOK 구조입니다. 사용자별 키로 호출되므로 키 소유자가 사용량을 부담합니다.
|
|
124
|
+
- 초대 링크 게이트는 기본 비활성화되어 있습니다. 다시 켜려면 `LATTICEAI_INVITE_GATE_ENABLED=true`를 설정하세요.
|
|
125
|
+
|
|
126
|
+
## 보안 기본값
|
|
127
|
+
|
|
128
|
+
- 기본 서버 바인딩은 `127.0.0.1:4825`입니다. 같은 네트워크에서 접속하게 하려면 명시적으로 `LATTICEAI_HOST=0.0.0.0`을 설정하세요.
|
|
129
|
+
- CORS는 기본적으로 localhost만 허용합니다. 네트워크 공개가 필요하면 `LATTICEAI_CORS_ALLOW_NETWORK=true`를 명시적으로 설정하세요.
|
|
130
|
+
- 사용자 API key는 OS keyring/Keychain에 저장합니다. keyring을 사용할 수 없는 환경에서 평문 저장을 허용하려면 `LATTICEAI_ALLOW_PLAINTEXT_API_KEYS=true`를 직접 설정해야 합니다.
|
|
131
|
+
- 히스토리 저장 전 API key/token/password 패턴은 마스킹됩니다.
|
|
132
|
+
|
|
133
|
+
## 지원 모델 예시 (M5 32GB 기준)
|
|
134
|
+
|
|
135
|
+
| 모델 | 용도 | 크기 | 추천도 |
|
|
136
|
+
|------|------|------|--------|
|
|
137
|
+
| `mlx-community/Qwen2.5-Coder-7B-Instruct-4bit` | 코딩 | ~4GB | ⭐⭐⭐ |
|
|
138
|
+
| `mlx-community/Qwen2.5-Coder-14B-Instruct-4bit` | 코딩 | ~8GB | ⭐⭐⭐⭐ |
|
|
139
|
+
| `mlx-community/Qwen2.5-Coder-32B-Instruct-4bit` | 코딩 | ~18GB | ⭐⭐⭐⭐⭐ |
|
|
140
|
+
| `mlx-community/Llama-3.1-8B-Instruct-4bit` | 범용 | ~4.5GB| ⭐⭐⭐ |
|
|
141
|
+
| `mlx-community/DeepSeek-R1-0528-4bit` | 추론 | ~38GB | ⭐⭐⭐⭐ |
|
|
142
|
+
| `mlx-community/Phi-4-4bit` | 코딩 | ~8GB | ⭐⭐⭐⭐ |
|
|
143
|
+
| `mlx-community/gemma-3-27b-it-4bit` | 범용 | ~15GB | ⭐⭐⭐ |
|
|
144
|
+
|
|
145
|
+
> **M5 32GB 추천**: Qwen2.5-Coder-32B-Instruct-4bit (18GB) — 32GB에서 여유롭게 동작
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 멀티모델 핫스왑
|
|
150
|
+
|
|
151
|
+
여러 모델을 동시에 메모리에 올려두고 즉시 전환 가능:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# 모델 A 로드
|
|
155
|
+
curl -X POST localhost:4825/models/load -d '{"model_id":"mlx-community/Qwen2.5-Coder-7B-Instruct-4bit"}'
|
|
156
|
+
|
|
157
|
+
# 모델 B도 함께 로드
|
|
158
|
+
curl -X POST localhost:4825/models/load -d '{"model_id":"mlx-community/Llama-3.1-8B-Instruct-4bit"}'
|
|
159
|
+
|
|
160
|
+
# B → A 즉시 전환 (재로드 없음)
|
|
161
|
+
curl -X POST localhost:4825/models/switch/mlx-community%2FQwen2.5-Coder-7B-Instruct-4bit
|
|
162
|
+
|
|
163
|
+
# 메모리 해제
|
|
164
|
+
curl -X DELETE localhost:4825/models/unload/mlx-community%2FLlama-3.1-8B-Instruct-4bit
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 키보드 단축키
|
|
170
|
+
|
|
171
|
+
| 단축키 | 기능 |
|
|
172
|
+
|--------|------|
|
|
173
|
+
| `Cmd+Shift+A` | 채팅 패널 열기 |
|
|
174
|
+
| `Cmd+Shift+E` | 선택 코드 편집 (선택 필요) |
|
|
175
|
+
| `Cmd+Shift+M` | 모델 로드 / 전환 |
|
|
176
|
+
| 우클릭 메뉴 | Explain / Edit / Garden에 저장 |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## P-Reinforce 지식 정원사
|
|
181
|
+
|
|
182
|
+
지식은 `~/.ltcai-ai-brain/`에 자동 분류 저장:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
~/.ltcai-ai-brain/
|
|
186
|
+
├── INDEX.md
|
|
187
|
+
├── 00_Raw/ # 원시 데이터, 아이디어
|
|
188
|
+
├── 10_Wiki/ # 검증된 개념, 레퍼런스
|
|
189
|
+
├── 20_Skills/ # 코드 스니펫, 프롬프트
|
|
190
|
+
├── 30_Projects/ # 프로젝트 컨텍스트
|
|
191
|
+
└── 40_Log/ # 날짜별 작업 로그
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
사용법: 에디터에서 텍스트 선택 → 우클릭 → **"Save to Knowledge Garden"**
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## API 엔드포인트
|
|
199
|
+
|
|
200
|
+
| Method | Path | 설명 |
|
|
201
|
+
|--------|------|------|
|
|
202
|
+
| GET | `/health` | 서버 상태, 현재 모델 |
|
|
203
|
+
| GET | `/models` | 추천 모델 목록 + 로드 상태 |
|
|
204
|
+
| POST | `/models/load` | 모델 로드 (캐시 지원) |
|
|
205
|
+
| POST | `/models/switch/{id}` | 활성 모델 전환 |
|
|
206
|
+
| DELETE | `/models/unload/{id}` | 모델 언로드 |
|
|
207
|
+
| POST | `/chat` | 생성 (stream=true/false) |
|
|
208
|
+
| POST | `/garden` | P-Reinforce 저장 |
|
|
209
|
+
| GET | `/garden/tree` | 지식 트리 조회 |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 자동 시작 설정 (선택)
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# launchd plist로 Mac 부팅시 자동 시작
|
|
217
|
+
cat > ~/Library/LaunchAgents/com.ltcai.mlx.plist << 'EOF'
|
|
218
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
219
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
220
|
+
<plist version="1.0">
|
|
221
|
+
<dict>
|
|
222
|
+
<key>Label</key><string>com.ltcai.mlx</string>
|
|
223
|
+
<key>ProgramArguments</key>
|
|
224
|
+
<array>
|
|
225
|
+
<string>/usr/bin/python3</string>
|
|
226
|
+
<string>/path/to/LTCAI-ai-mlx/server/server.py</string>
|
|
227
|
+
</array>
|
|
228
|
+
<key>RunAtLoad</key><true/>
|
|
229
|
+
<key>KeepAlive</key><true/>
|
|
230
|
+
</dict>
|
|
231
|
+
</plist>
|
|
232
|
+
EOF
|
|
233
|
+
|
|
234
|
+
launchctl load ~/Library/LaunchAgents/com.ltcai.mlx.plist
|
|
235
|
+
```
|
ltcai-0.1.0/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Lattice AI
|
|
2
|
+
|
|
3
|
+
Local/cloud LLM workspace server with MLX, Ollama, vLLM, OpenAI-compatible providers,
|
|
4
|
+
BYOK API keys, MCP recommendations, and editor extensions for VS Code, Cursor, and Antigravity.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 아키텍처
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
Lattice AI/
|
|
12
|
+
├── server.py # FastAPI bridge server (port 4825)
|
|
13
|
+
├── llm_router.py # local/cloud model router
|
|
14
|
+
├── tools.py # local workspace tools
|
|
15
|
+
├── static/ # web UI
|
|
16
|
+
├── bin/ltcai.js # npm CLI entrypoint
|
|
17
|
+
├── pyproject.toml # PyPI metadata
|
|
18
|
+
└── vscode-extension/ # VS Code/Cursor/Antigravity extension
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 빠른 시작
|
|
24
|
+
|
|
25
|
+
### 1. 서버 설치 & 실행
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# PyPI
|
|
29
|
+
pip install ltcai
|
|
30
|
+
|
|
31
|
+
# 로컬 MLX까지 함께 쓰려면
|
|
32
|
+
pip install "ltcai[local]"
|
|
33
|
+
|
|
34
|
+
# npm
|
|
35
|
+
npm install -g ltcai
|
|
36
|
+
|
|
37
|
+
# 서버 실행
|
|
38
|
+
LTCAI
|
|
39
|
+
# → http://localhost:4825 에서 실행됨
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
개발 중에는 설치 없이도 실행할 수 있습니다.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python ltcai_cli.py
|
|
46
|
+
python ltcai_cli.py --reload
|
|
47
|
+
LTCAI doctor
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`npm install -g ltcai`로 설치한 경우 첫 실행 시 `~/.ltcai/npm-python`에 Python 가상환경을 만들고
|
|
51
|
+
`requirements.txt`를 설치합니다. 자동 설치를 끄려면 `LTCAI_SKIP_NPM_BOOTSTRAP=1`을 설정하세요.
|
|
52
|
+
|
|
53
|
+
Lattice AI stores runtime data in `~/.ltcai/` by default. Override it with
|
|
54
|
+
`LATTICEAI_DATA_DIR=/path/to/data` when running `LTCAI`.
|
|
55
|
+
|
|
56
|
+
### 2. 첫 모델 로드 (터미널 or 확장 프로그램에서)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 터미널에서 직접
|
|
60
|
+
curl -X POST http://localhost:4825/models/load \
|
|
61
|
+
-H "Content-Type: application/json" \
|
|
62
|
+
-d '{"model_id": "mlx-community/Qwen2.5-Coder-7B-Instruct-4bit"}'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
또는 확장 프로그램에서 `Cmd+Shift+M` → 모델 선택
|
|
66
|
+
|
|
67
|
+
### 3. 확장 프로그램 설치
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cd vscode-extension
|
|
71
|
+
npm install
|
|
72
|
+
npm run build
|
|
73
|
+
npm run package:vsix
|
|
74
|
+
|
|
75
|
+
# VS Code / Cursor / Antigravity에서:
|
|
76
|
+
# 1. Extensions 패널 → "..." → "Install from VSIX" 또는
|
|
77
|
+
# 2. 로컬 CLI가 있으면:
|
|
78
|
+
npm run install:all
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 모델/비용 구조
|
|
84
|
+
|
|
85
|
+
- Local LLM: MLX, Ollama, vLLM, LM Studio, llama.cpp
|
|
86
|
+
- Cloud LLM: OpenAI, OpenRouter, Groq, Together, xAI 등 OpenAI-compatible provider
|
|
87
|
+
- API 비용: 사용자가 본인 API key를 입력하는 BYOK 구조입니다. 사용자별 키로 호출되므로 키 소유자가 사용량을 부담합니다.
|
|
88
|
+
- 초대 링크 게이트는 기본 비활성화되어 있습니다. 다시 켜려면 `LATTICEAI_INVITE_GATE_ENABLED=true`를 설정하세요.
|
|
89
|
+
|
|
90
|
+
## 보안 기본값
|
|
91
|
+
|
|
92
|
+
- 기본 서버 바인딩은 `127.0.0.1:4825`입니다. 같은 네트워크에서 접속하게 하려면 명시적으로 `LATTICEAI_HOST=0.0.0.0`을 설정하세요.
|
|
93
|
+
- CORS는 기본적으로 localhost만 허용합니다. 네트워크 공개가 필요하면 `LATTICEAI_CORS_ALLOW_NETWORK=true`를 명시적으로 설정하세요.
|
|
94
|
+
- 사용자 API key는 OS keyring/Keychain에 저장합니다. keyring을 사용할 수 없는 환경에서 평문 저장을 허용하려면 `LATTICEAI_ALLOW_PLAINTEXT_API_KEYS=true`를 직접 설정해야 합니다.
|
|
95
|
+
- 히스토리 저장 전 API key/token/password 패턴은 마스킹됩니다.
|
|
96
|
+
|
|
97
|
+
## 지원 모델 예시 (M5 32GB 기준)
|
|
98
|
+
|
|
99
|
+
| 모델 | 용도 | 크기 | 추천도 |
|
|
100
|
+
|------|------|------|--------|
|
|
101
|
+
| `mlx-community/Qwen2.5-Coder-7B-Instruct-4bit` | 코딩 | ~4GB | ⭐⭐⭐ |
|
|
102
|
+
| `mlx-community/Qwen2.5-Coder-14B-Instruct-4bit` | 코딩 | ~8GB | ⭐⭐⭐⭐ |
|
|
103
|
+
| `mlx-community/Qwen2.5-Coder-32B-Instruct-4bit` | 코딩 | ~18GB | ⭐⭐⭐⭐⭐ |
|
|
104
|
+
| `mlx-community/Llama-3.1-8B-Instruct-4bit` | 범용 | ~4.5GB| ⭐⭐⭐ |
|
|
105
|
+
| `mlx-community/DeepSeek-R1-0528-4bit` | 추론 | ~38GB | ⭐⭐⭐⭐ |
|
|
106
|
+
| `mlx-community/Phi-4-4bit` | 코딩 | ~8GB | ⭐⭐⭐⭐ |
|
|
107
|
+
| `mlx-community/gemma-3-27b-it-4bit` | 범용 | ~15GB | ⭐⭐⭐ |
|
|
108
|
+
|
|
109
|
+
> **M5 32GB 추천**: Qwen2.5-Coder-32B-Instruct-4bit (18GB) — 32GB에서 여유롭게 동작
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 멀티모델 핫스왑
|
|
114
|
+
|
|
115
|
+
여러 모델을 동시에 메모리에 올려두고 즉시 전환 가능:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# 모델 A 로드
|
|
119
|
+
curl -X POST localhost:4825/models/load -d '{"model_id":"mlx-community/Qwen2.5-Coder-7B-Instruct-4bit"}'
|
|
120
|
+
|
|
121
|
+
# 모델 B도 함께 로드
|
|
122
|
+
curl -X POST localhost:4825/models/load -d '{"model_id":"mlx-community/Llama-3.1-8B-Instruct-4bit"}'
|
|
123
|
+
|
|
124
|
+
# B → A 즉시 전환 (재로드 없음)
|
|
125
|
+
curl -X POST localhost:4825/models/switch/mlx-community%2FQwen2.5-Coder-7B-Instruct-4bit
|
|
126
|
+
|
|
127
|
+
# 메모리 해제
|
|
128
|
+
curl -X DELETE localhost:4825/models/unload/mlx-community%2FLlama-3.1-8B-Instruct-4bit
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 키보드 단축키
|
|
134
|
+
|
|
135
|
+
| 단축키 | 기능 |
|
|
136
|
+
|--------|------|
|
|
137
|
+
| `Cmd+Shift+A` | 채팅 패널 열기 |
|
|
138
|
+
| `Cmd+Shift+E` | 선택 코드 편집 (선택 필요) |
|
|
139
|
+
| `Cmd+Shift+M` | 모델 로드 / 전환 |
|
|
140
|
+
| 우클릭 메뉴 | Explain / Edit / Garden에 저장 |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## P-Reinforce 지식 정원사
|
|
145
|
+
|
|
146
|
+
지식은 `~/.ltcai-ai-brain/`에 자동 분류 저장:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
~/.ltcai-ai-brain/
|
|
150
|
+
├── INDEX.md
|
|
151
|
+
├── 00_Raw/ # 원시 데이터, 아이디어
|
|
152
|
+
├── 10_Wiki/ # 검증된 개념, 레퍼런스
|
|
153
|
+
├── 20_Skills/ # 코드 스니펫, 프롬프트
|
|
154
|
+
├── 30_Projects/ # 프로젝트 컨텍스트
|
|
155
|
+
└── 40_Log/ # 날짜별 작업 로그
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
사용법: 에디터에서 텍스트 선택 → 우클릭 → **"Save to Knowledge Garden"**
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## API 엔드포인트
|
|
163
|
+
|
|
164
|
+
| Method | Path | 설명 |
|
|
165
|
+
|--------|------|------|
|
|
166
|
+
| GET | `/health` | 서버 상태, 현재 모델 |
|
|
167
|
+
| GET | `/models` | 추천 모델 목록 + 로드 상태 |
|
|
168
|
+
| POST | `/models/load` | 모델 로드 (캐시 지원) |
|
|
169
|
+
| POST | `/models/switch/{id}` | 활성 모델 전환 |
|
|
170
|
+
| DELETE | `/models/unload/{id}` | 모델 언로드 |
|
|
171
|
+
| POST | `/chat` | 생성 (stream=true/false) |
|
|
172
|
+
| POST | `/garden` | P-Reinforce 저장 |
|
|
173
|
+
| GET | `/garden/tree` | 지식 트리 조회 |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 자동 시작 설정 (선택)
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# launchd plist로 Mac 부팅시 자동 시작
|
|
181
|
+
cat > ~/Library/LaunchAgents/com.ltcai.mlx.plist << 'EOF'
|
|
182
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
183
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
184
|
+
<plist version="1.0">
|
|
185
|
+
<dict>
|
|
186
|
+
<key>Label</key><string>com.ltcai.mlx</string>
|
|
187
|
+
<key>ProgramArguments</key>
|
|
188
|
+
<array>
|
|
189
|
+
<string>/usr/bin/python3</string>
|
|
190
|
+
<string>/path/to/LTCAI-ai-mlx/server/server.py</string>
|
|
191
|
+
</array>
|
|
192
|
+
<key>RunAtLoad</key><true/>
|
|
193
|
+
<key>KeepAlive</key><true/>
|
|
194
|
+
</dict>
|
|
195
|
+
</plist>
|
|
196
|
+
EOF
|
|
197
|
+
|
|
198
|
+
launchctl load ~/Library/LaunchAgents/com.ltcai.mlx.plist
|
|
199
|
+
```
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from openai import AsyncOpenAI
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_env_file(path=".env"):
|
|
12
|
+
env_path = Path(path)
|
|
13
|
+
if not env_path.exists():
|
|
14
|
+
return
|
|
15
|
+
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
|
|
16
|
+
line = raw_line.strip()
|
|
17
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
18
|
+
continue
|
|
19
|
+
key, value = line.split("=", 1)
|
|
20
|
+
os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'"))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
load_env_file()
|
|
24
|
+
|
|
25
|
+
TELEGRAM_TOKEN = os.getenv("CODEX_TELEGRAM_BOT_TOKEN", "")
|
|
26
|
+
TELEGRAM_API_URL = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}"
|
|
27
|
+
OPENAI_MODEL = os.getenv("CODEX_OPENAI_MODEL", "gpt-5.4")
|
|
28
|
+
STATE_FILE = Path(os.getenv("CODEX_TELEGRAM_STATE_FILE", "codex_telegram_state.json"))
|
|
29
|
+
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
|
|
30
|
+
GITHUB_REPO = os.getenv("GITHUB_REPO", "")
|
|
31
|
+
|
|
32
|
+
SYSTEM_PROMPT = """You are Lattice AI, the user's Codex development partner.
|
|
33
|
+
Discuss architecture, implementation plans, code reviews, and GitHub-ready changes.
|
|
34
|
+
Keep Korean responses concise and actionable.
|
|
35
|
+
When the user asks for changes that should land in the repository, propose a clear patch plan.
|
|
36
|
+
Use /issue to create GitHub issues when the user wants work tracked in git.
|
|
37
|
+
Do not claim that local Mac files were changed unless a GitHub action or explicit repository operation completed."""
|
|
38
|
+
|
|
39
|
+
logging.basicConfig(level=logging.INFO)
|
|
40
|
+
logger = logging.getLogger("codex_telegram_bot")
|
|
41
|
+
openai_client = AsyncOpenAI()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def load_state():
|
|
45
|
+
if not STATE_FILE.exists():
|
|
46
|
+
return {}
|
|
47
|
+
try:
|
|
48
|
+
return json.loads(STATE_FILE.read_text(encoding="utf-8"))
|
|
49
|
+
except json.JSONDecodeError:
|
|
50
|
+
return {}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def save_state(state):
|
|
54
|
+
STATE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def send_message(client, chat_id, text):
|
|
58
|
+
chunks = [text[i:i + 3900] for i in range(0, len(text), 3900)] or [""]
|
|
59
|
+
for chunk in chunks:
|
|
60
|
+
await client.post(
|
|
61
|
+
f"{TELEGRAM_API_URL}/sendMessage",
|
|
62
|
+
json={"chat_id": chat_id, "text": chunk},
|
|
63
|
+
timeout=30,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def get_updates(client, offset=None):
|
|
68
|
+
params = {"timeout": 30}
|
|
69
|
+
if offset is not None:
|
|
70
|
+
params["offset"] = offset
|
|
71
|
+
try:
|
|
72
|
+
res = await client.get(f"{TELEGRAM_API_URL}/getUpdates", params=params, timeout=35)
|
|
73
|
+
return res.json()
|
|
74
|
+
except Exception as exc:
|
|
75
|
+
logger.error("Telegram update failed: %s", exc)
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def create_github_issue(title, body):
|
|
80
|
+
if not GITHUB_TOKEN or not GITHUB_REPO:
|
|
81
|
+
return "GITHUB_TOKEN 또는 GITHUB_REPO가 설정되지 않아 이슈를 만들 수 없습니다."
|
|
82
|
+
|
|
83
|
+
async with httpx.AsyncClient() as client:
|
|
84
|
+
res = await client.post(
|
|
85
|
+
f"https://api.github.com/repos/{GITHUB_REPO}/issues",
|
|
86
|
+
headers={
|
|
87
|
+
"Authorization": f"Bearer {GITHUB_TOKEN}",
|
|
88
|
+
"Accept": "application/vnd.github+json",
|
|
89
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
90
|
+
},
|
|
91
|
+
json={"title": title, "body": body},
|
|
92
|
+
timeout=30,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if res.status_code >= 300:
|
|
96
|
+
return f"GitHub 이슈 생성 실패 ({res.status_code}): {res.text[:1000]}"
|
|
97
|
+
|
|
98
|
+
data = res.json()
|
|
99
|
+
return f"GitHub 이슈 생성 완료: {data.get('html_url')}"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def ask_codex(chat_id, message):
|
|
103
|
+
state = load_state()
|
|
104
|
+
chat_key = str(chat_id)
|
|
105
|
+
previous_response_id = state.get(chat_key, {}).get("previous_response_id")
|
|
106
|
+
|
|
107
|
+
kwargs = {
|
|
108
|
+
"model": OPENAI_MODEL,
|
|
109
|
+
"instructions": SYSTEM_PROMPT,
|
|
110
|
+
"input": message,
|
|
111
|
+
"max_output_tokens": 1800,
|
|
112
|
+
}
|
|
113
|
+
if previous_response_id:
|
|
114
|
+
kwargs["previous_response_id"] = previous_response_id
|
|
115
|
+
|
|
116
|
+
response = await openai_client.responses.create(**kwargs)
|
|
117
|
+
state[chat_key] = {"previous_response_id": response.id}
|
|
118
|
+
save_state(state)
|
|
119
|
+
return response.output_text
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def parse_issue_command(text):
|
|
123
|
+
content = text[len("/issue"):].strip()
|
|
124
|
+
if not content:
|
|
125
|
+
return None, None
|
|
126
|
+
parts = content.split("\n", 1)
|
|
127
|
+
title = parts[0].strip()
|
|
128
|
+
body = parts[1].strip() if len(parts) > 1 else title
|
|
129
|
+
return title, body
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def handle_message(client, chat_id, text):
|
|
133
|
+
if text == "/start":
|
|
134
|
+
await send_message(
|
|
135
|
+
client,
|
|
136
|
+
chat_id,
|
|
137
|
+
"Codex 전용 봇입니다.\n"
|
|
138
|
+
"- 일반 메시지: Codex와 개발 상담\n"
|
|
139
|
+
"- /reset: 이 대화 컨텍스트 초기화\n"
|
|
140
|
+
"- /issue 제목\\n내용: GitHub 이슈 생성",
|
|
141
|
+
)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
if text == "/reset":
|
|
145
|
+
state = load_state()
|
|
146
|
+
state.pop(str(chat_id), None)
|
|
147
|
+
save_state(state)
|
|
148
|
+
await send_message(client, chat_id, "Codex 대화 컨텍스트를 초기화했습니다.")
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
if text.startswith("/issue"):
|
|
152
|
+
title, body = parse_issue_command(text)
|
|
153
|
+
if not title:
|
|
154
|
+
await send_message(client, chat_id, "사용법: /issue 제목\\n내용")
|
|
155
|
+
return
|
|
156
|
+
await send_message(client, chat_id, await create_github_issue(title, body))
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
await send_message(client, chat_id, "Codex가 검토 중입니다...")
|
|
160
|
+
try:
|
|
161
|
+
answer = await ask_codex(chat_id, text)
|
|
162
|
+
except Exception as exc:
|
|
163
|
+
logger.exception("OpenAI request failed")
|
|
164
|
+
answer = f"OpenAI 요청 실패: {exc}"
|
|
165
|
+
await send_message(client, chat_id, answer)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
async def run_bot():
|
|
169
|
+
if not TELEGRAM_TOKEN:
|
|
170
|
+
raise RuntimeError("CODEX_TELEGRAM_BOT_TOKEN is required.")
|
|
171
|
+
if not os.getenv("OPENAI_API_KEY"):
|
|
172
|
+
raise RuntimeError("OPENAI_API_KEY is required.")
|
|
173
|
+
|
|
174
|
+
logger.info("Codex Telegram bot started.")
|
|
175
|
+
last_update_id = None
|
|
176
|
+
async with httpx.AsyncClient() as client:
|
|
177
|
+
while True:
|
|
178
|
+
updates = await get_updates(client, last_update_id)
|
|
179
|
+
if updates and updates.get("ok"):
|
|
180
|
+
for update in updates.get("result", []):
|
|
181
|
+
last_update_id = update["update_id"] + 1
|
|
182
|
+
msg = update.get("message") or {}
|
|
183
|
+
chat = msg.get("chat") or {}
|
|
184
|
+
text = msg.get("text") or ""
|
|
185
|
+
if chat.get("id") and text:
|
|
186
|
+
asyncio.create_task(handle_message(client, chat["id"], text))
|
|
187
|
+
await asyncio.sleep(0.5)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
asyncio.run(run_bot())
|