Download app.py from ayb-bh92/Getwayapi: direct link, hf CLI and curl.
- Browser
- Download file 25.8 kB
-
https://huggingface.co/spaces/ayb-bh92/Getwayapi/resolve/main/app.py
- Command line
-
hf download hf://spaces/ayb-bh92/Getwayapi/app.py
-
curl -L -o app.py https://huggingface.co/spaces/ayb-bh92/Getwayapi/resolve/main/app.py
25.8 kB
| import os | |
| import json | |
| import time | |
| import logging | |
| import subprocess | |
| import re | |
| import asyncio | |
| from concurrent.futures import ThreadPoolExecutor | |
| from typing import Any, Dict, Generator, List, Optional | |
| import g4f | |
| from fastapi import FastAPI, HTTPException, Request | |
| from fastapi.responses import StreamingResponse, JSONResponse | |
| # ===================================================== | |
| # LOGGING | |
| # ===================================================== | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s [%(levelname)s] %(message)s" | |
| ) | |
| logger = logging.getLogger("g4f-agent") | |
| # ===================================================== | |
| # THREAD POOL | |
| # ===================================================== | |
| _POOL = ThreadPoolExecutor(max_workers=8) | |
| # ===================================================== | |
| # COOKIES | |
| # ===================================================== | |
| def _load_cookies_raw() -> Dict[str, Any]: | |
| raw_env = (os.getenv("COOKIES_JSON") or "").strip() | |
| if raw_env: | |
| try: | |
| return json.loads(raw_env) | |
| except Exception as e: | |
| logger.warning(f"Cookies env error: {e}") | |
| try: | |
| if os.path.exists("cookies.json"): | |
| with open("cookies.json", "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except Exception as e: | |
| logger.warning(f"Cookies file error: {e}") | |
| return {} | |
| def load_cookies() -> str: | |
| data = _load_cookies_raw() | |
| if not data: | |
| return "โ ๏ธ No Cookies" | |
| try: | |
| from g4f.cookies import set_cookies | |
| except Exception: | |
| return "โ ๏ธ set_cookies unavailable" | |
| for domain, vals in data.items(): | |
| try: | |
| dom = domain if "." in domain else f".{domain}.com" | |
| if isinstance(vals, list): | |
| vals = {x["name"]: x["value"] for x in vals if isinstance(x, dict)} | |
| if isinstance(vals, dict): | |
| set_cookies(dom, vals) | |
| except Exception as e: | |
| logger.warning(f"Cookie set error [{domain}]: {e}") | |
| return "โ Cookies Loaded" | |
| COOKIE_STATUS = load_cookies() | |
| logger.info(f"Cookie status: {COOKIE_STATUS}") | |
| # ===================================================== | |
| # PROVIDERS & MODELS | |
| # ===================================================== | |
| def get_provider(name: str): | |
| try: | |
| return getattr(g4f.Provider, name) | |
| except Exception: | |
| return None | |
| QWEN_PROVIDER = get_provider("Qwen") | |
| QWEN_MODELS = [ | |
| "qwen-max", | |
| "qwen-max-latest", | |
| "qwen3.7-max", | |
| "qwen2.5-max", | |
| "qwen2.5-72b-instruct", | |
| "qwen-plus", | |
| "qwen-turbo", | |
| "", | |
| ] | |
| CLAUDE_MODEL_MAP = { | |
| "claude-opus-4-6": "qwen-max", | |
| "claude-sonnet-4-6": "qwen-max", | |
| "claude-haiku-4-5": "qwen-plus", | |
| } | |
| # ===================================================== | |
| # CACHE (ูููููู ููุท) | |
| # ===================================================== | |
| _CACHE: Dict[str, str] = {} | |
| _CACHE_TS: Dict[str, float] = {} | |
| def cache_get(key: str) -> Optional[str]: | |
| ts = _CACHE_TS.get(key) | |
| if ts and (time.time() - ts) < 300: | |
| return _CACHE.get(key) | |
| return None | |
| def cache_set(key: str, val: str): | |
| _CACHE[key] = val | |
| _CACHE_TS[key] = time.time() | |
| # ===================================================== | |
| # CHUNK CLEANER | |
| # ===================================================== | |
| def _clean_chunk(chunk) -> str: | |
| try: | |
| if isinstance(chunk, dict): | |
| if "choices" in chunk and chunk["choices"]: | |
| delta = chunk["choices"][0].get("delta", {}) | |
| return delta.get("content") or delta.get("text") or "" | |
| return chunk.get("content") or chunk.get("text") or "" | |
| if isinstance(chunk, str): | |
| s = chunk.strip() | |
| if s.startswith("{"): | |
| try: | |
| d = json.loads(s) | |
| if d.get("code") == "Not_Found": | |
| return "" | |
| if "choices" in d and d["choices"]: | |
| return d["choices"][0].get("delta", {}).get("content", "") | |
| return d.get("content") or d.get("text") or "" | |
| except Exception: | |
| pass | |
| return chunk.replace("\\n", "\n") | |
| return str(chunk) | |
| except Exception: | |
| return "" | |
| # ===================================================== | |
| # โ ุฅุตูุงุญ ุงูุฐุงูุฑุฉ โ ุชูุธูู ุงูุฑุณุงุฆู ุจุฏูู ูุทุนูุง | |
| # ===================================================== | |
| def build_messages(raw_messages: List[Dict]) -> List[Dict]: | |
| """ | |
| ูููุธูู ูุงุฆู ุฉ ุงูุฑุณุงุฆู ุงููุงุฏู ุฉ ู ู OpenAI ูููุนูุฏูุง ูุงู ูุฉ. | |
| - ูุฏุนู content ูู string ุฃู list (multi-part) | |
| - ูุญุชูุธ ุจูู ุงูุฑุณุงุฆู (user + assistant + system) | |
| - ูุง ููุทุน ุงูุณูุงู ููุงุฆูุงู | |
| """ | |
| result = [] | |
| for m in raw_messages: | |
| role = m.get("role", "user") | |
| content = m.get("content", "") | |
| # ุฏุนู content ูู list ู ู blocks | |
| if isinstance(content, list): | |
| parts = [] | |
| for block in content: | |
| if not isinstance(block, dict): | |
| continue | |
| btype = block.get("type", "") | |
| if btype in ("text", "input_text"): | |
| text = block.get("text", "") | |
| if text.strip(): | |
| parts.append(text) | |
| content = "\n".join(parts) | |
| else: | |
| content = str(content) if content else "" | |
| # ูุง ููุถูู ุฑุณุงุฆู ูุงุฑุบุฉ | |
| if content.strip() or role == "system": | |
| result.append({"role": role, "content": content}) | |
| logger.debug(f"build_messages: {len(raw_messages)} โ {len(result)} messages") | |
| return result | |
| # ===================================================== | |
| # โ ูุถุน ุงููู ูุฐุฌ: ุชุฏูู SSE ุญูููู token ุจู token | |
| # ===================================================== | |
| def _stream_generator( | |
| messages: List[Dict], | |
| model_name: str | |
| ) -> Generator[str, None, None]: | |
| """ | |
| Generator ู ุชุฒุงู ู โ ูููุชุฌ chunks ููุฑ ูุตูููุง ู ู g4f. | |
| ููุณุชุฎุฏู ุญุตุฑูุงู ูู ูุถุน ุงููู ูุฐุฌ ุงูุนุงุฏู. | |
| """ | |
| target = CLAUDE_MODEL_MAP.get(model_name, model_name) | |
| pobj = QWEN_PROVIDER | |
| if not pobj: | |
| yield "โ ู ุฒูุฏ Qwen ุบูุฑ ู ุชุงุญ." | |
| return | |
| candidates = [target] + [m for m in QWEN_MODELS if m != target] | |
| for model in candidates: | |
| try: | |
| stream = g4f.ChatCompletion.create( | |
| model=model, | |
| provider=pobj, | |
| messages=messages, | |
| stream=True, | |
| ) | |
| got = False | |
| for chunk in stream: | |
| text = _clean_chunk(chunk) | |
| if text: | |
| got = True | |
| yield text # โ ุชุฏูู ููุฑู ุจุฏูู ุชุฌู ูุน | |
| if got: | |
| logger.info(f"[Stream] โ model={model}") | |
| return | |
| except Exception as e: | |
| err = str(e) | |
| if "Not_Found" in err or "Model not found" in err: | |
| continue | |
| logger.warning(f"[Stream] model={model} error: {e}") | |
| continue | |
| yield "\nโ ูุดู ุงูุงุชุตุงู ุจู Qwen ููุงุฆูุงู." | |
| async def stream_to_queue( | |
| messages: List[Dict], | |
| model_name: str, | |
| queue: asyncio.Queue | |
| ): | |
| """ููุดุบูู _stream_generator ูู thread ููุถุน ุงููุชุงุฆุฌ ูู asyncio.Queue""" | |
| loop = asyncio.get_running_loop() | |
| def _run(): | |
| try: | |
| for chunk in _stream_generator(messages, model_name): | |
| asyncio.run_coroutine_threadsafe(queue.put(("chunk", chunk)), loop) | |
| except Exception as e: | |
| asyncio.run_coroutine_threadsafe(queue.put(("error", str(e))), loop) | |
| finally: | |
| asyncio.run_coroutine_threadsafe(queue.put(("done", None)), loop) | |
| _POOL.submit(_run) | |
| # ===================================================== | |
| # โ ูุถุน ุงููููู: ask_sync ุจุฏูู ุชุฏูู (ููุญููุฉ ุงูุฏุงุฎููุฉ) | |
| # ===================================================== | |
| def ask_sync(messages: List[Dict], model_name: str) -> str: | |
| """ | |
| ููุฑุฌุน ุงูุฑุฏ ูุงู ูุงู ุฏูุนุฉ ูุงุญุฏุฉ. | |
| ููุณุชุฎุฏู ุญุตุฑูุงู ุฏุงุฎู ุญููุฉ ุงููููู. | |
| """ | |
| target = CLAUDE_MODEL_MAP.get(model_name, model_name) | |
| pobj = QWEN_PROVIDER | |
| if not pobj: | |
| return "โ ู ุฒูุฏ Qwen ุบูุฑ ู ุชุงุญ." | |
| cache_key = f"sync|{target}|{hash(json.dumps(messages, ensure_ascii=False, sort_keys=True))}" | |
| cached = cache_get(cache_key) | |
| if cached: | |
| logger.debug("[Sync] cache hit") | |
| return cached | |
| candidates = [target] + [m for m in QWEN_MODELS if m != target] | |
| for model in candidates: | |
| # ุฌุฑุจ ุจุฏูู stream ุฃููุงู ุซู ู ุน stream | |
| for use_stream in (False, True): | |
| try: | |
| result = g4f.ChatCompletion.create( | |
| model=model, | |
| provider=pobj, | |
| messages=messages, | |
| stream=use_stream, | |
| ) | |
| if use_stream: | |
| text = "".join( | |
| _clean_chunk(c) for c in result | |
| ) | |
| else: | |
| text = _clean_chunk(result) if not isinstance(result, str) else result | |
| if text.strip(): | |
| cache_set(cache_key, text.strip()) | |
| logger.info(f"[Sync] โ model={model} stream={use_stream}") | |
| return text.strip() | |
| except Exception as e: | |
| err = str(e) | |
| if "Not_Found" in err or "Model not found" in err: | |
| break # ูู ูุฐุฌ ุบูุฑ ู ูุฌูุฏ โ ุงููู ูุฐุฌ ุงูุชุงูู | |
| logger.warning(f"[Sync] model={model} stream={use_stream} error: {e}") | |
| if not use_stream: | |
| continue # ุฌุฑุจ stream=True ูููุณ ุงููู ูุฐุฌ | |
| break | |
| return "โ ูุดู ุงูุงุชุตุงู ุจู Qwen ููุงุฆูุงู." | |
| # ===================================================== | |
| # TOOLS | |
| # ===================================================== | |
| TOOLS_NAMES = {"run_python", "run_shell", "create_file", "read_file", "list_files"} | |
| def execute_tool(name: str, args: Dict) -> str: | |
| """ุชูููุฐ ุงูุฃุฏุงุฉ ูุฅุฑุฌุงุน ุงููุชูุฌุฉ ููุต""" | |
| try: | |
| if name == "run_python": | |
| proc = subprocess.run( | |
| ["python3", "-c", args.get("code", "")], | |
| capture_output=True, text=True, timeout=30 | |
| ) | |
| return (proc.stdout or proc.stderr or "โ ูุง ู ุฎุฑุฌุงุช")[:3000] | |
| elif name == "run_shell": | |
| proc = subprocess.run( | |
| args.get("command", ""), | |
| shell=True, capture_output=True, text=True, timeout=30 | |
| ) | |
| return (proc.stdout or proc.stderr or "โ ุชู ุงูุชูููุฐ")[:3000] | |
| elif name == "create_file": | |
| path = args.get("path", "") | |
| if os.path.dirname(path): | |
| os.makedirs(os.path.dirname(path), exist_ok=True) | |
| with open(path, "w", encoding="utf-8") as f: | |
| f.write(args.get("content", "")) | |
| return f"โ ุชู ุฅูุดุงุก: {path}" | |
| elif name == "read_file": | |
| with open(args.get("path", ""), "r", encoding="utf-8") as f: | |
| return f.read()[:4000] | |
| elif name == "list_files": | |
| entries = os.listdir(args.get("directory", ".")) | |
| return "\n".join(entries) or "(ูุงุฑุบ)" | |
| return f"โ ุฃุฏุงุฉ ุบูุฑ ู ุนุฑููุฉ: {name}" | |
| except subprocess.TimeoutExpired: | |
| return f"โ ุงูุชูุช ุงูู ููุฉ: {name}" | |
| except Exception as e: | |
| return f"โ ุฎุทุฃ ูู {name}: {e}" | |
| # ===================================================== | |
| # AGENT SYSTEM PROMPT | |
| # ===================================================== | |
| AGENT_SYSTEM = """ุฃูุช ู ุณุงุนุฏ ุฐูู ูู ุชูู ุฃุฏูุงุช. ุนูุฏ ุงูุญุงุฌุฉ ูุฃุฏุงุฉ ุงุณุชุฎุฏู ูุฐุง ุงูุชูุณูู ุญุตุฑุงู: | |
| <tool_call> | |
| {"name": "ุงุณู _ุงูุฃุฏุงุฉ", "arguments": {"ู ูุชุงุญ": "ููู ุฉ"}} | |
| </tool_call> | |
| ุงูุฃุฏูุงุช ุงูู ุชุงุญุฉ: | |
| โข run_python(code) โ ุชูููุฐ Python | |
| โข run_shell(command) โ ุชูููุฐ shell | |
| โข create_file(path,content)โ ุฅูุดุงุก ู ูู | |
| โข read_file(path) โ ูุฑุงุกุฉ ู ูู | |
| โข list_files(directory) โ ุนุฑุถ ู ุฌูุฏ | |
| ููุงุนุฏ: | |
| 1. ูุง ุชุถุน ูุตุงู ูุจู/ุจุนุฏ <tool_call>. | |
| 2. ุจุนุฏ ุงุณุชูุงู ุงููุชูุฌุฉ ูุงุตู ุงูุชูููุฑ ุฃู ุงุณุชุฎุฏู ุฃุฏุงุฉ ุฃุฎุฑู. | |
| 3. ุฃููู ุจุฅุฌุงุจุฉ ููุงุฆูุฉ ุจุฏูู <tool_call>.""" | |
| def _parse_tool_call(text: str) -> Optional[Dict]: | |
| m = re.search(r"<tool_call>\s*(.*?)\s*</tool_call>", text, re.DOTALL) | |
| if not m: | |
| return None | |
| try: | |
| d = json.loads(m.group(1)) | |
| if isinstance(d, dict) and "name" in d: | |
| return d | |
| except Exception: | |
| pass | |
| return None | |
| # ===================================================== | |
| # โ ูุถุน ุงููููู: SSE ุญูููู ู ุน ุฃุญุฏุงุซ ู ูุตููุฉ | |
| # ุฃุญุฏุงุซ: thinking | tool_call | tool_result | token | done | error | |
| # ===================================================== | |
| def _sse_event(event_type: str, data: Dict) -> str: | |
| """ูููุดุฆ ุณุทุฑ SSE ู ููุณููู""" | |
| return f"event: {event_type}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n" | |
| async def agent_sse_generator( | |
| messages: List[Dict], | |
| model_name: str, | |
| completion_id: str, | |
| created: int | |
| ): | |
| """ | |
| โ ุงููููู ู ุน SSE ุญูููู: | |
| - ููุฑุณู ุญุฏุซ tool_call ุนูุฏ ุทูุจ ุฃุฏุงุฉ | |
| - ููุฑุณู ุญุฏุซ tool_result ุจุนุฏ ุชูููุฐูุง | |
| - ููุฑุณู ุงูุฅุฌุงุจุฉ ุงูููุงุฆูุฉ ูู tokens ุนุจุฑ OpenAI chunk format | |
| - ูุง ูุณุชุฎุฏู ุชุฏูู ุงููู ูุฐุฌ ุฏุงุฎููุงู (ask_sync ููุท) | |
| """ | |
| loop = asyncio.get_running_loop() | |
| # ุจูุงุก ุฑุณุงุฆู ุงููููู ู ุน system prompt | |
| agent_msgs: List[Dict] = [] | |
| existing_system = next( | |
| (m["content"] for m in messages if m.get("role") == "system"), None | |
| ) | |
| system_content = AGENT_SYSTEM | |
| if existing_system: | |
| system_content += f"\n\n---\n{existing_system}" | |
| agent_msgs.append({"role": "system", "content": system_content}) | |
| for m in messages: | |
| if m.get("role") != "system": | |
| agent_msgs.append(m) | |
| # โโ Chunk ุฃูู: ุชุนุฑูู ุงูุฏูุฑ โโ | |
| yield f"data: {json.dumps({'id': completion_id, 'object': 'chat.completion.chunk', 'created': created, 'model': model_name, 'choices': [{'index': 0, 'delta': {'role': 'assistant', 'content': ''}, 'finish_reason': None}]}, ensure_ascii=False)}\n\n" | |
| max_iter = 10 | |
| for iteration in range(1, max_iter + 1): | |
| logger.info(f"[Agent] Iter {iteration}/{max_iter}") | |
| # ุงุณุชุฏุนุงุก ุงููู ูุฐุฌ ุจุฏูู ุชุฏูู | |
| response = await loop.run_in_executor( | |
| _POOL, ask_sync, agent_msgs, model_name | |
| ) | |
| tool_call = _parse_tool_call(response) | |
| # โโ ูุง ุฃุฏุงุฉ โ ุฅุฌุงุจุฉ ููุงุฆูุฉ โ ุฃุฑุณููุง ูู tokens โโ | |
| if tool_call is None: | |
| logger.info(f"[Agent] Final answer at iter={iteration}") | |
| chunk_size = 30 | |
| for i in range(0, len(response), chunk_size): | |
| piece = response[i:i + chunk_size] | |
| yield f"data: {json.dumps({'id': completion_id, 'object': 'chat.completion.chunk', 'created': created, 'model': model_name, 'choices': [{'index': 0, 'delta': {'content': piece}, 'finish_reason': None}]}, ensure_ascii=False)}\n\n" | |
| await asyncio.sleep(0.003) | |
| # chunk ุฃุฎูุฑ | |
| yield f"data: {json.dumps({'id': completion_id, 'object': 'chat.completion.chunk', 'created': created, 'model': model_name, 'choices': [{'index': 0, 'delta': {}, 'finish_reason': 'stop'}]}, ensure_ascii=False)}\n\n" | |
| yield "data: [DONE]\n\n" | |
| return | |
| # โโ ููุฌุฏ ุทูุจ ุฃุฏุงุฉ โโ | |
| tool_name = tool_call.get("name", "") | |
| tool_args = tool_call.get("arguments", {}) | |
| # ุญุฏุซ: tool_call (ุฅุฎุจุงุฑ ุงูุนู ูู ุฃู ุงููููู ูููููุฐ ุฃุฏุงุฉ) | |
| yield _sse_event("tool_call", { | |
| "id": completion_id, | |
| "iteration": iteration, | |
| "tool": tool_name, | |
| "args": tool_args, | |
| }) | |
| if tool_name not in TOOLS_NAMES: | |
| err_result = f"โ ุฃุฏุงุฉ ุบูุฑ ู ูุฌูุฏุฉ: {tool_name}. ุงูู ุชุงุญุฉ: {', '.join(TOOLS_NAMES)}" | |
| yield _sse_event("tool_result", { | |
| "id": completion_id, | |
| "tool": tool_name, | |
| "result": err_result, | |
| "ok": False, | |
| }) | |
| agent_msgs.append({"role": "assistant", "content": response}) | |
| agent_msgs.append({"role": "user", "content": f"<tool_result>\n{err_result}\n</tool_result>"}) | |
| continue | |
| # ุชูููุฐ ุงูุฃุฏุงุฉ ูู thread ู ููุตู | |
| logger.info(f"[Agent] Executing tool: {tool_name}({tool_args})") | |
| tool_result = await loop.run_in_executor( | |
| _POOL, execute_tool, tool_name, tool_args | |
| ) | |
| logger.info(f"[Agent] Tool result: {tool_result[:80]}โฆ") | |
| # ุญุฏุซ: tool_result | |
| yield _sse_event("tool_result", { | |
| "id": completion_id, | |
| "tool": tool_name, | |
| "result": tool_result, | |
| "ok": True, | |
| }) | |
| # ุฅุถุงูุฉ ุฑุฏ ุงููู ูุฐุฌ ููุชูุฌุฉ ุงูุฃุฏุงุฉ ููุณูุงู (ุงูุฐุงูุฑุฉ) | |
| agent_msgs.append({"role": "assistant", "content": response}) | |
| agent_msgs.append({ | |
| "role": "user", | |
| "content": ( | |
| f"<tool_result>\n" | |
| f"ุงูุฃุฏุงุฉ: {tool_name}\n" | |
| f"ุงููุชูุฌุฉ:\n{tool_result}\n" | |
| f"</tool_result>\n\n" | |
| f"ูุงุตู." | |
| ) | |
| }) | |
| # ุชุฌุงูุฒ ุงูุญุฏ ุงูุฃูุตู | |
| timeout_msg = "โ ๏ธ ุชุฌุงูุฒ ุงููููู ุงูุญุฏ ุงูุฃูุตู ู ู ุงูุชูุฑุงุฑุงุช." | |
| yield _sse_event("error", {"id": completion_id, "message": timeout_msg}) | |
| yield f"data: {json.dumps({'id': completion_id, 'object': 'chat.completion.chunk', 'created': created, 'model': model_name, 'choices': [{'index': 0, 'delta': {'content': timeout_msg}, 'finish_reason': 'stop'}]}, ensure_ascii=False)}\n\n" | |
| yield "data: [DONE]\n\n" | |
| # ===================================================== | |
| # FASTAPI | |
| # ===================================================== | |
| app = FastAPI(title="G4F Agent API v2") | |
| API_KEY = os.getenv("API_KEY", "mysecretkey123") | |
| def verify(request: Request): | |
| auth = request.headers.get("Authorization", "") | |
| if auth.startswith("Bearer ") and auth[7:] == API_KEY: | |
| return True | |
| if request.headers.get("X-API-Key", "") == API_KEY: | |
| return True | |
| raise HTTPException(status_code=401, detail="Invalid or missing API key") | |
| async def root(): | |
| return JSONResponse({ | |
| "status": "โ running", | |
| "version": "2.0", | |
| "modes": "stream=true โ SSE tokens | use_agent=true โ SSE agent events", | |
| "tools": list(TOOLS_NAMES), | |
| "endpoint": "/v1/chat/completions", | |
| }) | |
| async def list_models(request: Request): | |
| verify(request) | |
| return JSONResponse({ | |
| "object": "list", | |
| "data": [ | |
| {"id": "claude-opus-4-6", "object": "model", "owned_by": "anthropic"}, | |
| {"id": "claude-sonnet-4-6", "object": "model", "owned_by": "anthropic"}, | |
| {"id": "claude-haiku-4-5", "object": "model", "owned_by": "anthropic"}, | |
| ] | |
| }) | |
| # ===================================================== | |
| # โ MAIN ENDPOINT โ ุงูู ูุทู ุงููุงู ู | |
| # ===================================================== | |
| async def chat_completions(request: Request): | |
| verify(request) | |
| body = await request.json() | |
| model = body.get("model", "claude-sonnet-4-6") | |
| raw_messages = body.get("messages", []) | |
| do_stream = body.get("stream", False) | |
| tools = body.get("tools", []) | |
| use_agent = body.get("use_agent", False) | |
| if not raw_messages: | |
| raise HTTPException(status_code=400, detail="messages required") | |
| # โ ุจูุงุก ุงูุฑุณุงุฆู ูุงู ูุฉ ู ุน ุงูุฐุงูุฑุฉ | |
| clean_msgs = build_messages(raw_messages) | |
| completion_id = f"chatcmpl-{int(time.time())}" | |
| created = int(time.time()) | |
| is_agent = use_agent or bool(tools) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # โ ูุถุน ุงููููู โ SSE ู ุน ุฃุญุฏุงุซ tool_call/tool_result | |
| # ูุนู ู ุฏุงุฆู ุงู ูู SSE ุจุบุถ ุงููุธุฑ ุนู stream ูู ุงูุทูุจ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| if is_agent: | |
| logger.info(f"[API] Agent SSE mode โ model={model}, msgs={len(clean_msgs)}") | |
| return StreamingResponse( | |
| agent_sse_generator(clean_msgs, model, completion_id, created), | |
| media_type="text/event-stream", | |
| headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"}, | |
| ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # โ ูุถุน ุงููู ูุฐุฌ ุงูุนุงุฏู ู ุน ุชุฏูู โ SSE ุญูููู token ุจู token | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| if do_stream: | |
| logger.info(f"[API] Stream mode โ model={model}, msgs={len(clean_msgs)}") | |
| async def normal_stream(): | |
| # chunk ุฃูู: ุชุนุฑูู ุงูุฏูุฑ | |
| first = { | |
| "id": completion_id, "object": "chat.completion.chunk", | |
| "created": created, "model": model, | |
| "choices": [{"index": 0, "delta": {"role": "assistant", "content": ""}, "finish_reason": None}] | |
| } | |
| yield f"data: {json.dumps(first, ensure_ascii=False)}\n\n" | |
| # ุถุน stream_generator ูู queue | |
| queue: asyncio.Queue = asyncio.Queue() | |
| await stream_to_queue(clean_msgs, model, queue) | |
| while True: | |
| event_type, data = await queue.get() | |
| if event_type == "done": | |
| break | |
| if event_type == "error": | |
| err_chunk = { | |
| "id": completion_id, "object": "chat.completion.chunk", | |
| "created": created, "model": model, | |
| "choices": [{"index": 0, "delta": {"content": f"\nโ {data}"}, "finish_reason": "stop"}] | |
| } | |
| yield f"data: {json.dumps(err_chunk, ensure_ascii=False)}\n\n" | |
| break | |
| # chunk ุนุงุฏู ุจุงูู token ุงููุนูู | |
| chunk = { | |
| "id": completion_id, "object": "chat.completion.chunk", | |
| "created": created, "model": model, | |
| "choices": [{"index": 0, "delta": {"content": data}, "finish_reason": None}] | |
| } | |
| yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n" | |
| # chunk ุฃุฎูุฑ | |
| final = { | |
| "id": completion_id, "object": "chat.completion.chunk", | |
| "created": created, "model": model, | |
| "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}] | |
| } | |
| yield f"data: {json.dumps(final, ensure_ascii=False)}\n\n" | |
| yield "data: [DONE]\n\n" | |
| return StreamingResponse( | |
| normal_stream(), | |
| media_type="text/event-stream", | |
| headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"}, | |
| ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # โ ูุถุน ุงููู ูุฐุฌ ุงูุนุงุฏู ุจุฏูู ุชุฏูู | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| logger.info(f"[API] Sync mode โ model={model}, msgs={len(clean_msgs)}") | |
| loop = asyncio.get_running_loop() | |
| response_text = await loop.run_in_executor(_POOL, ask_sync, clean_msgs, model) | |
| prompt_tokens = sum(len(m.get("content", "")) for m in clean_msgs) // 4 | |
| completion_tokens = len(response_text) // 4 | |
| return JSONResponse({ | |
| "id": completion_id, "object": "chat.completion", | |
| "created": created, "model": model, | |
| "choices": [{ | |
| "index": 0, | |
| "message": {"role": "assistant", "content": response_text}, | |
| "logprobs": None, "finish_reason": "stop" | |
| }], | |
| "usage": { | |
| "prompt_tokens": prompt_tokens, | |
| "completion_tokens": completion_tokens, | |
| "total_tokens": prompt_tokens + completion_tokens, | |
| } | |
| }) | |
| # ===================================================== | |
| # ENTRY POINT | |
| # ===================================================== | |
| if __name__ == "__main__": | |
| import uvicorn | |
| port = int(os.getenv("PORT", "7860")) | |
| logger.info(f"๐ Starting on port {port}") | |
| uvicorn.run(app, host="0.0.0.0", port=port) |