IDKHowToCodeFr commited on
Commit
2dc82f8
·
1 Parent(s): 6b1caf3

deleted bload and updated judge sim and gitignore

Browse files
Files changed (7) hide show
  1. .gitignore +1 -0
  2. judge_simulator.py +3 -3
  3. knowledge.md +0 -26
  4. knowledge.py +0 -20
  5. main.py +0 -188
  6. main_backup.py +0 -197
  7. main_copy.py +0 -188
.gitignore CHANGED
@@ -9,3 +9,4 @@ judge_output.txt
9
  .vscode/
10
  .idea/
11
  *.log
 
 
9
  .vscode/
10
  .idea/
11
  *.log
12
+ expanded/
judge_simulator.py CHANGED
@@ -24,19 +24,19 @@ Author: magicpin AI Challenge Team
24
  BOT_URL = "http://localhost:8080"
25
 
26
  # Choose your LLM provider: "openai", "anthropic", "gemini", "deepseek", "groq", "ollama", "openrouter"
27
- LLM_PROVIDER = "openai"
28
 
29
  # Your API key (paste your key here)
30
  LLM_API_KEY = "" # <-- PUT YOUR API KEY HERE
31
 
32
  # Model to use (leave empty for default, or specify like "gpt-4o", "claude-3-5-sonnet-20241022", etc.)
33
- LLM_MODEL = "" # <-- Optional: specify model or leave empty for default
34
 
35
  # For Ollama only: local server URL
36
  OLLAMA_URL = "http://localhost:11434"
37
 
38
  # Which test to run by default
39
- TEST_SCENARIO = "all"
40
 
41
  # =============================================================================
42
  # ██████ END OF CONFIGURATION - DON'T EDIT BELOW THIS LINE ██████
 
24
  BOT_URL = "http://localhost:8080"
25
 
26
  # Choose your LLM provider: "openai", "anthropic", "gemini", "deepseek", "groq", "ollama", "openrouter"
27
+ LLM_PROVIDER = "ollama"
28
 
29
  # Your API key (paste your key here)
30
  LLM_API_KEY = "" # <-- PUT YOUR API KEY HERE
31
 
32
  # Model to use (leave empty for default, or specify like "gpt-4o", "claude-3-5-sonnet-20241022", etc.)
33
+ LLM_MODEL = "llama3.2:latest" # <-- Optional: specify model or leave empty for default
34
 
35
  # For Ollama only: local server URL
36
  OLLAMA_URL = "http://localhost:11434"
37
 
38
  # Which test to run by default
39
+ TEST_SCENARIO = "full_evaluation"
40
 
41
  # =============================================================================
42
  # ██████ END OF CONFIGURATION - DON'T EDIT BELOW THIS LINE ██████
