rasAli02 commited on
Commit
6451a6a
Β·
1 Parent(s): 83d8e9c

πŸš€ Deployment: Final fix for backend 500 error (resilient startup + folder entrypoint)

Browse files
Files changed (2) hide show
  1. backend/app.py +179 -25
  2. vercel.json +1 -1
backend/app.py CHANGED
@@ -1,50 +1,204 @@
1
  import os
2
  import sys
 
 
 
3
  import traceback
 
 
 
4
  from fastapi import FastAPI, Request
5
- from fastapi.responses import JSONResponse
6
  from fastapi.middleware.cors import CORSMiddleware
7
 
8
- # Force current directory into path for local imports
9
- sys.path.append(os.path.dirname(__file__))
10
-
11
- # Global error capture for imports
12
- IMPORT_ERROR = None
13
  try:
 
 
14
  from agents import run_pipeline, AMD_INFERENCE_URL, AMD_MODEL_NAME, AMD_INFERENCE_TOKEN, generate_social_post
15
- except Exception as e:
16
- IMPORT_ERROR = f"Import Error: {str(e)}\n{traceback.format_exc()}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- app = FastAPI(title="ForgeSight Debug API")
19
 
20
  app.add_middleware(
21
  CORSMiddleware,
22
  allow_origins=["*"],
 
23
  allow_methods=["*"],
24
  allow_headers=["*"],
25
  )
26
 
27
- @app.middleware("http")
28
- async def error_logging_middleware(request: Request, call_next):
29
- if IMPORT_ERROR:
30
- return JSONResponse({"status": "error", "message": IMPORT_ERROR}, status_code=500)
 
 
31
  try:
32
- return await call_next(request)
 
 
 
 
 
 
 
 
 
 
 
33
  except Exception as e:
34
- return JSONResponse({
35
- "status": "error",
36
- "message": str(e),
37
- "traceback": traceback.format_exc()
38
- }, status_code=500)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  @app.get("/api/health")
41
- @app.get("/_/backend/api/health")
42
  async def health():
43
- return {"status": "online", "debug": True, "cwd": os.getcwd(), "path": sys.path}
 
 
 
 
 
44
 
45
  @app.get("/api/inspections")
