feat(sofiia-console): expose /metrics and add basic ops counters

Expose Prometheus-style metrics endpoint and add counters for send requests, idempotency replays, and cursor pagination calls, including a safe in-process fallback exposition when prometheus_client is unavailable.

Made-with: Cursor
This commit is contained in:
Apple
2026-03-02 04:52:04 -08:00
parent d9ce366538
commit 93f94030f4
4 changed files with 148 additions and 0 deletions

View File

@@ -57,6 +57,12 @@ from .monitor import collect_all_nodes
from .ops import run_ops_action, OPS_ACTIONS
from .docs_router import docs_router
from . import db as _app_db
from .metrics import (
SOFIIA_SEND_REQUESTS_TOTAL,
SOFIIA_IDEMPOTENCY_REPLAYS_TOTAL,
SOFIIA_CURSOR_REQUESTS_TOTAL,
render_metrics,
)
logger = logging.getLogger(__name__)
@@ -3134,6 +3140,7 @@ async def api_chats_list(
cursor: Optional[str] = Query(None),
_auth: str = Depends(require_auth),
):
SOFIIA_CURSOR_REQUESTS_TOTAL.labels(resource="chats").inc()
await _ensure_chat_project()
node_filter = {n.strip().upper() for n in nodes.split(",") if n.strip()}
cur = _cursor_decode(cursor)
@@ -3236,6 +3243,7 @@ async def api_chat_messages(
cursor: Optional[str] = Query(None),
_auth: str = Depends(require_auth),
):
SOFIIA_CURSOR_REQUESTS_TOTAL.labels(resource="messages").inc()
cur = _cursor_decode(cursor)
before_ts = str(cur.get("ts") or "").strip() or None
before_message_id = str(cur.get("message_id") or "").strip() or None
@@ -3295,6 +3303,7 @@ async def api_chat_send_v2(chat_id: str, body: ChatMessageSendBody, request: Req
if idem_key:
cached = _idem_get(chat_id, idem_key)
if cached:
SOFIIA_IDEMPOTENCY_REPLAYS_TOTAL.inc()
replay = dict(cached)
replay["idempotency"] = {"replayed": True, "key": idem_key}
return replay
@@ -3303,6 +3312,7 @@ async def api_chat_send_v2(chat_id: str, body: ChatMessageSendBody, request: Req
info = _parse_chat_id(chat_id)
target_node = ((body.routing or {}).get("force_node_id") or info["node_id"] or "NODA2").upper()
target_agent = info["agent_id"] or "sofiia"
SOFIIA_SEND_REQUESTS_TOTAL.labels(node_id=target_node).inc()
project_id = body.project_id or CHAT_PROJECT_ID
session_id = body.session_id or chat_id
user_id = body.user_id or "console_user"
@@ -3377,6 +3387,12 @@ async def api_chat_send_v2(chat_id: str, body: ChatMessageSendBody, request: Req
return result
@app.get("/metrics")
def metrics():
data, content_type = render_metrics()
return Response(content=data, media_type=content_type)
@app.post("/api/chat/send")
async def api_chat_send(body: ChatSendBody, request: Request):
"""BFF chat: Ollama or router. Returns runtime contract fields. Rate: 30/min."""