Update app.py
Browse files
app.py
CHANGED
|
@@ -21,7 +21,7 @@ logging.basicConfig(
|
|
| 21 |
logger = logging.getLogger("g4f-smart-router")
|
| 22 |
|
| 23 |
# =====================================================
|
| 24 |
-
# COOKIES
|
| 25 |
# =====================================================
|
| 26 |
def _load_cookies_raw() -> Dict[str, Any]:
|
| 27 |
raw_env = (os.getenv("COOKIES_JSON") or "").strip()
|
|
@@ -60,7 +60,7 @@ def load_cookies() -> str:
|
|
| 60 |
COOKIE_STATUS = load_cookies()
|
| 61 |
|
| 62 |
# =====================================================
|
| 63 |
-
# CACHE
|
| 64 |
# =====================================================
|
| 65 |
class TTLCache:
|
| 66 |
def __init__(self, max_size: int = 100, ttl_seconds: int = 300):
|
|
@@ -98,7 +98,7 @@ class TTLCache:
|
|
| 98 |
CACHE = TTLCache(max_size=100, ttl_seconds=300)
|
| 99 |
|
| 100 |
# =====================================================
|
| 101 |
-
# PROVIDERS -
|
| 102 |
# =====================================================
|
| 103 |
def get_provider(name: str):
|
| 104 |
try:
|
|
@@ -106,199 +106,29 @@ def get_provider(name: str):
|
|
| 106 |
except:
|
| 107 |
return None
|
| 108 |
|
|
|
|
| 109 |
REAL_PROVIDERS = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
"Qwen": get_provider("Qwen"),
|
| 111 |
}
|
| 112 |
REAL_PROVIDERS = {k: v for k, v in REAL_PROVIDERS.items() if v}
|
| 113 |
|
| 114 |
# =====================================================
|
| 115 |
-
# MODELS -
|
| 116 |
# =====================================================
|
| 117 |
PROVIDER_MODELS_FALLBACK = {
|
| 118 |
-
"
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
"qwen3.6-35b-a3b", # ุงููู
ูุฐุฌ ุงูู
ูุชูุญ ุงูู
ุตุฏุฑ 35B MoE
|
| 126 |
-
|
| 127 |
-
# =====================================
|
| 128 |
-
# Qwen 3.5 SERIES
|
| 129 |
-
# ุงูู
ุตุฏุฑ: Alibaba Cloud Model Catalog
|
| 130 |
-
# =====================================
|
| 131 |
-
"qwen3.5-plus", # Qwen3.5-Plus ุงูุฑุฆูุณู
|
| 132 |
-
"qwen3.5-plus-2026-02-15", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 133 |
-
"qwen3.5-flash", # ููุงุณุชุฌุงุจุฉ ุงูุณุฑูุนุฉ
|
| 134 |
-
"qwen3.5-flash-2026-02-23", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 135 |
-
"qwen3.5-122b-a10b", # 122B ู
ุน 10B ูุดุท
|
| 136 |
-
"qwen3.5-27b", # 27B ูุซูู
|
| 137 |
-
"qwen3.5-35b-a3b", # 35B MoE ูุซูู
|
| 138 |
-
|
| 139 |
-
# =====================================
|
| 140 |
-
# Qwen 3 MAX & FLAGSHIP
|
| 141 |
-
# ุงูู
ุตุฏุฑ: Promptfoo Documentation
|
| 142 |
-
# =====================================
|
| 143 |
-
"qwen3-max", # ุงูุฌูู ุงูุชุงูู ุงูุฑุงุฆุฏ
|
| 144 |
-
"qwen3-max-preview", # ูุณุฎุฉ ุชุฌุฑูุจูุฉ
|
| 145 |
-
"qwen3-max-2025-09-23", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 146 |
-
"qwen-max", # ุงูุฑุงุฆุฏ ุงูุฃุตูู
|
| 147 |
-
"qwen-max-latest", # ุฏุงุฆู
ุงู ู
ุญุฏุซ
|
| 148 |
-
"qwen-max-2025-01-25", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 149 |
-
|
| 150 |
-
# =====================================
|
| 151 |
-
# Qwen PLUS & TURBO (ู
ุชุนุฏุฏุฉ ุงูุงุณุชุฎุฏุงู
ุงุช)
|
| 152 |
-
# =====================================
|
| 153 |
-
"qwen-plus", # ู
ุชูุงุฒู ููุบุงูุฉ
|
| 154 |
-
"qwen-plus-latest", # ุฏุงุฆู
ุงู ู
ุญุฏุซ
|
| 155 |
-
"qwen-plus-2025-09-11", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 156 |
-
"qwen-plus-2025-07-28", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 157 |
-
"qwen-plus-2025-07-14", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 158 |
-
"qwen-plus-2025-04-28", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 159 |
-
"qwen-plus-2025-01-25", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 160 |
-
"qwen-turbo", # ุณุฑูุน ููุนุงู ู
ู ุญูุซ ุงูุชูููุฉ
|
| 161 |
-
"qwen-turbo-latest", # ุฏุงุฆู
ุงู ู
ุญุฏุซ
|
| 162 |
-
"qwen-turbo-2025-04-28", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 163 |
-
"qwen-turbo-2024-11-01", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 164 |
-
"qwen-flash", # ู
ุญุณูู ููููุช (ูุณุชุจุฏู ุงูููุงุซ)
|
| 165 |
-
"qwen-flash-2025-07-28", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 166 |
-
|
| 167 |
-
# =====================================
|
| 168 |
-
# Qwen LONG (ุณูุงู ูุงุฆู ุงูุทูู)
|
| 169 |
-
# =====================================
|
| 170 |
-
"qwen-long-latest", # ู
ูููู ุณูุงู
|
| 171 |
-
"qwen-long-2025-01-25", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 172 |
-
|
| 173 |
-
# =====================================
|
| 174 |
-
# Qwen 3 CODER (ูู
ุงุฐุฌ ุงูุจุฑู
ุฌุฉ ุงูู
ุชุฎุตุตุฉ)
|
| 175 |
-
# ุงูู
ุตุฏุฑ: Alibaba Cloud + Promptfoo
|
| 176 |
-
# =====================================
|
| 177 |
-
"qwen3-coder-plus", # ููุฏ ุงุญุชุฑุงูู ู
ุน ุฃุฏุงุฉ ุงูุงุชุตุงู
|
| 178 |
-
"qwen3-coder-plus-2025-09-23", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 179 |
-
"qwen3-coder-plus-2025-07-22", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 180 |
-
"qwen3-coder-flash", # ุชูููุฏ ููุฏ ุณุฑูุน
|
| 181 |
-
"qwen3-coder-flash-2025-07-28", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 182 |
-
"qwen3-coder-next", # ุงูุฌูู ุงูุชุงูู ู
ู ุงูููุฏ
|
| 183 |
-
"qwen3-coder-480b-a35b-instruct", # 480B ูู
ูุฐุฌ ููุฏ ู
ูุชูุญ
|
| 184 |
-
"qwen3-coder-30b-a3b-instruct", # 30B ูู
ูุฐุฌ ููุฏ ู
ูุชูุญ
|
| 185 |
-
|
| 186 |
-
# =====================================
|
| 187 |
-
# Qwen 3 VL (ุงูุฑุคูุฉ ูุงูููุฏูู)
|
| 188 |
-
# ุงูู
ุตุฏุฑ: Promptfoo + DashScope
|
| 189 |
-
# =====================================
|
| 190 |
-
"qwen3-vl-plus", # ุฑุคูุฉ ุฑุงุฆุฏุฉ
|
| 191 |
-
"qwen3-vl-plus-2025-09-23", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 192 |
-
"qwen3-vl-flash", # ูู
ูุฐุฌ ุฑุคูุฉ ุณุฑูุน
|
| 193 |
-
"qwen3-vl-flash-2025-10-15", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 194 |
-
"qwen3-vl-235b-a22b-thinking", # 235B ู
ุน ุงูุชูููุฑ
|
| 195 |
-
"qwen3-vl-235b-a22b-instruct", # 235B ุชุนููู
ุงุช
|
| 196 |
-
"qwen3-vl-32b-thinking", # 32B ู
ุน ุงูุชูููุฑ
|
| 197 |
-
"qwen3-vl-32b-instruct", # 32B ุชุนููู
ุงุช
|
| 198 |
-
"qwen3-vl-30b-a3b-thinking", # 30B ู
ุน ุงูุชูููุฑ
|
| 199 |
-
"qwen3-vl-30b-a3b-instruct", # 30B ุชุนููู
ุงุช
|
| 200 |
-
"qwen3-vl-8b-thinking", # 8B ู
ุน ุงูุชูููุฑ
|
| 201 |
-
"qwen3-vl-8b-instruct", # 8B ุชุนููู
ุงุช
|
| 202 |
-
"qwen-vl-max", # VL ู
ุงูุณ
|
| 203 |
-
"qwen-vl-plus", # VL ุจูุต
|
| 204 |
-
"qwen-vl-ocr", # ู
ุญุณูู ููุชุนุฑู ุงูุถูุฆู ุนูู ุงูุญุฑูู
|
| 205 |
-
|
| 206 |
-
# =====================================
|
| 207 |
-
# Qwen 3 OMN (ุฑุคูุฉ + ููุงู
)
|
| 208 |
-
# =====================================
|
| 209 |
-
"qwen3-omni-flash", # ู
ุชุนุฏุฏ ุงููุณุงุฆุท
|
| 210 |
-
"qwen3-omni-flash-2025-09-15", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 211 |
-
"qwen3-omni-flash-realtime", # ุจุซ ู
ุจุงุดุฑ ูู ุงูููุช ุงููุนูู
|
| 212 |
-
"qwen3-omni-flash-realtime-2025-09-15", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 213 |
-
"qwen3-omni-30b-a3b-captioner", # ุชุฐููู ุงูุตูุช
|
| 214 |
-
|
| 215 |
-
# =====================================
|
| 216 |
-
# QwQ REASONING (ุงูุงุณุชุฏูุงู ูุงูุจุญุซ)
|
| 217 |
-
# ุงูู
ุตุฏุฑ: Promptfoo
|
| 218 |
-
# =====================================
|
| 219 |
-
"qwq-plus", # ูู
ูุฐุฌ ุงุณุชุฏูุงู ุชุฌุงุฑู
|
| 220 |
-
"qwq-32b", # QwQ 32B ู
ูุชูุญ
|
| 221 |
-
"qwen-deep-research", # ู
ุณุงุนุฏ ุจุญุซ ู
ุน ุงุณุชุนูุงู
ููุจ
|
| 222 |
-
"qvq-max", # ุงุณุชุฏูุงู ุจุตุฑู
|
| 223 |
-
"qvq-max-latest", # ุฏุงุฆู
ุงู ู
ุญุฏุซ
|
| 224 |
-
"qvq-max-2025-03-25", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 225 |
-
"qvq-72b-preview", # ุงุณุชุฏูุงู ุจุตุฑู 72B
|
| 226 |
-
|
| 227 |
-
# =====================================
|
| 228 |
-
# Qwen 3 OPEN-SOURCE (ู
ูุชูุญุฉ ุงูู
ุตุฏุฑ)
|
| 229 |
-
# ุงูู
ุตุฏุฑ: Alibaba Cloud
|
| 230 |
-
# =====================================
|
| 231 |
-
"qwen3-next-80b-a3b-thinking", # ุงูุฌูู ุงูุชุงูู 80B ู
ุน ุงูุชูููุฑ
|
| 232 |
-
"qwen3-next-80b-a3b-instruct", # ุงูุฌูู ุงูุชุงูู 80B ุชุนููู
ุงุช
|
| 233 |
-
"qwen3-235b-a22b-thinking-2507", # 235B ููููู 2025 ู
ุน ุงูุชูููุฑ
|
| 234 |
-
"qwen3-235b-a22b-instruct-2507", # 235B ููููู 2025 ุชุนููู
ุงุช
|
| 235 |
-
"qwen3-30b-a3b-thinking-2507", # 30B ููููู 2025 ู
ุน ุงูุชูููุฑ
|
| 236 |
-
"qwen3-30b-a3b-instruct-2507", # 30B ููููู 2025 ุชุนููู
ุงุช
|
| 237 |
-
"qwen3-235b-a22b", # 235B ูุถุน ู
ุฒุฏูุฌ
|
| 238 |
-
"qwen3-32b", # 32B ูุถุน ู
ุฒุฏูุฌ
|
| 239 |
-
"qwen3-30b-a3b", # 30B ูุถุน ู
ุฒุฏูุฌ
|
| 240 |
-
"qwen3-14b", # 14B ูุถุน ู
ุฒุฏูุฌ
|
| 241 |
-
"qwen3-8b", # 8B ูุถุน ู
ุฒุฏูุฌ
|
| 242 |
-
"qwen3-4b", # 4B ูุถุน ู
ุฒุฏูุฌ
|
| 243 |
-
"qwen3-1.7b", # 1.7B ููุฃุฌูุฒุฉ ุงูุทุฑููุฉ
|
| 244 |
-
"qwen3-0.6b", # 0.6B ููุฃุฌูุฒุฉ ุงูุทุฑููุฉ
|
| 245 |
-
|
| 246 |
-
# =====================================
|
| 247 |
-
# Qwen 2.5 SERIES (ู
ุณุชูุฑุฉ ูู
ุฌุฑุจุฉ)
|
| 248 |
-
# =====================================
|
| 249 |
-
"qwen2.5-72b-instruct", # 72B ูู
ูุฐุฌ ูุซูู
|
| 250 |
-
"qwen2.5-32b-instruct", # 32B ูู
ูุฐุฌ ูุซูู
|
| 251 |
-
"qwen2.5-14b-instruct", # 14B ูู
ูุฐุฌ ูุซูู
|
| 252 |
-
"qwen2.5-7b-instruct", # 7B ูู
ูุฐุฌ ูุซูู
|
| 253 |
-
"qwen2.5-1.5b-instruct", # 1.5B ูู
ูุฐุฌ ุตุบูุฑ
|
| 254 |
-
"qwen2.5-0.5b-instruct", # 0.5B ูู
ูุฐุฌ ุตุบูุฑ ุฌุฏุงู
|
| 255 |
-
"qwen2.5-7b-instruct-1m", # 7B ู
ุน ุณูุงู ู
ููููู
|
| 256 |
-
"qwen2.5-14b-instruct-1m", # 14B ู
ุน ุณูุงู ู
ููููู
|
| 257 |
-
"qwen2.5-coder-32b-instruct", # 32B ููุฏ
|
| 258 |
-
"qwen2.5-coder-14b-instruct", # 14B ููุฏ
|
| 259 |
-
"qwen2.5-coder-7b-instruct", # 7B ููุฏ
|
| 260 |
-
"qwen2.5-coder-1.5b-instruct", # 1.5B ููุฏ
|
| 261 |
-
"qwen2.5-coder-0.5b-instruct", # 0.5B ููุฏ
|
| 262 |
-
|
| 263 |
-
# =====================================
|
| 264 |
-
# QWEN MATH (ุงููู
ุงุฐุฌ ุงูุฑูุงุถูุฉ)
|
| 265 |
-
# =====================================
|
| 266 |
-
"qwen-math-plus", # ุนุฏุฏู ุจูุต
|
| 267 |
-
"qwen-math-plus-latest", # ุฏุงุฆู
ุงู ู
ุญุฏุซ
|
| 268 |
-
"qwen-math-plus-2024-09-19", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 269 |
-
"qwen-math-plus-2024-08-16", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 270 |
-
"qwen-math-turbo", # ุนุฏุฏู ุชูุฑุจู
|
| 271 |
-
"qwen-math-turbo-latest", # ุฏุงุฆู
ุงู ู
ุญุฏุซ
|
| 272 |
-
"qwen-math-turbo-2024-09-19", # ุฅุตุฏุงุฑ ู
ุคุฑุฎ
|
| 273 |
-
"qwen2.5-math-72b-instruct", # 72B ูููู
ุงุฐุฌ ุงูุฑูุงุถูุฉ
|
| 274 |
-
"qwen2.5-math-7b-instruct", # 7B ูููู
ุงุฐุฌ ุงูุฑูุงุถูุฉ
|
| 275 |
-
|
| 276 |
-
# =====================================
|
| 277 |
-
# QWEN TRANSLATION (ุงูุชุฑุฌู
ุฉ)
|
| 278 |
-
# =====================================
|
| 279 |
-
"qwen-mt-plus", # ู
ุชุฑุฌู
ุฉ ุจูุต
|
| 280 |
-
"qwen-mt-turbo", # ู
ุชุฑุฌู
ุฉ ุชูุฑุจู
|
| 281 |
-
|
| 282 |
-
# =====================================
|
| 283 |
-
# QWEN DOCUMENT (ุงุณุชุฎุฑุงุฌ ุงูู
ุณุชูุฏุงุช)
|
| 284 |
-
# =====================================
|
| 285 |
-
"qwen-doc-turbo", # ุงุณุชุฎุฑุงุฌ ุงูู
ุณุชูุฏุงุช ุงูู
ููููุฉ
|
| 286 |
-
|
| 287 |
-
# =====================================
|
| 288 |
-
# QWEN IMAGE GENERATION (ุชูููุฏ ุงูุตูุฑ)
|
| 289 |
-
# =====================================
|
| 290 |
-
"qwen-image-plus", # ูุต ุฅูู ุตูุฑุฉ ู
ุน ุนุฑุถ ูุตู ู
ุนูุฏ
|
| 291 |
-
|
| 292 |
-
# =====================================
|
| 293 |
-
# ALIASES (ุงุณุชุนูุงู
ุงุช ู
ุฎุชุตุฑุฉ)
|
| 294 |
-
# =====================================
|
| 295 |
-
"qwen",
|
| 296 |
-
"qwen2.5",
|
| 297 |
-
"qwen-coder",
|
| 298 |
-
"qwen-vl",
|
| 299 |
-
"qwq",
|
| 300 |
-
"qvq",
|
| 301 |
-
],
|
| 302 |
}
|
| 303 |
|
| 304 |
# =====================================================
|
|
@@ -307,7 +137,12 @@ PROVIDER_MODELS_FALLBACK = {
|
|
| 307 |
_PROVIDER_MODEL_CACHE = {}
|
| 308 |
|
| 309 |
def discover_provider_models(provider_obj: Any, provider_name: str) -> List[str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
candidates = []
|
|
|
|
| 311 |
for attr in ("models", "model", "default_model", "available_models", "supported_models"):
|
| 312 |
try:
|
| 313 |
if hasattr(provider_obj, attr):
|
|
@@ -320,13 +155,18 @@ def discover_provider_models(provider_obj: Any, provider_name: str) -> List[str]
|
|
| 320 |
candidates.append(str(val))
|
| 321 |
except:
|
| 322 |
pass
|
|
|
|
| 323 |
if not candidates:
|
| 324 |
-
candidates = PROVIDER_MODELS_FALLBACK.get(provider_name, ["
|
|
|
|
|
|
|
| 325 |
seen = set()
|
| 326 |
-
|
|
|
|
|
|
|
| 327 |
|
| 328 |
# =====================================================
|
| 329 |
-
# STREAM CLEANER
|
| 330 |
# =====================================================
|
| 331 |
def clean_stream(chunk):
|
| 332 |
try:
|
|
@@ -338,7 +178,6 @@ def clean_stream(chunk):
|
|
| 338 |
if 'text' in delta:
|
| 339 |
return delta['text']
|
| 340 |
return chunk.get('content') or chunk.get('text') or ""
|
| 341 |
-
|
| 342 |
if isinstance(chunk, str):
|
| 343 |
if chunk and chunk[0] == '{' and chunk[-1] == '}':
|
| 344 |
try:
|
|
@@ -358,11 +197,12 @@ def clean_stream(chunk):
|
|
| 358 |
chunk = chunk.replace('\\t', ' ')
|
| 359 |
return chunk
|
| 360 |
return str(chunk)
|
| 361 |
-
except Exception:
|
|
|
|
| 362 |
return ""
|
| 363 |
|
| 364 |
# =====================================================
|
| 365 |
-
# CHAT LOGIC -
|
| 366 |
# =====================================================
|
| 367 |
def ask(message: str, history, provider_name: str, model_name: str, stop_flag=None):
|
| 368 |
message = (message or "").strip()
|
|
@@ -370,12 +210,14 @@ def ask(message: str, history, provider_name: str, model_name: str, stop_flag=No
|
|
| 370 |
yield ""
|
| 371 |
return
|
| 372 |
|
|
|
|
| 373 |
key = f"{provider_name}|{model_name}|{message}"
|
| 374 |
cached = CACHE.get(key)
|
| 375 |
if cached:
|
| 376 |
yield cached
|
| 377 |
return
|
| 378 |
|
|
|
|
| 379 |
msgs = []
|
| 380 |
try:
|
| 381 |
if history:
|
|
@@ -396,9 +238,16 @@ def ask(message: str, history, provider_name: str, model_name: str, stop_flag=No
|
|
| 396 |
|
| 397 |
msgs.append({"role": "user", "content": message})
|
| 398 |
|
| 399 |
-
# ุง
|
|
|
|
| 400 |
fallback_providers = [
|
| 401 |
provider_name,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
"Qwen"
|
| 403 |
]
|
| 404 |
used = []
|
|
@@ -409,25 +258,32 @@ def ask(message: str, history, provider_name: str, model_name: str, stop_flag=No
|
|
| 409 |
used.append(pname)
|
| 410 |
pobj = REAL_PROVIDERS.get(pname)
|
| 411 |
if not pobj:
|
|
|
|
| 412 |
continue
|
| 413 |
|
| 414 |
-
|
| 415 |
-
|
|
|
|
|
|
|
|
|
|
| 416 |
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
| 419 |
-
for m in model_candidates[:
|
| 420 |
try:
|
|
|
|
| 421 |
stream = g4f.ChatCompletion.create(
|
| 422 |
model=m,
|
| 423 |
provider=pobj,
|
| 424 |
messages=msgs,
|
| 425 |
stream=True,
|
| 426 |
-
timeout=
|
| 427 |
)
|
| 428 |
-
|
| 429 |
buffer = []
|
| 430 |
-
|
| 431 |
for chunk in stream:
|
| 432 |
if stop_flag and stop_flag.is_set():
|
| 433 |
return
|
|
@@ -436,35 +292,32 @@ def ask(message: str, history, provider_name: str, model_name: str, stop_flag=No
|
|
| 436 |
continue
|
| 437 |
buffer.append(c)
|
| 438 |
yield c
|
| 439 |
-
|
| 440 |
full = "".join(buffer)
|
| 441 |
if full.strip():
|
| 442 |
CACHE.set(key, full)
|
| 443 |
return
|
| 444 |
-
|
| 445 |
except Exception as e:
|
| 446 |
-
logger.warning(f"Provider {pname} model {m} failed: {e}")
|
| 447 |
continue
|
| 448 |
|
| 449 |
-
yield "โ
|
| 450 |
|
| 451 |
# =====================================================
|
| 452 |
# FASTAPI
|
| 453 |
# =====================================================
|
| 454 |
-
app = FastAPI(title="G4F Smart Router", description="AI Gateway -
|
| 455 |
|
| 456 |
API_KEY = os.getenv("API_KEY", "mysecretkey123")
|
| 457 |
|
| 458 |
class ChatRequest(BaseModel):
|
| 459 |
message: str
|
| 460 |
-
provider: str = "
|
| 461 |
-
model: str = "
|
| 462 |
history: List[Any] = []
|
| 463 |
|
| 464 |
# =====================================================
|
| 465 |
-
#
|
| 466 |
# =====================================================
|
| 467 |
-
|
| 468 |
def verify_api_key(request: Request):
|
| 469 |
auth = request.headers.get("Authorization", "").strip()
|
| 470 |
x_key = request.headers.get("X-API-Key", "").strip()
|
|
@@ -482,9 +335,8 @@ def verify_api_key(request: Request):
|
|
| 482 |
raise HTTPException(status_code=401, detail="Invalid API key. Use 'Authorization: Bearer KEY' or 'X-API-Key: KEY'")
|
| 483 |
|
| 484 |
# =====================================================
|
| 485 |
-
#
|
| 486 |
# =====================================================
|
| 487 |
-
|
| 488 |
@app.head("/")
|
| 489 |
async def head_root():
|
| 490 |
return Response(status_code=200)
|
|
@@ -498,64 +350,46 @@ async def head_models():
|
|
| 498 |
return Response(status_code=200)
|
| 499 |
|
| 500 |
# =====================================================
|
| 501 |
-
#
|
| 502 |
# =====================================================
|
| 503 |
-
|
| 504 |
@app.get("/v1/models")
|
| 505 |
async def v1_models(request: Request):
|
| 506 |
-
|
| 507 |
-
|
| 508 |
models = []
|
| 509 |
for pname, pobj in REAL_PROVIDERS.items():
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
for model in _PROVIDER_MODEL_CACHE[pname][:30]:
|
| 513 |
models.append({
|
| 514 |
"id": model,
|
| 515 |
"type": "model",
|
| 516 |
"display_name": f"{pname} - {model}"
|
| 517 |
})
|
| 518 |
-
|
| 519 |
if not models:
|
| 520 |
-
models = [
|
| 521 |
-
{"id": "qwen3.6-plus", "type": "model", "display_name": "Qwen 3.6 - Plus"},
|
| 522 |
-
{"id": "qwen3-max", "type": "model", "display_name": "Qwen 3 - Max"},
|
| 523 |
-
{"id": "qwen-plus", "type": "model", "display_name": "Qwen - Plus"},
|
| 524 |
-
{"id": "qwen3-coder-plus", "type": "model", "display_name": "Qwen 3 - Coder Plus"},
|
| 525 |
-
{"id": "qwen3-vl-plus", "type": "model", "display_name": "Qwen 3 - Vision Plus"},
|
| 526 |
-
]
|
| 527 |
-
|
| 528 |
return {"data": models}
|
| 529 |
|
| 530 |
@app.post("/v1/messages")
|
| 531 |
async def v1_messages(request: Request):
|
| 532 |
-
"""ููุทุฉ ููุงูุฉ ู
ุชูุงููุฉ ู
ุน Claude Desktop"""
|
| 533 |
verify_api_key(request)
|
| 534 |
-
|
| 535 |
body = await request.json()
|
| 536 |
-
|
| 537 |
messages = body.get("messages", [])
|
| 538 |
if not messages:
|
| 539 |
raise HTTPException(status_code=400, detail="No messages provided")
|
| 540 |
-
|
| 541 |
last_message = messages[-1]
|
| 542 |
user_message = last_message.get("content", "")
|
| 543 |
-
|
| 544 |
-
model = body.get("model", "qwen-max")
|
| 545 |
system_prompt = body.get("system", "")
|
| 546 |
-
|
| 547 |
history = []
|
| 548 |
for msg in messages[:-1]:
|
| 549 |
role = msg.get("role", "user")
|
| 550 |
content = msg.get("content", "")
|
| 551 |
history.append({"role": role, "content": content})
|
| 552 |
-
|
| 553 |
full_message = user_message
|
| 554 |
if system_prompt:
|
| 555 |
full_message = f"[System: {system_prompt}]\n\n{user_message}"
|
| 556 |
|
| 557 |
full_response = ""
|
| 558 |
-
for chunk in ask(full_message, history, "
|
| 559 |
full_response = chunk
|
| 560 |
|
| 561 |
return {
|
|
@@ -566,75 +400,52 @@ async def v1_messages(request: Request):
|
|
| 566 |
"model": model,
|
| 567 |
"stop_reason": "end_turn",
|
| 568 |
"stop_sequence": None,
|
| 569 |
-
"usage": {
|
| 570 |
-
"input_tokens": len(user_message) // 4,
|
| 571 |
-
"output_tokens": len(full_response) // 4
|
| 572 |
-
}
|
| 573 |
}
|
| 574 |
|
| 575 |
@app.post("/v1/messages/stream")
|
| 576 |
async def v1_messages_stream(request: Request):
|
| 577 |
-
"""ููุทุฉ ููุงูุฉ ู
ุชุฏููุฉ ู
ุชูุงููุฉ ู
ุน Claude Desktop"""
|
| 578 |
verify_api_key(request)
|
| 579 |
-
|
| 580 |
body = await request.json()
|
| 581 |
messages = body.get("messages", [])
|
| 582 |
if not messages:
|
| 583 |
raise HTTPException(status_code=400, detail="No messages provided")
|
| 584 |
-
|
| 585 |
last_message = messages[-1]
|
| 586 |
user_message = last_message.get("content", "")
|
| 587 |
-
model = body.get("model", "
|
| 588 |
system_prompt = body.get("system", "")
|
| 589 |
-
|
| 590 |
full_message = user_message
|
| 591 |
if system_prompt:
|
| 592 |
full_message = f"[System: {system_prompt}]\n\n{user_message}"
|
| 593 |
|
| 594 |
async def generate_stream():
|
| 595 |
message_id = f"msg_{int(time.time())}_{os.urandom(4).hex()}"
|
| 596 |
-
|
| 597 |
yield f"event: message_start\ndata: {{\"message\": {{\"id\": \"{message_id}\", \"type\": \"message\", \"role\": \"assistant\", \"content\": [], \"model\": \"{model}\", \"stop_reason\": null, \"stop_sequence\": null, \"usage\": {{\"input_tokens\": 0, \"output_tokens\": 0}}}}}}\n\n"
|
| 598 |
-
|
| 599 |
yield f"event: content_block_start\ndata: {{\"type\": \"content_block_start\", \"index\": 0, \"content_block\": {{\"type\": \"text\", \"text\": \"\"}}}}\n\n"
|
| 600 |
-
|
| 601 |
-
for chunk in ask(full_message, [], "Qwen", model):
|
| 602 |
yield f"event: content_block_delta\ndata: {{\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {{\"type\": \"text_delta\", \"text\": {json.dumps(chunk, ensure_ascii=False)}}}}}\n\n"
|
| 603 |
-
|
| 604 |
yield f"event: message_delta\ndata: {{\"type\": \"message_delta\", \"delta\": {{\"stop_reason\": \"end_turn\", \"stop_sequence\": null}}, \"usage\": {{\"output_tokens\": 100}}}}\n\n"
|
| 605 |
yield f"event: message_stop\ndata: {{}}\n\n"
|
| 606 |
|
| 607 |
-
return StreamingResponse(
|
| 608 |
-
generate_stream(),
|
| 609 |
-
media_type="text/event-stream",
|
| 610 |
-
headers={
|
| 611 |
-
"Cache-Control": "no-cache",
|
| 612 |
-
"Connection": "keep-alive"
|
| 613 |
-
}
|
| 614 |
-
)
|
| 615 |
|
| 616 |
# =====================================================
|
| 617 |
-
#
|
| 618 |
# =====================================================
|
| 619 |
-
|
| 620 |
@app.get("/")
|
| 621 |
async def root():
|
| 622 |
return {
|
| 623 |
-
"message": "G4F Smart Router is running (
|
| 624 |
-
"
|
| 625 |
"endpoints": {
|
| 626 |
-
"GET /": "Home
|
| 627 |
-
"GET /health": "Health
|
| 628 |
"GET /v1/models": "List models (NO AUTH)",
|
| 629 |
-
"POST /v1/messages": "Send message (
|
| 630 |
-
"POST /v1/messages/stream": "Stream
|
| 631 |
-
"GET /providers": "
|
| 632 |
-
"POST /chat": "Legacy chat (REQUIRES AUTH)",
|
| 633 |
-
"POST /chat/stream": "Legacy stream (REQUIRES AUTH)"
|
| 634 |
},
|
| 635 |
-
"authentication": "Bearer YOUR_API_KEY or X-API-Key: YOUR_API_KEY",
|
| 636 |
"cookies": COOKIE_STATUS,
|
| 637 |
-
"models_count": len(PROVIDER_MODELS_FALLBACK.get("Qwen", [])),
|
| 638 |
"status": "โ
Server is working"
|
| 639 |
}
|
| 640 |
|
|
@@ -642,48 +453,32 @@ async def root():
|
|
| 642 |
async def health():
|
| 643 |
return {"status": "ok", "cookies": COOKIE_STATUS, "providers": list(REAL_PROVIDERS.keys())}
|
| 644 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
@app.post("/chat")
|
| 646 |
async def chat(request: Request, chat_req: ChatRequest):
|
| 647 |
verify_api_key(request)
|
| 648 |
-
|
| 649 |
result = ""
|
| 650 |
for chunk in ask(chat_req.message, chat_req.history, chat_req.provider, chat_req.model):
|
| 651 |
result = chunk
|
| 652 |
-
|
| 653 |
return JSONResponse({"response": result})
|
| 654 |
|
| 655 |
@app.post("/chat/stream")
|
| 656 |
async def chat_stream(request: Request, chat_req: ChatRequest):
|
| 657 |
verify_api_key(request)
|
| 658 |
-
|
| 659 |
async def generate():
|
| 660 |
for chunk in ask(chat_req.message, chat_req.history, chat_req.provider, chat_req.model):
|
| 661 |
yield f"data: {json.dumps({'delta': chunk}, ensure_ascii=False)}\n\n"
|
| 662 |
yield "data: [DONE]\n\n"
|
| 663 |
-
|
| 664 |
return StreamingResponse(generate(), media_type="text/event-stream")
|
| 665 |
|
| 666 |
-
@app.get("/providers")
|
| 667 |
-
async def get_providers(request: Request):
|
| 668 |
-
verify_api_key(request)
|
| 669 |
-
|
| 670 |
-
providers_info = {}
|
| 671 |
-
for pname, pobj in REAL_PROVIDERS.items():
|
| 672 |
-
if pname not in _PROVIDER_MODEL_CACHE:
|
| 673 |
-
_PROVIDER_MODEL_CACHE[pname] = discover_provider_models(pobj, pname)
|
| 674 |
-
providers_info[pname] = _PROVIDER_MODEL_CACHE[pname]
|
| 675 |
-
|
| 676 |
-
return JSONResponse({"providers": providers_info})
|
| 677 |
-
|
| 678 |
# =====================================================
|
| 679 |
-
#
|
| 680 |
# =====================================================
|
| 681 |
if __name__ == "__main__":
|
| 682 |
import uvicorn
|
| 683 |
port = int(os.getenv("PORT", 7860))
|
| 684 |
-
uvicorn.run(
|
| 685 |
-
"app:app",
|
| 686 |
-
host="0.0.0.0",
|
| 687 |
-
port=port,
|
| 688 |
-
reload=False
|
| 689 |
-
)
|
|
|
|
| 21 |
logger = logging.getLogger("g4f-smart-router")
|
| 22 |
|
| 23 |
# =====================================================
|
| 24 |
+
# COOKIES (ููู
ุฒูุฏุงุช ุงูุชู ุชุญุชุงุฌูุง ู
ุซู Perplexity)
|
| 25 |
# =====================================================
|
| 26 |
def _load_cookies_raw() -> Dict[str, Any]:
|
| 27 |
raw_env = (os.getenv("COOKIES_JSON") or "").strip()
|
|
|
|
| 60 |
COOKIE_STATUS = load_cookies()
|
| 61 |
|
| 62 |
# =====================================================
|
| 63 |
+
# CACHE ู
ุญุณู
|
| 64 |
# =====================================================
|
| 65 |
class TTLCache:
|
| 66 |
def __init__(self, max_size: int = 100, ttl_seconds: int = 300):
|
|
|
|
| 98 |
CACHE = TTLCache(max_size=100, ttl_seconds=300)
|
| 99 |
|
| 100 |
# =====================================================
|
| 101 |
+
# PROVIDERS - ูุงุฆู
ุฉ ู
ุฒูุฏุงุช ูุฏ ุชุนู
ู
|
| 102 |
# =====================================================
|
| 103 |
def get_provider(name: str):
|
| 104 |
try:
|
|
|
|
| 106 |
except:
|
| 107 |
return None
|
| 108 |
|
| 109 |
+
# ุชุฑุชูุจ ุงูู
ุฒูุฏุงุช ุญุณุจ ุงุญุชู
ุงู ุงููุฌุงุญ (ุฌุฑุจูุงูุง)
|
| 110 |
REAL_PROVIDERS = {
|
| 111 |
+
"Blackbox": get_provider("Blackbox"),
|
| 112 |
+
"DeepSeek": get_provider("DeepSeek"),
|
| 113 |
+
"Perplexity": get_provider("Perplexity") or get_provider("PerplexityAi"),
|
| 114 |
+
"Copilot": get_provider("Copilot"),
|
| 115 |
+
"You": get_provider("You"),
|
| 116 |
+
"Bing": get_provider("Bing"),
|
| 117 |
"Qwen": get_provider("Qwen"),
|
| 118 |
}
|
| 119 |
REAL_PROVIDERS = {k: v for k, v in REAL_PROVIDERS.items() if v}
|
| 120 |
|
| 121 |
# =====================================================
|
| 122 |
+
# MODELS - ูุงุฆู
ุฉ ูู
ุงุฐุฌ ููู ู
ุฒูุฏ (ู
ุตุงุฏุฑ ู
ูุซููุฉ)
|
| 123 |
# =====================================================
|
| 124 |
PROVIDER_MODELS_FALLBACK = {
|
| 125 |
+
"Blackbox": ["gpt-4o", "claude-3.5-sonnet", "llama-3.1-70b", "gemini-pro"],
|
| 126 |
+
"DeepSeek": ["deepseek-chat", "deepseek-coder"],
|
| 127 |
+
"Perplexity": ["sonar", "sonar-pro", "gpt-4o", "llama-3"],
|
| 128 |
+
"Copilot": ["gpt-4o", "claude-3.5-sonnet"],
|
| 129 |
+
"You": ["gpt-4o", "claude-3.5-sonnet"],
|
| 130 |
+
"Bing": ["gpt-4o"],
|
| 131 |
+
"Qwen": ["qwen-max", "qwen-plus", "qwen-turbo"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
}
|
| 133 |
|
| 134 |
# =====================================================
|
|
|
|
| 137 |
_PROVIDER_MODEL_CACHE = {}
|
| 138 |
|
| 139 |
def discover_provider_models(provider_obj: Any, provider_name: str) -> List[str]:
|
| 140 |
+
# ุฅุฐุง ูุงู ููุงู cache ุงุณุชุฎุฏู
ู
|
| 141 |
+
if provider_name in _PROVIDER_MODEL_CACHE:
|
| 142 |
+
return _PROVIDER_MODEL_CACHE[provider_name]
|
| 143 |
+
|
| 144 |
candidates = []
|
| 145 |
+
# ุญุงูู ุงูุชุดุงู ุงููู
ุงุฐุฌ ู
ู ุงููุงุฆู
|
| 146 |
for attr in ("models", "model", "default_model", "available_models", "supported_models"):
|
| 147 |
try:
|
| 148 |
if hasattr(provider_obj, attr):
|
|
|
|
| 155 |
candidates.append(str(val))
|
| 156 |
except:
|
| 157 |
pass
|
| 158 |
+
# ุฅุฐุง ูู
ููุชุดู ุดูุฆุงู ุงุณุชุฎุฏู
fallback
|
| 159 |
if not candidates:
|
| 160 |
+
candidates = PROVIDER_MODELS_FALLBACK.get(provider_name, ["gpt-4o"])
|
| 161 |
+
|
| 162 |
+
# ุฅุฒุงูุฉ ุงูุชูุฑุงุฑ
|
| 163 |
seen = set()
|
| 164 |
+
unique = [m for m in candidates if not (m in seen or seen.add(m))]
|
| 165 |
+
_PROVIDER_MODEL_CACHE[provider_name] = unique
|
| 166 |
+
return unique
|
| 167 |
|
| 168 |
# =====================================================
|
| 169 |
+
# STREAM CLEANER - ูุชูุธูู ุดุฐุฑุงุช ุงูุฑุฏ
|
| 170 |
# =====================================================
|
| 171 |
def clean_stream(chunk):
|
| 172 |
try:
|
|
|
|
| 178 |
if 'text' in delta:
|
| 179 |
return delta['text']
|
| 180 |
return chunk.get('content') or chunk.get('text') or ""
|
|
|
|
| 181 |
if isinstance(chunk, str):
|
| 182 |
if chunk and chunk[0] == '{' and chunk[-1] == '}':
|
| 183 |
try:
|
|
|
|
| 197 |
chunk = chunk.replace('\\t', ' ')
|
| 198 |
return chunk
|
| 199 |
return str(chunk)
|
| 200 |
+
except Exception as e:
|
| 201 |
+
logger.warning(f"clean_stream error: {e}")
|
| 202 |
return ""
|
| 203 |
|
| 204 |
# =====================================================
|
| 205 |
+
# CHAT LOGIC - ู
ุน fallback ุฐูู
|
| 206 |
# =====================================================
|
| 207 |
def ask(message: str, history, provider_name: str, model_name: str, stop_flag=None):
|
| 208 |
message = (message or "").strip()
|
|
|
|
| 210 |
yield ""
|
| 211 |
return
|
| 212 |
|
| 213 |
+
# ู
ูุชุงุญ ุงูุชุฎุฒูู ุงูู
ุคูุช
|
| 214 |
key = f"{provider_name}|{model_name}|{message}"
|
| 215 |
cached = CACHE.get(key)
|
| 216 |
if cached:
|
| 217 |
yield cached
|
| 218 |
return
|
| 219 |
|
| 220 |
+
# ุจูุงุก ุณุฌู ุงูู
ุญุงุฏุซุฉ
|
| 221 |
msgs = []
|
| 222 |
try:
|
| 223 |
if history:
|
|
|
|
| 238 |
|
| 239 |
msgs.append({"role": "user", "content": message})
|
| 240 |
|
| 241 |
+
# ูุงุฆู
ุฉ ุงูู
ุฒูุฏุงุช ุงูุชู ุณูุญุงูููุง (ู
ุฑุชุจุฉ ุญุณุจ ุงุญุชู
ุงู ุงููุฌุงุญ)
|
| 242 |
+
# ุงูู
ุฒูุฏ ุงูู
ุทููุจ ุฃููุงู ุซู
Blackbox ุซู
DeepSeek ุซู
ุงูุจููุฉ
|
| 243 |
fallback_providers = [
|
| 244 |
provider_name,
|
| 245 |
+
"Blackbox",
|
| 246 |
+
"DeepSeek",
|
| 247 |
+
"Perplexity",
|
| 248 |
+
"Copilot",
|
| 249 |
+
"You",
|
| 250 |
+
"Bing",
|
| 251 |
"Qwen"
|
| 252 |
]
|
| 253 |
used = []
|
|
|
|
| 258 |
used.append(pname)
|
| 259 |
pobj = REAL_PROVIDERS.get(pname)
|
| 260 |
if not pobj:
|
| 261 |
+
logger.info(f"Provider {pname} not available, skipping")
|
| 262 |
continue
|
| 263 |
|
| 264 |
+
# ุงูุญุตูู ุนูู ูุงุฆู
ุฉ ุงููู
ุงุฐุฌ ููุฐุง ุงูู
ุฒูุฏ
|
| 265 |
+
models_list = discover_provider_models(pobj, pname)
|
| 266 |
+
if not models_list:
|
| 267 |
+
logger.warning(f"No models for provider {pname}")
|
| 268 |
+
continue
|
| 269 |
|
| 270 |
+
# ุชุฑุชูุจ ุงููู
ุงุฐุฌ: ุงููู
ูุฐุฌ ุงูู
ุทููุจ ุฃููุงู ุซู
ุจููุฉ ุงููู
ุงุฐุฌ
|
| 271 |
+
if model_name in models_list:
|
| 272 |
+
model_candidates = [model_name] + [m for m in models_list if m != model_name]
|
| 273 |
+
else:
|
| 274 |
+
model_candidates = models_list
|
| 275 |
|
| 276 |
+
for m in model_candidates[:10]: # ุฌุฑุจ ุฃูู 10 ูู
ุงุฐุฌ ูุญุฏ ุฃูุตู
|
| 277 |
try:
|
| 278 |
+
logger.info(f"Trying provider {pname} with model {m}")
|
| 279 |
stream = g4f.ChatCompletion.create(
|
| 280 |
model=m,
|
| 281 |
provider=pobj,
|
| 282 |
messages=msgs,
|
| 283 |
stream=True,
|
| 284 |
+
timeout=45
|
| 285 |
)
|
|
|
|
| 286 |
buffer = []
|
|
|
|
| 287 |
for chunk in stream:
|
| 288 |
if stop_flag and stop_flag.is_set():
|
| 289 |
return
|
|
|
|
| 292 |
continue
|
| 293 |
buffer.append(c)
|
| 294 |
yield c
|
|
|
|
| 295 |
full = "".join(buffer)
|
| 296 |
if full.strip():
|
| 297 |
CACHE.set(key, full)
|
| 298 |
return
|
|
|
|
| 299 |
except Exception as e:
|
| 300 |
+
logger.warning(f"Provider {pname} model {m} failed: {str(e)[:200]}")
|
| 301 |
continue
|
| 302 |
|
| 303 |
+
yield "โ ูุดูุช ุฌู
ูุน ุงูู
ุฒูุฏุงุช. ุชุฃูุฏ ู
ู ุงุชุตุงู ุงูุฅูุชุฑูุช ุฃู ุญุงูู ูุงุญูุงู."
|
| 304 |
|
| 305 |
# =====================================================
|
| 306 |
# FASTAPI
|
| 307 |
# =====================================================
|
| 308 |
+
app = FastAPI(title="G4F Smart Router", description="AI Gateway - ู
ุชุนุฏุฏ ุงูู
ุฒูุฏุงุช")
|
| 309 |
|
| 310 |
API_KEY = os.getenv("API_KEY", "mysecretkey123")
|
| 311 |
|
| 312 |
class ChatRequest(BaseModel):
|
| 313 |
message: str
|
| 314 |
+
provider: str = "Blackbox"
|
| 315 |
+
model: str = "gpt-4o"
|
| 316 |
history: List[Any] = []
|
| 317 |
|
| 318 |
# =====================================================
|
| 319 |
+
# ุงูุชุญูู ู
ู ุงูู
ูุชุงุญ
|
| 320 |
# =====================================================
|
|
|
|
| 321 |
def verify_api_key(request: Request):
|
| 322 |
auth = request.headers.get("Authorization", "").strip()
|
| 323 |
x_key = request.headers.get("X-API-Key", "").strip()
|
|
|
|
| 335 |
raise HTTPException(status_code=401, detail="Invalid API key. Use 'Authorization: Bearer KEY' or 'X-API-Key: KEY'")
|
| 336 |
|
| 337 |
# =====================================================
|
| 338 |
+
# ุฏุนู
HEAD (ูุฅุตูุงุญ 405)
|
| 339 |
# =====================================================
|
|
|
|
| 340 |
@app.head("/")
|
| 341 |
async def head_root():
|
| 342 |
return Response(status_code=200)
|
|
|
|
| 350 |
return Response(status_code=200)
|
| 351 |
|
| 352 |
# =====================================================
|
| 353 |
+
# ููุงุท ููุงูุฉ ู
ุชูุงููุฉ ู
ุน Claude Desktop
|
| 354 |
# =====================================================
|
|
|
|
| 355 |
@app.get("/v1/models")
|
| 356 |
async def v1_models(request: Request):
|
| 357 |
+
# ูุง ุชุญุชุงุฌ ู
ูุชุงุญ ููุฐู ุงูููุทุฉ (Claude Desktop ูุทูุจูุง ุฃููุงู)
|
|
|
|
| 358 |
models = []
|
| 359 |
for pname, pobj in REAL_PROVIDERS.items():
|
| 360 |
+
models_list = discover_provider_models(pobj, pname)
|
| 361 |
+
for model in models_list[:5]: # ูุนุฑุถ ุฃูู 5 ูู
ุงุฐุฌ ููู ู
ุฒูุฏ
|
|
|
|
| 362 |
models.append({
|
| 363 |
"id": model,
|
| 364 |
"type": "model",
|
| 365 |
"display_name": f"{pname} - {model}"
|
| 366 |
})
|
|
|
|
| 367 |
if not models:
|
| 368 |
+
models = [{"id": "gpt-4o", "type": "model", "display_name": "Default"}]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
return {"data": models}
|
| 370 |
|
| 371 |
@app.post("/v1/messages")
|
| 372 |
async def v1_messages(request: Request):
|
|
|
|
| 373 |
verify_api_key(request)
|
|
|
|
| 374 |
body = await request.json()
|
|
|
|
| 375 |
messages = body.get("messages", [])
|
| 376 |
if not messages:
|
| 377 |
raise HTTPException(status_code=400, detail="No messages provided")
|
|
|
|
| 378 |
last_message = messages[-1]
|
| 379 |
user_message = last_message.get("content", "")
|
| 380 |
+
model = body.get("model", "gpt-4o")
|
|
|
|
| 381 |
system_prompt = body.get("system", "")
|
|
|
|
| 382 |
history = []
|
| 383 |
for msg in messages[:-1]:
|
| 384 |
role = msg.get("role", "user")
|
| 385 |
content = msg.get("content", "")
|
| 386 |
history.append({"role": role, "content": content})
|
|
|
|
| 387 |
full_message = user_message
|
| 388 |
if system_prompt:
|
| 389 |
full_message = f"[System: {system_prompt}]\n\n{user_message}"
|
| 390 |
|
| 391 |
full_response = ""
|
| 392 |
+
for chunk in ask(full_message, history, "Blackbox", model):
|
| 393 |
full_response = chunk
|
| 394 |
|
| 395 |
return {
|
|
|
|
| 400 |
"model": model,
|
| 401 |
"stop_reason": "end_turn",
|
| 402 |
"stop_sequence": None,
|
| 403 |
+
"usage": {"input_tokens": len(user_message)//4, "output_tokens": len(full_response)//4}
|
|
|
|
|
|
|
|
|
|
| 404 |
}
|
| 405 |
|
| 406 |
@app.post("/v1/messages/stream")
|
| 407 |
async def v1_messages_stream(request: Request):
|
|
|
|
| 408 |
verify_api_key(request)
|
|
|
|
| 409 |
body = await request.json()
|
| 410 |
messages = body.get("messages", [])
|
| 411 |
if not messages:
|
| 412 |
raise HTTPException(status_code=400, detail="No messages provided")
|
|
|
|
| 413 |
last_message = messages[-1]
|
| 414 |
user_message = last_message.get("content", "")
|
| 415 |
+
model = body.get("model", "gpt-4o")
|
| 416 |
system_prompt = body.get("system", "")
|
|
|
|
| 417 |
full_message = user_message
|
| 418 |
if system_prompt:
|
| 419 |
full_message = f"[System: {system_prompt}]\n\n{user_message}"
|
| 420 |
|
| 421 |
async def generate_stream():
|
| 422 |
message_id = f"msg_{int(time.time())}_{os.urandom(4).hex()}"
|
|
|
|
| 423 |
yield f"event: message_start\ndata: {{\"message\": {{\"id\": \"{message_id}\", \"type\": \"message\", \"role\": \"assistant\", \"content\": [], \"model\": \"{model}\", \"stop_reason\": null, \"stop_sequence\": null, \"usage\": {{\"input_tokens\": 0, \"output_tokens\": 0}}}}}}\n\n"
|
|
|
|
| 424 |
yield f"event: content_block_start\ndata: {{\"type\": \"content_block_start\", \"index\": 0, \"content_block\": {{\"type\": \"text\", \"text\": \"\"}}}}\n\n"
|
| 425 |
+
for chunk in ask(full_message, [], "Blackbox", model):
|
|
|
|
| 426 |
yield f"event: content_block_delta\ndata: {{\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {{\"type\": \"text_delta\", \"text\": {json.dumps(chunk, ensure_ascii=False)}}}}}\n\n"
|
|
|
|
| 427 |
yield f"event: message_delta\ndata: {{\"type\": \"message_delta\", \"delta\": {{\"stop_reason\": \"end_turn\", \"stop_sequence\": null}}, \"usage\": {{\"output_tokens\": 100}}}}\n\n"
|
| 428 |
yield f"event: message_stop\ndata: {{}}\n\n"
|
| 429 |
|
| 430 |
+
return StreamingResponse(generate_stream(), media_type="text/event-stream", headers={"Cache-Control": "no-cache", "Connection": "keep-alive"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
|
| 432 |
# =====================================================
|
| 433 |
+
# ููุงุท ููุงูุฉ ุฅุถุงููุฉ ููุชูุงูู ุงููุฏูู
|
| 434 |
# =====================================================
|
|
|
|
| 435 |
@app.get("/")
|
| 436 |
async def root():
|
| 437 |
return {
|
| 438 |
+
"message": "G4F Smart Router is running (Multi-Provider)",
|
| 439 |
+
"providers": list(REAL_PROVIDERS.keys()),
|
| 440 |
"endpoints": {
|
| 441 |
+
"GET /": "Home",
|
| 442 |
+
"GET /health": "Health",
|
| 443 |
"GET /v1/models": "List models (NO AUTH)",
|
| 444 |
+
"POST /v1/messages": "Send message (AUTH)",
|
| 445 |
+
"POST /v1/messages/stream": "Stream (AUTH)",
|
| 446 |
+
"GET /providers": "Providers list (AUTH)",
|
|
|
|
|
|
|
| 447 |
},
|
|
|
|
| 448 |
"cookies": COOKIE_STATUS,
|
|
|
|
| 449 |
"status": "โ
Server is working"
|
| 450 |
}
|
| 451 |
|
|
|
|
| 453 |
async def health():
|
| 454 |
return {"status": "ok", "cookies": COOKIE_STATUS, "providers": list(REAL_PROVIDERS.keys())}
|
| 455 |
|
| 456 |
+
@app.get("/providers")
|
| 457 |
+
async def get_providers(request: Request):
|
| 458 |
+
verify_api_key(request)
|
| 459 |
+
return {"providers": list(REAL_PROVIDERS.keys())}
|
| 460 |
+
|
| 461 |
@app.post("/chat")
|
| 462 |
async def chat(request: Request, chat_req: ChatRequest):
|
| 463 |
verify_api_key(request)
|
|
|
|
| 464 |
result = ""
|
| 465 |
for chunk in ask(chat_req.message, chat_req.history, chat_req.provider, chat_req.model):
|
| 466 |
result = chunk
|
|
|
|
| 467 |
return JSONResponse({"response": result})
|
| 468 |
|
| 469 |
@app.post("/chat/stream")
|
| 470 |
async def chat_stream(request: Request, chat_req: ChatRequest):
|
| 471 |
verify_api_key(request)
|
|
|
|
| 472 |
async def generate():
|
| 473 |
for chunk in ask(chat_req.message, chat_req.history, chat_req.provider, chat_req.model):
|
| 474 |
yield f"data: {json.dumps({'delta': chunk}, ensure_ascii=False)}\n\n"
|
| 475 |
yield "data: [DONE]\n\n"
|
|
|
|
| 476 |
return StreamingResponse(generate(), media_type="text/event-stream")
|
| 477 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
# =====================================================
|
| 479 |
+
# ุงูุชุดุบูู
|
| 480 |
# =====================================================
|
| 481 |
if __name__ == "__main__":
|
| 482 |
import uvicorn
|
| 483 |
port = int(os.getenv("PORT", 7860))
|
| 484 |
+
uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|