Skip to content

Commit ebb97b2

Browse files
Merge pull request #9 from rwilliamspbg-ops/feat/model-library-worker-reconnect-compose
Feat/model library worker reconnect compose
2 parents fc2749c + 1fec575 commit ebb97b2

11 files changed

Lines changed: 634 additions & 62 deletions

File tree

.github/workflows/ci.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,57 @@ jobs:
8686
- name: Build Docker images
8787
run: |
8888
docker compose -f docker-compose.dev.yml build
89+
90+
compose-smoke:
91+
needs: build
92+
runs-on: ubuntu-latest
93+
steps:
94+
- uses: actions/checkout@v4
95+
96+
- name: Start compose stack
97+
run: |
98+
docker compose -f docker-compose.yml up -d --build
99+
100+
- name: Wait for services
101+
run: |
102+
python - <<'PY'
103+
import time
104+
import urllib.request
105+
106+
urls = [
107+
"http://127.0.0.1:8003/health",
108+
"http://127.0.0.1:8004/health",
109+
"http://127.0.0.1:8003/api/workers",
110+
"http://127.0.0.1:8003/api/metrics",
111+
]
112+
113+
deadline = time.time() + 180
114+
while time.time() < deadline:
115+
try:
116+
for url in urls:
117+
with urllib.request.urlopen(url, timeout=5) as response:
118+
if response.status != 200:
119+
raise RuntimeError(f"{url} returned {response.status}")
120+
break
121+
except Exception:
122+
time.sleep(5)
123+
else:
124+
raise SystemExit("compose services did not become healthy in time")
125+
PY
126+
127+
- name: Verify GUI-facing endpoints
128+
run: |
129+
curl -fsS http://127.0.0.1:8003/health
130+
curl -fsS http://127.0.0.1:8003/api/workers
131+
curl -fsS http://127.0.0.1:8003/api/metrics
132+
curl -fsS http://127.0.0.1:8004/health
133+
134+
- name: Dump compose logs on failure
135+
if: failure()
136+
run: |
137+
docker compose -f docker-compose.yml logs --no-color
138+
139+
- name: Tear down compose stack
140+
if: always()
141+
run: |
142+
docker compose -f docker-compose.yml down -v

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ RUN apt-get update && apt-get install -y gcc git libgl1 libegl1
1515
# Copy requirements first for better caching
1616
COPY requirements.txt .
1717
RUN pip install --no-cache-dir -r requirements.txt
18+
RUN pip install --no-cache-dir "fastapi>=0.104.0" "uvicorn>=0.24.0" "requests>=2.31.0"
1819

1920
# Copy application code
2021
COPY mohawk_gui/ ./mohawk_gui/
22+
COPY prototype/ ./prototype/
2123

2224
# Create non-root user for security
2325
RUN groupadd mohawk && useradd -r -g mohawk mohawk

Dockerfile.controller

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ RUN chown -R appuser:appuser /app
3737
USER appuser
3838

3939
# Expose controller port
40-
EXPOSE 9000
40+
EXPOSE 8000
4141

4242
# Health check
4343
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
44-
CMD curl -f http://localhost:9000/health || exit 1
44+
CMD curl -f http://localhost:8000/health || exit 1
4545

