Download docker_openwebui_router_stack/router_api/router_api.py from koreallmdev/qwen3-8b-korean-coding-router: direct link, hf CLI and curl.
- Browser
- Download file 23.9 kB
-
https://huggingface.co/koreallmdev/qwen3-8b-korean-coding-router/resolve/main/docker_openwebui_router_stack/router_api/router_api.py
- Command line
-
hf download hf://koreallmdev/qwen3-8b-korean-coding-router/docker_openwebui_router_stack/router_api/router_api.py
-
curl -L -o router_api.py https://huggingface.co/koreallmdev/qwen3-8b-korean-coding-router/resolve/main/docker_openwebui_router_stack/router_api/router_api.py
23.9 kB
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import re | |
| import time | |
| import urllib.request | |
| from collections import Counter | |
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
| from pathlib import Path | |
| from typing import Any | |
| ROOT = Path("/app") | |
| OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://ollama:11434").rstrip("/") | |
| OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "qwen3-8b-korean-coding-router:latest") | |
| ROUTER_MODEL_ID = os.environ.get("ROUTER_MODEL_ID", "qwen3-8b-korean-coding-router") | |
| GLOBAL = """๋น์ ์ ํ๊ตญ์ด ๊ฐ๋ฐ/์ด์ ๋ณด์กฐ AI์ ๋๋ค. | |
| ์ต์ข ๋ต๋ณ๋ง ์ถ๋ ฅํฉ๋๋ค. ๋ด๋ถ ์ฌ๊ณ ๊ณผ์ ๊ณผ ๋ฉํ ์ค๋ช ์ ์ถ๋ ฅํ์ง ์์ต๋๋ค. | |
| ๋ฐ๋์ ํ๊ตญ์ด ์กด๋๋ง๋ก ๋ตํฉ๋๋ค. | |
| Qwen3 ์ฌ๊ณ ํ๊ทธ(<think>)์ ์ค๊ตญ์ด ํ์ ์ถ๋ ฅ์ ๊ธ์งํฉ๋๋ค. | |
| ์ํํ ์์ ์ ํ์ธ ๋ช ๋ น๊ณผ ์คํ ๋ช ๋ น์ ๋ถ๋ฆฌํฉ๋๋ค. | |
| ๊ธด ๋ช ๋ น์ .sh/.py/zip ๋ฐฉ์์ผ๋ก ์๋ดํฉ๋๋ค. | |
| ํ์ ๋ฌธ์์ด์ ๋์์ด๋ก ๋ฐ๊พธ์ง ๋ง๊ณ ๊ทธ๋๋ก ํฌํจํฉ๋๋ค. | |
| """ | |
| LONGFORM_TEMPLATE = """์ฃผ์ : longform writing / ์ฅ๋ฌธ ์์ฑ ์์ ํ | |
| - ๋ฐ๋ณต ๋ฃจํ ๊ธ์ง | |
| - ๊ฐ์ ๋ฌธ์ฅ๊ณผ ๊ฐ์ ๊ตฌ์ ๋ฐ๋ณต ๊ธ์ง | |
| - ์์ ๋ชฉ ์ฌ์ฉ | |
| - ์ฝ๋ฉ ์ฃผ์ ๋ผ๋ฉด ๊ฐ๋ , ์ค๋ฌด ํ๋ฆ, ์์, ์ฃผ์์ , ํ์ต ๋ฐฉํฅ์ผ๋ก ๋๋๋๋ค. | |
| """ | |
| ROUTE_KEYWORDS = { | |
| "linux": ["ํฌํธ", "ss -lntp", "pid", "8010", "8000", "๋ก๊ทธ", "ํ๋ก์ธ์ค", "๋ฆฌ์ค๋", "listen"], | |
| "docker": ["docker", "๋์ปค", "compose", "์ปจํ ์ด๋", "open webui", "open-webui", "webui", "์คํ์น", "ollama", "์ฌ๋ผ๋ง", "router-api", "3001", "11435"], | |
| "style": ["๊ธด ๋ช ๋ น", "๋ถ์ฌ๋ฃ๊ธฐ", "์งง์ ๋ช ๋ น", "ํ ๋จ๊ณ", "zip", "์คํฌ๋ฆฝํธ"], | |
| "lora": ["lora", "adapter", "์ด๋ํฐ"], | |
| "security": ["ํ ํฐ", "api key", "hf_token", "๋ณด์"], | |
| "eval": ["raw", "cleaned", "ํ๊ฐ", "benchmark"], | |
| "dataset": ["dataset", "๋ฐ์ดํฐ์ ", "clean", "rejected", "bad_json"], | |
| "systemd": ["systemd", "systemctl", "service", "disable"], | |
| "vllm": ["vllm", "/v1/models", "gpu_memory_utilization"], | |
| "ops": ["์๋ ์ฌ์์", "๋ค์ ์ด์", "crontab", "nohup", "tmux"], | |
| "python": ["python", "ํ์ด์ฌ", "json", "glob", "path"], | |
| "workflow": ["์ฌ๊ฐ", "์ํ", "๋ค์ ๋จ๊ณ", "stable"], | |
| } | |
| RULES = { | |
| "linux": {"must": [], "any": ["ss -lntp", "ps", "PID", "ํฌํธ"]}, | |
| "style": {"must": ["์คํฌ๋ฆฝํธ", "ํ ๋จ๊ณ"], "any": ["zip", ".sh", ".py", "์งง์ ๋ช ๋ น"]}, | |
| "docker": {"must": [], "any": ["docker compose ps", "http://ollama:11434", "http://router-api:8011/v1"]}, | |
| "lora": {"must": ["adapter", "benchmark"], "any": ["dataset", "stable", "rejected"]}, | |
| "security": {"must": ["ํ ํฐ", "๋ก๊ทธ"], "any": ["ํ๊ฒฝ๋ณ์", "Git", "HF_TOKEN"]}, | |
| "eval": {"must": ["raw", "cleaned"], "any": ["think", "meta", "cleanup"]}, | |
| "dataset": {"must": ["clean", "rejected"], "any": ["report", "duplicate", "bad_json"]}, | |
| "systemd": {"must": ["systemctl"], "any": ["status", "disable", "mask"]}, | |
| "ops": {"must": ["systemd", "Docker"], "any": ["crontab", "ps", "ss"]}, | |
| "workflow": {"must": [], "any": ["์ํ", "๋ค์ ๋จ๊ณ", "stable"]}, | |
| "longform": {"must": [], "any": ["์์ ๋ชฉ", "์์ฝ"]}, | |
| } | |
| def load_templates() -> dict[str, Any]: | |
| marker = ROOT / "policies/STABLE_ROUTER_TEMPLATE.txt" | |
| if marker.exists(): | |
| template_path = Path(marker.read_text(encoding="utf-8").strip()) | |
| if not template_path.is_absolute(): | |
| template_path = ROOT / template_path | |
| else: | |
| candidates = sorted((ROOT / "policies").glob("*templates*.json")) | |
| if not candidates: | |
| return {"templates": {}} | |
| template_path = candidates[-1] | |
| try: | |
| obj = json.loads(template_path.read_text(encoding="utf-8")) | |
| except Exception: | |
| obj = {"templates": {}} | |
| obj = dict(obj) | |
| obj["templates"] = dict(obj.get("templates", {})) | |
| obj["templates"]["longform"] = {"name": "longform_docker_router", "template": LONGFORM_TEMPLATE} | |
| return obj | |
| TEMPLATES = load_templates() | |
| def contains_any(text: str, terms: list[str]) -> bool: | |
| low = text.lower() | |
| return any(term.lower() in low for term in terms) | |
| def is_stack_question(user: str) -> bool: | |
| low = user.lower() | |
| has_webui = ("open webui" in low) or ("open-webui" in low) or ("webui" in low) or ("์คํ์น" in low) | |
| has_ollama_or_docker = ( | |
| ("ollama" in low) or ("์ฌ๋ผ๋ง" in low) or ("docker" in low) or ("๋์ปค" in low) | |
| or ("compose" in low) or ("๋ชจ๋ธ" in low) or ("3001" in low) or ("router-api" in low) | |
| ) | |
| return has_webui and has_ollama_or_docker | |
| def is_port_or_process_question_text(user: str) -> bool: | |
| low = user.lower() | |
| strong = ["ss -lntp", "pid", "8010", "8000", "ํฌํธ", "ํ๋ก์ธ์ค", "๋ฆฌ์ค๋", "listen"] | |
| return any(item in low for item in strong) | |
| def is_short_answer_request(user: str) -> bool: | |
| low = user.lower() | |
| return any(item in low for item in ["์งง๊ฒ", "๊ฐ๋จํ", "์์ฝ", "ํ ๋จ๊ณ", "ํ์ธ ์์", "๋ช ๋ น์ด"]) | |
| def is_longform_context_only(user: str) -> bool: | |
| low = user.lower() | |
| context_patterns = [ | |
| "๋ฐฉ๊ธ ์ฅ๋ฌธ", | |
| "์ฅ๋ฌธ ์ดํ", | |
| "์ฅ๋ฌธ ๋ค์", | |
| "๊ธด ๊ธ ์ดํ", | |
| "4์ฒ์ ์ดํ", | |
| "4000์ ์ดํ", | |
| "์ดํ์๋ ์ ์", | |
| ] | |
| return any(p in low for p in context_patterns) | |
| def is_explicit_longform_request(user: str) -> bool: | |
| low = user.lower() | |
| if is_longform_context_only(user) or is_short_answer_request(user): | |
| return False | |
| length_signal = any(k in low for k in ["4์ฒ์", "4000", "3์ฒ์", "3000", "๊ธด ๊ธ", "์ฅ๋ฌธ", "longform"]) | |
| write_signal = any(k in low for k in ["์จ์ค", "์์ฑ", "์์ฑ", "ํ ์คํธ", "์์ธ์ด", "๊ธ์", "์ค๋ช ํด์ค"]) | |
| return length_signal and write_signal | |
| def route_reason(user: str) -> dict[str, Any]: | |
| low = user.lower() | |
| signals: list[str] = [] | |
| blocked: list[str] = [] | |
| port_signal = is_port_or_process_question_text(user) | |
| stack_signal = is_stack_question(user) | |
| short_signal = is_short_answer_request(user) | |
| long_context_only = is_longform_context_only(user) | |
| explicit_longform = is_explicit_longform_request(user) | |
| if port_signal: | |
| signals.append("port_or_process_signal") | |
| if stack_signal: | |
| signals.append("docker_stack_signal") | |
| if short_signal: | |
| signals.append("short_answer_signal") | |
| blocked.append("longform") | |
| if long_context_only: | |
| signals.append("longform_context_only") | |
| blocked.append("longform") | |
| if explicit_longform: | |
| signals.append("explicit_longform_request") | |
| # Priority 1: safety/operation command questions. | |
| # A port/PID question should not become longform even if it mentions "์ฅ๋ฌธ ์ดํ". | |
| if port_signal: | |
| return {"route": "linux", "signals": signals, "blocked_routes": sorted(set(blocked)), "priority": "safety_linux_first"} | |
| # Priority 2: current Docker/Open WebUI/Ollama stack. | |
| if stack_signal: | |
| return {"route": "docker", "signals": signals, "blocked_routes": sorted(set(blocked)), "priority": "docker_stack_second"} | |
| # Priority 3: script/style preference. | |
| if contains_any(user, ROUTE_KEYWORDS["style"]): | |
| signals.append("style_script_signal") | |
| return {"route": "style", "signals": signals, "blocked_routes": sorted(set(blocked)), "priority": "style_third"} | |
| # Priority 4: more specific engineering domains. | |
| for cat in ["systemd", "vllm", "ops", "dataset", "lora", "security", "eval", "python"]: | |
| if contains_any(user, ROUTE_KEYWORDS[cat]): | |
| signals.append(f"{cat}_signal") | |
| return {"route": cat, "signals": signals, "blocked_routes": sorted(set(blocked)), "priority": "domain_specific"} | |
| # Priority 5: longform only when explicit and not context-only. | |
| if explicit_longform: | |
| return {"route": "longform", "signals": signals, "blocked_routes": sorted(set(blocked)), "priority": "explicit_longform_late"} | |
| # Fallback scoring for remaining weak signals. | |
| hits: list[tuple[int, str]] = [] | |
| for cat, kws in ROUTE_KEYWORDS.items(): | |
| count = sum(1 for k in kws if k.lower() in low) | |
| if count: | |
| hits.append((count, cat)) | |
| if hits: | |
| hits.sort(reverse=True) | |
| return {"route": hits[0][1], "signals": signals + [f"weak_keyword_{hits[0][1]}"], "blocked_routes": sorted(set(blocked)), "priority": "weak_keyword"} | |
| return {"route": "workflow", "signals": signals, "blocked_routes": sorted(set(blocked)), "priority": "fallback"} | |
| def route(text: str) -> str: | |
| return str(route_reason(text)["route"]) | |
| def strip_think(text: str) -> str: | |
| text = re.sub(r"<think>.*?</think>", "", text, flags=re.I | re.S) | |
| return re.sub(r"</?think>", "", text, flags=re.I).strip() | |
| def leak_flags(text: str) -> list[str]: | |
| flags: list[str] = [] | |
| if re.search(r"<\s*/?\s*think\s*>|chain\s+of\s+thought|internal\s+monologue", text, re.I): | |
| flags.append("think") | |
| if re.search(r"[\u4e00-\u9fff]", text): | |
| flags.append("cjk") | |
| return flags | |
| def missing_terms(text: str, terms: list[str]) -> list[str]: | |
| low = text.lower() | |
| return [x for x in terms if x.lower() not in low] | |
| def repetition_flags(text: str) -> list[str]: | |
| compact = re.sub(r"\s+", " ", text) | |
| if re.search(r"(.{3,40}?)(?:\1){3,}", compact): | |
| return ["phrase_repeat"] | |
| chars = re.sub(r"\s+", "", text) | |
| if len(chars) > 500: | |
| grams = [chars[i:i + 8] for i in range(max(0, len(chars) - 7))] | |
| if grams and Counter(grams).most_common(1)[0][1] >= 16: | |
| return ["ngram_repeat"] | |
| return [] | |
| def select_rules(cat: str, user: str) -> dict[str, list[str]]: | |
| rule = RULES.get(cat, {"must": [], "any": []}) | |
| must = list(rule.get("must", [])) | |
| any_terms = list(rule.get("any", [])) | |
| if is_port_or_process_question_text(user): | |
| must = sorted(set(must + ["ss -lntp", "ps"])) | |
| any_terms = sorted(set(any_terms + ["PID", "ํฌํธ"])) | |
| if cat == "style" and ("๊ธด ๋ช ๋ น" in user or "์งง์ ๋ช ๋ น" in user or "๋ถ์ฌ๋ฃ" in user): | |
| must = sorted(set(must + ["์คํฌ๋ฆฝํธ", "ํ ๋จ๊ณ"])) | |
| any_terms = sorted(set(any_terms + ["zip", ".sh", ".py", "์งง์ ๋ช ๋ น"])) | |
| return {"must": must, "any": any_terms} | |
| def safety_kill_block() -> str: | |
| return """[์์ ๊ธฐ์ค] | |
| - ํ์ธ ์ ์๋ killํ์ง ์์ต๋๋ค. | |
| - ๋จผ์ ss -lntp๋ก ํฌํธ์ PID๋ฅผ ํ์ธํฉ๋๋ค. | |
| - ๊ทธ ๋ค์ ps๋ก ์ค์ ํ๋ก์ธ์ค์ ์คํ ์ธ์๋ฅผ ํ์ธํฉ๋๋ค. | |
| - systemd ์๋น์ค๋ฉด systemctl status๋ฅผ ๋จผ์ ํ์ธํ๊ณ , ์ข ๋ฃ๊ฐ ํ์ํ๋ฉด systemctl stop์ ์ฐ์ ๊ฒํ ํฉ๋๋ค. | |
| - Docker ์๋น์ค๋ฉด docker compose ps์ docker compose stop์ ์ฐ์ ๊ฒํ ํฉ๋๋ค. | |
| - kill -9๋ ๋ฐ์ดํฐ ์์ ์ํ์ด ์์ผ๋ฏ๋ก ๋ง์ง๋ง ์๋จ์ ๋๋ค. | |
| """ | |
| def current_stack_block() -> str: | |
| return """[ํ์ฌ Docker stack ๊ธฐ์ค] | |
| ```text | |
| Open WebUI = http://127.0.0.1:3001 | |
| Router API = http://127.0.0.1:8011/v1 | |
| Ollama API = http://127.0.0.1:11435 | |
| Open WebUI -> Ollama = http://ollama:11434 | |
| Open WebUI -> Router API = http://router-api:8011/v1 | |
| Router API -> Ollama = http://ollama:11434 | |
| ``` | |
| ```bash | |
| cd /home/saul9523/dgx_ai_factory/101_qwen_next_coding_1m_train/dist/docker_openwebui_router/qwen3-router-openwebui-stack | |
| docker compose ps | |
| curl -fsS http://127.0.0.1:8011/v1/models | |
| curl -fsS http://127.0.0.1:11435/api/tags | |
| ``` | |
| ์ฃผ์: `http://127.0.0.1:3001`์ Open WebUI ํ๋ฉด ์ฃผ์์ ๋๋ค. Ollama API ์ฃผ์๋ก ๋ฃ์ผ๋ฉด ์ ๋ฉ๋๋ค. | |
| """ | |
| def remove_unsafe_kill_lines(answer: str) -> str: | |
| safe_words = ("๊ธ์ง", "ํ์ง", "๋ง์ง๋ง", "์ํ", "๊ถ์ฅํ์ง", "ํ์ธ ์ ", "์๋จ") | |
| kept: list[str] = [] | |
| for line in answer.splitlines(): | |
| normalized = line.replace("`kill`", "kill").replace("`kill -9`", "kill -9") | |
| has_kill = re.search(r"\bkill\b", normalized, re.I) is not None | |
| if has_kill and not any(word in normalized for word in safe_words): | |
| continue | |
| kept.append(normalized) | |
| return "\n".join(kept).strip() | |
| def remove_wrong_stack_url_lines(answer: str) -> str: | |
| wrong = [ | |
| "host.docker.internal:11434", | |
| "172.17.0.1:11434", | |
| "http://127.0.0.1:3000", | |
| "http://127.0.0.1:3001/api", | |
| ] | |
| kept: list[str] = [] | |
| for line in answer.splitlines(): | |
| if any(item in line for item in wrong): | |
| continue | |
| kept.append(line) | |
| return "\n".join(kept).strip() | |
| def apply_safety_patch(answer: str, cat: str, user: str) -> str: | |
| patched = answer.strip() | |
| port_question = is_port_or_process_question_text(user) | |
| stack_question = is_stack_question(user) | |
| # Category-independent safety guard: | |
| # Even if routing ever misclassifies a port/PID prompt, this guard still applies. | |
| if port_question: | |
| patched = remove_unsafe_kill_lines(patched) | |
| patched = patched.replace("`kill`", "kill").replace("`kill -9`", "kill -9") | |
| if "ํ์ธ ์ ์๋ killํ์ง ์์ต๋๋ค" not in patched: | |
| patched = safety_kill_block() + "\n\n" + patched | |
| if stack_question: | |
| patched = remove_wrong_stack_url_lines(patched) | |
| if "http://ollama:11434" not in patched or "http://router-api:8011/v1" not in patched or "http://127.0.0.1:3001" not in patched: | |
| patched = current_stack_block() + "\n\n" + patched | |
| if re.search(r"\bkill\s+-9\b", patched, re.I) and "๋ง์ง๋ง ์๋จ" not in patched: | |
| patched = safety_kill_block() + "\n\n" + remove_unsafe_kill_lines(patched) | |
| return patched.strip() | |
| def system_prompt(cat: str, user: str = "", repair: bool = False, missing_must: list[str] | None = None, missing_any: list[str] | None = None) -> str: | |
| template = TEMPLATES.get("templates", {}).get(cat) or TEMPLATES.get("templates", {}).get("workflow") or {"template": ""} | |
| text = GLOBAL + "\n\n์ ํ๋ ์ฃผ์ ํ ํ๋ฆฟ:\n" + template.get("template", "") | |
| rr = route_reason(user) if user else {"signals": [], "blocked_routes": []} | |
| text += "\n\n๋ผ์ฐํ ํ์ ํํธ:\n" | |
| text += "route=" + cat + "\n" | |
| text += "signals=" + ", ".join(rr.get("signals", [])) + "\n" | |
| text += "blocked_routes=" + ", ".join(rr.get("blocked_routes", [])) + "\n" | |
| if cat in {"linux", "ops", "systemd"} or is_port_or_process_question_text(user): | |
| text += "\n\n์ด์ ์์ ๊ท์น:\n" + safety_kill_block() | |
| if cat == "docker" or is_stack_question(user): | |
| text += "\n\nDocker/Open WebUI stack ์ฃผ์ ๊ท์น:\n" + current_stack_block() | |
| if repair: | |
| text += "\n\nPOST_CHECK_REWRITE=ON\n" | |
| text += "๋๋ฝ must ๋ฌธ์์ด: " + (", ".join(missing_must or []) or "(์์)") + "\n" | |
| text += "๋๋ฝ any ๋ฌธ์์ด: " + (", ".join(missing_any or []) or "(์์)") + "\n" | |
| text += "์ ๋ฌธ์์ด์ ๊ทธ๋๋ก ํฌํจํ๊ณ ์ต์ข ๋ต๋ณ๋ง ์ถ๋ ฅํฉ๋๋ค.\n" | |
| return text | |
| def ollama_chat(messages: list[dict[str, str]], longform: bool = False, num_predict: int = 900) -> str: | |
| payload = { | |
| "model": OLLAMA_MODEL, | |
| "messages": messages, | |
| "stream": False, | |
| "options": { | |
| "temperature": 0.15 if longform else 0, | |
| "top_p": 0.9, | |
| "repeat_penalty": 1.18 if longform else 1.1, | |
| "num_predict": num_predict, | |
| }, | |
| } | |
| req = urllib.request.Request( | |
| OLLAMA_URL + "/api/chat", | |
| data=json.dumps(payload, ensure_ascii=False).encode("utf-8"), | |
| headers={"Content-Type": "application/json"}, | |
| ) | |
| with urllib.request.urlopen(req, timeout=300) as resp: | |
| obj = json.loads(resp.read().decode("utf-8")) | |
| return strip_think(obj.get("message", {}).get("content", "")) | |
| def gen_normal(cat: str, user: str, history: list[dict[str, str]]) -> str: | |
| rule = select_rules(cat, user) | |
| messages = [{"role": "system", "content": system_prompt(cat, user)}] + history[-6:] + [{"role": "user", "content": user}] | |
| first = ollama_chat(messages) | |
| mm = missing_terms(first, rule["must"]) | |
| ma = missing_terms(first, rule["any"]) | |
| leaks = leak_flags(first) | |
| reps = repetition_flags(first) | |
| needs_repair = bool(mm or leaks or reps or (cat in {"linux", "style", "docker", "lora"} and len(ma) >= 2)) | |
| if not needs_repair: | |
| return apply_safety_patch(first, cat, user) | |
| repair_user = ( | |
| f"์๋ ์ง๋ฌธ:\n{user}\n\n" | |
| f"๊ธฐ์กด ๋ต๋ณ:\n{first}\n\n" | |
| f"๋๋ฝ must ๋ฌธ์์ด: {', '.join(mm) if mm else '(์์)'}\n" | |
| f"๋๋ฝ any ๋ฌธ์์ด: {', '.join(ma) if ma else '(์์)'}\n" | |
| "์ ๋ฌธ์์ด์ ๊ทธ๋๋ก ํฌํจํ๊ณ ์ต์ข ๋ต๋ณ๋ง ์ถ๋ ฅํด ์ฃผ์ธ์." | |
| ) | |
| second = ollama_chat([ | |
| {"role": "system", "content": system_prompt(cat, user, True, mm, ma)}, | |
| {"role": "user", "content": repair_user}, | |
| ]) | |
| mm2 = missing_terms(second, rule["must"]) | |
| ma2 = missing_terms(second, rule["any"]) | |
| leaks2 = leak_flags(second) | |
| reps2 = repetition_flags(second) | |
| if len(mm2) < len(mm) or (not leaks2 and leaks) or (not reps2 and reps) or (len(mm2) == len(mm) and len(ma2) < len(ma)): | |
| return apply_safety_patch(second, cat, user) | |
| return apply_safety_patch(first, cat, user) | |
| def target_chars(user: str) -> int: | |
| if "4์ฒ" in user or "4000" in user: | |
| return 4000 | |
| if "3์ฒ" in user or "3000" in user: | |
| return 3000 | |
| return 1800 | |
| def gen_long(user: str) -> str: | |
| target = target_chars(user) | |
| sections = 5 if target >= 3800 else 4 | |
| titles = ["๋ฌธ์ ์ ์์ ์ฝ๋ฉ์ ์๋ฏธ", "์ค๋ฌด ๊ฐ๋ฐ ํ๋ฆ", "๋๋ฒ๊น ๊ณผ ํ ์คํธ", "ํ์ ๊ณผ ์ด์", "ํ์ต ๋ก๋๋งต๊ณผ ์์ฝ"] | |
| parts: list[str] = [] | |
| for idx in range(sections): | |
| title = titles[idx] if idx < len(titles) else "์ถ๊ฐ" | |
| prompt = ( | |
| f"์๋ ์์ฒญ:\n{user}\n\n" | |
| f"์ด ๋ชฉํ ๋ถ๋: ์ต์ {target}์.\n" | |
| f"ํ์ฌ section: {idx + 1}/{sections}\n" | |
| f"์ด๋ฒ section ์ ๋ชฉ: {title}\n" | |
| "์ด๋ฒ section๋ง ์์ฑํ๊ณ ๋ฐ๋ณต ๋ฃจํ๋ฅผ ๋ง๋ค์ง ๋ง์ธ์." | |
| ) | |
| text = ollama_chat([ | |
| {"role": "system", "content": system_prompt("longform", user)}, | |
| {"role": "user", "content": prompt}, | |
| ], True, 720) | |
| if repetition_flags(text): | |
| text = ollama_chat([ | |
| {"role": "system", "content": system_prompt("longform", user)}, | |
| {"role": "user", "content": prompt + "\n๋ฐ๋ณต ์์ด ์ ๋ฌธ์ฅ์ผ๋ก ๋ค์ ์์ฑํ์ธ์."}, | |
| ], True, 720) | |
| parts.append(text.strip()) | |
| return "\n\n".join(parts) | |
| def generate(messages: list[dict[str, str]]) -> str: | |
| user_messages = [m for m in messages if m.get("role") == "user"] | |
| user = str(user_messages[-1].get("content", "")) if user_messages else "" | |
| history: list[dict[str, str]] = [] | |
| for m in messages[-8:-1]: | |
| if m.get("role") in {"user", "assistant"}: | |
| content = str(m.get("content", "")) | |
| # Long previous assistant answers are summarized as a placeholder to avoid context spillover. | |
| if m.get("role") == "assistant" and len(content) > 1200: | |
| content = "[์ด์ ๋ต๋ณ์ ์ฅ๋ฌธ์ด๋ฏ๋ก ํ์คํ ๋ฆฌ์์๋ ์๋ต๋จ]" | |
| else: | |
| content = content[:1000] | |
| history.append({"role": str(m["role"]), "content": content}) | |
| rr = route_reason(user) | |
| cat = str(rr["route"]) | |
| if cat == "longform": | |
| answer = gen_long(user) | |
| else: | |
| answer = gen_normal(cat, user, history) | |
| return apply_safety_patch(answer, cat, user) | |
| class Handler(BaseHTTPRequestHandler): | |
| server_version = "Qwen3RouterAPI/4.1" | |
| def send_json(self, obj: Any, status: int = 200) -> None: | |
| body = json.dumps(obj, ensure_ascii=False).encode("utf-8") | |
| self.send_response(status) | |
| self.send_header("Content-Type", "application/json; charset=utf-8") | |
| self.send_header("Content-Length", str(len(body))) | |
| self.end_headers() | |
| self.wfile.write(body) | |
| def do_GET(self) -> None: | |
| if self.path.rstrip("/") == "/v1/models": | |
| self.send_json({ | |
| "object": "list", | |
| "data": [{ | |
| "id": ROUTER_MODEL_ID, | |
| "object": "model", | |
| "created": int(time.time()), | |
| "owned_by": "local", | |
| }], | |
| }) | |
| elif self.path == "/health": | |
| self.send_json({ | |
| "status": "ok", | |
| "server": self.server_version, | |
| "ollama_url": OLLAMA_URL, | |
| "ollama_model": OLLAMA_MODEL, | |
| "routing": "priority_intent_router_v4_1", | |
| }) | |
| else: | |
| self.send_json({"error": "not found"}, 404) | |
| def do_POST(self) -> None: | |
| if self.path.rstrip("/") == "/v1/route/debug": | |
| try: | |
| length = int(self.headers.get("Content-Length", "0")) | |
| req = json.loads(self.rfile.read(length).decode("utf-8")) | |
| text = str(req.get("text") or req.get("prompt") or "") | |
| self.send_json(route_reason(text)) | |
| except Exception as exc: | |
| self.send_json({"error": f"{type(exc).__name__}: {exc}"}, 500) | |
| return | |
| if self.path.rstrip("/") != "/v1/chat/completions": | |
| self.send_json({"error": "not found"}, 404) | |
| return | |
| try: | |
| length = int(self.headers.get("Content-Length", "0")) | |
| req = json.loads(self.rfile.read(length).decode("utf-8")) | |
| answer = generate(req.get("messages", [])) | |
| if req.get("stream"): | |
| self.send_response(200) | |
| self.send_header("Content-Type", "text/event-stream; charset=utf-8") | |
| self.send_header("Cache-Control", "no-cache") | |
| self.end_headers() | |
| chunk = { | |
| "id": "chatcmpl-router", | |
| "object": "chat.completion.chunk", | |
| "created": int(time.time()), | |
| "model": ROUTER_MODEL_ID, | |
| "choices": [{"index": 0, "delta": {"role": "assistant", "content": answer}, "finish_reason": None}], | |
| } | |
| self.wfile.write(("data: " + json.dumps(chunk, ensure_ascii=False) + "\n\n").encode("utf-8")) | |
| done = { | |
| "id": "chatcmpl-router", | |
| "object": "chat.completion.chunk", | |
| "created": int(time.time()), | |
| "model": ROUTER_MODEL_ID, | |
| "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}], | |
| } | |
| self.wfile.write(("data: " + json.dumps(done, ensure_ascii=False) + "\n\n").encode("utf-8")) | |
| self.wfile.write(b"data: [DONE]\n\n") | |
| return | |
| self.send_json({ | |
| "id": "chatcmpl-router", | |
| "object": "chat.completion", | |
| "created": int(time.time()), | |
| "model": ROUTER_MODEL_ID, | |
| "choices": [{"index": 0, "message": {"role": "assistant", "content": answer}, "finish_reason": "stop"}], | |
| "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}, | |
| }) | |
| except Exception as exc: | |
| self.send_json({"error": {"message": f"{type(exc).__name__}: {exc}", "type": "router_error"}}, 500) | |
| def log_message(self, fmt: str, *args: Any) -> None: | |
| print("%s - %s" % (self.address_string(), fmt % args), flush=True) | |
| if __name__ == "__main__": | |
| print( | |
| f"router api serving on 0.0.0.0:8011, server=Qwen3RouterAPI/4.1, model={ROUTER_MODEL_ID}, ollama={OLLAMA_URL}/{OLLAMA_MODEL}", | |
| flush=True, | |
| ) | |
| ThreadingHTTPServer(("0.0.0.0", 8011), Handler).serve_forever() | |