Spaces:
Running
Running
dboa9 commited on
Commit ·
e787148
1
Parent(s): 9a86887
update
Browse files- Dockerfile +21 -5
- README.md +13 -2
- app.py +66 -22
- requirements.txt +2 -1
- start.sh +18 -1
Dockerfile
CHANGED
|
@@ -1,8 +1,7 @@
|
|
| 1 |
# Moltbot Hybrid Engine - Multi-service Dockerfile
|
| 2 |
-
# Runs: FastAPI (port 7860) + Ollama (optional
|
| 3 |
-
# Build: 2026-02-
|
| 4 |
# FIX v6: Dual LLM backend - Ollama (if available) + HF Inference API fallback
|
| 5 |
-
# HF Inference API works on Free tier without GPU/Ollama
|
| 6 |
|
| 7 |
FROM python:3.11-slim
|
| 8 |
|
|
@@ -15,9 +14,22 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 15 |
git \
|
| 16 |
git-lfs \
|
| 17 |
file \
|
|
|
|
| 18 |
&& apt-get clean \
|
| 19 |
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
# Install Ollama AS ROOT - pinned version, force amd64
|
| 22 |
# Using pinned version URL to avoid redirect issues during Docker build
|
| 23 |
# Mark as OPTIONAL - app works without it via HF Inference API fallback
|
|
@@ -52,14 +64,18 @@ RUN pip install --no-cache-dir --upgrade pip
|
|
| 52 |
# Copy all files with correct ownership
|
| 53 |
COPY --chown=user . /app
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
# Install Python dependencies (includes huggingface_hub for Inference API)
|
| 56 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 57 |
|
| 58 |
# Make start script executable
|
| 59 |
RUN chmod +x start.sh
|
| 60 |
|
| 61 |
-
# Expose HF Spaces port
|
| 62 |
-
EXPOSE 7860
|
| 63 |
|
| 64 |
# CMD required (not ENTRYPOINT) for dev mode compatibility
|
| 65 |
CMD ["./start.sh"]
|
|
|
|
| 1 |
# Moltbot Hybrid Engine - Multi-service Dockerfile
|
| 2 |
+
# Runs: FastAPI (port 7860) + Ollama (optional) + OpenClaw/Clawdbot gateway (port 18789)
|
| 3 |
+
# Build: 2026-02-14 v7.0 — Add Clawdbot (OpenClaw) for autonomous agent in HF Space
|
| 4 |
# FIX v6: Dual LLM backend - Ollama (if available) + HF Inference API fallback
|
|
|
|
| 5 |
|
| 6 |
FROM python:3.11-slim
|
| 7 |
|
|
|
|
| 14 |
git \
|
| 15 |
git-lfs \
|
| 16 |
file \
|
| 17 |
+
ca-certificates \
|
| 18 |
&& apt-get clean \
|
| 19 |
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
|
| 21 |
+
# Install Node.js 22 (required for OpenClaw/Clawdbot)
|
| 22 |
+
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
| 23 |
+
&& apt-get install -y nodejs \
|
| 24 |
+
&& apt-get clean \
|
| 25 |
+
&& rm -rf /var/lib/apt/lists/* \
|
| 26 |
+
&& node -v \
|
| 27 |
+
&& npm -v
|
| 28 |
+
|
| 29 |
+
# Install OpenClaw (Clawdbot) globally so gateway can run in Space
|
| 30 |
+
RUN npm install -g openclaw@latest \
|
| 31 |
+
&& (command -v openclaw || true)
|
| 32 |
+
|
| 33 |
# Install Ollama AS ROOT - pinned version, force amd64
|
| 34 |
# Using pinned version URL to avoid redirect issues during Docker build
|
| 35 |
# Mark as OPTIONAL - app works without it via HF Inference API fallback
|
|
|
|
| 64 |
# Copy all files with correct ownership
|
| 65 |
COPY --chown=user . /app
|
| 66 |
|
| 67 |
+
# OpenClaw/Clawdbot: minimal config so gateway starts without interactive onboarding
|
| 68 |
+
RUN mkdir -p /home/user/.openclaw/workspace
|
| 69 |
+
COPY --chown=user openclaw.json /home/user/.openclaw/openclaw.json
|
| 70 |
+
|
| 71 |
# Install Python dependencies (includes huggingface_hub for Inference API)
|
| 72 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 73 |
|
| 74 |
# Make start script executable
|
| 75 |
RUN chmod +x start.sh
|
| 76 |
|
| 77 |
+
# Expose HF Spaces port (7860) and OpenClaw gateway (18789, internal)
|
| 78 |
+
EXPOSE 7860 18789
|
| 79 |
|
| 80 |
# CMD required (not ENTRYPOINT) for dev mode compatibility
|
| 81 |
CMD ["./start.sh"]
|
README.md
CHANGED
|
@@ -9,9 +9,9 @@ app_port: 7860
|
|
| 9 |
---
|
| 10 |
|
| 11 |
# Moltbot Hybrid Engine
|
| 12 |
-
Safe AI agent for legal document processing - Dual LLM backend + file matching +
|
| 13 |
|
| 14 |
-
**Version
|
| 15 |
|
| 16 |
## Required Space Secrets
|
| 17 |
|
|
@@ -26,3 +26,14 @@ Set these in Space Settings > Repository secrets:
|
|
| 26 |
|
| 27 |
1. **Ollama** (local in container) - runs qwen2.5:1.5b if binary installs correctly
|
| 28 |
2. **HF Inference API** (fallback) - uses Qwen/Qwen2.5-7B-Instruct hosted by HuggingFace (requires HF_TOKEN)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
---
|
| 10 |
|
| 11 |
# Moltbot Hybrid Engine
|
| 12 |
+
Safe AI agent for legal document processing - Dual LLM backend + file matching + **Clawdbot (OpenClaw)** in the Space.
|
| 13 |
|
| 14 |
+
**Version 7.0.0** - Last updated: 2026-02-14 — Clawdbot installed; gateway runs on port 18789, proxied at `/gateway`
|
| 15 |
|
| 16 |
## Required Space Secrets
|
| 17 |
|
|
|
|
| 26 |
|
| 27 |
1. **Ollama** (local in container) - runs qwen2.5:1.5b if binary installs correctly
|
| 28 |
2. **HF Inference API** (fallback) - uses Qwen/Qwen2.5-7B-Instruct hosted by HuggingFace (requires HF_TOKEN)
|
| 29 |
+
|
| 30 |
+
## Clawdbot (OpenClaw) in this Space
|
| 31 |
+
|
| 32 |
+
OpenClaw/Clawdbot is **installed and started** in this Space so you can use the autonomous agent with Qwen and Claude.
|
| 33 |
+
|
| 34 |
+
- **Control UI / WebChat:** After the Space is running, open: **`https://<your-space>.hf.space/gateway`**
|
| 35 |
+
- The gateway runs internally on port 18789; the FastAPI app proxies `/gateway` and `/gateway/*` to it so a single Space port (7860) serves both the API and Clawdbot.
|
| 36 |
+
- **To get autonomous behaviour** (Clawdbot working on court bundle changes with Qwen/Claude):
|
| 37 |
+
1. Open the Control UI at `/gateway` and complete pairing/setup (model, API keys).
|
| 38 |
+
2. Add a **skill** that triggers your pipeline (e.g. run the web editor flow, call this Engine, run the bundler). Skills live in `~/.openclaw/workspace/skills/`; you can add a custom skill that calls your court bundle project endpoints or scripts.
|
| 39 |
+
3. Wire that skill to the same Qwen/Claude endpoints the editor uses (this Space’s `/api/generate` and your Claude API).
|
app.py
CHANGED
|
@@ -1,24 +1,16 @@
|
|
| 1 |
"""
|
| 2 |
-
Moltbot Hybrid Engine - Production
|
| 3 |
-
Multi-service: FastAPI
|
| 4 |
Runs on Hugging Face Spaces
|
| 5 |
-
Build: 2026-02-
|
| 6 |
-
|
| 7 |
-
LLM Strategy:
|
| 8 |
-
1. Try Ollama (local, if installed and running)
|
| 9 |
-
2. Fallback to HuggingFace Inference API (always available, no GPU needed)
|
| 10 |
|
| 11 |
Endpoints:
|
| 12 |
GET / - Health check
|
| 13 |
GET /health - Detailed health status
|
| 14 |
-
GET /
|
| 15 |
-
|
| 16 |
-
POST /api/
|
| 17 |
-
|
| 18 |
-
POST /api/extract_date - Date extraction from filenames
|
| 19 |
-
POST /tools/analyze_report - Report analysis via file upload
|
| 20 |
-
POST /v1/chat/completions - OpenAI-compatible endpoint (for Cursor IDE)
|
| 21 |
-
GET /v1/models - OpenAI-compatible model listing
|
| 22 |
"""
|
| 23 |
import os
|
| 24 |
import re
|
|
@@ -26,8 +18,8 @@ import json
|
|
| 26 |
import subprocess
|
| 27 |
import logging
|
| 28 |
from pathlib import Path
|
| 29 |
-
from fastapi import FastAPI, HTTPException, Header, UploadFile, File
|
| 30 |
-
from fastapi.responses import StreamingResponse
|
| 31 |
from pydantic import BaseModel
|
| 32 |
from typing import List, Optional, Dict, Any, Union
|
| 33 |
|
|
@@ -37,8 +29,8 @@ logger = logging.getLogger("moltbot-engine")
|
|
| 37 |
# Initialize App
|
| 38 |
app = FastAPI(
|
| 39 |
title="Moltbot Hybrid Engine",
|
| 40 |
-
description="AI agent for legal document processing - Dual LLM + file matching +
|
| 41 |
-
version="
|
| 42 |
)
|
| 43 |
|
| 44 |
# API Key for authentication
|
|
@@ -255,8 +247,9 @@ def health_check():
|
|
| 255 |
return {
|
| 256 |
"status": "running",
|
| 257 |
"service": "Moltbot Hybrid Engine",
|
| 258 |
-
"version": "
|
| 259 |
"ollama": ollama,
|
|
|
|
| 260 |
"hf_inference_api": {
|
| 261 |
"available": True,
|
| 262 |
"model": HF_MODEL,
|
|
@@ -271,7 +264,7 @@ def detailed_health():
|
|
| 271 |
return {
|
| 272 |
"status": "healthy",
|
| 273 |
"service": "moltbot-hybrid-engine",
|
| 274 |
-
"version": "
|
| 275 |
"llm_backends": {
|
| 276 |
"ollama": {
|
| 277 |
"running": ollama.get("running", False),
|
|
@@ -287,7 +280,7 @@ def detailed_health():
|
|
| 287 |
},
|
| 288 |
"endpoints": ["/", "/health", "/api/generate", "/api/search",
|
| 289 |
"/api/analyze", "/api/extract_date", "/tools/analyze_report",
|
| 290 |
-
"/v1/chat/completions", "/v1/models"]
|
| 291 |
}
|
| 292 |
|
| 293 |
@app.get("/security")
|
|
@@ -302,6 +295,57 @@ def security_info():
|
|
| 302 |
}
|
| 303 |
|
| 304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
# Legal document exhibit reference instruction — injected into every generate/chat so edit sources always get it
|
| 306 |
_prompts_dir = Path(__file__).resolve().parent / "prompts"
|
| 307 |
_LEGAL_EXHIBIT_PROMPT_PATH = _prompts_dir / "legal_exhibit_instruction.txt"
|
|
|
|
| 1 |
"""
|
| 2 |
+
Moltbot Hybrid Engine - Production v7.0.0
|
| 3 |
+
Multi-service: FastAPI + Ollama (optional) + OpenClaw/Clawdbot gateway (proxied at /gateway)
|
| 4 |
Runs on Hugging Face Spaces
|
| 5 |
+
Build: 2026-02-14 — Clawdbot installed in Space; gateway on 18789, proxied at /gateway
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
Endpoints:
|
| 8 |
GET / - Health check
|
| 9 |
GET /health - Detailed health status
|
| 10 |
+
GET /gateway - OpenClaw/Clawdbot Control UI (reverse proxy to gateway :18789)
|
| 11 |
+
GET /gateway/{path} - OpenClaw proxy (path)
|
| 12 |
+
POST /api/generate - LLM text generation
|
| 13 |
+
...
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
"""
|
| 15 |
import os
|
| 16 |
import re
|
|
|
|
| 18 |
import subprocess
|
| 19 |
import logging
|
| 20 |
from pathlib import Path
|
| 21 |
+
from fastapi import FastAPI, HTTPException, Header, UploadFile, File, Request
|
| 22 |
+
from fastapi.responses import StreamingResponse, Response
|
| 23 |
from pydantic import BaseModel
|
| 24 |
from typing import List, Optional, Dict, Any, Union
|
| 25 |
|
|
|
|
| 29 |
# Initialize App
|
| 30 |
app = FastAPI(
|
| 31 |
title="Moltbot Hybrid Engine",
|
| 32 |
+
description="AI agent for legal document processing - Dual LLM + file matching + Clawdbot gateway at /gateway",
|
| 33 |
+
version="7.0.0"
|
| 34 |
)
|
| 35 |
|
| 36 |
# API Key for authentication
|
|
|
|
| 247 |
return {
|
| 248 |
"status": "running",
|
| 249 |
"service": "Moltbot Hybrid Engine",
|
| 250 |
+
"version": "7.0.0",
|
| 251 |
"ollama": ollama,
|
| 252 |
+
"clawdbot": "OpenClaw gateway proxied at /gateway (if running)",
|
| 253 |
"hf_inference_api": {
|
| 254 |
"available": True,
|
| 255 |
"model": HF_MODEL,
|
|
|
|
| 264 |
return {
|
| 265 |
"status": "healthy",
|
| 266 |
"service": "moltbot-hybrid-engine",
|
| 267 |
+
"version": "7.0.0",
|
| 268 |
"llm_backends": {
|
| 269 |
"ollama": {
|
| 270 |
"running": ollama.get("running", False),
|
|
|
|
| 280 |
},
|
| 281 |
"endpoints": ["/", "/health", "/api/generate", "/api/search",
|
| 282 |
"/api/analyze", "/api/extract_date", "/tools/analyze_report",
|
| 283 |
+
"/v1/chat/completions", "/v1/models", "/gateway (Clawdbot UI)"]
|
| 284 |
}
|
| 285 |
|
| 286 |
@app.get("/security")
|
|
|
|
| 295 |
}
|
| 296 |
|
| 297 |
|
| 298 |
+
# OpenClaw/Clawdbot gateway reverse proxy (gateway runs on 18789; Space exposes single port 7860)
|
| 299 |
+
OPENCLAW_GATEWAY_URL = "http://127.0.0.1:18789"
|
| 300 |
+
|
| 301 |
+
@app.api_route("/gateway", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
|
| 302 |
+
@app.api_route("/gateway/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
|
| 303 |
+
async def proxy_openclaw_gateway(request: Request, path: str = ""):
|
| 304 |
+
"""Proxy requests to OpenClaw/Clawdbot gateway so Control UI and WebChat are reachable at /gateway."""
|
| 305 |
+
try:
|
| 306 |
+
import httpx
|
| 307 |
+
except ImportError:
|
| 308 |
+
raise HTTPException(status_code=503, detail="httpx not installed; cannot proxy to Clawdbot gateway")
|
| 309 |
+
target_path = request.url.path
|
| 310 |
+
if target_path.startswith("/gateway"):
|
| 311 |
+
target_path = target_path[7:] or "/" # strip /gateway -> / or /foo
|
| 312 |
+
target = f"{OPENCLAW_GATEWAY_URL}{target_path}"
|
| 313 |
+
if request.url.query:
|
| 314 |
+
target += "?" + request.url.query
|
| 315 |
+
headers = {k: v for k, v in request.headers.raw if k.lower() not in (b"host", b"connection")}
|
| 316 |
+
try:
|
| 317 |
+
body = await request.body()
|
| 318 |
+
except Exception:
|
| 319 |
+
body = b""
|
| 320 |
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
| 321 |
+
try:
|
| 322 |
+
r = await client.request(
|
| 323 |
+
request.method,
|
| 324 |
+
target,
|
| 325 |
+
headers=headers,
|
| 326 |
+
content=body,
|
| 327 |
+
)
|
| 328 |
+
except httpx.ConnectError:
|
| 329 |
+
return Response(
|
| 330 |
+
content="Clawdbot gateway not reachable (is it running on 18789?). Start the Space and try again.",
|
| 331 |
+
status_code=503,
|
| 332 |
+
media_type="text/plain",
|
| 333 |
+
)
|
| 334 |
+
except Exception as e:
|
| 335 |
+
logger.warning(f"[GATEWAY PROXY] {e}")
|
| 336 |
+
return Response(content=str(e), status_code=502, media_type="text/plain")
|
| 337 |
+
out_headers = {}
|
| 338 |
+
for k, v in r.headers.items():
|
| 339 |
+
if k.lower() not in ("transfer-encoding", "connection"):
|
| 340 |
+
out_headers[k] = v
|
| 341 |
+
return Response(
|
| 342 |
+
content=r.content,
|
| 343 |
+
status_code=r.status_code,
|
| 344 |
+
headers=out_headers,
|
| 345 |
+
media_type=r.headers.get("content-type", "application/octet-stream"),
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
|
| 349 |
# Legal document exhibit reference instruction — injected into every generate/chat so edit sources always get it
|
| 350 |
_prompts_dir = Path(__file__).resolve().parent / "prompts"
|
| 351 |
_LEGAL_EXHIBIT_PROMPT_PATH = _prompts_dir / "legal_exhibit_instruction.txt"
|
requirements.txt
CHANGED
|
@@ -4,4 +4,5 @@ uvicorn>=0.24.0
|
|
| 4 |
pydantic>=2.0.0
|
| 5 |
python-multipart>=0.0.6
|
| 6 |
huggingface_hub>=0.20.0
|
| 7 |
-
requests>=2.31.0
|
|
|
|
|
|
| 4 |
pydantic>=2.0.0
|
| 5 |
python-multipart>=0.0.6
|
| 6 |
huggingface_hub>=0.20.0
|
| 7 |
+
requests>=2.31.0
|
| 8 |
+
httpx>=0.25.0
|
start.sh
CHANGED
|
@@ -79,8 +79,25 @@ echo " 💡 HF Inference API fallback is always available"
|
|
| 79 |
echo " (Uses Qwen/Qwen2.5-7B-Instruct hosted by HuggingFace)"
|
| 80 |
echo ""
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
# 4. Start FastAPI (foreground - keeps container alive)
|
| 83 |
-
echo "[4/
|
| 84 |
echo "============================================================"
|
| 85 |
echo ""
|
| 86 |
python -m uvicorn app:app --host 0.0.0.0 --port 7860
|
|
|
|
| 79 |
echo " (Uses Qwen/Qwen2.5-7B-Instruct hosted by HuggingFace)"
|
| 80 |
echo ""
|
| 81 |
|
| 82 |
+
# 3b. Start OpenClaw/Clawdbot gateway in background (port 18789) if available
|
| 83 |
+
echo "[3b/5] Starting OpenClaw (Clawdbot) gateway..."
|
| 84 |
+
if command -v openclaw &> /dev/null; then
|
| 85 |
+
export OPENCLAW_HOME="${HOME:-/home/user}/.openclaw"
|
| 86 |
+
if [ -d "$OPENCLAW_HOME" ] || [ -f "$OPENCLAW_HOME/openclaw.json" ]; then
|
| 87 |
+
nohup openclaw gateway --port 18789 > /tmp/openclaw-gateway.log 2>&1 &
|
| 88 |
+
OPENCLAW_PID=$!
|
| 89 |
+
echo " ✅ Clawdbot gateway started (PID $OPENCLAW_PID, port 18789)"
|
| 90 |
+
echo " → Control UI / WebChat available via this Space at /gateway (see app)"
|
| 91 |
+
else
|
| 92 |
+
echo " ⚠️ OpenClaw config not found at $OPENCLAW_HOME — skip gateway"
|
| 93 |
+
fi
|
| 94 |
+
else
|
| 95 |
+
echo " ⚠️ openclaw binary not found — skip Clawdbot gateway"
|
| 96 |
+
fi
|
| 97 |
+
echo ""
|
| 98 |
+
|
| 99 |
# 4. Start FastAPI (foreground - keeps container alive)
|
| 100 |
+
echo "[4/5] Starting FastAPI on port 7860..."
|
| 101 |
echo "============================================================"
|
| 102 |
echo ""
|
| 103 |
python -m uvicorn app:app --host 0.0.0.0 --port 7860
|