Make Ollama chat the primary workflow
This commit is contained in:
+122
-37
@@ -440,6 +440,27 @@ def _metric_values(state: dict[str, Any], status: str | None = None, error: str
|
||||
}
|
||||
|
||||
|
||||
def _conversation_history(conversation_id: str, exclude_request_id: str = "") -> list[dict[str, str]]:
|
||||
"""Read canonical prior turns from storage, excluding the current request."""
|
||||
db = _chat_db()
|
||||
try:
|
||||
rows = db.execute(
|
||||
"SELECT role,content,request_id FROM messages WHERE conversation_id=? ORDER BY id",
|
||||
(conversation_id,),
|
||||
).fetchall()
|
||||
history: list[dict[str, str]] = []
|
||||
for row in rows:
|
||||
if exclude_request_id and str(row["request_id"]) == exclude_request_id:
|
||||
continue
|
||||
role = str(row["role"] or "")
|
||||
content = str(row["content"] or "").strip()
|
||||
if role in {"user", "assistant"} and content:
|
||||
history.append({"role": role, "content": content})
|
||||
return history
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _persist_metric(conversation_id: str, request_id: str, model: str, state: dict[str, Any], status: str | None = None, error: str = "") -> dict[str, Any]:
|
||||
values = _metric_values(state, status=status, error=error)
|
||||
db = _chat_db()
|
||||
@@ -774,15 +795,6 @@ def _local_ps() -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
|
||||
def _local_ps() -> list[dict[str, Any]]:
|
||||
try:
|
||||
payload = _json_request(LOCAL_OLLAMA + "/api/ps", timeout=10)
|
||||
models = payload.get("models", [])
|
||||
return [item for item in models if isinstance(item, dict)]
|
||||
except (HTTPError, URLError, OSError, ValueError):
|
||||
return []
|
||||
|
||||
|
||||
def _model_load_update(name: str, **values: Any) -> None:
|
||||
with _model_loads_lock:
|
||||
current = _model_loads.setdefault(name, {"name": name, "state": "queued", "stage": "Queued for Ollama", "started_at": time.time()})
|
||||
@@ -2064,8 +2076,9 @@ def _run_chat_job(request_id: str) -> None:
|
||||
_update_chat_job(request_id, status="canceled", finished_at=time.time())
|
||||
_persist_chat_event(request_id, job.get("conversation_id"), "job-canceled-before-start", level="warning")
|
||||
return
|
||||
body = _chat_request_from_job(job)
|
||||
conversation_id = str(job["conversation_id"])
|
||||
body = _chat_request_from_job(job)
|
||||
body.history = _conversation_history(conversation_id, exclude_request_id=request_id)
|
||||
primary = str(job["primary_model"])
|
||||
validators = json.loads(job.get("validator_models_json") or "[]")
|
||||
mode = str(job.get("mode") or "direct")
|
||||
@@ -2488,7 +2501,8 @@ def chat(body: ChatRequest) -> dict[str, Any]:
|
||||
attachment_meta = [{"name": item.name, "mime_type": item.mime_type, "url": item.url} for item in body.attachments[:12]]
|
||||
_persist_message(conversation_id, request_id, "user", body.message.strip() or "[Attachments]", primary, attachment_meta)
|
||||
_chat_state(request_id, state="queued", stage="Queued for server-side execution", models=selected, primary_model=primary, validator_models=validators, harness=harness, conversation_id=conversation_id)
|
||||
_create_chat_job(request_id, conversation_id, body, primary, validators, harness)
|
||||
job_body = body.model_copy(update={"history": []})
|
||||
_create_chat_job(request_id, conversation_id, job_body, primary, validators, harness)
|
||||
_submit_chat_job(request_id)
|
||||
job = _get_chat_job(request_id)
|
||||
return _job_status_response(job or {"request_id": request_id, "conversation_id": conversation_id, "status": "queued", "mode": "harness" if harness else "direct", "primary_model": primary, "validator_models_json": json.dumps(validators), "result_json": "{}", "error": "", "updated_at": time.time()})
|
||||
@@ -2549,7 +2563,7 @@ def connections_delete(role: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
def status() -> dict[str, Any]:
|
||||
def status(include_catalog: bool = False) -> dict[str, Any]:
|
||||
tags = _local_tags()
|
||||
ps_rows = _local_ps()
|
||||
loaded = {str(row.get("name") or row.get("model")): row for row in ps_rows}
|
||||
@@ -2576,28 +2590,29 @@ def status() -> dict[str, Any]:
|
||||
family_rows = catalog.get("families") if isinstance(catalog.get("families"), dict) else {}
|
||||
catalog_rows = list(catalog.get("models", []))
|
||||
catalog_names = {str(row.get("name") or row.get("model") or "") for row in catalog_rows}
|
||||
candidate_rows = catalog_rows + [
|
||||
variant
|
||||
for rows in family_rows.values()
|
||||
for variant in rows
|
||||
if str(variant.get("name") or variant.get("model") or "") not in catalog_names
|
||||
]
|
||||
downloadable = []
|
||||
all_downloadable = []
|
||||
seen_downloads: set[str] = set()
|
||||
for rank, row in enumerate(candidate_rows, 1):
|
||||
name = str(row.get("name") or row.get("model") or "")
|
||||
if _is_mlx(row) or not name or name in installed_names or name in seen_downloads:
|
||||
continue
|
||||
view = catalog_view(row, "catalog")
|
||||
view["memory_fit"] = _known_ram_fit(view)
|
||||
view["memory_warning"] = "Estimated runtime RAM exceeds detected host RAM" if view["memory_fit"] is False else ""
|
||||
if name in catalog_names:
|
||||
view["popularity_rank"] = rank
|
||||
seen_downloads.add(name)
|
||||
all_downloadable.append(view)
|
||||
if view["memory_fit"]:
|
||||
downloadable.append(dict(view))
|
||||
downloadable: list[dict[str, Any]] = []
|
||||
all_downloadable: list[dict[str, Any]] = []
|
||||
if include_catalog:
|
||||
candidate_rows = catalog_rows + [
|
||||
variant
|
||||
for rows in family_rows.values()
|
||||
for variant in rows
|
||||
if str(variant.get("name") or variant.get("model") or "") not in catalog_names
|
||||
]
|
||||
seen_downloads: set[str] = set()
|
||||
for rank, row in enumerate(candidate_rows, 1):
|
||||
name = str(row.get("name") or row.get("model") or "")
|
||||
if _is_mlx(row) or not name or name in installed_names or name in seen_downloads:
|
||||
continue
|
||||
view = catalog_view(row, "catalog")
|
||||
view["memory_fit"] = _known_ram_fit(view)
|
||||
view["memory_warning"] = "Estimated runtime RAM exceeds detected host RAM" if view["memory_fit"] is False else ""
|
||||
if name in catalog_names:
|
||||
view["popularity_rank"] = rank
|
||||
seen_downloads.add(name)
|
||||
all_downloadable.append(view)
|
||||
if view["memory_fit"]:
|
||||
downloadable.append(dict(view))
|
||||
|
||||
popular = _popular_fit_models(
|
||||
[row for row in catalog.get("models", []) if not _is_mlx(row)],
|
||||
@@ -2638,6 +2653,7 @@ def status() -> dict[str, Any]:
|
||||
"disk": _disk_snapshot(),
|
||||
"models": local,
|
||||
"popular": popular,
|
||||
"catalog_count": len(catalog_rows),
|
||||
"popular_filter": {
|
||||
"max_expected_ram_gib": _host_ram_gib(),
|
||||
"basis": "detected MemTotal from the running host",
|
||||
@@ -2645,8 +2661,6 @@ def status() -> dict[str, Any]:
|
||||
"requires_known_ram": True,
|
||||
"smaller_fit_variants_substituted": True,
|
||||
},
|
||||
"catalog": downloadable,
|
||||
"catalog_all": all_downloadable,
|
||||
"catalog_filter": {
|
||||
"max_expected_ram_gib": _host_ram_gib(),
|
||||
"basis": "detected MemTotal from the running host",
|
||||
@@ -2655,7 +2669,7 @@ def status() -> dict[str, Any]:
|
||||
},
|
||||
"catalog_filter_options": {
|
||||
"types": ["all", "moe", "dense"],
|
||||
"capabilities": sorted({cap for row in downloadable for cap in row.get("capabilities", [])}),
|
||||
"capabilities": sorted({cap for row in (downloadable if include_catalog else catalog_rows) for cap in row.get("capabilities", [])}),
|
||||
},
|
||||
"catalog_source": catalog.get("source"),
|
||||
"catalog_updated_at": catalog.get("fetched_at"),
|
||||
@@ -2663,9 +2677,80 @@ def status() -> dict[str, Any]:
|
||||
"next_catalog_refresh": _next_refresh(),
|
||||
"jobs": _job_snapshot(),
|
||||
"generated_at": time.time(),
|
||||
**({"catalog": downloadable, "catalog_all": all_downloadable} if include_catalog else {}),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/catalog")
|
||||
def catalog(
|
||||
q: str = "",
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
model_type: str = "all",
|
||||
capability: str = "all",
|
||||
sort: str = "popularity",
|
||||
recent_only: bool = True,
|
||||
show_oversized: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
snapshot = status(include_catalog=True)
|
||||
rows = list(snapshot.get("catalog_all", [])) if show_oversized else list(snapshot.get("catalog", []))
|
||||
needle = str(q or "").strip().lower()
|
||||
if needle:
|
||||
rows = [
|
||||
row for row in rows
|
||||
if needle in " ".join([
|
||||
str(row.get("name") or ""), str(row.get("family") or ""),
|
||||
" ".join(str(value) for value in row.get("strengths", [])),
|
||||
" ".join(str(value) for value in row.get("capabilities", [])),
|
||||
]).lower()
|
||||
]
|
||||
if model_type in {"moe", "dense"}:
|
||||
rows = [row for row in rows if bool(row.get("is_moe")) == (model_type == "moe")]
|
||||
if capability != "all":
|
||||
rows = [row for row in rows if capability in row.get("capabilities", [])]
|
||||
if recent_only:
|
||||
cutoff = time.time() - 365 * 24 * 60 * 60
|
||||
recent_rows: list[dict[str, Any]] = []
|
||||
for row in rows:
|
||||
modified = row.get("modified_at")
|
||||
timestamp = _catalog_timestamp(modified)
|
||||
if not modified or timestamp is None or timestamp >= cutoff:
|
||||
recent_rows.append(row)
|
||||
rows = recent_rows
|
||||
if sort == "size_asc":
|
||||
rows.sort(key=lambda row: int(row.get("size_bytes") or 0))
|
||||
elif sort == "size_desc":
|
||||
rows.sort(key=lambda row: int(row.get("size_bytes") or 0), reverse=True)
|
||||
elif sort == "newest":
|
||||
rows.sort(key=lambda row: _catalog_timestamp(row.get("modified_at")) or 0, reverse=True)
|
||||
elif sort == "name":
|
||||
rows.sort(key=lambda row: str(row.get("name") or "").lower())
|
||||
page_size = max(1, min(int(page_size), 100))
|
||||
page = max(1, int(page))
|
||||
start = (page - 1) * page_size
|
||||
return {
|
||||
"catalog": rows[start:start + page_size],
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"total": len(rows),
|
||||
"has_more": start + page_size < len(rows),
|
||||
"catalog_count": snapshot.get("catalog_count", 0),
|
||||
"catalog_filter_options": snapshot.get("catalog_filter_options", {}),
|
||||
"catalog_source": snapshot.get("catalog_source"),
|
||||
"catalog_updated_at": snapshot.get("catalog_updated_at"),
|
||||
"catalog_error": snapshot.get("catalog_error"),
|
||||
}
|
||||
|
||||
|
||||
def _catalog_timestamp(value: Any) -> float | None:
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
return datetime.fromisoformat(str(value).replace("Z", "+00:00")).timestamp()
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
return None
|
||||
|
||||
|
||||
def _ollama_version() -> str | None:
|
||||
try:
|
||||
return str(_json_request(LOCAL_OLLAMA + "/api/version", timeout=5).get("version") or "unknown")
|
||||
|
||||
Reference in New Issue
Block a user