Expose DuckLM runtime status
This commit is contained in:
+53
-2
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi import FastAPI, HTTPException, Query, Request
|
||||
from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
@@ -165,7 +165,31 @@ def create_app() -> FastAPI:
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.get("/v1/status")
|
||||
async def status() -> dict[str, Any]:
|
||||
async def status(probe: bool = Query(False)) -> dict[str, Any]:
|
||||
role_configs = model_client.list_roles()
|
||||
endpoints = sorted(
|
||||
{
|
||||
f"{role_config['provider']}:{role_config['base_url']}:{role_config['model']}"
|
||||
for role_config in role_configs.values()
|
||||
}
|
||||
)
|
||||
llama_status: dict[str, Any] = {"probed": False, "ok": None, "roles": {}}
|
||||
vector_status: dict[str, Any] = {
|
||||
**vector_memory.config_status(),
|
||||
"probed": False,
|
||||
"ok": None,
|
||||
}
|
||||
if probe:
|
||||
model_ping = await model_client.ping()
|
||||
llama_status = {
|
||||
"probed": True,
|
||||
"ok": all(role.get("ok") for role in model_ping.values()) if model_ping else False,
|
||||
"roles": model_ping,
|
||||
}
|
||||
vector_status = {
|
||||
**await vector_memory.health(),
|
||||
"probed": True,
|
||||
}
|
||||
return {
|
||||
"name": "DuckLM",
|
||||
"version": "0.1.0",
|
||||
@@ -173,6 +197,33 @@ def create_app() -> FastAPI:
|
||||
"api_port": settings.api_port,
|
||||
"workspace": settings.workspace,
|
||||
"db_path": settings.db_path,
|
||||
"api": {
|
||||
"host": settings.api_host,
|
||||
"port": settings.api_port,
|
||||
"base_url": f"http://{settings.api_host}:{settings.api_port}",
|
||||
},
|
||||
"paths": {
|
||||
"workspace": settings.workspace,
|
||||
"db_path": settings.db_path,
|
||||
"models_config": str(model_client.config_path),
|
||||
},
|
||||
"token_budget": {
|
||||
"ctx_size": settings.ctx_size,
|
||||
"max_input_tokens": settings.max_input_tokens,
|
||||
"max_recent_events_tokens": settings.max_recent_events_tokens,
|
||||
"max_memory_tokens": settings.max_memory_tokens,
|
||||
"max_skill_tokens": settings.max_skill_tokens,
|
||||
},
|
||||
"models": {
|
||||
"default_provider": model_client.default_provider,
|
||||
"roles": role_configs,
|
||||
"endpoints": endpoints,
|
||||
},
|
||||
"services": {
|
||||
"duck_api": {"ok": True, "probed": True},
|
||||
"llama": llama_status,
|
||||
"vector_memory": vector_status,
|
||||
},
|
||||
}
|
||||
|
||||
@app.get("/v1/models/roles")
|
||||
|
||||
@@ -94,6 +94,43 @@ class VectorMemory:
|
||||
response.raise_for_status()
|
||||
return response.json().get("result", [])
|
||||
|
||||
def config_status(self) -> dict[str, Any]:
|
||||
if self.embeddings_base_url:
|
||||
embedding_source = f"remote:{self.embeddings_base_url}"
|
||||
elif self._local_model_path:
|
||||
embedding_source = f"local:{self._local_model_path}"
|
||||
else:
|
||||
embedding_source = "none"
|
||||
return {
|
||||
"configured": bool(self.qdrant_url and embedding_source != "none"),
|
||||
"qdrant_url": self.qdrant_url,
|
||||
"collection": self.collection_name,
|
||||
"embedding_source": embedding_source,
|
||||
}
|
||||
|
||||
async def health(self) -> dict[str, Any]:
|
||||
"""Probe Qdrant without loading the local embedding model."""
|
||||
import time
|
||||
|
||||
status = self.config_status()
|
||||
started = time.perf_counter()
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0, trust_env=False) as client:
|
||||
response = await client.get(f"{self.qdrant_url}/")
|
||||
response.raise_for_status()
|
||||
return {
|
||||
**status,
|
||||
"ok": True,
|
||||
"latency_ms": round((time.perf_counter() - started) * 1000, 1),
|
||||
}
|
||||
except httpx.HTTPError as exc:
|
||||
return {
|
||||
**status,
|
||||
"ok": False,
|
||||
"error": str(exc),
|
||||
"latency_ms": round((time.perf_counter() - started) * 1000, 1),
|
||||
}
|
||||
|
||||
async def _embed(self, text: str) -> list[float]:
|
||||
"""Generate embeddings using local model or remote endpoint."""
|
||||
# Prefer local model if available
|
||||
|
||||
Reference in New Issue
Block a user