46-
# Run controller (in production, would use proper entrypoint)
47-
CMD ["python", "-c", "from prototype.controller import Controller; print('Controller ready')"]
46+
# Run a lightweight controller API used for local compose verification.
47+
CMD ["python", "-m", "uvicorn", "prototype.controller_service:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose.dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ services:
77
- "8000:8000"
88
environment:
99
- WORKER_URLS=http://worker1:8000,http://worker2:8000
10-
command: python -m http.server 8000
10+
command: python -m uvicorn prototype.controller_service:app --host 0.0.0.0 --port 8000
1111
depends_on:
1212
- worker1
1313
- worker2

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ services:
3030
- PYTHONUNBUFFERED=1
3131
- PYTHONDONTWRITEBYTECODE=1
3232
- QT_QPA_PLATFORM=offscreen
33-
# Lightweight API backend used by the desktop GUI for local verification.
34-
command: python -m uvicorn mohawk_gui.mock_backend:app --host 0.0.0.0 --port 8003
33+
# Real controller API backend used by the desktop GUI.
34+
command: python -m uvicorn prototype.controller_service:app --host 0.0.0.0 --port 8003
3535
healthcheck:
3636
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8003/health', timeout=3)"]
3737
interval: 30s

mohawk/models/loader.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def add_local_model(self, model_path: str, alias: Optional[str] = None) -> Dict[
8787
model_id = alias or path.name
8888
return self.add_to_library(model_id=model_id, local_path=str(path), source="local")
8989

90-
def list_library(self) -> list:
90+
def list_library(self) -> list[Dict[str, Any]]:
9191
"""List registered models in the local model library."""
9292
return list(self._library.values())
9393

@@ -124,7 +124,7 @@ def load(
124124
self,
125125
model_path: str,
126126
model_format: Optional[ModelFormat] = None,
127-
**kwargs,
127+
**kwargs: Any,
128128
) -> Dict[str, Any]:
129129
"""
130130
Load a model and return model objects.
@@ -157,7 +157,7 @@ def load(
157157
else:
158158
raise ValueError(f"Unsupported model format: {model_format}")
159159

160-
def _load_gguf(self, model_path: str, **kwargs) -> Dict[str, Any]:
160+
def _load_gguf(self, model_path: str, **kwargs: Any) -> Dict[str, Any]:
161161
"""Load GGUF format model using llama-cpp-python"""
162162
try:
163163
from llama_cpp import Llama
@@ -173,7 +173,7 @@ def _load_gguf(self, model_path: str, **kwargs) -> Dict[str, Any]:
173173

174174
return {"model": llm, "tokenizer": None, "format": "gguf"}
175175

176-
def _load_huggingface(self, model_path: str, **kwargs) -> Dict[str, Any]:
176+
def _load_huggingface(self, model_path: str, **kwargs: Any) -> Dict[str, Any]:
177177
"""Load HuggingFace transformers model"""
178178
try:
179179
from transformers import AutoModelForCausalLM, AutoTokenizer
@@ -196,7 +196,7 @@ def _load_huggingface(self, model_path: str, **kwargs) -> Dict[str, Any]:
196196

197197
return {"model": model, "tokenizer": tokenizer, "format": "huggingface"}
198198

199-
def _load_onnx(self, model_path: str, **kwargs) -> Dict[str, Any]:
199+
def _load_onnx(self, model_path: str, **kwargs: Any) -> Dict[str, Any]:
200200
"""Load ONNX model"""
201201
try:
202202
import onnxruntime as ort
@@ -207,12 +207,12 @@ def _load_onnx(self, model_path: str, **kwargs) -> Dict[str, Any]:
207207

208208
return {"model": session, "tokenizer": None, "format": "onnx"}
209209

210-
def _load_safetensors(self, model_path: str, **kwargs) -> Dict[str, Any]:
210+
def _load_safetensors(self, model_path: str, **kwargs: Any) -> Dict[str, Any]:
211211
"""Load safetensors format model"""
212212
# Safetensors is typically used with transformers
213213
return self._load_huggingface(model_path, **kwargs)
214214

215-
def download(self, model_id: str, **kwargs) -> str:
215+
def download(self, model_id: str, **kwargs: Any) -> str:
216216
"""
217217
Download a model from HuggingFace Hub.
218218
@@ -246,7 +246,7 @@ def download(self, model_id: str, **kwargs) -> str:
246246

247247
return local_path
248248

249-
def list_cached_models(self) -> list:
249+
def list_cached_models(self) -> list[str]:
250250
"""List all cached models"""
251251
return [str(p) for p in self.cache_dir.iterdir() if p.is_dir()]
252252

0 commit comments

Comments
 (0)