Files changed (1) hide show
  1. app.py +22 -11
app.py CHANGED
@@ -12,7 +12,6 @@ import time
12
  from pathlib import Path
13
 
14
  import gradio as gr
15
- import spaces # Must import before configuring CUDA on ZeroGPU
16
  from google import genai
17
  from google.genai import types
18
  from pydantic import BaseModel, ConfigDict, Field, ValidationError
@@ -25,9 +24,9 @@ ROOT = Path(__file__).resolve().parent
25
  DATA = ROOT / 'assets/projects_clean.csv'
26
  VECTORS = ROOT / 'assets/project_vectors.npz'
27
 
28
- # On ZeroGPU, CUDA placement at module scope is supported by simulated CUDA.
29
- # Do not call model.encode() at startup or move the model to GPU inside callback.
30
- model = SentenceTransformer(MODEL_ID, device='cuda')
31
  retriever = OriginRetrieval(DATA, VECTORS)
32
 
33
  class Idea(BaseModel):
@@ -91,13 +90,25 @@ def both(value):
91
  return {'en':value, 'ar':value}
92
 
93
 
94
- @spaces.GPU(duration=45)
95
  def search(title: str, description: str, language: str):
96
- """GPU portion only. Returns a JSON-serializable dict (no Gemini call)."""
97
- idea, lang = validate_idea(title, description, language)
98
- vector = retriever.query_vector(model, idea.title, idea.description)
99
- top = retriever.search_from_vector(vector, idea.title, idea.description, lang, top_k=5)
100
- return retriever.public_retrieval(top, idea.title, idea.description, lang)
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
 
103
  def enhance(title: str, description: str, language: str, search_payload: dict):
@@ -245,7 +256,7 @@ with gr.Blocks(title='Origin — ZeroGPU research demo') as demo:
245
  submit = gr.Button('Search and enhance', variant='primary')
246
  search_data = gr.JSON(label='Retrieved projects')
247
  final_data = gr.JSON(label='Idea Intelligence Report')
248
- # Separate named Gradio endpoints, so Gemini runs AFTER GPU is released.
249
  submit.click(fn=search, inputs=[title,description,language], outputs=[search_data],
250
  api_name='search', concurrency_limit=1).then(
251
  fn=enhance, inputs=[title,description,language,search_data],
 
12
  from pathlib import Path
13
 
14
  import gradio as gr
 
15
  from google import genai
16
  from google.genai import types
17
  from pydantic import BaseModel, ConfigDict, Field, ValidationError
 
24
  DATA = ROOT / 'assets/projects_clean.csv'
25
  VECTORS = ROOT / 'assets/project_vectors.npz'
26
 
27
+ # CPU retrieval avoids unauthenticated ZeroGPU per-user GPU quota for website visitors.
28
+ # The frozen 103-project index, embedding model, ranking and /search contract are unchanged.
29
+ model = SentenceTransformer(MODEL_ID, device='cpu')
30
  retriever = OriginRetrieval(DATA, VECTORS)
31
 
32
  class Idea(BaseModel):
 
90
  return {'en':value, 'ar':value}
91
 
92
 
 
93
  def search(title: str, description: str, language: str):
94
+ """CPU search. Uses the same embedding model/index; no ZeroGPU quota required."""
95
+ started = time.perf_counter()
96
+ stage = 'validation'
97
+ try:
98
+ idea, lang = validate_idea(title, description, language)
99
+ stage = 'embedding'
100
+ vector = retriever.query_vector(model, idea.title, idea.description)
101
+ stage = 'ranking'
102
+ top = retriever.search_from_vector(vector, idea.title, idea.description, lang, top_k=5)
103
+ result = retriever.public_retrieval(top, idea.title, idea.description, lang)
104
+ logger.info('origin.search_ok mode=cpu language=%s count=%d elapsed_ms=%d',
105
+ lang, len(top), int((time.perf_counter()-started)*1000))
106
+ return result
107
+ except Exception as exc:
108
+ # Never log a student's title/description, credentials, or prompt.
109
+ logger.error('origin.search_failed mode=cpu stage=%s type=%s',
110
+ stage, type(exc).__name__)
111
+ raise
112
 
113
 
114
  def enhance(title: str, description: str, language: str, search_payload: dict):
 
256
  submit = gr.Button('Search and enhance', variant='primary')
257
  search_data = gr.JSON(label='Retrieved projects')
258
  final_data = gr.JSON(label='Idea Intelligence Report')
259
+ # Separate named endpoints. Gemini runs only after retrieval completes.
260
  submit.click(fn=search, inputs=[title,description,language], outputs=[search_data],
261
  api_name='search', concurrency_limit=1).then(
262
  fn=enhance, inputs=[title,description,language,search_data],