atakan Claude Sonnet 5 commited on
Commit
b24b1e1
·
1 Parent(s): ef72bf0

fix: Include the actual Gradio/ZeroGPU code changes missed in the last commit

Browse files

The previous commit's git add silently failed on the already-deleted
Dockerfile pathspec, so it only recorded the Dockerfile removal --
README.md, app.py, and requirements.txt (the actual SDK/ZeroGPU
changes described in that commit's message) never got staged. This
left the Space briefly in a broken half-migrated state (no Dockerfile,
but README still declaring sdk: docker). This commit adds the missing
changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Files changed (3) hide show
  1. README.md +2 -2
  2. app.py +35 -3
  3. requirements.txt +2 -1
README.md CHANGED
@@ -2,8 +2,8 @@
2
  title: ControlAI Agent
3
  colorFrom: blue
4
  colorTo: indigo
5
- sdk: docker
6
- app_port: 7860
7
  pinned: false
8
  ---
9
 
 
2
  title: ControlAI Agent
3
  colorFrom: blue
4
  colorTo: indigo
5
+ sdk: gradio
6
+ app_file: app.py
7
  pinned: false
8
  ---
9
 
app.py CHANGED
@@ -12,6 +12,8 @@ from contextlib import asynccontextmanager
12
  from pathlib import Path
13
  from typing import Any
14
 
 
 
15
  from fastapi import FastAPI, File, HTTPException, UploadFile
16
  from fastapi.middleware.cors import CORSMiddleware
17
  from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
@@ -80,6 +82,20 @@ def get_agent() -> ControlAIAgent:
80
  return agent_instance
81
 
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  class ChatRequest(BaseModel):
84
  message: str
85
  history: list[dict[str, str]] = []
@@ -186,7 +202,7 @@ async def chat_stream_endpoint(req: ChatRequest):
186
  def event_generator():
187
  try:
188
  with inference_lock:
189
- for event in agent.run_stream(req.message.strip(), history=req.history):
190
  yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
191
  except Exception as exc:
192
  yield f"data: {json.dumps({'type': 'error', 'error': str(exc)}, ensure_ascii=False)}\n\n"
@@ -211,7 +227,7 @@ async def chat_endpoint(req: ChatRequest) -> ChatResponse:
211
  try:
212
  agent = get_agent()
213
  with inference_lock:
214
- result = agent.run(req.message.strip(), history=req.history, verbose=False)
215
  elapsed = time.time() - t0
216
 
217
  # Collect tool traces
@@ -249,10 +265,26 @@ async def chat_endpoint(req: ChatRequest) -> ChatResponse:
249
  )
250
 
251
 
 
 
 
 
 
 
 
 
 
252
  def main() -> None:
 
 
 
 
 
 
 
 
253
  import threading
254
  import webbrowser
255
- import uvicorn
256
 
257
  def _open_browser() -> None:
258
  time.sleep(1.2)
 
12
  from pathlib import Path
13
  from typing import Any
14
 
15
+ import gradio as gr
16
+ import spaces
17
  from fastapi import FastAPI, File, HTTPException, UploadFile
18
  from fastapi.middleware.cors import CORSMiddleware
19
  from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
 
82
  return agent_instance
83
 
84
 
85
+ # On ZeroGPU Spaces, actual CUDA work may only happen inside a function
86
+ # decorated with @spaces.GPU (it requests physical GPU time for the call and
87
+ # releases it afterward). Outside of a ZeroGPU Space this decorator is a
88
+ # harmless no-op, so it's safe to always wrap these.
89
+ @spaces.GPU(duration=120)
90
+ def _run_stream_on_gpu(agent: ControlAIAgent, message: str, history: list[dict[str, str]]):
91
+ yield from agent.run_stream(message, history=history)
92
+
93
+
94
+ @spaces.GPU(duration=120)
95
+ def _run_on_gpu(agent: ControlAIAgent, message: str, history: list[dict[str, str]]):
96
+ return agent.run(message, history=history, verbose=False)
97
+
98
+
99
  class ChatRequest(BaseModel):
100
  message: str
101
  history: list[dict[str, str]] = []
 
202
  def event_generator():
203
  try:
204
  with inference_lock:
205
+ for event in _run_stream_on_gpu(agent, req.message.strip(), req.history):
206
  yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
207
  except Exception as exc:
208
  yield f"data: {json.dumps({'type': 'error', 'error': str(exc)}, ensure_ascii=False)}\n\n"
 
227
  try:
228
  agent = get_agent()
229
  with inference_lock:
230
+ result = _run_on_gpu(agent, req.message.strip(), req.history)
231
  elapsed = time.time() - t0
232
 
233
  # Collect tool traces
 
265
  )
266
 
267
 
268
+ # A minimal Gradio Blocks app is mounted (at a sub-path, not "/") purely so
269
+ # this Space is recognized as a Gradio SDK app -- required for ZeroGPU
270
+ # hardware. The real UI is still served by our own FastAPI routes above.
271
+ _gpu_demo = gr.Blocks()
272
+ with _gpu_demo:
273
+ gr.Markdown("ControlAI is running. Visit the Space's root URL for the full app.")
274
+ app = gr.mount_gradio_app(app, _gpu_demo, path="/gradio")
275
+
276
+
277
  def main() -> None:
278
+ import uvicorn
279
+
280
+ if os.environ.get("SPACE_ID"):
281
+ port = int(os.environ.get("PORT", 7860))
282
+ print(f"ControlAI Web UI running on Hugging Face Spaces (port {port})...")
283
+ uvicorn.run(app, host="0.0.0.0", port=port, log_level="info")
284
+ return
285
+
286
  import threading
287
  import webbrowser
 
288
 
289
  def _open_browser() -> None:
290
  time.sleep(1.2)
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
- llama-cpp-python>=0.2.80
 
2
  huggingface-hub>=0.23.0
3
  torch>=2.0.0
4
  transformers>=4.40.0
 
1
+ gradio>=5.9.0
2
+ spaces>=0.30.0
3
  huggingface-hub>=0.23.0
4
  torch>=2.0.0
5
  transformers>=4.40.0