Spaces:
Running
Running
| import os | |
| import psycopg2 | |
| import subprocess | |
| from datetime import datetime, timezone | |
| # ๐ฑ Imperial Gemini API 2026 Quota Limits (Gemini 2.5 / 3.1 Pro/Flash-Lite / etc.) | |
| # 0 means Unlimited (๋ฌด์ ํ) | |
| MODEL_LIMITS = { | |
| "gemini-3.1-flash-lite": {"rpd": 150000, "category": "ํ ์คํธ ์ถ๋ ฅ ๋ชจ๋ธ", "display_name": "Gemini 3.1 Flash-Lite"}, | |
| "gemini-3.1-pro": {"rpd": 250, "category": "ํ ์คํธ ์ถ๋ ฅ ๋ชจ๋ธ", "display_name": "Gemini 3.1 Pro"}, | |
| "gemini-3.0-flash": {"rpd": 10000, "category": "ํ ์คํธ ์ถ๋ ฅ ๋ชจ๋ธ", "display_name": "Gemini 3.0 Flash"}, | |
| "gemini-2.0-flash": {"rpd": 0, "category": "ํ ์คํธ ์ถ๋ ฅ ๋ชจ๋ธ", "display_name": "Gemini 2.0 Flash"}, | |
| "gemini-2.0-flash-lite": {"rpd": 0, "category": "ํ ์คํธ ์ถ๋ ฅ ๋ชจ๋ธ", "display_name": "Gemini 2.0 Flash Lite"}, | |
| "gemini-3.1-flash-tts-preview": {"rpd": 100, "category": "๋ฉํฐ๋ชจ๋ฌ ์์ฑ ๋ชจ๋ธ", "display_name": "Gemini 3.1 Flash TTS"}, | |
| "gemini-3-flash-live": {"rpd": 0, "category": "Live API", "display_name": "Gemini 3 Flash Live"}, | |
| "imagen-4.0-generate-001": {"rpd": 70, "category": "๋ฉํฐ๋ชจ๋ฌ ์์ฑ ๋ชจ๋ธ", "display_name": "Imagen 4.0 Pro"}, | |
| "imagen-4.0-fast-generate-001": {"rpd": 70, "category": "๋ฉํฐ๋ชจ๋ฌ ์์ฑ ๋ชจ๋ธ", "display_name": "Imagen 4.0 Fast"}, | |
| "gemini-3.1-flash-image-preview": {"rpd": 1000, "category": "๋ฉํฐ๋ชจ๋ฌ ์์ฑ ๋ชจ๋ธ", "display_name": "๋๋ ธ๋ฐ๋๋ 2"}, | |
| "gemini-3-pro-image-preview": {"rpd": 250, "category": "๋ฉํฐ๋ชจ๋ฌ ์์ฑ ๋ชจ๋ธ", "display_name": "๋๋ ธ๋ฐ๋๋ Pro"}, | |
| "gemini-embedding-2-preview": {"rpd": 0, "category": "๊ธฐํ ๋ชจ๋ธ", "display_name": "Gemini Embedding 2"}, | |
| "gemma-4-26b-it": {"rpd": 14400, "category": "๊ธฐํ ๋ชจ๋ธ", "display_name": "Gemma 4 26B"}, | |
| "gemma-4-31b-it": {"rpd": 14400, "category": "๊ธฐํ ๋ชจ๋ธ", "display_name": "Gemma 4 31B"}, | |
| "google-search-grounding-default": {"rpd": 5000, "category": "๊ฒ์ ๊ทธ๋ผ์ฐ๋ฉ", "display_name": "Default"}, | |
| "google-search-grounding-gemini2": {"rpd": 5000, "category": "๊ฒ์ ๊ทธ๋ผ์ฐ๋ฉ", "display_name": "Gemini 2"}, | |
| "google-search-grounding-gemini3": {"rpd": 1500, "category": "๊ฒ์ ๊ทธ๋ผ์ฐ๋ฉ", "display_name": "Gemini 3"}, | |
| } | |
| # ๐ฑ GitHub Copilot Model List with Premium Multipliers | |
| # multiplier=0 means FREE (no premium requests consumed) | |
| COPILOT_MONTHLY_BUDGET = 300 # GitHub Copilot Pro plan default | |
| COPILOT_MODEL_INFO = { | |
| "claude-sonnet-4.6": {"display_name": "Claude Sonnet 4.6", "multiplier": 1.0, "category": "Anthropic"}, | |
| "claude-sonnet-4.5": {"display_name": "Claude Sonnet 4.5", "multiplier": 1.0, "category": "Anthropic"}, | |
| "claude-opus-4.6": {"display_name": "Claude Opus 4.6", "multiplier": 3.0, "category": "Anthropic"}, | |
| "claude-opus-4.6-fast": {"display_name": "Claude Opus 4.6 (fast mode)", "multiplier": 30.0, "category": "Anthropic"}, | |
| "claude-haiku-4.5": {"display_name": "Claude Haiku 4.5", "multiplier": 0.33, "category": "Anthropic"}, | |
| "gpt-4.1": {"display_name": "GPT-4.1", "multiplier": 0.0, "category": "OpenAI"}, | |
| "gpt-4o": {"display_name": "GPT-4o", "multiplier": 0.0, "category": "OpenAI"}, | |
| "gpt-4.1-mini": {"display_name": "GPT-4.1 mini", "multiplier": 0.0, "category": "OpenAI"}, | |
| "gpt-4.1-nano": {"display_name": "GPT-4.1 nano", "multiplier": 0.0, "category": "OpenAI"}, | |
| "gpt-5.4": {"display_name": "GPT-5.4", "multiplier": 1.0, "category": "OpenAI"}, | |
| "gpt-5.4-mini": {"display_name": "GPT-5.4 mini", "multiplier": 0.33, "category": "OpenAI"}, | |
| "gpt-5.1": {"display_name": "GPT-5.1", "multiplier": 1.0, "category": "OpenAI"}, | |
| "gpt-5.2": {"display_name": "GPT-5.2", "multiplier": 1.0, "category": "OpenAI"}, | |
| "gpt-5.3-codex": {"display_name": "GPT-5.3-Codex", "multiplier": 1.0, "category": "OpenAI"}, | |
| "gpt-5.2-codex": {"display_name": "GPT-5.2-Codex", "multiplier": 1.0, "category": "OpenAI"}, | |
| "gpt-5.1-codex": {"display_name": "GPT-5.1-Codex", "multiplier": 1.0, "category": "OpenAI"}, | |
| "gpt-5.1-codex-max": {"display_name": "GPT-5.1-Codex-Max", "multiplier": 1.0, "category": "OpenAI"}, | |
| "gpt-5.1-codex-mini": {"display_name": "GPT-5.1-Codex-Mini", "multiplier": 0.33, "category": "OpenAI"}, | |
| "gpt-5-mini": {"display_name": "GPT-5 mini", "multiplier": 0.0, "category": "OpenAI"}, | |
| "o1": {"display_name": "o1", "multiplier": 1.0, "category": "OpenAI"}, | |
| "o1-mini": {"display_name": "o1-mini", "multiplier": 0.0, "category": "OpenAI"}, | |
| "o3-mini": {"display_name": "o3-mini", "multiplier": 0.0, "category": "OpenAI"}, | |
| "gemini-2.0-pro": {"display_name": "Gemini 2.0 Pro", "multiplier": 1.0, "category": "Google"}, | |
| "gemini-3-flash": {"display_name": "Gemini 3 Flash", "multiplier": 0.33, "category": "Google"}, | |
| "gemini-3-pro": {"display_name": "Gemini 3 Pro", "multiplier": 1.0, "category": "Google"}, | |
| "gemini-3.1-pro": {"display_name": "Gemini 3.1 Pro", "multiplier": 1.0, "category": "Google"}, | |
| "grok-code-fast-1": {"display_name": "Grok Code Fast 1", "multiplier": 0.25, "category": "xAI"}, | |
| "raptor-mini": {"display_name": "Raptor mini", "multiplier": 0.0, "category": "๊ธฐํ"}, | |
| } | |
| class CopilotUsageManager: | |
| """GitHub Copilot API ์ฌ์ฉ๋์ ์๊ฐ ๊ธฐ์ค์ผ๋ก ์ถ์ ํฉ๋๋ค.""" | |
| DB_PREFIX = "copilot:" | |
| def get_db_conn(): | |
| if os.environ.get("LOCAL_AGENT_MODE", "false").lower() == "true": | |
| return None | |
| return psycopg2.connect(_resolve_db_url(), connect_timeout=5) | |
| def _month_start(): | |
| now = datetime.now(timezone.utc) | |
| return now.replace(day=1).date() | |
| def track_usage(cls, model_id): | |
| db_key = cls.DB_PREFIX + model_id | |
| now_utc = datetime.now(timezone.utc) | |
| month_start = cls._month_start() | |
| conn = None | |
| cur = None | |
| try: | |
| conn = cls.get_db_conn() | |
| if not conn: | |
| return True | |
| cur = conn.cursor() | |
| cur.execute( | |
| "SELECT last_reset_date FROM taemingames.api_usage_tracker WHERE model_id = %s FOR UPDATE;", | |
| (db_key,) | |
| ) | |
| row = cur.fetchone() | |
| if not row: | |
| cur.execute( | |
| "INSERT INTO taemingames.api_usage_tracker (model_id, last_reset_date, daily_requests, daily_tokens) VALUES (%s, %s, 1, 0) ON CONFLICT DO NOTHING;", | |
| (db_key, month_start) | |
| ) | |
| cur.execute( | |
| "UPDATE taemingames.api_usage_tracker SET last_request_at = %s WHERE model_id = %s;", | |
| (now_utc, db_key) | |
| ) | |
| else: | |
| last_reset = row[0] | |
| if last_reset < month_start: | |
| cur.execute( | |
| "UPDATE taemingames.api_usage_tracker SET daily_requests=1, last_request_at=%s, last_reset_date=%s WHERE model_id=%s;", | |
| (now_utc, month_start, db_key) | |
| ) | |
| else: | |
| cur.execute( | |
| "UPDATE taemingames.api_usage_tracker SET daily_requests=daily_requests+1, last_request_at=%s WHERE model_id=%s;", | |
| (now_utc, db_key) | |
| ) | |
| conn.commit() | |
| return True | |
| except Exception as e: | |
| print(f"โ [CopilotUsageManager] track failed: {e}") | |
| return False | |
| finally: | |
| if cur: | |
| try: cur.close() | |
| except: pass | |
| if conn: | |
| try: conn.close() | |
| except: pass | |
| def get_all_usage(cls, monthly_budget=None): | |
| if monthly_budget is None: | |
| monthly_budget = COPILOT_MONTHLY_BUDGET | |
| results = [] | |
| conn = None | |
| cur = None | |
| try: | |
| prefix = cls.DB_PREFIX | |
| conn = cls.get_db_conn() | |
| if not conn: | |
| return {"models": [], "monthly_budget": monthly_budget, "total_premium_used": 0} | |
| cur = conn.cursor() | |
| cur.execute( | |
| "SELECT model_id, daily_requests, last_request_at FROM taemingames.api_usage_tracker WHERE model_id LIKE %s;", | |
| (prefix + "%",) | |
| ) | |
| rows = {r[0].replace(prefix, ""): (r[1], r[2]) for r in cur.fetchall()} | |
| total_premium = 0.0 | |
| for mid, info in COPILOT_MODEL_INFO.items(): | |
| calls, last = rows.get(mid, (0, None)) | |
| premium = round(calls * info["multiplier"], 2) | |
| total_premium += premium | |
| results.append({ | |
| "model_id": mid, | |
| "display_name": info["display_name"], | |
| "category": info["category"], | |
| "multiplier": info["multiplier"], | |
| "calls_this_month": calls, | |
| "premium_used": premium, | |
| "last_request_at": last.isoformat() if last else None, | |
| }) | |
| return { | |
| "models": results, | |
| "monthly_budget": monthly_budget, | |
| "total_premium_used": round(total_premium, 2), | |
| } | |
| except Exception as e: | |
| print(f"โ [CopilotUsageManager] get_all_usage failed: {e}") | |
| return {"models": [], "monthly_budget": monthly_budget, "total_premium_used": 0} | |
| finally: | |
| if cur: | |
| try: cur.close() | |
| except: pass | |
| if conn: | |
| try: conn.close() | |
| except: pass | |
| def _sanitize_db_url(url: str) -> str: | |
| from urllib.parse import urlparse, urlunparse, urlencode, parse_qs | |
| url = url.strip().strip('"').strip("'").strip() | |
| parsed = urlparse(url) | |
| qs = parse_qs(parsed.query, keep_blank_values=True) | |
| qs.pop("schema", None) | |
| qs.pop("search_path", None) | |
| qs.pop("connection_limit", None) | |
| clean_query = urlencode(qs, doseq=True) | |
| return urlunparse(parsed._replace(query=clean_query)) | |
| def _resolve_db_url(): | |
| url = os.environ.get("DATABASE_URL") | |
| if url: return _sanitize_db_url(url) | |
| try: | |
| result = subprocess.run( | |
| 'bw get password "Imperial: LOCAL_DATABASE_URL" --raw', | |
| capture_output=True, text=True, shell=True, timeout=10, | |
| encoding='utf-8', errors='replace' | |
| ) | |
| val = result.stdout.strip() | |
| if val and "postgresql://" in val: | |
| val = _sanitize_db_url(val) | |
| os.environ["DATABASE_URL"] = val | |
| return val | |
| except: pass | |
| return "postgresql://localhost:5432/postgres" | |
| class QuotaExceededError(Exception): | |
| """API ์ฟผํฐ(RPD)๊ฐ ์ด๊ณผ๋์์ ๋ ๋ฐ์ํ๋ ์ ๊ตญ ํ์ค ์์ธ์ ๋๋ค.""" | |
| def __init__(self, model_id, limit): | |
| self.model_id = model_id | |
| self.limit = limit | |
| super().__init__(f"๐ฑ [ํฉ์ค ๋ณด์ ๊ฒฝ๋ณด] '{model_id}' ๋ชจ๋ธ์ ์ผ์ผ ์ฌ์ฉ ํ๋({limit} RPD)๋ฅผ ์ด๊ณผํ์ต๋๋ค. ๋ด์ผ ๋ค์ ์ด๊ธฐํ๋ฉ๋๋ค.") | |
| class ImperialQuotaManager: | |
| """Gemini API ์ฌ์ฉ๋์ ์ถ์ ํ๊ณ ํ๋๋ฅผ ๊ฐ์ ํ๋ ์ ๋ด ๊ด๋ฆฌ์์ ๋๋ค.""" | |
| def get_db_conn(): | |
| if os.environ.get("LOCAL_AGENT_MODE", "false").lower() == "true": | |
| return None | |
| return psycopg2.connect(_resolve_db_url(), connect_timeout=5) | |
| def _get_utc_now(): | |
| return datetime.now(timezone.utc) | |
| def validate_quota(cls, model_id): | |
| """API ํธ์ถ ์ RPD ํ๋๋ฅผ ์ฒดํฌํ๊ณ ์ด๊ณผ ์ QuotaExceededError๋ฅผ ๋ฐ์์ํต๋๋ค.""" | |
| limit_data = MODEL_LIMITS.get(model_id) | |
| if not limit_data: | |
| return # ๋ฏธ๋ฑ๋ก ๋ชจ๋ธ์ ํต๊ณผ | |
| rpd_limit = int(limit_data.get("rpd", 0)) | |
| if rpd_limit <= 0: | |
| return # ๋ฌด์ ํ(0 or -1) ํต๊ณผ | |
| conn = None | |
| cur = None | |
| try: | |
| conn = cls.get_db_conn() | |
| if not conn: | |
| return | |
| cur = conn.cursor() | |
| cur.execute( | |
| "SELECT daily_requests, last_reset_date FROM taemingames.api_usage_tracker WHERE model_id = %s;", | |
| (model_id,) | |
| ) | |
| row = cur.fetchone() | |
| if not row: | |
| return # ๊ธฐ๋ก ์์ผ๋ฉด ์ฒซ ํธ์ถ์ด๋ฏ๋ก ํต๊ณผ | |
| used_requests, last_reset = row | |
| today_utc = cls._get_utc_now().date() | |
| # ๋ ์ง๊ฐ ์ค๋์ด๋ฉด ์ค์ ์ฌ์ฉ๋ ์ฒดํฌ | |
| if last_reset == today_utc: | |
| if used_requests >= rpd_limit: | |
| raise QuotaExceededError(model_id, rpd_limit) | |
| except QuotaExceededError: | |
| raise | |
| except Exception as e: | |
| print(f"[Quota Guard] Validation failed: {e}") | |
| return # DB ์ฅ์ ์ ๊ฐ๋ ์ ์ง๋ฅผ ์ํด ํต๊ณผ | |
| finally: | |
| if cur: | |
| try: cur.close() | |
| except: pass | |
| if conn: | |
| try: conn.close() | |
| except: pass | |
| def track_usage(cls, model_id, tokens=0): | |
| """API ํธ์ถ ์ ์ฌ์ฉ๋์ ๊ธฐ๋กํ๊ณ ์ผ์ผ ๋ฆฌ์ ์ ์ฒ๋ฆฌํฉ๋๋ค.""" | |
| now_utc = cls._get_utc_now() | |
| today_utc = now_utc.date() | |
| conn = None | |
| cur = None | |
| try: | |
| conn = cls.get_db_conn() | |
| if not conn: | |
| return True | |
| cur = conn.cursor() | |
| # 1. ํ์ฌ ์ํ ์กฐํ ๋ฐ ๋ฆฌ์ ์ฒดํฌ | |
| cur.execute( | |
| "SELECT last_reset_date FROM taemingames.api_usage_tracker WHERE model_id = %s FOR UPDATE;", | |
| (model_id,) | |
| ) | |
| row = cur.fetchone() | |
| if not row: | |
| # ๋ชจ๋ธ์ด ์์ผ๋ฉด ์์ฑ | |
| cur.execute( | |
| "INSERT INTO taemingames.api_usage_tracker (model_id, last_reset_date, daily_requests, daily_tokens) VALUES (%s, %s, 1, 0) ON CONFLICT DO NOTHING;", | |
| (model_id, today_utc) | |
| ) | |
| last_reset = today_utc | |
| else: | |
| last_reset = row[0] | |
| # 2. ๋ ์ง๊ฐ ๋ฐ๋์์ผ๋ฉด ๋ฆฌ์ (๊ตฌ๊ธ ๋ฆฌ์ ์ฃผ๊ธฐ: 00:00 UTC) | |
| if last_reset != today_utc: | |
| cur.execute( | |
| """UPDATE taemingames.api_usage_tracker | |
| SET daily_requests = 1, daily_tokens = 0, last_request_at = %s, last_reset_date = %s | |
| WHERE model_id = %s;""", | |
| (now_utc, today_utc, model_id) | |
| ) | |
| else: | |
| # ์ผ๋ฐ ๋์ ์ ๋ฐ์ดํธ | |
| cur.execute( | |
| """UPDATE taemingames.api_usage_tracker | |
| SET daily_requests = daily_requests + 1, daily_tokens = 0, last_request_at = %s | |
| WHERE model_id = %s;""", | |
| (now_utc, model_id) | |
| ) | |
| conn.commit() | |
| return True | |
| except Exception as e: | |
| print(f"โ [QuotaManager] Failed to track usage for {model_id}: {e}") | |
| return False | |
| finally: | |
| if cur: | |
| try: cur.close() | |
| except: pass | |
| if conn: | |
| try: conn.close() | |
| except: pass | |
| def get_all_usage_with_limits(cls): | |
| """๋ชจ๋ ๋ชจ๋ธ์ ํ์ฌ ์ฌ์ฉ๋๊ณผ ์ค์ ๋ ํ๋๋ฅผ ๋ฐํํฉ๋๋ค. (์ฌ์ฉ๋ 0์ธ ๋ชจ๋ธ ํฌํจ)""" | |
| results = [] | |
| conn = None | |
| cur = None | |
| try: | |
| conn = cls.get_db_conn() | |
| if not conn: | |
| # Return dummy limits when bypassing DB | |
| for mid, limit in MODEL_LIMITS.items(): | |
| results.append({ | |
| "model_id": mid, | |
| "rpm": 0, | |
| "tpm": 0, | |
| "rpd": 0, | |
| "last_request_at": None, | |
| "limit": limit | |
| }) | |
| return results | |
| cur = conn.cursor() | |
| cur.execute("SELECT model_id, daily_requests, daily_tokens, last_request_at FROM taemingames.api_usage_tracker;") | |
| rows_data = cur.fetchall() | |
| rows_dict = {r[0]: (r[1], r[2], r[3]) for r in rows_data} | |
| # MODEL_LIMITS์ ์ ์๋ ๋ชจ๋ ๋ชจ๋ธ์ ์ํํ๋ฉฐ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด 0์ผ๋ก ์ฑ์ | |
| for mid, limit in MODEL_LIMITS.items(): | |
| req, tok, last = rows_dict.get(mid, (0, 0, None)) | |
| results.append({ | |
| "model_id": mid, | |
| "rpm": 0, # ์ค์๊ฐ RPM์ DB์ ๋์ ํ์ง ์์ผ๋ฏ๋ก 0์ผ๋ก ํ์ (ํ๋๋ limit์ ํฌํจ) | |
| "tpm": tok, # Daily Tokens (UI์์ TPM์ผ๋ก ํ์๋ ์ ์์ผ๋ ์ค์ ๋ก ์ผ์ผ ๋์ ) | |
| "rpd": req, # Daily Requests | |
| "last_request_at": last.isoformat() if last else None, | |
| "limit": limit | |
| }) | |
| return results | |
| except Exception as e: | |
| print(f"[QuotaManager] ERROR: Failed to get usage: {e}") | |
| return [] | |
| finally: | |
| if cur: | |
| try: cur.close() | |
| except: pass | |
| if conn: | |
| try: conn.close() | |
| except: pass | |
| if __name__ == "__main__": | |
| # Test | |
| ImperialQuotaManager.track_usage("gemini-3.1-flash-lite", tokens=100) | |
| print(ImperialQuotaManager.get_all_usage_with_limits()) | |