knowledge.md DELETED
@@ -1,26 +0,0 @@
1
- # Knowledge Base: Vera Lead Solver
2
-
3
- ## Failure Pattern Audit (2026-05-02)
4
-
5
- ### 1. Metric Fabrication (Grounding Failure)
6
- - **Pattern**: Bot invented "45 directions" and "0.18% view increase" to sound specific.
7
- - **Deduction**: Specificity score dropped to 6/10.
8
- - **Fix**: Sanitize context and provide a "Whitelist of Grounded Metrics". Warn against inventing secondary metrics.
9
-
10
- ### 2. Language Imbalance
11
- - **Pattern**: When Hinglish is requested, the bot writes "mostly in Hindi", which the judge penalizes for not being a proper "mix".
12
- - **Fix**: Instruct bot to use 60% English / 40% Hindi for Hinglish.
13
-
14
- ### 3. Abrupt Tones
15
- - **Pattern**: Short messages often sound too formal or clinical without enough "warmth" or "persuasion".
16
- - **Fix**: Use specific persuasive triggers: loss aversion ("losing 5% weekly"), peer proof ("other clinics in [Locality]"), or scarcity.
17
-
18
- ### 4. Decision Quality Voids
19
- - **Pattern**: Bot mentions the trigger but doesn't explain the *financial* or *business* impact.
20
- - **Fix**: Prompt bot to explicitly state "WHY ACT NOW" (e.g., "Non-compliance risks clinic closure" or "Recall improves LTV").
21
-
22
- ## Technical Strategy for 50/50
23
- - **Deterministic Metrics**: Only allow metrics explicitly found in `perf` or `payload`.
24
- - **Character Buffer**: Hard-coded pruning to 300 chars to avoid truncation penalty.
25
- - **Persona**: "Elite Growth Strategist" - peer of the merchant, but authoritative.
26
- - **Citations**: Verbatim inclusion of sources is mandatory.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
knowledge.py DELETED
@@ -1,20 +0,0 @@
1
-
2
- # High-Score Block (9/10)
3
- # Trigger: performance update
4
- # Message: Dr. Meera, your clinic had 2410 views & 18 calls in 30 days. Fluoride research is surging. Act now to capture demand.
5
-
6
- # High-Score Block (9/10)
7
- # Trigger: performance update
8
- # Message: Dr. Meera, your clinic had 2410 views, 18 calls & 45 directions in 30 days. You're losing 5% calls weekly. Launch Fluoride offer now.
9
-
10
- # High-Score Block (9/10)
11
- # Trigger: performance update
12
- # Message: Dr. Meera, your clinic had 2410 views, 18 calls & 45 directions in 30 days. You're losing 5% calls weekly. Fluoride research is surging with 18% increase in views.
13
-
14
- # High-Score Block (9/10)
15
- # Trigger: performance update
16
- # Message: Dr. Meera, your clinic had 2410 views, 18 calls & 45 directions in 30 days. You're losing 5% calls weekly.
17
-
18
- # High-Score Block (9/10)
19
- # Trigger: performance update
20
- # Message: Dr. Meera, your clinic had 2410 views, 18 calls & 45 directions in 30 days. You're losing 5% calls weekly with an 18% surge in views.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py DELETED
@@ -1,188 +0,0 @@
1
- # BEST SCORE: 41/50 (82%) - EXCELLENT
2
- # Model: Gemini-2.0-Flash-001 (via OpenRouter)
3
- # Tested on: 2026-05-02
4
- # Features: Strict grounding, Trigger primacy, Hinglish support, Source citations.
5
-
6
- import os
7
- import json
8
- import logging
9
- import asyncio
10
- import time
11
- import re
12
- from typing import List, Dict, Any, Optional
13
- from fastapi import FastAPI
14
- from pydantic import BaseModel
15
- import httpx
16
- from dotenv import load_dotenv
17
-
18
- load_dotenv()
19
-
20
- # Lead AI Problem Solver: Sequential Reinforcement Architecture
21
- logging.basicConfig(level=logging.INFO)
22
- logger = logging.getLogger("VeraLeadSolver")
23
-
24
- app = FastAPI()
25
-
26
- class ContextPayload(BaseModel):
27
- scope: str
28
- context_id: str
29
- version: int
30
- payload: Dict[Any, Any]
31
-
32
- class TickRequest(BaseModel):
33
- available_triggers: List[str]
34
-
35
- # Distributed Credential Strategy: Round-Robin Key Pool
36
- OPENROUTER_KEYS = [
37
- os.getenv("OPENROUTER_KEY_1"),
38
- os.getenv("OPENROUTER_KEY_2"),
39
- os.getenv("OPENROUTER_KEY_3"),
40
- os.getenv("OPENROUTER_KEY_4"),
41
- os.getenv("OPENROUTER_KEY_5"),
42
- os.getenv("OPENROUTER_KEY_6")
43
- ]
44
- OPENROUTER_KEYS = [k for k in OPENROUTER_KEYS if k]
45
-
46
- class KeyRotator:
47
- def __init__(self, keys: List[str]):
48
- self.keys = keys
49
- self.index = 0
50
- self.lock = asyncio.Lock()
51
-
52
- async def get_key(self) -> str:
53
- if not self.keys:
54
- return ""
55
- async with self.lock:
56
- key = self.keys[self.index]
57
- self.index = (self.index + 1) % len(self.keys)
58
- return key
59
-
60
- or_rotator = KeyRotator(OPENROUTER_KEYS)
61
-
62
- contexts = {"category": {}, "merchant": {}, "customer": {}, "trigger": {}}
63
- semaphore = asyncio.Semaphore(1) # STRICT SEQUENTIAL
64
-
65
- async def call_llm(prompt: str, system: str = "", temperature: float = 0.0) -> Optional[Dict]:
66
- key = await or_rotator.get_key()
67
- url = "https://openrouter.ai/api/v1/chat/completions"
68
- headers = {
69
- "Authorization": f"Bearer {key}",
70
- "Content-Type": "application/json",
71
- "HTTP-Referer": "https://vera.ai",
72
- "X-Title": "Vera Lead Solver"
73
- }
74
- # Using openrouter/free to get the best free model
75
- payload = {
76
- "model": "openrouter/free",
77
- "messages": [
78
- {"role": "system", "content": system + "\n\nCRITICAL: Return ONLY valid JSON. No markdown, no preambles."},
79
- {"role": "user", "content": prompt}
80
- ],
81
- "temperature": temperature,
82
- "max_tokens": 1000
83
- }
84
-
85
- async with httpx.AsyncClient() as client:
86
- try:
87
- response = await client.post(url, headers=headers, json=payload, timeout=60.0)
88
- if response.status_code == 200:
89
- resp_json = response.json()
90
- content = resp_json["choices"][0]["message"].get("content")
91
- if not content:
92
- return None
93
- content = re.sub(r'```json\s*|\s*```', '', content).strip()
94
- start = content.find('{')
95
- end = content.rfind('}')
96
- if start != -1 and end != -1:
97
- content = content[start:end+1]
98
- return json.loads(content)
99
- else:
100
- logger.error(f"LLM Error {response.status_code}: {response.text}")
101
- return None
102
- except Exception as e:
103
- logger.error(f"LLM Exception: {e}")
104
- return None
105
-
106
- async def compose(category: dict, merchant: dict, trigger: dict, customer: Optional[dict] = None) -> dict:
107
- ident = merchant.get("identity", {})
108
- perf = merchant.get("performance", {})
109
- owner_name = ident.get("owner_first_name", "Partner")
110
-
111
- sys_prompt = f"""Role: Elite Growth Strategist for magicpin.
112
- Objective: Compose a high-impact WhatsApp message.
113
-
114
- STRICT GROUNDING RULES:
115
- 1. NO FABRICATION: Only use numbers present in context.
116
- 2. TRIGGER PRIMACY: Message MUST address the trigger event first.
117
- 3. SPECIFICITY: Use metrics like views, calls, directions, or CTR from Context.
118
- 4. CATEGORY FIT: For dentists, use "Dr." and clinical peer tone.
119
- 5. LANGUAGE: If 'hi' in languages, use Hinglish.
120
- 6. SOURCE: Include source citations (e.g. "JIDA Oct") if present.
121
-
122
- Output JSON: {{"body": "...", "cta": "...", "rationale": "..."}}
123
- """
124
-
125
- context_str = f"CATEGORY: {json.dumps(category)}\nMERCHANT: {json.dumps(merchant)}\nTRIGGER: {json.dumps(trigger)}\nCUSTOMER: {json.dumps(customer) if customer else 'None'}"
126
-
127
- res = await call_llm(f"Context: {context_str}\n\nCompose a grounded message based on trigger.", system=sys_prompt)
128
- if not res:
129
- v = perf.get('views', '?')
130
- c = perf.get('calls', '?')
131
- return {"body": f"Hi {owner_name}, noticed {v} views and {c} calls on your profile. Important {trigger.get('kind')} update inside. Check?", "cta": "Reply YES", "rationale": "Fallback"}
132
-
133
- return res
134
-
135
- @app.get("/v1/healthz")
136
- def healthz():
137
- return {"status": "ok", "contexts_loaded": {s: len(c) for s, c in contexts.items()}}
138
-
139
- @app.get("/v1/metadata")
140
- def metadata():
141
- return {"team_name": "Vera Lead Solver", "model": "OpenRouter-Free", "version": "26.0.0"}
142
-
143
- @app.post("/v1/context")
144
- def push_context(data: ContextPayload):
145
- contexts[data.scope][data.context_id] = data.payload
146
- return {"accepted": True}
147
-
148
- async def process_trigger(trigger_id: str):
149
- async with semaphore:
150
- trigger = contexts["trigger"].get(trigger_id, {})
151
- merchant_id = trigger.get("merchant_id")
152
- merchant = contexts["merchant"].get(merchant_id, {})
153
- category_slug = merchant.get("category_slug", "general")
154
- category = contexts["category"].get(category_slug, {"slug": category_slug})
155
- customer_id = trigger.get("customer_id")
156
- customer = contexts["customer"].get(customer_id, {}) if customer_id else None
157
-
158
- composed_data = await compose(category, merchant, trigger, customer)
159
- is_customer_facing = trigger.get("scope") == "customer"
160
-
161
- return {
162
- "conversation_id": f"conv_{trigger_id}",
163
- "merchant_id": merchant_id,
164
- "customer_id": customer_id if is_customer_facing else None,
165
- "send_as": "merchant_on_behalf" if is_customer_facing else "vera",
166
- "trigger_id": trigger_id,
167
- "template_name": "vera_v26",
168
- "body": composed_data["body"],
169
- "cta": composed_data["cta"],
170
- "suppression_key": trigger_id,
171
- "rationale": composed_data.get("rationale", "Free Router composition")
172
- }
173
-
174
- @app.post("/v1/tick")
175
- async def tick(req: TickRequest):
176
- results = []
177
- for tid in req.available_triggers:
178
- res = await process_trigger(tid)
179
- if res: results.append(res)
180
- return {"actions": results}
181
-
182
- @app.post("/v1/reply")
183
- async def reply(req: Dict[str, Any]):
184
- return {"action": "send", "body": "Got it. Let me set that up for you.", "cta": "Reply YES"}
185
-
186
- if __name__ == "__main__":
187
- import uvicorn
188
- uvicorn.run(app, host="0.0.0.0", port=8080)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main_backup.py DELETED
@@ -1,197 +0,0 @@
1
- import os
2
- import json
3
- import logging
4
- import asyncio
5
- import time
6
- import re
7
- from typing import List, Dict, Any, Optional
8
- from fastapi import FastAPI, Request
9
- from pydantic import BaseModel
10
- import httpx
11
- from dotenv import load_dotenv
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
-
19
- app = FastAPI()
20
-
21
- class ContextPayload(BaseModel):
22
- scope: str
23
- context_id: str
24
- version: int
25
- payload: Dict[Any, Any]
26
-
27
- class TickRequest(BaseModel):
28
- available_triggers: List[str]
29
-
30
- # API Keys from Environment
31
- OPENROUTER_KEYS = [os.getenv(f"OPENROUTER_KEY_{i}") for i in range(1, 7)]
32
- OPENROUTER_KEYS = [k for k in OPENROUTER_KEYS if k] or [os.getenv("OPENROUTER_API_KEY")]
33
- OPENROUTER_KEYS = [k for k in OPENROUTER_KEYS if k]
34
-
35
- class KeyRotator:
36
- def __init__(self, keys: List[str]):
37
- self.keys, self.index, self.lock = keys, 0, asyncio.Lock()
38
- async def get_key(self) -> str:
39
- if not self.keys: return ""
40
- async with self.lock:
41
- key = self.keys[self.index]
42
- self.index = (self.index + 1) % len(self.keys)
43
- return key
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,
53
- "recall_due": 80, "high_churn": 70, "competitor_action": 65, "perf_spike": 60
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",
64
- "messages": [
65
- {"role": "system", "content": system + "\n\nRETURN JSON ONLY. NO MARKDOWN. NO FABRICATION."},
66
- {"role": "user", "content": prompt}
67
- ],
68
- "temperature": 0.0,
69
- "response_format": {"type": "json_object"}
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
98
-
99
- def prune_message(text: str) -> str:
100
- text = text.replace("\n", " ").strip()
101
- if len(text) <= 315: return text
102
- match = re.findall(r'.*?[.!?]', text)
103
- res = ""
104
- for m in match:
105
- if len(res) + len(m) <= 312: res += m
106
- else: break
107
- return res if res else text[:309] + "..."
108
-
109
- async def compose(category: dict, merchant: dict, trigger: dict, customer: Optional[dict] = None) -> dict:
110
- ident, perf = merchant.get("identity", {}), merchant.get("performance", {})
111
- owner_name = ident.get("owner_first_name", "Partner")
112
- m = {k: v for k, v in perf.items() if v and v != "0" and v != 0 and v != "?"}
113
- payload = trigger.get("payload", {})
114
- prefix = "Dr." if category.get('slug') == 'dentists' else ""
115
- lang_pref = ident.get('languages', ['en'])
116
- hinglish_note = "USE NATURAL HINGLISH (50% English words, 50% Hindi script/transliteration)." if 'hi' in lang_pref else "USE PROFESSIONAL ENGLISH."
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": "..."}}"""
127
-
128
- prompt = f"CONTEXT: {json.dumps({'merchant': ident, 'perf': m, 'trigger': trigger})}"
129
- res = await call_llm(prompt, system=sys_prompt)
130
- if not res:
131
- v, c = m.get('views', '2410'), m.get('calls', '18')
132
- body = f"{prefix} {owner_name}, noticed {v} views and {c} calls in {ident.get('locality')}. Found critical {trigger.get('kind')} update. Potential 12% revenue lift. Review now?"
133
- res = {"body": body, "cta": "Reply YES", "rationale": "Hard Fallback"}
134
-
135
- res["body"] = prune_message(res.get("body", ""))
136
- return res
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):
152
- contexts[data.scope][data.context_id] = data.payload
153
- return {"accepted": True}
154
-
155
- async def process_trigger(trigger_id: str):
156
- trigger = contexts["trigger"].get(trigger_id, {})
157
- if not trigger: return None
158
- merchant = contexts["merchant"].get(trigger.get("merchant_id"), {})
159
- category = contexts["category"].get(merchant.get("category_slug", ""), {"slug": "general"})
160
- customer = contexts["customer"].get(trigger.get("customer_id"))
161
- composed = await compose(category, merchant, trigger, customer)
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
196
- port = int(os.environ.get("PORT", 8086))
197
- uvicorn.run(app, host="0.0.0.0", port=port)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main_copy.py DELETED
@@ -1,188 +0,0 @@
1
- # BEST SCORE: 41/50 (82%) - EXCELLENT
2
- # Model: Gemini-2.0-Flash-001 (via OpenRouter)
3
- # Tested on: 2026-05-02
4
- # Features: Strict grounding, Trigger primacy, Hinglish support, Source citations.
5
-
6
- import os
7
- import json
8
- import logging
9
- import asyncio
10
- import time
11
- import re
12
- from typing import List, Dict, Any, Optional
13
- from fastapi import FastAPI
14
- from pydantic import BaseModel
15
- import httpx
16
- from dotenv import load_dotenv
17
-
18
- load_dotenv()
19
-
20
- # Lead AI Problem Solver: Sequential Reinforcement Architecture
21
- logging.basicConfig(level=logging.INFO)
22
- logger = logging.getLogger("VeraLeadSolver")
23
-
24
- app = FastAPI()
25
-
26
- class ContextPayload(BaseModel):
27
- scope: str
28
- context_id: str
29
- version: int
30
- payload: Dict[Any, Any]
31
-
32
- class TickRequest(BaseModel):
33
- available_triggers: List[str]
34
-
35
- # Distributed Credential Strategy: Round-Robin Key Pool
36
- OPENROUTER_KEYS = [
37
- os.getenv("OPENROUTER_KEY_1"),
38
- os.getenv("OPENROUTER_KEY_2"),
39
- os.getenv("OPENROUTER_KEY_3"),
40
- os.getenv("OPENROUTER_KEY_4"),
41
- os.getenv("OPENROUTER_KEY_5"),
42
- os.getenv("OPENROUTER_KEY_6")
43
- ]
44
- OPENROUTER_KEYS = [k for k in OPENROUTER_KEYS if k]
45
-
46
- class KeyRotator:
47
- def __init__(self, keys: List[str]):
48
- self.keys = keys
49
- self.index = 0
50
- self.lock = asyncio.Lock()
51
-
52
- async def get_key(self) -> str:
53
- if not self.keys:
54
- return ""
55
- async with self.lock:
56
- key = self.keys[self.index]
57
- self.index = (self.index + 1) % len(self.keys)
58
- return key
59
-
60
- or_rotator = KeyRotator(OPENROUTER_KEYS)
61
-
62
- contexts = {"category": {}, "merchant": {}, "customer": {}, "trigger": {}}
63
- semaphore = asyncio.Semaphore(1) # STRICT SEQUENTIAL
64
-
65
- async def call_llm(prompt: str, system: str = "", temperature: float = 0.0) -> Optional[Dict]:
66
- key = await or_rotator.get_key()
67
- url = "https://openrouter.ai/api/v1/chat/completions"
68
- headers = {
69
- "Authorization": f"Bearer {key}",
70
- "Content-Type": "application/json",
71
- "HTTP-Referer": "https://vera.ai",
72
- "X-Title": "Vera Lead Solver"
73
- }
74
- # Using openrouter/free to get the best free model
75
- payload = {
76
- "model": "openrouter/free",
77
- "messages": [
78
- {"role": "system", "content": system + "\n\nCRITICAL: Return ONLY valid JSON. No markdown, no preambles."},
79
- {"role": "user", "content": prompt}
80
- ],
81
- "temperature": temperature,
82
- "max_tokens": 1000
83
- }
84
-
85
- async with httpx.AsyncClient() as client:
86
- try:
87
- response = await client.post(url, headers=headers, json=payload, timeout=60.0)
88
- if response.status_code == 200:
89
- resp_json = response.json()
90
- content = resp_json["choices"][0]["message"].get("content")
91
- if not content:
92
- return None
93
- content = re.sub(r'```json\s*|\s*```', '', content).strip()
94
- start = content.find('{')
95
- end = content.rfind('}')
96
- if start != -1 and end != -1:
97
- content = content[start:end+1]
98
- return json.loads(content)
99
- else:
100
- logger.error(f"LLM Error {response.status_code}: {response.text}")
101
- return None
102
- except Exception as e:
103
- logger.error(f"LLM Exception: {e}")
104
- return None
105
-
106
- async def compose(category: dict, merchant: dict, trigger: dict, customer: Optional[dict] = None) -> dict:
107
- ident = merchant.get("identity", {})
108
- perf = merchant.get("performance", {})
109
- owner_name = ident.get("owner_first_name", "Partner")
110
-
111
- sys_prompt = f"""Role: Elite Growth Strategist for magicpin.
112
- Objective: Compose a high-impact WhatsApp message.
113
-
114
- STRICT GROUNDING RULES:
115
- 1. NO FABRICATION: Only use numbers present in context.
116
- 2. TRIGGER PRIMACY: Message MUST address the trigger event first.
117
- 3. SPECIFICITY: Use metrics like views, calls, directions, or CTR from Context.
118
- 4. CATEGORY FIT: For dentists, use "Dr." and clinical peer tone.
119
- 5. LANGUAGE: If 'hi' in languages, use Hinglish.
120
- 6. SOURCE: Include source citations (e.g. "JIDA Oct") if present.
121
-
122
- Output JSON: {{"body": "...", "cta": "...", "rationale": "..."}}
123
- """
124
-
125
- context_str = f"CATEGORY: {json.dumps(category)}\nMERCHANT: {json.dumps(merchant)}\nTRIGGER: {json.dumps(trigger)}\nCUSTOMER: {json.dumps(customer) if customer else 'None'}"
126
-
127
- res = await call_llm(f"Context: {context_str}\n\nCompose a grounded message based on trigger.", system=sys_prompt)
128
- if not res:
129
- v = perf.get('views', '?')
130
- c = perf.get('calls', '?')
131
- return {"body": f"Hi {owner_name}, noticed {v} views and {c} calls on your profile. Important {trigger.get('kind')} update inside. Check?", "cta": "Reply YES", "rationale": "Fallback"}
132
-
133
- return res
134
-
135
- @app.get("/v1/healthz")
136
- def healthz():
137
- return {"status": "ok", "contexts_loaded": {s: len(c) for s, c in contexts.items()}}
138
-
139
- @app.get("/v1/metadata")
140
- def metadata():
141
- return {"team_name": "Vera Lead Solver", "model": "OpenRouter-Free", "version": "26.0.0"}
142
-
143
- @app.post("/v1/context")
144
- def push_context(data: ContextPayload):
145
- contexts[data.scope][data.context_id] = data.payload
146
- return {"accepted": True}
147
-
148
- async def process_trigger(trigger_id: str):
149
- async with semaphore:
150
- trigger = contexts["trigger"].get(trigger_id, {})
151
- merchant_id = trigger.get("merchant_id")
152
- merchant = contexts["merchant"].get(merchant_id, {})
153
- category_slug = merchant.get("category_slug", "general")
154
- category = contexts["category"].get(category_slug, {"slug": category_slug})
155
- customer_id = trigger.get("customer_id")
156
- customer = contexts["customer"].get(customer_id, {}) if customer_id else None
157
-
158
- composed_data = await compose(category, merchant, trigger, customer)
159
- is_customer_facing = trigger.get("scope") == "customer"
160
-
161
- return {
162
- "conversation_id": f"conv_{trigger_id}",
163
- "merchant_id": merchant_id,
164
- "customer_id": customer_id if is_customer_facing else None,
165
- "send_as": "merchant_on_behalf" if is_customer_facing else "vera",
166
- "trigger_id": trigger_id,
167
- "template_name": "vera_v26",
168
- "body": composed_data["body"],
169
- "cta": composed_data["cta"],
170
- "suppression_key": trigger_id,
171
- "rationale": composed_data.get("rationale", "Free Router composition")
172
- }
173
-
174
- @app.post("/v1/tick")
175
- async def tick(req: TickRequest):
176
- results = []
177
- for tid in req.available_triggers:
178
- res = await process_trigger(tid)
179
- if res: results.append(res)
180
- return {"actions": results}
181
-
182
- @app.post("/v1/reply")
183
- async def reply(req: Dict[str, Any]):
184
- return {"action": "send", "body": "Got it. Let me set that up for you.", "cta": "Reply YES"}
185
-
186
- if __name__ == "__main__":
187
- import uvicorn
188
- uvicorn.run(app, host="0.0.0.0", port=8086)