migrate knowledge atlas to llama.cpp

This commit is contained in:
wuyang
2026-07-28 17:16:34 +08:00
parent 88fb212985
commit efbd82532f
9 changed files with 152 additions and 109 deletions
+8 -7
View File
@@ -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
+7 -7
View File
@@ -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.
+10
View File
@@ -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:
+5 -4
View File
@@ -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()
+8 -7
View File
@@ -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 固定 8KTerra/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
```
+5 -4
View File
@@ -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
+98 -70
View File
@@ -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:
+5 -4
View File
@@ -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"
}
+6 -6
View File
@@ -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;
}