Document and harden project handoff
This commit is contained in:
+41
-7
@@ -4,6 +4,13 @@
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
web/manage.sh start
|
||||
web/manage.sh status
|
||||
```
|
||||
|
||||
前台调试仍可直接运行:
|
||||
|
||||
```bash
|
||||
python3 web/app.py --host 100.114.68.27 --port 18080
|
||||
```
|
||||
@@ -30,17 +37,40 @@ https://lab.k1412.top/
|
||||
|
||||
## AI Policy
|
||||
|
||||
- 默认摘要模型:`ChatGPT-5.6:fast`
|
||||
- 默认翻译模型:`ChatGPT-5.6:light`
|
||||
- 深度分析模型:`ChatGPT-5.6:large`,只在前端手动选择时使用
|
||||
- Atlas 解释:`ChatGPT-5.6:fast`
|
||||
- 主题导读、阅读路径、主题比较:`ChatGPT-5.6:large`,只在前端手动确认后调用
|
||||
- Search Agent 查询理解:`ChatGPT-5.6:light`
|
||||
- Search Agent 结果简报、搜索追问、论文对话:`ChatGPT-5.6:fast`
|
||||
- 默认摘要模型:`ChatGPT-5.6:Terra`
|
||||
- 默认翻译模型:`ChatGPT-5.6:Luna`
|
||||
- 深度分析模型:`ChatGPT-5.6:Sol`,只在前端手动选择时使用
|
||||
- Atlas 解释:`ChatGPT-5.6:Terra`
|
||||
- 主题导读、阅读路径、主题比较:`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 服务端统一控制
|
||||
- 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 \
|
||||
web/manage.sh restart
|
||||
```
|
||||
|
||||
`/api/health` 会返回可用模型、默认模型和缺失的默认标签。维护时先看健康接口,不要假设旧 alias 仍存在。
|
||||
|
||||
## Service Management
|
||||
|
||||
```bash
|
||||
web/manage.sh start
|
||||
web/manage.sh stop
|
||||
web/manage.sh restart
|
||||
web/manage.sh status
|
||||
web/manage.sh logs
|
||||
```
|
||||
|
||||
`manage.sh` 会把仓库中的 `agent-knowledge-atlas.service` 链接到用户级 systemd,并把可覆盖环境写入忽略提交的 `web/cache/service.env`。日志使用 systemd journal。它不替代 TLS、反向代理或鉴权。
|
||||
|
||||
## Knowledge Design
|
||||
|
||||
先由后端从本地语料轻量计算:
|
||||
@@ -62,3 +92,7 @@ https://lab.k1412.top/
|
||||
- 搜索意图理解、关键词扩展、结果简报
|
||||
- 基于搜索结果继续追问
|
||||
- 基于单篇论文继续对话式研读
|
||||
|
||||
## Product Boundary
|
||||
|
||||
当前 Atlas 是资料探索原型,不是已经验证的学习产品。用户对“只浏览论文、图谱和 AI 摘要”的实用性评价很低,因为它没有保存个人理解、主张、反证、应用和复习状态。后续产品方向见 `docs/08-next-work.md`,不要把下一阶段继续收敛为图谱视觉优化。
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=Agent Knowledge Atlas
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
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=HOST=100.114.68.27
|
||||
Environment=PORT=18080
|
||||
EnvironmentFile=-%h/Code/agent/web/cache/service.env
|
||||
ExecStart=/usr/bin/python3 %h/Code/agent/web/app.py --host ${HOST} --port ${PORT}
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
+28
-13
@@ -31,7 +31,7 @@ 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-20260709-01"
|
||||
ASSET_VERSION = "atlas-20260712-01"
|
||||
|
||||
|
||||
def detect_app_version() -> str:
|
||||
@@ -51,18 +51,22 @@ 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")
|
||||
|
||||
DEFAULT_MODELS = {
|
||||
"translate": "ChatGPT-5.6:light",
|
||||
"summary": "ChatGPT-5.6:fast",
|
||||
"deep": "ChatGPT-5.6:large",
|
||||
"atlas": "ChatGPT-5.6:fast",
|
||||
"topic": "ChatGPT-5.6:large",
|
||||
"path": "ChatGPT-5.6:large",
|
||||
"compare": "ChatGPT-5.6:large",
|
||||
"search_plan": "ChatGPT-5.6:light",
|
||||
"search_summary": "ChatGPT-5.6:fast",
|
||||
"search_followup": "ChatGPT-5.6:fast",
|
||||
"paper_chat": "ChatGPT-5.6:fast",
|
||||
"translate": MODEL_LIGHT,
|
||||
"summary": MODEL_FAST,
|
||||
"deep": MODEL_LARGE,
|
||||
"atlas": MODEL_FAST,
|
||||
"topic": MODEL_LARGE,
|
||||
"path": MODEL_LARGE,
|
||||
"compare": MODEL_LARGE,
|
||||
"search_plan": MODEL_LIGHT,
|
||||
"search_summary": MODEL_FAST,
|
||||
"search_followup": MODEL_FAST,
|
||||
"paper_chat": MODEL_FAST,
|
||||
}
|
||||
|
||||
NS = {
|
||||
@@ -1376,12 +1380,20 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
def handle_health(self) -> None:
|
||||
papers, _ = load_papers()
|
||||
ollama_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
|
||||
except (urllib.error.URLError, TimeoutError):
|
||||
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")
|
||||
)
|
||||
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError):
|
||||
ollama_ok = False
|
||||
missing_default_models = sorted(set(DEFAULT_MODELS.values()) - set(available_models))
|
||||
write_json(
|
||||
self,
|
||||
{
|
||||
@@ -1391,6 +1403,9 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
"asset_version": ASSET_VERSION,
|
||||
"ollama_url": OLLAMA_URL,
|
||||
"ollama_ok": ollama_ok,
|
||||
"ollama_ready": ollama_ok and not missing_default_models,
|
||||
"available_models": available_models,
|
||||
"missing_default_models": missing_default_models,
|
||||
"default_models": DEFAULT_MODELS,
|
||||
},
|
||||
)
|
||||
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
UNIT_NAME="agent-knowledge-atlas.service"
|
||||
UNIT_SOURCE="$ROOT/web/$UNIT_NAME"
|
||||
UNIT_TARGET="$HOME/.config/systemd/user/$UNIT_NAME"
|
||||
ENV_FILE="$ROOT/web/cache/service.env"
|
||||
HOST="${HOST:-100.114.68.27}"
|
||||
PORT="${PORT:-18080}"
|
||||
HEALTH_URL="${HEALTH_URL:-http://$HOST:$PORT/api/health}"
|
||||
|
||||
write_environment() {
|
||||
mkdir -p "$(dirname "$ENV_FILE")"
|
||||
{
|
||||
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}"
|
||||
} >"$ENV_FILE"
|
||||
}
|
||||
|
||||
install_unit() {
|
||||
mkdir -p "$(dirname "$UNIT_TARGET")"
|
||||
ln -sfn "$UNIT_SOURCE" "$UNIT_TARGET"
|
||||
write_environment
|
||||
systemctl --user daemon-reload
|
||||
}
|
||||
|
||||
wait_for_health() {
|
||||
for _ in $(seq 1 30); do
|
||||
if curl -fsS --max-time 2 "$HEALTH_URL" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
start_server() {
|
||||
install_unit
|
||||
systemctl --user enable --now "$UNIT_NAME"
|
||||
if ! wait_for_health; then
|
||||
echo "Server did not become healthy" >&2
|
||||
journalctl --user -u "$UNIT_NAME" -n 60 --no-pager >&2 || true
|
||||
return 1
|
||||
fi
|
||||
echo "Agent Knowledge Atlas started"
|
||||
echo "Health: $HEALTH_URL"
|
||||
}
|
||||
|
||||
stop_server() {
|
||||
if systemctl --user is-active --quiet "$UNIT_NAME"; then
|
||||
systemctl --user stop "$UNIT_NAME"
|
||||
echo "Agent Knowledge Atlas stopped"
|
||||
else
|
||||
echo "Agent Knowledge Atlas is not running"
|
||||
fi
|
||||
}
|
||||
|
||||
status_server() {
|
||||
systemctl --user is-active "$UNIT_NAME"
|
||||
curl -fsS --max-time 5 "$HEALTH_URL"
|
||||
echo
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
install)
|
||||
install_unit
|
||||
systemctl --user enable "$UNIT_NAME"
|
||||
echo "Installed $UNIT_NAME"
|
||||
;;
|
||||
start) start_server ;;
|
||||
stop) stop_server ;;
|
||||
restart)
|
||||
install_unit
|
||||
systemctl --user restart "$UNIT_NAME"
|
||||
wait_for_health
|
||||
echo "Agent Knowledge Atlas restarted"
|
||||
;;
|
||||
status) status_server ;;
|
||||
logs) journalctl --user -u "$UNIT_NAME" -n "${LINES:-80}" --no-pager ;;
|
||||
*)
|
||||
echo "Usage: $0 {install|start|stop|restart|status|logs}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
+15
-5
@@ -141,7 +141,12 @@ async function loadHealth() {
|
||||
try {
|
||||
const health = await api("/api/health");
|
||||
state.health = health;
|
||||
elements.healthLine.textContent = `${health.paper_count} papers · ${health.ollama_ok ? "Ollama online" : "Ollama offline"}`;
|
||||
const ollamaState = health.ollama_ready
|
||||
? "Ollama ready"
|
||||
: health.ollama_ok
|
||||
? "Ollama model mismatch"
|
||||
: "Ollama offline";
|
||||
elements.healthLine.textContent = `${health.paper_count} papers · ${ollamaState}`;
|
||||
} catch (error) {
|
||||
elements.healthLine.textContent = error.message;
|
||||
}
|
||||
@@ -1103,10 +1108,15 @@ function setAiBusy(isBusy, label = "") {
|
||||
}
|
||||
}
|
||||
|
||||
function isLargeModel(model) {
|
||||
const configuredLarge = state.health?.default_models?.deep;
|
||||
return model === configuredLarge || /:(large|sol)$/i.test(model);
|
||||
}
|
||||
|
||||
async function runPaperAi(mode) {
|
||||
if (!state.selectedPaper) return;
|
||||
const model = state.health?.default_models?.[mode] || (mode === "translate" ? "ChatGPT-5.6:light" : "ChatGPT-5.6:fast");
|
||||
if (model.includes(":large") && !window.confirm("将调用 large 模型,占用更多 GPU。继续?")) return;
|
||||
const model = state.health?.default_models?.[mode] || (mode === "translate" ? "ChatGPT-5.6:Luna" : "ChatGPT-5.6:Terra");
|
||||
if (isLargeModel(model) && !window.confirm("将调用 large 模型,占用更多 GPU。继续?")) return;
|
||||
setAiBusy(true, `${mode} · ${model}`);
|
||||
try {
|
||||
const payload = await api("/api/ai", {
|
||||
@@ -1124,8 +1134,8 @@ async function runPaperAi(mode) {
|
||||
}
|
||||
|
||||
async function runAtlasAi(mode, topic = "", topicB = "") {
|
||||
const model = state.health?.default_models?.[mode] || "ChatGPT-5.6:fast";
|
||||
if (model.includes(":large") && !window.confirm("将调用 large 模型做高维综合,占用更多 GPU。继续?")) return;
|
||||
const model = state.health?.default_models?.[mode] || "ChatGPT-5.6:Terra";
|
||||
if (isLargeModel(model) && !window.confirm("将调用 large 模型做高维综合,占用更多 GPU。继续?")) return;
|
||||
setAiBusy(true, `${mode} · ${model}`);
|
||||
try {
|
||||
const payload = await api("/api/atlas/ai", {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Agent Knowledge Atlas</title>
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%23cfe8e3'/%3E%3Ctext x='32' y='40' text-anchor='middle' font-size='24' font-family='Arial' font-weight='700' fill='%23064c47'%3EAK%3C/text%3E%3C/svg%3E" />
|
||||
<link rel="stylesheet" href="/static/styles.css?v=atlas-20260709-01" />
|
||||
<link rel="stylesheet" href="/static/styles.css?v=atlas-20260712-01" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="atlas-app">
|
||||
@@ -79,6 +79,6 @@
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<script src="/static/app.js?v=atlas-20260709-01"></script>
|
||||
<script src="/static/app.js?v=atlas-20260712-01"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user