feat: initial scaffold — FastAPI shell + stub vision routers
Phase 4a of the BTE refactor (audit 2026-05-24 §3 V). svrnty-vision is a sovereign HTTP gateway in front of four vision capabilities — VLM (Spark 2 Qwen3-VL), FLUX image gen (Spark 1 ComfyUI), palette extraction, and background removal. This commit lays only the scaffold: FastAPI app, /healthz, four 501-stub routers, pydantic-settings config, pytest smoke. Real implementations land in Phase 4b. BTE code is untouched in 4a. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
2a90c3f884
18
.env.example
Normal file
18
.env.example
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# svrnty-vision configuration
|
||||||
|
#
|
||||||
|
# svrnty-vision is an HTTP gateway. It does NOT run ML models in-process.
|
||||||
|
# It calls existing Spark services (FLUX, Qwen3-VL) over HTTP.
|
||||||
|
|
||||||
|
# Server
|
||||||
|
SVRNTY_VISION_HOST=0.0.0.0
|
||||||
|
SVRNTY_VISION_PORT=8090
|
||||||
|
|
||||||
|
# Spark 1 — FLUX image generation (ComfyUI HTTP endpoint)
|
||||||
|
SPARK1_FLUX_URL=http://spark1.lan:8188
|
||||||
|
|
||||||
|
# Spark 2 — Qwen3-VL via vLLM (OpenAI-compatible HTTP endpoint)
|
||||||
|
SPARK2_VLM_URL=http://spark2.lan:8000
|
||||||
|
SPARK2_VLM_MODEL=Qwen/Qwen3-VL-7B-Instruct
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
VISION_REQUEST_TIMEOUT_SECONDS=120
|
||||||
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
|
||||||
|
# Distribution
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
*.egg
|
||||||
|
wheels/
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Tests / cache
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
78
CLAUDE.md
Normal file
78
CLAUDE.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# svrnty-vision — orientation for Claude
|
||||||
|
|
||||||
|
*Inherits Karpathy 4 rules from `~/.claude/CLAUDE.md` and the workspace
|
||||||
|
contract from `/home/svrnty/workspaces/hermes/CLAUDE.md`. Read both before
|
||||||
|
touching anything here.*
|
||||||
|
|
||||||
|
## What this repo is
|
||||||
|
|
||||||
|
A FastAPI HTTP gateway in front of four vision capabilities (VLM analysis,
|
||||||
|
FLUX image generation, palette extraction, background removal). It is a
|
||||||
|
**sibling of `bte/`**, not a child. BTE calls it over HTTP.
|
||||||
|
|
||||||
|
## Hard invariants
|
||||||
|
|
||||||
|
- **Thin gateway only.** Qwen3-VL runs on Spark 2 (vLLM). FLUX runs on
|
||||||
|
Spark 1 (ComfyUI). svrnty-vision proxies — it does NOT load model
|
||||||
|
weights or pull torch/transformers/diffusers in-process. Two exceptions
|
||||||
|
permitted in Phase 4b: `palette` (Pillow + colorthief) and `rembg`
|
||||||
|
(rembg lib) — both CPU-light, no GPU.
|
||||||
|
- **No cloud VLM providers.** The whole point of this extraction is to
|
||||||
|
delete Anthropic/OpenAI/Google/Higgsfield SDK dependencies from BTE.
|
||||||
|
Do not reintroduce them here. Sovereign-first.
|
||||||
|
- **Secrets via env only.** No keys in code, logs, or argv. Use
|
||||||
|
`pydantic-settings` + `.env` (gitignored).
|
||||||
|
- **Stay in Python ≥3.11.** Workspace standard.
|
||||||
|
|
||||||
|
## Phase status
|
||||||
|
|
||||||
|
| Phase | Scope | State |
|
||||||
|
|---|---|---|
|
||||||
|
| 4a | Scaffold: FastAPI shell, `/healthz`, four 501 stubs, tests | **done (this commit)** |
|
||||||
|
| 4b | Port real implementations from BTE; HTTP clients for Spark 1/2 | not started |
|
||||||
|
| 4c | Delete the corresponding .NET code from BTE | not started |
|
||||||
|
| 4d | Wire BTE to call svrnty-vision over HTTP via thin adapter | not started |
|
||||||
|
|
||||||
|
See `/home/svrnty/workspaces/hermes/sot/01-ROADMAP/BTE-REFACTOR-EXECUTION-PLAN.md`
|
||||||
|
and `/home/svrnty/workspaces/hermes/bte/docs/REFACTOR-AUDIT-2026-05-24.md` §3 V.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
src/svrnty_vision/
|
||||||
|
server.py # FastAPI app + /healthz + router includes
|
||||||
|
settings.py # pydantic-settings (env-driven)
|
||||||
|
routers/
|
||||||
|
vlm.py # POST /vlm/analyze (501 stub → Spark 2)
|
||||||
|
flux.py # POST /flux/render (501 stub → Spark 1)
|
||||||
|
palette.py # POST /palette/extract (501 stub → in-process)
|
||||||
|
rembg.py # POST /rembg/cutout (501 stub → in-process)
|
||||||
|
tests/
|
||||||
|
test_healthz.py # TestClient smoke
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run / test
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python -m venv .venv && source .venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install -e . # required: src/ layout
|
||||||
|
uvicorn svrnty_vision.server:app --port 8090 # serve
|
||||||
|
pytest tests/ # test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
- Default branch: `jp` (workspace convention).
|
||||||
|
- Local-only until JP authorises the gitea push:
|
||||||
|
`git remote add openharbor git@git.openharbor.io:svrnty/svrnty-vision.git`
|
||||||
|
then `git push -u openharbor jp`.
|
||||||
|
|
||||||
|
## When extending
|
||||||
|
|
||||||
|
- New endpoint? Add a router under `src/svrnty_vision/routers/`, register
|
||||||
|
it in `server.py`, add a test in `tests/`.
|
||||||
|
- New Spark dependency? Add the URL to `settings.py` and `.env.example`,
|
||||||
|
never hardcode.
|
||||||
|
- Surgical changes only. Don't refactor adjacent stubs while implementing
|
||||||
|
one — each phase has its own commit.
|
||||||
67
README.md
Normal file
67
README.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# svrnty-vision
|
||||||
|
|
||||||
|
Sovereign vision HTTP gateway. FastAPI shell that exposes four endpoints:
|
||||||
|
|
||||||
|
| Endpoint | Purpose | Implementation |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST /vlm/analyze` | Vision-language model evaluation | Proxies to Spark 2 (Qwen3-VL via vLLM) |
|
||||||
|
| `POST /flux/render` | Image generation | Proxies to Spark 1 (FLUX on ComfyUI) |
|
||||||
|
| `POST /palette/extract` | Dominant-color palette | In-process (Pillow + colorthief) |
|
||||||
|
| `POST /rembg/cutout` | Background removal | In-process (rembg) |
|
||||||
|
|
||||||
|
Plus `GET /healthz` for liveness.
|
||||||
|
|
||||||
|
## Why it exists
|
||||||
|
|
||||||
|
Brand Truth Engine (BTE) is a .NET 10 + Svrnty.CQRS service. Vision tooling
|
||||||
|
(VLM, image gen, palette, rembg) was originally embedded in BTE with cloud
|
||||||
|
provider SDKs (Anthropic, OpenAI, Google, Higgsfield). Per the
|
||||||
|
2026-05-24 refactor audit, that surface is extracted into this sibling repo
|
||||||
|
so BTE stays narrow and sovereign-first.
|
||||||
|
|
||||||
|
## Architecture invariant
|
||||||
|
|
||||||
|
**svrnty-vision is a thin HTTP gateway — it does NOT run heavy ML models
|
||||||
|
in-process.** Qwen3-VL runs on Spark 2 (vLLM). FLUX runs on Spark 1
|
||||||
|
(ComfyUI). svrnty-vision orchestrates HTTP calls to those services and
|
||||||
|
normalises the responses for BTE.
|
||||||
|
|
||||||
|
The two in-process exceptions (palette, rembg) are CPU-light Python
|
||||||
|
libraries — Pillow/colorthief and rembg respectively. They land in 4b.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
**Phase 4a (this commit):** scaffold only. All four endpoints return
|
||||||
|
HTTP 501 Not Implemented. `/healthz` returns 200.
|
||||||
|
|
||||||
|
Phase 4b will port the real logic. Phase 4c deletes the corresponding
|
||||||
|
.NET code from BTE. Phase 4d wires BTE to call svrnty-vision over HTTP.
|
||||||
|
See `/home/svrnty/workspaces/hermes/sot/01-ROADMAP/BTE-REFACTOR-EXECUTION-PLAN.md`.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install -e . # required: src/ layout
|
||||||
|
|
||||||
|
# either:
|
||||||
|
python -m svrnty_vision.server
|
||||||
|
# or:
|
||||||
|
uvicorn svrnty_vision.server:app --port 8090
|
||||||
|
|
||||||
|
curl http://localhost:8090/healthz
|
||||||
|
# {"status":"ok","version":"0.1.0"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env` and edit. All settings have safe defaults
|
||||||
|
for local development (Spark 1/2 hostnames are placeholders).
|
||||||
33
pyproject.toml
Normal file
33
pyproject.toml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "svrnty-vision"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Sovereign vision HTTP gateway — VLM analysis, FLUX image gen, palette extraction, background removal. Calls Spark services over HTTP."
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
license = { text = "Proprietary" }
|
||||||
|
authors = [
|
||||||
|
{ name = "Svrnty", email = "mathias@openharbor.io" },
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"fastapi>=0.115,<1.0",
|
||||||
|
"uvicorn[standard]>=0.32,<1.0",
|
||||||
|
"pydantic>=2.9,<3.0",
|
||||||
|
"pydantic-settings>=2.6,<3.0",
|
||||||
|
"httpx>=0.27,<1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=8.3,<9.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
pythonpath = ["src"]
|
||||||
|
testpaths = ["tests"]
|
||||||
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fastapi>=0.115,<1.0
|
||||||
|
uvicorn[standard]>=0.32,<1.0
|
||||||
|
pydantic>=2.9,<3.0
|
||||||
|
pydantic-settings>=2.6,<3.0
|
||||||
|
httpx>=0.27,<1.0
|
||||||
|
|
||||||
|
# Test deps (kept here for simplicity in Phase 4a)
|
||||||
|
pytest>=8.3,<9.0
|
||||||
3
src/svrnty_vision/__init__.py
Normal file
3
src/svrnty_vision/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"""svrnty-vision — sovereign vision HTTP gateway."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
1
src/svrnty_vision/routers/__init__.py
Normal file
1
src/svrnty_vision/routers/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Vision endpoint routers — all 501-stubs in Phase 4a."""
|
||||||
17
src/svrnty_vision/routers/flux.py
Normal file
17
src/svrnty_vision/routers/flux.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""FLUX image generation — stub until Phase 4b wires the ComfyUI HTTP client."""
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/flux", tags=["flux"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/render")
|
||||||
|
async def render() -> None:
|
||||||
|
"""Render an image via Spark 1 (FLUX on ComfyUI).
|
||||||
|
|
||||||
|
Phase 4a: stub. Phase 4b: proxies to Spark 1 ComfyUI workflow.
|
||||||
|
"""
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||||
|
detail="flux.render not implemented in Phase 4a — see BTE-REFACTOR-EXECUTION-PLAN Phase 4b",
|
||||||
|
)
|
||||||
17
src/svrnty_vision/routers/palette.py
Normal file
17
src/svrnty_vision/routers/palette.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""Palette extraction (ColorThief-equivalent) — stub until Phase 4b."""
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/palette", tags=["palette"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/extract")
|
||||||
|
async def extract() -> None:
|
||||||
|
"""Extract a dominant-color palette from an image.
|
||||||
|
|
||||||
|
Phase 4a: stub. Phase 4b: runs in-process (Pillow + colorthief).
|
||||||
|
"""
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||||
|
detail="palette.extract not implemented in Phase 4a — see BTE-REFACTOR-EXECUTION-PLAN Phase 4b",
|
||||||
|
)
|
||||||
17
src/svrnty_vision/routers/rembg.py
Normal file
17
src/svrnty_vision/routers/rembg.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""Background removal — stub until Phase 4b."""
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/rembg", tags=["rembg"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/cutout")
|
||||||
|
async def cutout() -> None:
|
||||||
|
"""Remove the background of an image (alpha cutout).
|
||||||
|
|
||||||
|
Phase 4a: stub. Phase 4b: runs in-process (rembg) or proxies to a Spark service.
|
||||||
|
"""
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||||
|
detail="rembg.cutout not implemented in Phase 4a — see BTE-REFACTOR-EXECUTION-PLAN Phase 4b",
|
||||||
|
)
|
||||||
17
src/svrnty_vision/routers/vlm.py
Normal file
17
src/svrnty_vision/routers/vlm.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""VLM (vision-language model) analysis — stub until Phase 4b moves Qwen3-VL code."""
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/vlm", tags=["vlm"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/analyze")
|
||||||
|
async def analyze() -> None:
|
||||||
|
"""Analyze an image with a vision-language model.
|
||||||
|
|
||||||
|
Phase 4a: stub. Phase 4b: proxies to Spark 2 (Qwen3-VL via vLLM).
|
||||||
|
"""
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||||
|
detail="vlm.analyze not implemented in Phase 4a — see BTE-REFACTOR-EXECUTION-PLAN Phase 4b",
|
||||||
|
)
|
||||||
40
src/svrnty_vision/server.py
Normal file
40
src/svrnty_vision/server.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""FastAPI application entry point."""
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from svrnty_vision import __version__
|
||||||
|
from svrnty_vision.routers import flux, palette, rembg, vlm
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="svrnty-vision",
|
||||||
|
version=__version__,
|
||||||
|
description="Sovereign vision HTTP gateway — VLM, FLUX, palette, rembg.",
|
||||||
|
)
|
||||||
|
|
||||||
|
app.include_router(vlm.router)
|
||||||
|
app.include_router(flux.router)
|
||||||
|
app.include_router(palette.router)
|
||||||
|
app.include_router(rembg.router)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/healthz")
|
||||||
|
async def healthz() -> dict[str, str]:
|
||||||
|
"""Liveness probe."""
|
||||||
|
return {"status": "ok", "version": __version__}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Run with `python -m svrnty_vision.server`."""
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
from svrnty_vision.settings import settings
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
"svrnty_vision.server:app",
|
||||||
|
host=settings.svrnty_vision_host,
|
||||||
|
port=settings.svrnty_vision_port,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
30
src/svrnty_vision/settings.py
Normal file
30
src/svrnty_vision/settings.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"""Settings loaded from environment / .env."""
|
||||||
|
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
"""Runtime configuration for svrnty-vision."""
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=".env",
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
extra="ignore",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Server
|
||||||
|
svrnty_vision_host: str = "0.0.0.0"
|
||||||
|
svrnty_vision_port: int = 8090
|
||||||
|
|
||||||
|
# Spark 1 — FLUX (ComfyUI)
|
||||||
|
spark1_flux_url: str = "http://spark1.lan:8188"
|
||||||
|
|
||||||
|
# Spark 2 — Qwen3-VL (vLLM, OpenAI-compatible)
|
||||||
|
spark2_vlm_url: str = "http://spark2.lan:8000"
|
||||||
|
spark2_vlm_model: str = "Qwen/Qwen3-VL-7B-Instruct"
|
||||||
|
|
||||||
|
# Common
|
||||||
|
vision_request_timeout_seconds: int = 120
|
||||||
|
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
35
tests/test_healthz.py
Normal file
35
tests/test_healthz.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"""Smoke tests for the FastAPI scaffold."""
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from svrnty_vision.server import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_healthz_returns_200() -> None:
|
||||||
|
response = client.get("/healthz")
|
||||||
|
assert response.status_code == 200
|
||||||
|
body = response.json()
|
||||||
|
assert body["status"] == "ok"
|
||||||
|
assert "version" in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_vlm_analyze_returns_501() -> None:
|
||||||
|
response = client.post("/vlm/analyze")
|
||||||
|
assert response.status_code == 501
|
||||||
|
|
||||||
|
|
||||||
|
def test_flux_render_returns_501() -> None:
|
||||||
|
response = client.post("/flux/render")
|
||||||
|
assert response.status_code == 501
|
||||||
|
|
||||||
|
|
||||||
|
def test_palette_extract_returns_501() -> None:
|
||||||
|
response = client.post("/palette/extract")
|
||||||
|
assert response.status_code == 501
|
||||||
|
|
||||||
|
|
||||||
|
def test_rembg_cutout_returns_501() -> None:
|
||||||
|
response = client.post("/rembg/cutout")
|
||||||
|
assert response.status_code == 501
|
||||||
Loading…
Reference in New Issue
Block a user