| |
| |
| 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") |
|
|
| |
| |
| if port_signal: |
| return {"route": "linux", "signals": signals, "blocked_routes": sorted(set(blocked)), "priority": "safety_linux_first"} |
|
|
| |
| if stack_signal: |
| return {"route": "docker", "signals": signals, "blocked_routes": sorted(set(blocked)), "priority": "docker_stack_second"} |
|
|
| |
| 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"} |
|
|
| |
| 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"} |
|
|
| |
| if explicit_longform: |
| return {"route": "longform", "signals": signals, "blocked_routes": sorted(set(blocked)), "priority": "explicit_longform_late"} |
|
|
| |
| 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) |
|
|
| |
| |
| 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", "")) |
| |
| 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() |
|
|