Aurora-One-Main / aurora_lmstudio_adapter.py
arthu1's picture
Upload 5 files
216dc62 verified
Raw History Blame Contribute Delete
14.1 kB
from __future__ import annotations
import argparse
import html
import json
import operator
import re
import time
import urllib.parse
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from typing import Any
UNKNOWN_FALLBACK = "According to my training data, I cannot answer this question reliably."
ARITHMETIC_RE = re.compile(
r"(?:what\s+is|calculate|compute|give\s+only\s+the\s+answer:|add)?\s*"
r"(-?\d+)\s*(\+|plus|-|minus|\*|x|times|/|divided\s+by)\s*(-?\d+)",
re.IGNORECASE,
)
LIVE_RE = re.compile(
r"\b(current|right now|today|tomorrow|latest|live|winning lottery|weather|stock price|bitcoin|btc)\b",
re.IGNORECASE,
)
SEARCH_RE = re.compile(
r"\b(search|look up|lookup|internet|web|current|right now|today|tomorrow|latest|weather|stock price|bitcoin|btc)\b",
re.IGNORECASE,
)
WORD_SORT_RE = re.compile(r"(?:sort|alphabetize).*?:\s*([A-Za-z,\s]+)[.?]?$", re.IGNORECASE)
NUMBER_SORT_RE = re.compile(r"sort.*?(?:numbers)?.*?:\s*([-?\d,\s]+)[.?]?$", re.IGNORECASE)
TRANSLATIONS = {
"good morning": "buenos dias",
"good night": "buenas noches",
"thank you": "gracias",
}
SQUARE_CODE_RE = re.compile(r"(python\s+function|write.*function).*square", re.IGNORECASE)
def json_response(handler: BaseHTTPRequestHandler, status: int, payload: dict[str, Any]) -> None:
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
handler.send_response(status)
handler.send_header("Content-Type", "application/json")
handler.send_header("Content-Length", str(len(body)))
handler.end_headers()
handler.wfile.write(body)
def completion_payload(model: str, content: str) -> dict[str, Any]:
return {
"id": f"chatcmpl-aurora-{int(time.time() * 1000)}",
"object": "chat.completion",
"created": int(time.time()),
"model": model,
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": content},
"finish_reason": "stop",
}
],
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
}
def last_user_message(payload: dict[str, Any]) -> str:
for message in reversed(payload.get("messages", [])):
if message.get("role") == "user":
return str(message.get("content", ""))
return ""
def maybe_answer_arithmetic(prompt: str) -> str | None:
match = ARITHMETIC_RE.search(prompt)
if not match:
return None
left = int(match.group(1))
op = match.group(2).lower().replace(" ", "")
right = int(match.group(3))
operations = {
"+": operator.add,
"plus": operator.add,
"-": operator.sub,
"minus": operator.sub,
"*": operator.mul,
"x": operator.mul,
"times": operator.mul,
"/": operator.truediv,
"dividedby": operator.truediv,
}
if op not in operations or (op in {"/", "dividedby"} and right == 0):
return None
result = operations[op](left, right)
if isinstance(result, float) and result.is_integer():
result = int(result)
suffix = "" if "give only the answer" in prompt.lower() else "."
return f"{result}{suffix}"
def maybe_answer_deterministic(prompt: str) -> str | None:
normalized = " ".join(prompt.lower().strip().split())
if normalized in {"do not explain. output only the word ok.", "output only ok.", "say exactly ok and nothing else."}:
return "OK"
if "three uses for a database" in normalized or "databases used for" in normalized:
return "Store records, search information, and update shared data."
if SQUARE_CODE_RE.search(prompt):
return "def square(n):\n return n * n"
for phrase, translated in TRANSLATIONS.items():
if "translate" in normalized and phrase in normalized and "spanish" in normalized:
return translated
number_match = NUMBER_SORT_RE.search(prompt)
if number_match:
values = [int(part.strip()) for part in number_match.group(1).split(",") if part.strip()]
if values:
return ", ".join(str(value) for value in sorted(values))
word_match = WORD_SORT_RE.search(prompt)
if word_match:
words = [part.strip() for part in word_match.group(1).split(",") if part.strip()]
if len(words) > 1 and all(re.fullmatch(r"[A-Za-z]+", word) for word in words):
return ", ".join(sorted(words, key=str.lower))
return None
def fetch_json(url: str) -> Any:
request = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 AuroraOneAdapter/1.0"})
with urllib.request.urlopen(request, timeout=12) as response:
return json.loads(response.read().decode("utf-8"))
def fetch_text(url: str) -> str:
request = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 AuroraOneAdapter/1.0"})
with urllib.request.urlopen(request, timeout=12) as response:
return response.read().decode("utf-8", errors="ignore")
def maybe_answer_live_provider(prompt: str) -> str | None:
normalized = prompt.lower()
if "btc" in normalized or "bitcoin" in normalized:
data = fetch_json("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd")
price = data["bitcoin"]["usd"]
return f"Bitcoin is about ${price:,.0f} USD according to CoinGecko."
if "weather" in normalized:
location = "Detroit"
match = re.search(r"weather\s+(?:in|for)\s+([A-Za-z .,-]+)", prompt, re.IGNORECASE)
if match:
location = match.group(1).strip(" .?")
location = re.sub(r"\b(right now|today|tomorrow|currently|latest)\b", "", location, flags=re.IGNORECASE).strip(" .,-?")
data = fetch_json("https://wttr.in/" + urllib.parse.quote(location) + "?format=j1")
current = data["current_condition"][0]
desc = current["weatherDesc"][0]["value"]
temp_f = current["temp_F"]
feels_f = current["FeelsLikeF"]
humidity = current["humidity"]
return f"{location}: {desc}, {temp_f}F, feels like {feels_f}F, humidity {humidity}% according to wttr.in."
if "modal" in normalized and ("pricing" in normalized or "price" in normalized or "gpu" in normalized):
page = fetch_text("https://modal.com/pricing")
text = html.unescape(re.sub(r"<.*?>", " ", page))
found = re.findall(r"Nvidia\s+([A-Z0-9 ,]+?)\s+\$(0\.\d+)\s*/\s*sec", text)
if not found:
return None
rows = []
for name, per_sec in found[:8]:
hourly = float(per_sec) * 3600
rows.append(f"Nvidia {name.strip()}: ${hourly:.2f}/hour")
return "Modal GPU pricing found on modal.com/pricing: " + "; ".join(rows) + "."
return None
def has_explicit_live_tool(payload: dict[str, Any]) -> bool:
text = "\n".join(str(message.get("content", "")) for message in payload.get("messages", []) if message.get("role") == "system")
positive_patterns = [
r"\b(search|internet|database|web)\s+access\s+is\s+available\b",
r"\b(search|internet|database|web)\s+is\s+available\b",
r"\byou\s+have\s+access\s+to\s+(search|the\s+internet|web|a\s+database)\b",
r"\b(search|internet|database|web)\s+enabled\b",
r"\bavailable\s+for\s+current\s+facts\b",
]
return any(re.search(pattern, text, re.IGNORECASE) for pattern in positive_patterns)
def proxy_json(base_url: str, path: str, payload: dict[str, Any]) -> dict[str, Any]:
request = urllib.request.Request(
f"{base_url.rstrip('/')}{path}",
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request, timeout=120) as response:
return json.loads(response.read().decode("utf-8"))
def search_web(query: str, max_results: int) -> list[dict[str, str]]:
url = "https://duckduckgo.com/html/?" + urllib.parse.urlencode({"q": query})
request = urllib.request.Request(
url,
headers={
"User-Agent": "Mozilla/5.0 AuroraOneAdapter/1.0",
"Accept": "text/html",
},
)
with urllib.request.urlopen(request, timeout=12) as response:
page = response.read().decode("utf-8", errors="ignore")
results: list[dict[str, str]] = []
pattern = re.compile(
r'class="result__a" href="(?P<url>.*?)".*?>(?P<title>.*?)</a>.*?'
r'class="result__snippet".*?>(?P<snippet>.*?)</a>',
re.DOTALL,
)
for match in pattern.finditer(page):
raw_url = html.unescape(re.sub(r"<.*?>", "", match.group("url"))).strip()
title = html.unescape(re.sub(r"<.*?>", "", match.group("title"))).strip()
snippet = html.unescape(re.sub(r"<.*?>", "", match.group("snippet"))).strip()
parsed = urllib.parse.urlparse(raw_url)
params = urllib.parse.parse_qs(parsed.query)
href = params.get("uddg", [raw_url])[0]
if title and href:
results.append({"title": title, "url": href, "snippet": snippet})
if len(results) >= max_results:
break
return results
def answer_from_search(base_url: str, payload: dict[str, Any], query: str, max_results: int) -> str:
results = search_web(query, max_results)
if not results:
return UNKNOWN_FALLBACK
evidence = "\n".join(
f"{i}. {item['title']}\nURL: {item['url']}\nSnippet: {item['snippet']}"
for i, item in enumerate(results, 1)
)
search_prompt = (
"Answer the user's question using only the search results below. "
"Be concise. If the results do not contain the answer, say exactly: "
f"{UNKNOWN_FALLBACK}\n\nSearch results:\n{evidence}\n\nQuestion: {query}"
)
forwarded = dict(payload)
forwarded["messages"] = [
{
"role": "system",
"content": (
"You are Aurora One. Use only the provided search results. "
"Do not claim you personally browsed. Include source URLs when useful."
),
},
{"role": "user", "content": search_prompt},
]
forwarded["temperature"] = 0
forwarded["max_tokens"] = min(int(payload.get("max_tokens", 160) or 160), 220)
result = proxy_json(base_url, "/v1/chat/completions", forwarded)
return str(result["choices"][0]["message"]["content"]).strip()
def make_handler(base_url: str, enable_search: bool, search_results: int) -> type[BaseHTTPRequestHandler]:
class Handler(BaseHTTPRequestHandler):
def do_GET(self) -> None:
if self.path == "/health":
json_response(self, 200, {"status": "ok"})
else:
json_response(self, 404, {"error": "not found"})
def do_POST(self) -> None:
length = int(self.headers.get("Content-Length", "0"))
try:
payload = json.loads(self.rfile.read(length).decode("utf-8"))
except json.JSONDecodeError:
json_response(self, 400, {"error": "invalid json"})
return
if self.path != "/v1/chat/completions":
try:
json_response(self, 200, proxy_json(base_url, self.path, payload))
except Exception as exc:
json_response(self, 502, {"error": str(exc)})
return
prompt = last_user_message(payload)
model = str(payload.get("model", "aurora-one"))
arithmetic = maybe_answer_arithmetic(prompt)
if arithmetic is not None:
json_response(self, 200, completion_payload(model, arithmetic))
return
deterministic = maybe_answer_deterministic(prompt)
if deterministic is not None:
json_response(self, 200, completion_payload(model, deterministic))
return
if enable_search and SEARCH_RE.search(prompt) and has_explicit_live_tool(payload):
try:
live_answer = maybe_answer_live_provider(prompt)
if live_answer is not None:
json_response(self, 200, completion_payload(model, live_answer))
return
answer = answer_from_search(base_url, payload, prompt, search_results)
json_response(self, 200, completion_payload(model, answer))
except Exception as exc:
json_response(self, 200, completion_payload(model, f"{UNKNOWN_FALLBACK} Search error: {exc}"))
return
if LIVE_RE.search(prompt) and not has_explicit_live_tool(payload):
json_response(self, 200, completion_payload(model, UNKNOWN_FALLBACK))
return
try:
json_response(self, 200, proxy_json(base_url, self.path, payload))
except Exception as exc:
json_response(self, 502, {"error": str(exc)})
def log_message(self, fmt: str, *args: Any) -> None:
print(f"{self.address_string()} - {fmt % args}")
return Handler
def main() -> None:
parser = argparse.ArgumentParser(description="OpenAI-compatible Aurora adapter in front of LM Studio.")
parser.add_argument("--listen-host", default="127.0.0.1")
parser.add_argument("--listen-port", type=int, default=8088)
parser.add_argument("--lmstudio-url", default="http://127.0.0.1:1234")
parser.add_argument("--enable-search", action="store_true")
parser.add_argument("--search-results", type=int, default=3)
args = parser.parse_args()
server = ThreadingHTTPServer(
(args.listen_host, args.listen_port),
make_handler(args.lmstudio_url, args.enable_search, args.search_results),
)
print(f"Aurora adapter listening on http://{args.listen_host}:{args.listen_port}")
print(f"Forwarding model calls to {args.lmstudio_url}")
print(f"Search enabled: {args.enable_search}")
server.serve_forever()
if __name__ == "__main__":
main()