koreallmdev's picture
Combined local Ollama runtime and Router API v4.1 sanitized upload
53023f4 verified
Raw
History Blame Contribute Delete
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()