Spaces:
Paused
Paused
File size: 4,030 Bytes
ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc 8c7fe70 ef271cc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | """
gemini_client.py β API client menggunakan PuruBoy NoteGPT API (SSE streaming).
Endpoint: https://puruboy-api.vercel.app/api/ai/notegpt
Payload : {"prompt": "<teks>"}
Format SSE:
data: {"text": "..."} β akumulasi teks
data: {"text": "", "done": true} β sinyal hampir selesai (mungkin ada teks terakhir)
data: {"type": "finish"} β akhir stream sesungguhnya
"""
from __future__ import annotations
import json
import time
import urllib.request
import urllib.error
GEMINI_API_URL = "https://puruboy-api.vercel.app/api/ai/notegpt"
DEFAULT_MODEL = "notegpt"
class GeminiAPIError(Exception):
pass
def _parse_sse_response(resp) -> str:
"""
Baca SSE stream, akumulasi semua chunk text.
Validasi:
- Jika done/finish diterima tapi teks kosong β raise GeminiAPIError (trigger retry).
"""
accumulated = []
for raw_line in resp:
line = raw_line.decode("utf-8", errors="replace").rstrip("\r\n")
if not line.startswith("data:"):
continue
data_str = line[len("data:"):].strip()
if not data_str:
continue
try:
event = json.loads(data_str)
except json.JSONDecodeError:
continue
# Akumulasi teks
text_chunk = event.get("text", "")
if text_chunk:
accumulated.append(text_chunk)
# Cek sinyal selesai
is_done = event.get("done", False)
is_finish = event.get("type") == "finish"
if is_done or is_finish:
if not accumulated:
raise GeminiAPIError(
"API mengirim sinyal selesai tapi tidak ada konten teks "
f"({'done=true' if is_done else 'type=finish'})"
)
if is_finish:
break
result = "".join(accumulated).strip()
if not result:
raise GeminiAPIError("Respons API kosong (tidak ada teks terakumulasi)")
return result
def call_gemini(prompt: str, model: str = DEFAULT_MODEL) -> str:
"""
Kirim prompt ke PuruBoy NoteGPT API (SSE) dengan Exponential Backoff.
Retry jika:
- Error jaringan / HTTP error
- Respons kosong atau done/finish tanpa konten
"""
payload = json.dumps({"prompt": prompt}).encode("utf-8")
max_retries = 5
delay = 4
for attempt in range(max_retries):
req = urllib.request.Request(
GEMINI_API_URL,
data=payload,
headers={
"Content-Type": "application/json",
"User-Agent": "GeminiClaw/3.0",
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=90) as resp:
result = _parse_sse_response(resp)
return result
except GeminiAPIError as e:
if attempt == max_retries - 1:
raise GeminiAPIError(f"Gagal setelah {max_retries} percobaan: {e}")
print(f"[Percobaan {attempt+1}] Gagal: {e}. Retry dalam {delay}s...")
time.sleep(delay)
delay = min(delay * 2, 60)
except urllib.error.HTTPError as e:
err_body = ""
try:
err_body = e.read().decode("utf-8", errors="replace")[:200]
except Exception:
pass
msg = f"HTTP {e.code}: {err_body}"
if attempt == max_retries - 1:
raise GeminiAPIError(f"Gagal setelah {max_retries} percobaan: {msg}")
print(f"[Percobaan {attempt+1}] {msg}. Retry dalam {delay}s...")
time.sleep(delay)
delay = min(delay * 2, 60)
except urllib.error.URLError as e:
msg = str(e.reason)
if attempt == max_retries - 1:
raise GeminiAPIError(f"Gagal setelah {max_retries} percobaan: {msg}")
print(f"[Percobaan {attempt+1}] URLError: {msg}. Retry dalam {delay}s...")
time.sleep(delay)
delay = min(delay * 2, 60)
|