46
- @app.get("/_/backend/api/inspections")
47
- async def list_inspections():
48
- return {"items": [], "total": 0, "note": "Debug mode"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- # ... other minimal routes to avoid crashes ...
 
 
 
 
1
  import os
2
  import sys
3
+ import uuid
4
+ import asyncio
5
+ import httpx
6
  import traceback
7
+ from datetime import datetime, timezone
8
+ from typing import List, Optional
9
+
10
  from fastapi import FastAPI, Request
11
+ from fastapi.responses import JSONResponse, FileResponse
12
  from fastapi.middleware.cors import CORSMiddleware
13
 
14
+ # Import agent pipeline logic
 
 
 
 
15
  try:
16
+ # Ensure current directory is in path for Vercel
17
+ sys.path.append(os.path.dirname(__file__))
18
  from agents import run_pipeline, AMD_INFERENCE_URL, AMD_MODEL_NAME, AMD_INFERENCE_TOKEN, generate_social_post
19
+ except ImportError:
20
+ # Fallback if running from root
21
+ from backend.agents import run_pipeline, AMD_INFERENCE_URL, AMD_MODEL_NAME, AMD_INFERENCE_TOKEN, generate_social_post
22
+
23
+ # ── CONFIGURATION ────────────────────────────────────────────────────────────
24
+
25
+ MONGO_URL = os.environ.get("MONGO_URL") or os.environ.get("MONGODB_URI")
26
+ # Global database references
27
+ _db = None
28
+ _inspections_col = None
29
+ _journal_col = None
30
+
31
+ # In-memory fallbacks
32
+ _mem_inspections = []
33
+ _mem_journal = []
34
+
35
+ # ── APP INITIALIZATION ───────────────────────────────────────────────────────
36
 
37
+ app = FastAPI(title="ForgeSight Backend")
38
 
39
  app.add_middleware(
40
  CORSMiddleware,
41
  allow_origins=["*"],
42
+ allow_credentials=True,
43
  allow_methods=["*"],
44
  allow_headers=["*"],
45
  )
46
 
47
+ async def _init_db():
48
+ """Attempt to connect to MongoDB; silently fall back to in-memory if unavailable."""
49
+ global _db, _inspections_col, _journal_col
50
+ if not MONGO_URL:
51
+ print("⚠️ MONGO_URL not set – using in-memory storage")
52
+ return
53
  try:
54
+ from motor.motor_asyncio import AsyncIOMotorClient
55
+ import certifi
56
+ client = AsyncIOMotorClient(
57
+ MONGO_URL,
58
+ serverSelectionTimeoutMS=5000,
59
+ tlsCAFile=certifi.where(),
60
+ tlsAllowInvalidCertificates=True
61
+ )
62
+ _db = client["forgesight"]
63
+ _inspections_col = _db["inspections"]
64
+ _journal_col = _db["journal"]
65
+ print("βœ… MongoDB client initialized")
66
  except Exception as e:
67
+ print(f"⚠️ MongoDB unavailable ({e}) – using in-memory storage")
68
+
69
+ async def _seed_journal():
70
+ """Seed the journal with initial milestones (instant, no LLM calls)."""
71
+ try:
72
+ existing = await _db_list_journal(1)
73
+ if existing: return
74
+ except: return
75
+
76
+ seeds = [
77
+ {"id": str(uuid.uuid4()), "type": "system", "content": "ForgeSight Backend initialized.", "created_at": datetime.now(timezone.utc).isoformat()},
78
+ {"id": str(uuid.uuid4()), "type": "checkpoint", "content": "AMD MI300X vLLM pipeline linked.", "created_at": datetime.now(timezone.utc).isoformat()},
79
+ ]
80
+ for s in seeds:
81
+ await _db_insert_journal(s)
82
+
83
+ @app.on_event("startup")
84
+ async def startup_event():
85
+ await _init_db()
86
+ await _seed_journal()
87
+
88
+ # ── DATABASE HELPERS ────────────────────────────────────────────────────────
89
+
90
+ async def _db_insert_inspection(data):
91
+ if _inspections_col is not None:
92
+ await _inspections_col.insert_one(data.copy())
93
+ else:
94
+ _mem_inspections.append(data)
95
+
96
+ async def _db_list_inspections(limit=20):
97
+ if _inspections_col is not None:
98
+ cursor = _inspections_col.find({}, {"_id": 0}).sort("timestamp", -1).limit(limit)
99
+ return await cursor.to_list(length=limit)
100
+ return sorted(_mem_inspections, key=lambda x: x.get("timestamp", ""), reverse=True)[:limit]
101
+
102
+ async def _db_insert_journal(data):
103
+ if _journal_col is not None:
104
+ await _journal_col.insert_one(data.copy())
105
+ else:
106
+ _mem_journal.append(data)
107
+
108
+ async def _db_list_journal(limit=50):
109
+ if _journal_col is not None:
110
+ cursor = _journal_col.find({}, {"_id": 0}).sort("created_at", -1).limit(limit)
111
+ return await cursor.to_list(length=limit)
112
+ return sorted(_mem_journal, key=lambda x: x.get("created_at", ""), reverse=True)[:limit]
113
+
114
+ # ─�� API ENDPOINTS ───────────────────────────────────────────────────────────
115
 
116
  @app.get("/api/health")
117
+ @app.get("/health")
118
  async def health():
119
+ return {
120
+ "status": "online",
121
+ "service": "forgesight",
122
+ "db": "mongodb" if _inspections_col is not None else "memory",
123
+ "timestamp": datetime.now(timezone.utc).isoformat()
124
+ }
125
 
126
  @app.get("/api/inspections")
127
+ async def get_inspections():
128
+ items = await _db_list_inspections()
129
+ return items
130
+
131
+ @app.get("/api/journal")
132
+ async def get_journal():
133
+ items = await _db_list_journal()
134
+ return items
135
+
136
+ @app.post("/api/inspect")
137
+ async def handle_inspect(request: Request):
138
+ """Triggers the full multi-agent QC pipeline."""
139
+ try:
140
+ body = await request.json()
141
+ image_url = body.get("image_url")
142
+ if not image_url:
143
+ return JSONResponse({"error": "image_url is required"}, status_code=400)
144
+
145
+ # Add a journal entry for the start
146
+ await _db_insert_journal({
147
+ "id": str(uuid.uuid4()),
148
+ "type": "process",
149
+ "content": f"Starting multimodal inspection for image: {image_url[:40]}...",
150
+ "created_at": datetime.now(timezone.utc).isoformat()
151
+ })
152
+
153
+ # Run pipeline
154
+ result = await run_pipeline(image_url)
155
+
156
+ # Save to DB
157
+ inspection_data = {
158
+ "id": result.get("id", str(uuid.uuid4())),
159
+ "timestamp": datetime.now(timezone.utc).isoformat(),
160
+ "image_url": image_url,
161
+ "status": result.get("status", "COMPLETED"),
162
+ "score": result.get("score", 0),
163
+ "findings": result.get("findings", []),
164
+ "agents": result.get("agents", {}),
165
+ "report_url": result.get("report_url", "")
166
+ }
167
+ await _db_insert_inspection(inspection_data)
168
+
169
+ # Generate social post
170
+ try:
171
+ social = await generate_social_post(inspection_data)
172
+ inspection_data["social"] = social
173
+ except:
174
+ inspection_data["social"] = "Social generation unavailable."
175
+
176
+ return inspection_data
177
+ except Exception as e:
178
+ return JSONResponse({"error": str(e), "traceback": traceback.format_exc()}, status_code=500)
179
+
180
+ @app.get("/api/metrics")
181
+ async def get_metrics():
182
+ """Returns system-wide metrics for the dashboard."""
183
+ inspections = await _db_list_inspections(100)
184
+ total = len(inspections)
185
+ if total == 0:
186
+ return {"avg_score": 0, "total_inspections": 0, "status_distribution": {}}
187
+
188
+ avg_score = sum(i.get("score", 0) for i in inspections) / total
189
+ dist = {}
190
+ for i in inspections:
191
+ s = i.get("status", "UNKNOWN")
192
+ dist[s] = dist.get(s, 0) + 1
193
+
194
+ return {
195
+ "avg_score": round(avg_score, 2),
196
+ "total_inspections": total,
197
+ "status_distribution": dist,
198
+ "system_load": "nominal"
199
+ }
200
 
201
+ if __name__ == "__main__":
202
+ import uvicorn
203
+ import traceback
204
+ uvicorn.run(app, host="0.0.0.0", port=7860)
vercel.json CHANGED
@@ -6,7 +6,7 @@
6
  "framework": "create-react-app"
7
  },
8
  "backend": {
9
- "entrypoint": "backend/app.py",
10
  "routePrefix": "/_/backend",
11
  "framework": "python"
12
  }
 
6
  "framework": "create-react-app"
7
  },
8
  "backend": {
9
+ "entrypoint": "backend",
10
  "routePrefix": "/_/backend",
11
  "framework": "python"
12
  }