Rachit-Mangawa commited on
Commit
4be69d9
·
1 Parent(s): c21f62a

perf: Optimize for 30s timeout w/ SmollM2-1.7B and parallel execution

Browse files
Files changed (2) hide show
  1. Dockerfile +3 -3
  2. app.py +44 -31
Dockerfile CHANGED
@@ -3,7 +3,7 @@ FROM python:3.10-slim
3
  # System setup
4
  RUN apt-get update && apt-get install -y curl procps zstd && rm -rf /var/lib/apt/lists/*
5
 
6
- # Install Ollama via official script (More robust than direct binary link)
7
  RUN curl -fsSL https://ollama.com/install.sh | sh
8
 
9
  WORKDIR /app
@@ -17,8 +17,8 @@ ENV PORT=8086
17
  ENV OLLAMA_HOST=127.0.0.1:11434
18
  ENV PYTHONUNBUFFERED=1
19
 
20
- # Execution Script: Starts Ollama, pulls Phi, starts App
21
- RUN echo '#!/bin/bash\nollama serve & sleep 10 && ollama pull phi3.5 && python app.py' > /app/run.sh && \
22
  chmod +x /app/run.sh
23
 
24
  EXPOSE 8086
 
3
  # System setup
4
  RUN apt-get update && apt-get install -y curl procps zstd && rm -rf /var/lib/apt/lists/*
5
 
6
+ # Install Ollama via official script
7
  RUN curl -fsSL https://ollama.com/install.sh | sh
8
 
9
  WORKDIR /app
 
17
  ENV OLLAMA_HOST=127.0.0.1:11434
18
  ENV PYTHONUNBUFFERED=1
19
 
20
+ # Execution Script: Starts Ollama, pulls SmollM2 (Fast for CPU), starts App
21
+ RUN echo '#!/bin/bash\nollama serve & sleep 10 && ollama pull smollm2:1.7b && python app.py' > /app/run.sh && \
22
  chmod +x /app/run.sh
23
 
24
  EXPOSE 8086
app.py CHANGED
@@ -12,7 +12,7 @@ from dotenv import load_dotenv
12
 
13
  load_dotenv()
14
 
15
- # Vera Engine - Unstoppable Platinum Build (Local Fallback)
16
  logging.basicConfig(level=logging.INFO)
17
  logger = logging.getLogger("VeraEngine")
18
 
@@ -44,7 +44,9 @@ class KeyRotator:
44
 
45
  or_rotator = KeyRotator(OPENROUTER_KEYS)
46
  contexts = {"category": {}, "merchant": {}, "customer": {}, "trigger": {}}
47
- llm_semaphore = asyncio.Semaphore(1)
 
 
48
 
49
  PRIORITY_MAP = {
50
  "drop_in_orders": 100, "revenue_drop": 95, "regulation_change": 90,
@@ -52,10 +54,10 @@ PRIORITY_MAP = {
52
  }
53
 
54
  async def call_llm(prompt: str, system: str = "") -> Optional[Dict]:
55
- async with llm_semaphore:
56
- # Tier 1: OpenRouter (Gemini 2.0 Flash)
57
- key = await or_rotator.get_key()
58
- if key:
59
  headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json", "HTTP-Referer": "https://vera.ai", "X-Title": "Vera Engine"}
60
  payload = {
61
  "model": "google/gemini-2.0-flash-exp:free",
@@ -68,28 +70,28 @@ async def call_llm(prompt: str, system: str = "") -> Optional[Dict]:
68
  }
69
  async with httpx.AsyncClient() as client:
70
  try:
71
- response = await client.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload, timeout=15.0)
72
  if response.status_code == 200:
73
  return json.loads(response.json()["choices"][0]["message"]["content"])
74
- logger.warning(f"OpenRouter failed with status {response.status_code}. Checking local fallback...")
75
- except Exception as e:
76
- logger.warning(f"OpenRouter connection error: {e}. Checking local fallback...")
77
 
78
- # Tier 2: Local Fallback (Ollama + Phi-3.5-mini)
79
- logger.info("Triggering Local Fallback: Phi-3.5")
 
80
  local_payload = {
81
- "model": "phi3.5",
82
- "prompt": f"System: {system}. Under 320 chars, 1 CTA, strict grounding. Context: {prompt}\nUser: Generate the message JSON.",
83
  "stream": False,
84
  "format": "json",
85
- "options": {"num_predict": 150, "temperature": 0.1}
86
  }
87
  async with httpx.AsyncClient() as client:
88
  try:
89
- response = await client.post("http://127.0.0.1:11434/api/generate", json=local_payload, timeout=50.0)
 
90
  if response.status_code == 200:
91
- res_text = response.json().get('response')
92
- return json.loads(res_text)
93
  except Exception as e:
94
  logger.error(f"Local Fallback Failed: {e}")
95
  return None
@@ -115,10 +117,10 @@ async def compose(category: dict, merchant: dict, trigger: dict, customer: Optio
115
 
116
  sys_prompt = f"""Role: Elite Growth Strategist. Target: {prefix} {owner_name}.
117
  RULES:
118
- 1. NO FABRICATION: Use metrics provided: {json.dumps(m)}.
119
  2. CITATION: Include verbatim source from {json.dumps(payload)}.
120
  3. WHY ACT: Link {trigger.get('kind')} to revenue loss or business growth.
121
- 4. MERCHANT FIT: Mention locality {ident.get('locality')}. {hinglish_note}
122
  5. LENGTH: 280-310 chars.
123
 
124
  JSON: {{"body": "...", "cta": "...", "rationale": "..."}}"""
@@ -135,16 +137,15 @@ JSON: {{"body": "...", "cta": "...", "rationale": "..."}}"""
135
 
136
  @app.get("/v1/healthz")
137
  async def healthz():
138
- # Only return ok if local model is ready
139
  async with httpx.AsyncClient() as client:
140
  try:
141
  res = await client.get("http://127.0.0.1:11434/api/tags")
142
- if "phi3.5" in res.text: return {"status": "ok", "local_model": "ready"}
143
  except: pass
144
- return {"status": "starting", "message": "Ollama/Phi-3.5 warming up"}
145
 
146
  @app.get("/v1/metadata")
147
- def metadata(): return {"team_name": "Vera Lead Solver", "version": "Platinum-Unstoppable-v1"}
148
 
149
  @app.post("/v1/context")
150
  def push_context(data: ContextPayload):
@@ -161,22 +162,34 @@ async def process_trigger(trigger_id: str):
161
  return {
162
  "conversation_id": f"conv_{trigger_id}", "merchant_id": trigger.get("merchant_id"),
163
  "customer_id": trigger.get("customer_id"), "send_as": "merchant_on_behalf" if trigger.get("scope") == "customer" else "vera",
164
- "trigger_id": trigger_id, "template_name": "vera_unstoppable_v1", "body": composed["body"],
165
  "cta": composed["cta"], "suppression_key": trigger_id, "rationale": composed.get("rationale")
166
  }
167
 
168
  @app.post("/v1/tick")
169
  async def tick(req: TickRequest):
 
170
  tids = sorted(req.available_triggers, key=lambda t: PRIORITY_MAP.get(contexts["trigger"].get(t, {}).get("kind"), 0), reverse=True)
171
- results = []
172
- for tid in tids:
173
- res = await process_trigger(tid)
174
- if res: results.append(res)
175
- return {"actions": results}
 
 
 
 
 
 
176
 
177
  @app.post("/v1/reply")
178
  async def reply(req: Dict[str, Any]):
179
- return {"action": "send", "body": "Understood. Setting that up. Ready?", "cta": "Reply YES"}
 
 
 
 
 
180
 
181
  if __name__ == "__main__":
182
  import uvicorn
 
12
 
13
  load_dotenv()
14
 
15
+ # Vera Engine - Unstoppable Platinum Build v2 (Latency Optimized)
16
  logging.basicConfig(level=logging.INFO)
17
  logger = logging.getLogger("VeraEngine")
18
 
 
44
 
45
  or_rotator = KeyRotator(OPENROUTER_KEYS)
46
  contexts = {"category": {}, "merchant": {}, "customer": {}, "trigger": {}}
47
+ # Semaphore(1) for Local CPU inference to prevent thrashing; Semaphore(5) for API
48
+ llm_semaphore_api = asyncio.Semaphore(5)
49
+ llm_semaphore_local = asyncio.Semaphore(1)
50
 
51
  PRIORITY_MAP = {
52
  "drop_in_orders": 100, "revenue_drop": 95, "regulation_change": 90,
 
54
  }
55
 
56
  async def call_llm(prompt: str, system: str = "") -> Optional[Dict]:
57
+ # Tier 1: OpenRouter (Gemini 2.0 Flash)
58
+ key = await or_rotator.get_key()
59
+ if key:
60
+ async with llm_semaphore_api:
61
  headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json", "HTTP-Referer": "https://vera.ai", "X-Title": "Vera Engine"}
62
  payload = {
63
  "model": "google/gemini-2.0-flash-exp:free",
 
70
  }
71
  async with httpx.AsyncClient() as client:
72
  try:
73
+ response = await client.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload, timeout=12.0)
74
  if response.status_code == 200:
75
  return json.loads(response.json()["choices"][0]["message"]["content"])
76
+ except Exception:
77
+ pass
 
78
 
79
+ # Tier 2: Local Fallback (Ollama + SmollM2-1.7B for speed)
80
+ async with llm_semaphore_local:
81
+ logger.info("Triggering Local Fallback: SmollM2-1.7B")
82
  local_payload = {
83
+ "model": "smollm2:1.7b",
84
+ "prompt": f"System: {system}\nUnder 320 chars, 1 CTA, strict grounding. Context: {prompt}\nUser: Generate the message JSON.",
85
  "stream": False,
86
  "format": "json",
87
+ "options": {"num_predict": 120, "temperature": 0.1}
88
  }
89
  async with httpx.AsyncClient() as client:
90
  try:
91
+ # 20s timeout to leave room for other logic under the 30s judge limit
92
+ response = await client.post("http://127.0.0.1:11434/api/generate", json=local_payload, timeout=20.0)
93
  if response.status_code == 200:
94
+ return json.loads(response.json().get('response', '{}'))
 
95
  except Exception as e:
96
  logger.error(f"Local Fallback Failed: {e}")
97
  return None
 
117
 
118
  sys_prompt = f"""Role: Elite Growth Strategist. Target: {prefix} {owner_name}.
119
  RULES:
120
+ 1. NO FABRICATION: Use metrics: {json.dumps(m)}.
121
  2. CITATION: Include verbatim source from {json.dumps(payload)}.
122
  3. WHY ACT: Link {trigger.get('kind')} to revenue loss or business growth.
123
+ 4. MERCHANT FIT: Locality {ident.get('locality')}. {hinglish_note}
124
  5. LENGTH: 280-310 chars.
125
 
126
  JSON: {{"body": "...", "cta": "...", "rationale": "..."}}"""
 
137
 
138
  @app.get("/v1/healthz")
139
  async def healthz():
 
140
  async with httpx.AsyncClient() as client:
141
  try:
142
  res = await client.get("http://127.0.0.1:11434/api/tags")
143
+ if "smollm2:1.7b" in res.text: return {"status": "ok", "local_model": "ready"}
144
  except: pass
145
+ return {"status": "starting", "message": "Ollama/SmollM2 warming up"}
146
 
147
  @app.get("/v1/metadata")
148
+ def metadata(): return {"team_name": "Vera Lead Solver", "version": "Platinum-Unstoppable-v2"}
149
 
150
  @app.post("/v1/context")
151
  def push_context(data: ContextPayload):
 
162
  return {
163
  "conversation_id": f"conv_{trigger_id}", "merchant_id": trigger.get("merchant_id"),
164
  "customer_id": trigger.get("customer_id"), "send_as": "merchant_on_behalf" if trigger.get("scope") == "customer" else "vera",
165
+ "trigger_id": trigger_id, "template_name": "vera_unstoppable_v2", "body": composed["body"],
166
  "cta": composed["cta"], "suppression_key": trigger_id, "rationale": composed.get("rationale")
167
  }
168
 
169
  @app.post("/v1/tick")
170
  async def tick(req: TickRequest):
171
+ # Sort triggers by priority
172
  tids = sorted(req.available_triggers, key=lambda t: PRIORITY_MAP.get(contexts["trigger"].get(t, {}).get("kind"), 0), reverse=True)
173
+
174
+ # Take top 3 to ensure we finish under 30s
175
+ tasks = [process_trigger(tid) for tid in tids[:3]]
176
+
177
+ try:
178
+ # 25s total deadline for the entire tick call
179
+ results = await asyncio.wait_for(asyncio.gather(*tasks), timeout=25.0)
180
+ return {"actions": [r for r in results if r]}
181
+ except asyncio.TimeoutError:
182
+ logger.warning("Tick timed out partially. Returning partial results.")
183
+ return {"actions": []}
184
 
185
  @app.post("/v1/reply")
186
  async def reply(req: Dict[str, Any]):
187
+ # Use call_llm for replies too
188
+ prompt = f"Merchant said: {req.get('body', '')}. History: {req.get('history', [])}"
189
+ sys = "Role: Vera AI. Goal: Help merchant join magicpin or fix GBP. Be helpful, concise."
190
+ res = await call_llm(prompt, system=sys)
191
+ body = res.get("body", "Understood. Let's move forward. Ready?") if res else "I'm on it. Should we proceed?"
192
+ return {"action": "send", "body": body, "cta": "Reply YES"}
193
 
194
  if __name__ == "__main__":
195
  import uvicorn