Update handler.py
Browse files- handler.py +82 -80
handler.py
CHANGED
|
@@ -1,21 +1,20 @@
|
|
| 1 |
-
# handler.py —
|
| 2 |
|
| 3 |
-
from typing import Any, Dict,
|
| 4 |
-
import os
|
|
|
|
| 5 |
import google.generativeai as genai
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
|
| 14 |
def _extract_text(resp: Any) -> str:
|
| 15 |
-
# 1) standard property
|
| 16 |
if getattr(resp, "text", None):
|
| 17 |
return resp.text
|
| 18 |
-
# 2) candidates/parts
|
| 19 |
try:
|
| 20 |
for c in getattr(resp, "candidates", []) or []:
|
| 21 |
content = getattr(c, "content", None)
|
|
@@ -27,93 +26,96 @@ def _extract_text(resp: Any) -> str:
|
|
| 27 |
pass
|
| 28 |
return ""
|
| 29 |
|
| 30 |
-
def _last_user_from_messages(msgs: List[Dict[str, Any]]) -> str:
|
| 31 |
-
for m in reversed(msgs or []):
|
| 32 |
-
if (m.get("role") or "user").lower() == "user":
|
| 33 |
-
return str(m.get("content", "")).strip()
|
| 34 |
-
return ""
|
| 35 |
-
|
| 36 |
class EndpointHandler:
|
| 37 |
def __init__(self, path: str = ""):
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
| 42 |
return
|
| 43 |
-
self._init_error = None
|
| 44 |
-
genai.configure(api_key=api_key)
|
| 45 |
|
| 46 |
-
#
|
| 47 |
-
self.model = genai.GenerativeModel(MODEL, system_instruction=SYSTEM_PROMPT)
|
| 48 |
-
|
| 49 |
-
# Optional: slightly relaxed safety to avoid silent blocks of normal prompts
|
| 50 |
-
self.safety_settings = None
|
| 51 |
try:
|
| 52 |
-
|
| 53 |
-
self.
|
| 54 |
-
HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH,
|
| 55 |
-
HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
|
| 56 |
-
HarmCategory.HARM_CATEGORY_SEXUAL: HarmBlockThreshold.BLOCK_ONLY_HIGH,
|
| 57 |
-
HarmCategory.HARM_CATEGORY_DANGEROUS: HarmBlockThreshold.BLOCK_ONLY_HIGH,
|
| 58 |
-
}
|
| 59 |
except Exception as e:
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
| 65 |
-
if self._init_error:
|
| 66 |
-
return {"text": "", "debug": {"error": self._init_error}}
|
| 67 |
|
|
|
|
| 68 |
try:
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
# B) {"inputs":{"messages":[{"role":"user","content":"..."}]}}
|
| 76 |
-
# (compat) Also accept top-level "messages" if present.
|
| 77 |
-
if isinstance(inputs, str):
|
| 78 |
-
user_text = inputs.strip()
|
| 79 |
-
elif isinstance(inputs, dict) and "messages" in inputs:
|
| 80 |
-
user_text = _last_user_from_messages(inputs.get("messages"))
|
| 81 |
-
elif "messages" in data:
|
| 82 |
-
user_text = _last_user_from_messages(data.get("messages"))
|
| 83 |
-
else:
|
| 84 |
-
user_text = ""
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
}
|
| 94 |
|
| 95 |
-
|
|
|
|
|
|
|
| 96 |
resp = self.model.generate_content(
|
| 97 |
user_text,
|
| 98 |
-
generation_config=
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
| 100 |
)
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
# Diagnostics if empty
|
| 108 |
-
debug = {}
|
| 109 |
try:
|
| 110 |
fr = [getattr(c, "finish_reason", None) for c in (resp.candidates or [])]
|
| 111 |
-
if fr:
|
| 112 |
-
debug["finish_reasons"] = fr
|
| 113 |
except Exception:
|
| 114 |
pass
|
| 115 |
-
return {"text":
|
| 116 |
-
|
| 117 |
except Exception as e:
|
| 118 |
-
|
| 119 |
-
return {"text": "", "debug": {"exception": str(e)}}
|
|
|
|
| 1 |
+
# handler.py — self-diagnostic, always returns a clear result
|
| 2 |
|
| 3 |
+
from typing import Any, Dict, Union
|
| 4 |
+
import os, socket, json, time
|
| 5 |
+
import requests
|
| 6 |
import google.generativeai as genai
|
| 7 |
|
| 8 |
+
MODEL = os.getenv("GEMINI_MODEL", "gemini-1.5-flash") # safe default
|
| 9 |
+
TEMPERATURE = float(os.getenv("TEMPERATURE", "0.7"))
|
| 10 |
+
TOP_P = float(os.getenv("TOP_P", "0.95"))
|
| 11 |
+
MAX_TOKENS = int(os.getenv("MAX_OUTPUT_TOKENS", "512"))
|
| 12 |
+
SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT", "You are a helpful assistant.")
|
| 13 |
+
GOOGLE_HOST = "generativelanguage.googleapis.com"
|
| 14 |
|
| 15 |
def _extract_text(resp: Any) -> str:
|
|
|
|
| 16 |
if getattr(resp, "text", None):
|
| 17 |
return resp.text
|
|
|
|
| 18 |
try:
|
| 19 |
for c in getattr(resp, "candidates", []) or []:
|
| 20 |
content = getattr(c, "content", None)
|
|
|
|
| 26 |
pass
|
| 27 |
return ""
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
class EndpointHandler:
|
| 30 |
def __init__(self, path: str = ""):
|
| 31 |
+
self.diag = {"stage":"init", "ok":True, "notes":[]}
|
| 32 |
+
self.api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
|
| 33 |
+
if not self.api_key:
|
| 34 |
+
self.diag["ok"] = False
|
| 35 |
+
self.diag["error"] = "Missing GEMINI_API_KEY (Endpoint → Settings → Environment Variables)."
|
| 36 |
+
print("[diag] ENV KEY MISSING", flush=True)
|
| 37 |
return
|
|
|
|
|
|
|
| 38 |
|
| 39 |
+
# DNS test
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
try:
|
| 41 |
+
ip = socket.gethostbyname(GOOGLE_HOST)
|
| 42 |
+
self.diag["dns_ip"] = ip
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
except Exception as e:
|
| 44 |
+
self.diag["ok"] = False
|
| 45 |
+
self.diag["dns_error"] = repr(e)
|
| 46 |
+
print("[diag] DNS FAIL:", repr(e), flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
+
# HTTPS reachability (no auth) — should return 404/403 but prove TLS/egress works
|
| 49 |
try:
|
| 50 |
+
r = requests.get(f"https://{GOOGLE_HOST}/", timeout=5)
|
| 51 |
+
self.diag["https_status"] = r.status_code
|
| 52 |
+
except Exception as e:
|
| 53 |
+
self.diag["ok"] = False
|
| 54 |
+
self.diag["https_error"] = repr(e)
|
| 55 |
+
print("[diag] HTTPS FAIL:", repr(e), flush=True)
|
| 56 |
|
| 57 |
+
if not self.diag["ok"]:
|
| 58 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
try:
|
| 61 |
+
genai.configure(api_key=self.api_key)
|
| 62 |
+
self.model = genai.GenerativeModel(MODEL, system_instruction=SYSTEM_PROMPT)
|
| 63 |
+
self.diag["model_ready"] = True
|
| 64 |
+
except Exception as e:
|
| 65 |
+
self.diag["ok"] = False
|
| 66 |
+
self.diag["model_init_error"] = repr(e)
|
| 67 |
+
print("[diag] MODEL INIT FAIL:", repr(e), flush=True)
|
| 68 |
|
| 69 |
+
def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
| 70 |
+
# Normalize input
|
| 71 |
+
inputs = data.get("inputs")
|
| 72 |
+
if isinstance(inputs, str):
|
| 73 |
+
user_text = inputs.strip()
|
| 74 |
+
elif isinstance(inputs, dict) and "messages" in inputs:
|
| 75 |
+
# accept chat shape, use last user content
|
| 76 |
+
user_text = ""
|
| 77 |
+
for m in reversed(inputs.get("messages") or []):
|
| 78 |
+
if (m.get("role") or "user").lower() == "user":
|
| 79 |
+
user_text = str(m.get("content", "")).strip()
|
| 80 |
+
break
|
| 81 |
+
else:
|
| 82 |
+
user_text = ""
|
| 83 |
+
|
| 84 |
+
if not user_text:
|
| 85 |
+
return {"text":"", "debug":{"where":"handler","note":"Empty prompt received at endpoint."}}
|
| 86 |
+
|
| 87 |
+
# If init failed, return diagnostics + echo so frontend proves it works
|
| 88 |
+
if not getattr(self, "model", None):
|
| 89 |
+
return {
|
| 90 |
+
"text":"",
|
| 91 |
+
"debug":{
|
| 92 |
+
"where":"init",
|
| 93 |
+
"diag": self.diag,
|
| 94 |
+
"echo": user_text[:160]
|
| 95 |
+
}
|
| 96 |
}
|
| 97 |
|
| 98 |
+
# Try Gemini
|
| 99 |
+
try:
|
| 100 |
+
t0 = time.time()
|
| 101 |
resp = self.model.generate_content(
|
| 102 |
user_text,
|
| 103 |
+
generation_config={
|
| 104 |
+
"temperature": TEMPERATURE,
|
| 105 |
+
"top_p": TOP_P,
|
| 106 |
+
"max_output_tokens": MAX_TOKENS
|
| 107 |
+
},
|
| 108 |
)
|
| 109 |
+
dt = round((time.time() - t0)*1000)
|
| 110 |
+
txt = _extract_text(resp)
|
| 111 |
+
if txt:
|
| 112 |
+
return {"text": txt, "debug":{"latency_ms": dt}}
|
| 113 |
+
# no text -> return finish reasons for clarity
|
| 114 |
+
fr = []
|
|
|
|
|
|
|
| 115 |
try:
|
| 116 |
fr = [getattr(c, "finish_reason", None) for c in (resp.candidates or [])]
|
|
|
|
|
|
|
| 117 |
except Exception:
|
| 118 |
pass
|
| 119 |
+
return {"text":"", "debug":{"where":"gemini_empty","finish_reasons":fr,"latency_ms":dt,"echo":user_text[:160]}}
|
|
|
|
| 120 |
except Exception as e:
|
| 121 |
+
return {"text":"", "debug":{"where":"gemini_exception","exception":repr(e),"echo":user_text[:160]}}
|
|
|