From efbd82532ffcc4dd189e77d4c0be928aafde54e5 Mon Sep 17 00:00:00 2001 From: wuyang <5700876+banisherwy@user.noreply.gitee.com> Date: Tue, 28 Jul 2026 17:16:34 +0800 Subject: [PATCH] migrate knowledge atlas to llama.cpp --- AGENTS.md | 15 +-- docs/06-project-status.md | 14 +-- docs/07-exploration-history.md | 10 ++ tools/check_project.py | 9 +- web/README.md | 15 +-- web/agent-knowledge-atlas.service | 9 +- web/app.py | 168 +++++++++++++++++------------- web/manage.sh | 9 +- web/static/app.js | 12 +-- 9 files changed, 152 insertions(+), 109 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 365f821..420fa45 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -78,23 +78,24 @@ The existing evaluation contract must be revised against that research before ru - Keep generated cache and PID/log files under `web/cache/`; they are intentionally ignored. - Never commit credentials. Git authentication is configured outside the repository. -## Ollama Contract +## Model API Contract -Default endpoint: +Default internal llama.cpp endpoints: ```text -http://192.168.1.10:11434 +Luna: http://192.168.1.10:11435/v1 +Terra/Sol: http://192.168.1.10:11436/v1 ``` Current model tiers are configurable with: ```text -OLLAMA_MODEL_LIGHT=ChatGPT-5.6:Luna -OLLAMA_MODEL_FAST=ChatGPT-5.6:Terra -OLLAMA_MODEL_LARGE=ChatGPT-5.6:Sol +MODEL_LIGHT=ChatGPT-5.6:Luna +MODEL_FAST=ChatGPT-5.6:Terra +MODEL_LARGE=ChatGPT-5.6:Sol ``` -The application serializes Ollama calls because all models share one V100 32G GPU. Application requests must not send `num_ctx` or `keep_alive`; the Ollama server owns context size and residency. Expensive `Sol` actions should remain explicit, not automatic. +Luna runs on the NAS AMD iGPU. Terra and Sol are aliases of one resident llama.cpp runner on the V100 32G. The application serializes model calls and must not override server context sizing. Expensive `Sol` actions should remain explicit, not automatic. ## Common Commands diff --git a/docs/06-project-status.md b/docs/06-project-status.md index e668940..e4dcf2b 100644 --- a/docs/06-project-status.md +++ b/docs/06-project-status.md @@ -85,7 +85,7 @@ evaluation contract. | research tools | `tools/research/` | paginated search, abstract fetch, screening, classification, landscape build | | experiments | `experiments/knowledge-compilation/` | failed pilot and completed full-text research audits | | learning prototype | `learning/agent-memory/` | preserved failed 12-paper learning module | -| web application | `web/` | Atlas, search agent, paper reader/chat, Ollama actions | +| web application | `web/` | Atlas, search agent, paper reader/chat, llama.cpp actions | | project integrity | `tools/check_project.py` | offline structural and data checks | `projects/` and `references/` are still mostly scaffolding. They do not yet contain substantive project or general-reference catalogs. @@ -161,7 +161,7 @@ Capabilities currently implemented: - asynchronous search activity, aggregate statistics and AI brief; - follow-up questions on a search result; - per-paper abstract fetch, summary, translation, deep analysis and chat; -- single-concurrency Ollama access and local caches. +- single-concurrency llama.cpp access and local caches. Run it with: @@ -182,9 +182,9 @@ At the beginning of the 2026-07-12 handoff audit, no process was listening on 18 local and public health then returned 200. Runtime status is external state and must always be checked live. After a corpus update, restart the service because the index is loaded at process startup. On 2026-07-27 both the Tailscale endpoint and `https://lab.k1412.top/api/health` returned the refreshed -1,125-paper count with `ollama_ready: true`. +1,125-paper count. After the 2026-07-28 migration, readiness is reported as `model_api_ready`. -The live Ollama inventory checked on 2026-07-12 included `ChatGPT-5.6:Luna`, `Terra`, `Sol`, `auto`, `embed`, and `gpt-4o:latest`. The web defaults were updated from stale `light/fast/large` tags to configurable Luna/Terra/Sol tiers. +On 2026-07-28, inference moved from Ollama to two resident llama.cpp servers. Luna runs as Qwen3 0.6B Q8 on the NAS Radeon 780M through Vulkan. Terra and Sol share one Qwen3.6 27B Q4_K_M runner on the V100 through CUDA 12, preserving three public tiers without loading a second large model. The web application calls the two internal OpenAI-compatible endpoints directly. The static Agent completion research document is packaged separately: @@ -216,9 +216,9 @@ verified. The existing `AGENT EVAL` homepage entry remains under `projects` at o ## Runtime Constraints - Python standard library only; no package installation is required for the current web server and collection scripts. -- Ollama endpoint defaults to `http://192.168.1.10:11434`. -- One V100 32G serves all local models; AI calls are serialized. -- Do not send `num_ctx` or `keep_alive` from clients. Server-side context and residency must remain authoritative. +- llama.cpp endpoints default to `http://192.168.1.10:11435/v1` for Luna and `http://192.168.1.10:11436/v1` for Terra/Sol. +- Luna uses the NAS AMD iGPU; one V100 32G serves the shared Terra/Sol runner. AI calls are serialized. +- Context is fixed server-side at 8K for Luna and 32K for Terra/Sol. - `Sol` is the expensive tier and should require an explicit user action. - The user mentioned an unlimited company GPT-5.5 data-agent API as a possible stronger offline worker, but no endpoint/authentication contract is committed. It is not currently integrated. - The public site has no repository-managed authentication or TLS configuration; the domain mapping is external infrastructure. diff --git a/docs/07-exploration-history.md b/docs/07-exploration-history.md index b37e610..0fe2e76 100644 --- a/docs/07-exploration-history.md +++ b/docs/07-exploration-history.md @@ -98,6 +98,16 @@ Decision retained: always verify `/api/tags`; never infer deployed model aliases Relevant commits: `47c301c`, `ac6e0e0`, and the 2026-07-12 handoff update. +On 2026-07-28 the runtime moved to llama.cpp after repeated Ollama runner churn and +resource-residency ambiguity. Luna was isolated on the NAS AMD iGPU through Vulkan, +while Terra and Sol became two API behaviors over one fixed 27B CUDA runner on the +V100. The web app now uses the servers' OpenAI-compatible endpoints, fixes context +at deployment time, and controls thinking by tier. + +Decision retained: product tiers do not require separate physical model copies. +Keep resource ownership in the serving layer and let Terra/Sol share one resident +runner when their weights are identical. + ## 6. Learning-System Reframing The user clarified the real objective with three levels: diff --git a/tools/check_project.py b/tools/check_project.py index 8d2841a..c59dd93 100755 --- a/tools/check_project.py +++ b/tools/check_project.py @@ -113,10 +113,11 @@ def check_python_syntax() -> int: return len(paths) -def check_ollama_contract() -> None: +def check_model_api_contract() -> None: source = (ROOT / "web" / "app.py").read_text(encoding="utf-8") - require('"num_ctx"' not in source, "web app must not override Ollama num_ctx") - require('"keep_alive"' not in source, "web app must not override Ollama keep_alive") + require('"num_ctx"' not in source, "web app must not override model API context") + require('"keep_alive"' not in source, "web app must not send legacy keep_alive") + require("/api/generate" not in source, "web app must use the OpenAI-compatible model API") def check_experiment_state() -> None: @@ -207,7 +208,7 @@ def main() -> int: collections = check_collection_index() research = check_research_data() python_files = check_python_syntax() - check_ollama_contract() + check_model_api_contract() check_experiment_state() evaluation_tasks = check_evaluation_contract() completion_evidence = check_evaluation_site() diff --git a/web/README.md b/web/README.md index 9f1a32a..e8c9249 100644 --- a/web/README.md +++ b/web/README.md @@ -15,10 +15,11 @@ web/manage.sh status python3 web/app.py --host 100.114.68.27 --port 18080 ``` -默认 Ollama 地址: +默认 llama.cpp 地址: ```bash -OLLAMA_URL=http://192.168.1.10:11434 +MODEL_API_LUNA_URL=http://192.168.1.10:11435/v1 +MODEL_API_SOL_URL=http://192.168.1.10:11436/v1 ``` 访问地址: @@ -44,16 +45,16 @@ https://lab.k1412.top/ - 主题导读、阅读路径、主题比较:`ChatGPT-5.6:Sol`,只在前端手动确认后调用 - Search Agent 查询理解:`ChatGPT-5.6:Luna` - Search Agent 结果简报、搜索追问、论文对话:`ChatGPT-5.6:Terra` -- Ollama 请求单并发执行,并缓存结果到 `web/cache/ai/` -- 应用侧不下发 `num_ctx` / `keep_alive`,上下文长度和模型常驻由 Ollama 服务端统一控制 +- llama.cpp 请求单并发执行,并缓存结果到 `web/cache/ai/` +- 应用侧不覆盖上下文长度,Luna 固定 8K,Terra/Sol 固定 32K - arXiv 摘要缓存到 `web/cache/arxiv/` 模型 tier 可以覆盖: ```bash -OLLAMA_MODEL_LIGHT=ChatGPT-5.6:Luna \ -OLLAMA_MODEL_FAST=ChatGPT-5.6:Terra \ -OLLAMA_MODEL_LARGE=ChatGPT-5.6:Sol \ +MODEL_LIGHT=ChatGPT-5.6:Luna \ +MODEL_FAST=ChatGPT-5.6:Terra \ +MODEL_LARGE=ChatGPT-5.6:Sol \ web/manage.sh restart ``` diff --git a/web/agent-knowledge-atlas.service b/web/agent-knowledge-atlas.service index 168bc54..6cab668 100644 --- a/web/agent-knowledge-atlas.service +++ b/web/agent-knowledge-atlas.service @@ -7,10 +7,11 @@ Wants=network-online.target Type=simple WorkingDirectory=%h/Code/agent Environment=PYTHONUNBUFFERED=1 -Environment=OLLAMA_URL=http://192.168.1.10:11434 -Environment=OLLAMA_MODEL_LIGHT=ChatGPT-5.6:Luna -Environment=OLLAMA_MODEL_FAST=ChatGPT-5.6:Terra -Environment=OLLAMA_MODEL_LARGE=ChatGPT-5.6:Sol +Environment=MODEL_API_LUNA_URL=http://192.168.1.10:11435/v1 +Environment=MODEL_API_SOL_URL=http://192.168.1.10:11436/v1 +Environment=MODEL_LIGHT=ChatGPT-5.6:Luna +Environment=MODEL_FAST=ChatGPT-5.6:Terra +Environment=MODEL_LARGE=ChatGPT-5.6:Sol Environment=HOST=100.114.68.27 Environment=PORT=18080 EnvironmentFile=-%h/Code/agent/web/cache/service.env diff --git a/web/app.py b/web/app.py index 2511bb4..5b5aef5 100644 --- a/web/app.py +++ b/web/app.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Serve a local paper browser with lightweight Ollama-backed actions.""" +"""Serve a local paper browser with lightweight llama.cpp-backed actions.""" from __future__ import annotations @@ -30,8 +30,13 @@ INDEX_PATH = ROOT / "data" / "index.json" CACHE_ROOT = WEB_ROOT / "cache" ARXIV_CACHE = CACHE_ROOT / "arxiv" AI_CACHE = CACHE_ROOT / "ai" -OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://192.168.1.10:11434").rstrip("/") -ASSET_VERSION = "atlas-20260712-01" +MODEL_API_LUNA_URL = os.environ.get( + "MODEL_API_LUNA_URL", "http://192.168.1.10:11435/v1" +).rstrip("/") +MODEL_API_SOL_URL = os.environ.get( + "MODEL_API_SOL_URL", "http://192.168.1.10:11436/v1" +).rstrip("/") +ASSET_VERSION = "atlas-20260728-01" def detect_app_version() -> str: @@ -51,9 +56,9 @@ def detect_app_version() -> str: APP_VERSION = detect_app_version() -MODEL_LIGHT = os.environ.get("OLLAMA_MODEL_LIGHT", "ChatGPT-5.6:Luna") -MODEL_FAST = os.environ.get("OLLAMA_MODEL_FAST", "ChatGPT-5.6:Terra") -MODEL_LARGE = os.environ.get("OLLAMA_MODEL_LARGE", "ChatGPT-5.6:Sol") +MODEL_LIGHT = os.environ.get("MODEL_LIGHT", "ChatGPT-5.6:Luna") +MODEL_FAST = os.environ.get("MODEL_FAST", "ChatGPT-5.6:Terra") +MODEL_LARGE = os.environ.get("MODEL_LARGE", "ChatGPT-5.6:Sol") DEFAULT_MODELS = { "translate": MODEL_LIGHT, @@ -75,7 +80,7 @@ NS = { } INDEX_LOCK = threading.Lock() -OLLAMA_LOCK = threading.Lock() +MODEL_API_LOCK = threading.Lock() SEARCH_LOCK = threading.Lock() INDEX_CACHE: dict[str, Any] = {"mtime": 0.0, "papers": [], "by_id": {}} ATLAS_CACHE: dict[str, Any] = {"mtime": 0.0, "atlas": None} @@ -512,8 +517,8 @@ def plan_search_query(query: str, scope_topic: str = "") -> tuple[dict[str, Any] model = DEFAULT_MODELS["search_plan"] result: dict[str, Any] | None = None try: - with OLLAMA_LOCK: - result = call_ollama(model, search_plan_prompt(query, scope_topic), "search_plan") + with MODEL_API_LOCK: + result = call_model(model, search_plan_prompt(query, scope_topic), "search_plan") plan = extract_json_object(result["response"]) merged = { **fallback, @@ -676,8 +681,8 @@ def fallback_search_summary(query: str, plan: dict[str, Any], aggregation: dict[ def generate_search_summary(query: str, plan: dict[str, Any], items: list[dict[str, Any]], aggregation: dict[str, Any]) -> dict[str, Any]: model = DEFAULT_MODELS["search_summary"] try: - with OLLAMA_LOCK: - result = call_ollama(model, search_summary_prompt(query, plan, items, aggregation), "search_summary") + with MODEL_API_LOCK: + result = call_model(model, search_summary_prompt(query, plan, items, aggregation), "search_summary") return { "text": result["response"], "model": result["model"], @@ -1240,45 +1245,84 @@ def clean_model_response(value: str) -> str: return text or value.strip() -def call_ollama(model: str, prompt: str, mode: str) -> dict[str, Any]: +def model_api_url(model: str) -> str: + if model in {MODEL_LIGHT, "ChatGPT-5.6:auto", "gpt-4o"}: + return MODEL_API_LUNA_URL + return MODEL_API_SOL_URL + + +def model_api_request(url: str, *, data: bytes | None = None) -> urllib.request.Request: + headers = {"content-type": "application/json"} if data is not None else {} + return urllib.request.Request(url, data=data, headers=headers) + + +def available_model_inventory(timeout: float = 5) -> list[dict[str, Any]]: + aliases: set[str] = set() + for base_url in (MODEL_API_LUNA_URL, MODEL_API_SOL_URL): + request = model_api_request(f"{base_url}/models") + with urllib.request.urlopen(request, timeout=timeout) as response: + payload = json.loads(response.read().decode("utf-8")) + for item in payload.get("data", []): + if item.get("id"): + aliases.add(str(item["id"])) + aliases.update(str(alias) for alias in item.get("aliases", []) if alias) + + context_lengths = { + MODEL_LIGHT: 8192, + MODEL_FAST: 32768, + MODEL_LARGE: 32768, + } + return [ + { + "name": model, + "parameter_size": None, + "context_length": context_lengths.get(model), + } + for model in sorted(aliases) + ] + + +def call_model(model: str, prompt: str, mode: str) -> dict[str, Any]: options = { "temperature": 0.2, - "num_predict": 520, + "max_tokens": 520, } if mode == "translate": - options.update({"num_predict": 700, "temperature": 0.1}) + options.update({"max_tokens": 700, "temperature": 0.1}) elif mode == "deep": - options.update({"num_predict": 900, "temperature": 0.25}) + options.update({"max_tokens": 900, "temperature": 0.25}) elif mode in {"atlas", "topic", "path", "compare"}: - options.update({"num_predict": 950, "temperature": 0.22}) + options.update({"max_tokens": 950, "temperature": 0.22}) elif mode == "search_plan": - options.update({"num_predict": 420, "temperature": 0.1}) + options.update({"max_tokens": 420, "temperature": 0.1}) elif mode in {"search_summary", "search_followup", "paper_chat"}: - options.update({"num_predict": 780, "temperature": 0.18}) + options.update({"max_tokens": 780, "temperature": 0.18}) payload = { "model": model, - "prompt": prompt, + "messages": [{"role": "user", "content": prompt}], "stream": False, - "options": options, + "chat_template_kwargs": {"enable_thinking": model == MODEL_LARGE}, + **options, } data = json.dumps(payload, ensure_ascii=False).encode("utf-8") - request = urllib.request.Request( - f"{OLLAMA_URL}/api/generate", + request = model_api_request( + f"{model_api_url(model)}/chat/completions", data=data, - headers={"content-type": "application/json"}, - method="POST", ) started = time.time() with urllib.request.urlopen(request, timeout=240) as response: result = json.loads(response.read().decode("utf-8")) + choice = (result.get("choices") or [{}])[0] + message = choice.get("message") or {} + usage = result.get("usage") or {} return { "model": model, "mode": mode, - "response": clean_model_response(str(result.get("response") or "")), + "response": clean_model_response(str(message.get("content") or "")), "duration_seconds": round(time.time() - started, 2), - "eval_count": result.get("eval_count"), - "prompt_eval_count": result.get("prompt_eval_count"), + "eval_count": usage.get("completion_tokens"), + "prompt_eval_count": usage.get("prompt_tokens"), } @@ -1379,20 +1423,14 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): def handle_health(self) -> None: papers, _ = load_papers() - ollama_ok = False + model_api_ok = False available_models: list[str] = [] try: - request = urllib.request.Request(f"{OLLAMA_URL}/api/tags") - with urllib.request.urlopen(request, timeout=3) as response: - ollama_ok = response.status == 200 - payload = json.loads(response.read().decode("utf-8")) - available_models = sorted( - str(item.get("name")) - for item in payload.get("models", []) - if item.get("name") - ) + inventory = available_model_inventory(timeout=3) + available_models = [str(item["name"]) for item in inventory] + model_api_ok = True except (urllib.error.URLError, TimeoutError, json.JSONDecodeError): - ollama_ok = False + model_api_ok = False missing_default_models = sorted(set(DEFAULT_MODELS.values()) - set(available_models)) write_json( self, @@ -1401,9 +1439,9 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): "paper_count": len(papers), "app_version": APP_VERSION, "asset_version": ASSET_VERSION, - "ollama_url": OLLAMA_URL, - "ollama_ok": ollama_ok, - "ollama_ready": ollama_ok and not missing_default_models, + "model_api_urls": [MODEL_API_LUNA_URL, MODEL_API_SOL_URL], + "model_api_ok": model_api_ok, + "model_api_ready": model_api_ok and not missing_default_models, "available_models": available_models, "missing_default_models": missing_default_models, "default_models": DEFAULT_MODELS, @@ -1412,20 +1450,10 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): def handle_models(self) -> None: try: - request = urllib.request.Request(f"{OLLAMA_URL}/api/tags") - with urllib.request.urlopen(request, timeout=5) as response: - payload = json.loads(response.read().decode("utf-8")) - models = [ - { - "name": item.get("name"), - "parameter_size": (item.get("details") or {}).get("parameter_size"), - "context_length": (item.get("details") or {}).get("context_length"), - } - for item in payload.get("models", []) - ] + models = available_model_inventory() write_json(self, {"models": models, "default_models": DEFAULT_MODELS}) except Exception as exc: # noqa: BLE001 - write_error(self, HTTPStatus.BAD_GATEWAY, f"ollama unavailable: {exc}") + write_error(self, HTTPStatus.BAD_GATEWAY, f"model API unavailable: {exc}") def handle_paper_search(self, query: dict[str, list[str]]) -> None: papers, _ = load_papers() @@ -1508,11 +1536,11 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): if not job or job.get("status") != "done": write_error(self, HTTPStatus.NOT_FOUND, "completed search job not found") return - if not OLLAMA_LOCK.acquire(blocking=False): - write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "ollama is busy; try again later") + if not MODEL_API_LOCK.acquire(blocking=False): + write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "model API is busy; try again later") return try: - result = call_ollama(model, search_followup_prompt(job, question), "search_followup") + result = call_model(model, search_followup_prompt(job, question), "search_followup") write_json( self, { @@ -1524,7 +1552,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): }, ) finally: - OLLAMA_LOCK.release() + MODEL_API_LOCK.release() except Exception as exc: # noqa: BLE001 write_error(self, HTTPStatus.INTERNAL_SERVER_ERROR, str(exc)) @@ -1581,8 +1609,8 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): if not paper: write_error(self, HTTPStatus.NOT_FOUND, "paper not found") return - if not OLLAMA_LOCK.acquire(blocking=False): - write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "ollama is busy; try again later") + if not MODEL_API_LOCK.acquire(blocking=False): + write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "model API is busy; try again later") return try: abstract = None @@ -1592,7 +1620,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): abstract = fetch_arxiv(arxiv_id) except Exception: abstract = None - result = call_ollama(model, paper_chat_prompt(paper, abstract, question, history), "paper_chat") + result = call_model(model, paper_chat_prompt(paper, abstract, question, history), "paper_chat") write_json( self, { @@ -1604,7 +1632,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): }, ) finally: - OLLAMA_LOCK.release() + MODEL_API_LOCK.release() except Exception as exc: # noqa: BLE001 write_error(self, HTTPStatus.INTERNAL_SERVER_ERROR, str(exc)) @@ -1633,8 +1661,8 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): write_json(self, result) return - if not OLLAMA_LOCK.acquire(blocking=False): - write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "ollama is busy; try again later") + if not MODEL_API_LOCK.acquire(blocking=False): + write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "model API is busy; try again later") return try: abstract = None @@ -1642,7 +1670,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): if arxiv_id: abstract = fetch_arxiv(arxiv_id) context = paper_context(paper, abstract) - result = call_ollama(model, prompt_for(mode, context), mode) + result = call_model(model, prompt_for(mode, context), mode) result.update( { "id": paper_id, @@ -1654,7 +1682,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): cache_path.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") write_json(self, result) finally: - OLLAMA_LOCK.release() + MODEL_API_LOCK.release() except Exception as exc: # noqa: BLE001 write_error(self, HTTPStatus.INTERNAL_SERVER_ERROR, str(exc)) @@ -1685,12 +1713,12 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): write_json(self, result) return - if not OLLAMA_LOCK.acquire(blocking=False): - write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "ollama is busy; try again later") + if not MODEL_API_LOCK.acquire(blocking=False): + write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "model API is busy; try again later") return try: context = atlas_context(topic, topic_b) - result = call_ollama(model, atlas_prompt_for(mode, context), mode) + result = call_model(model, atlas_prompt_for(mode, context), mode) result.update( { "scope": cache_key, @@ -1701,7 +1729,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler): cache_path.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") write_json(self, result) finally: - OLLAMA_LOCK.release() + MODEL_API_LOCK.release() except Exception as exc: # noqa: BLE001 write_error(self, HTTPStatus.INTERNAL_SERVER_ERROR, str(exc)) @@ -1719,7 +1747,7 @@ def main() -> int: load_papers() server = ThreadingHTTPServer((args.host, args.port), PaperBrowserHandler) print(f"Paper browser: http://{args.host}:{args.port}") - print(f"Ollama: {OLLAMA_URL}") + print(f"Model APIs: Luna={MODEL_API_LUNA_URL}, Sol/Terra={MODEL_API_SOL_URL}") try: server.serve_forever() except KeyboardInterrupt: diff --git a/web/manage.sh b/web/manage.sh index 47211da..64704e0 100755 --- a/web/manage.sh +++ b/web/manage.sh @@ -15,10 +15,11 @@ write_environment() { { printf 'HOST=%s\n' "$HOST" printf 'PORT=%s\n' "$PORT" - printf 'OLLAMA_URL=%s\n' "${OLLAMA_URL:-http://192.168.1.10:11434}" - printf 'OLLAMA_MODEL_LIGHT=%s\n' "${OLLAMA_MODEL_LIGHT:-ChatGPT-5.6:Luna}" - printf 'OLLAMA_MODEL_FAST=%s\n' "${OLLAMA_MODEL_FAST:-ChatGPT-5.6:Terra}" - printf 'OLLAMA_MODEL_LARGE=%s\n' "${OLLAMA_MODEL_LARGE:-ChatGPT-5.6:Sol}" + printf 'MODEL_API_LUNA_URL=%s\n' "${MODEL_API_LUNA_URL:-http://192.168.1.10:11435/v1}" + printf 'MODEL_API_SOL_URL=%s\n' "${MODEL_API_SOL_URL:-http://192.168.1.10:11436/v1}" + printf 'MODEL_LIGHT=%s\n' "${MODEL_LIGHT:-ChatGPT-5.6:Luna}" + printf 'MODEL_FAST=%s\n' "${MODEL_FAST:-ChatGPT-5.6:Terra}" + printf 'MODEL_LARGE=%s\n' "${MODEL_LARGE:-ChatGPT-5.6:Sol}" } >"$ENV_FILE" } diff --git a/web/static/app.js b/web/static/app.js index 53ab6bf..37d4801 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -141,12 +141,12 @@ async function loadHealth() { try { const health = await api("/api/health"); state.health = health; - const ollamaState = health.ollama_ready - ? "Ollama ready" - : health.ollama_ok - ? "Ollama model mismatch" - : "Ollama offline"; - elements.healthLine.textContent = `${health.paper_count} papers · ${ollamaState}`; + const modelState = health.model_api_ready + ? "Model API ready" + : health.model_api_ok + ? "Model API mismatch" + : "Model API offline"; + elements.healthLine.textContent = `${health.paper_count} papers · ${modelState}`; } catch (error) { elements.healthLine.textContent = error.message; }