refactor: stateless signatures and logging
This commit is contained in:
+13
-18
@@ -24,7 +24,8 @@ class SXNGPlugin(Plugin):
|
|||||||
self.max_tokens = int(os.getenv('GEMINI_MAX_TOKENS', 500))
|
self.max_tokens = int(os.getenv('GEMINI_MAX_TOKENS', 500))
|
||||||
self.temperature = float(os.getenv('GEMINI_TEMPERATURE', 0.2))
|
self.temperature = float(os.getenv('GEMINI_TEMPERATURE', 0.2))
|
||||||
self.base_url = os.getenv('OPENROUTER_BASE_URL', 'openrouter.ai')
|
self.base_url = os.getenv('OPENROUTER_BASE_URL', 'openrouter.ai')
|
||||||
self.secret = os.getenv('SXNG_LLM_SECRET', secrets.token_hex(32))
|
# Stable secret for multi-worker environments
|
||||||
|
self.secret = os.getenv('SXNG_LLM_SECRET') or hashlib.sha256(self.api_key.encode()).hexdigest()
|
||||||
|
|
||||||
def init(self, app):
|
def init(self, app):
|
||||||
@app.route('/gemini-stream', methods=['POST'])
|
@app.route('/gemini-stream', methods=['POST'])
|
||||||
@@ -34,26 +35,16 @@ class SXNGPlugin(Plugin):
|
|||||||
q = data.get('q', '')
|
q = data.get('q', '')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ts, sig = token.split('.')
|
ts, sig = token.split('.', 1)
|
||||||
query_clean = q.strip()
|
query_clean = q.strip()
|
||||||
expected = hashlib.sha256(f"{ts}|{query_clean}|{self.secret}".encode()).hexdigest()
|
expected = hashlib.sha256(f"{ts}{query_clean}{self.secret}".encode()).hexdigest()
|
||||||
if sig != expected or (time.time() - float(ts)) > 60:
|
if sig != expected or (time.time() - float(ts)) > 60:
|
||||||
abort(403)
|
abort(403)
|
||||||
except: abort(403)
|
except: abort(403)
|
||||||
|
|
||||||
context_text = data.get('context', '')
|
context_text = data.get('context', '')
|
||||||
if not self.api_key or not q:
|
if not self.api_key or not q:
|
||||||
return Response("Error: Missing Key or Query", status=400)
|
return Response("Error: Missing Key", status=400)
|
||||||
|
|
||||||
prompt = (
|
|
||||||
f"SYSTEM: Answer USER QUERY by integrating SEARCH RESULTS with expert knowledge.\n"
|
|
||||||
f"HIERARCHY: Use RESULTS for facts/data. Use KNOWLEDGE for context/synthesis.\n"
|
|
||||||
f"CONSTRAINTS: <4 sentences | Dense information | Complete thoughts.\n"
|
|
||||||
f"FALLBACK: If results are empty, answer from knowledge but note the lack of sources.\n\n"
|
|
||||||
f"SEARCH RESULTS:\n{context_text}\n\n"
|
|
||||||
f"USER QUERY: {q}\n\n"
|
|
||||||
f"ANSWER:"
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_gemini():
|
def generate_gemini():
|
||||||
host = "generativelanguage.googleapis.com"
|
host = "generativelanguage.googleapis.com"
|
||||||
@@ -63,7 +54,9 @@ class SXNGPlugin(Plugin):
|
|||||||
payload = {"contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"maxOutputTokens": self.max_tokens, "temperature": self.temperature}}
|
payload = {"contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"maxOutputTokens": self.max_tokens, "temperature": self.temperature}}
|
||||||
conn.request("POST", path, body=json.dumps(payload), headers={"Content-Type": "application/json"})
|
conn.request("POST", path, body=json.dumps(payload), headers={"Content-Type": "application/json"})
|
||||||
res = conn.getresponse()
|
res = conn.getresponse()
|
||||||
if res.status != 200: return
|
if res.status != 200:
|
||||||
|
logger.error(f"Gemini API Error {res.status}: {res.read().decode('utf-8')}")
|
||||||
|
return
|
||||||
|
|
||||||
decoder = json.JSONDecoder()
|
decoder = json.JSONDecoder()
|
||||||
buffer = ""
|
buffer = ""
|
||||||
@@ -86,7 +79,7 @@ class SXNGPlugin(Plugin):
|
|||||||
buffer = buffer[idx:]
|
buffer = buffer[idx:]
|
||||||
except json.JSONDecodeError: break
|
except json.JSONDecodeError: break
|
||||||
conn.close()
|
conn.close()
|
||||||
except Exception: pass
|
except Exception as e: logger.error(f"Gemini Stream Exception: {e}")
|
||||||
|
|
||||||
def generate_openrouter():
|
def generate_openrouter():
|
||||||
try:
|
try:
|
||||||
@@ -106,7 +99,9 @@ class SXNGPlugin(Plugin):
|
|||||||
}
|
}
|
||||||
conn.request("POST", "/api/v1/chat/completions", body=json.dumps(payload), headers=headers)
|
conn.request("POST", "/api/v1/chat/completions", body=json.dumps(payload), headers=headers)
|
||||||
res = conn.getresponse()
|
res = conn.getresponse()
|
||||||
if res.status != 200: return
|
if res.status != 200:
|
||||||
|
logger.error(f"OpenRouter API Error {res.status}: {res.read().decode('utf-8')}")
|
||||||
|
return
|
||||||
|
|
||||||
decoder = json.JSONDecoder()
|
decoder = json.JSONDecoder()
|
||||||
buffer = ""
|
buffer = ""
|
||||||
@@ -125,7 +120,7 @@ class SXNGPlugin(Plugin):
|
|||||||
if content: yield content
|
if content: yield content
|
||||||
except: pass
|
except: pass
|
||||||
conn.close()
|
conn.close()
|
||||||
except Exception: pass
|
except Exception as e: logger.error(f"OpenRouter Stream Exception: {e}")
|
||||||
|
|
||||||
generator = generate_openrouter if self.provider == 'openrouter' else generate_gemini
|
generator = generate_openrouter if self.provider == 'openrouter' else generate_gemini
|
||||||
return Response(generator(), mimetype='text/plain', headers={'X-Accel-Buffering': 'no'})
|
return Response(generator(), mimetype='text/plain', headers={'X-Accel-Buffering': 'no'})
|
||||||
|
|||||||
Reference in New Issue
Block a user