5809ad3adb
Shared runtime - One agent entry point (runtime.invoke_agent_for_user / prepare_agent_turn) for web, email, iMessage, WhatsApp, scheduled and cron, so identity, permissions, memories, summaries and metrics match across channels. - Serialize turns per checkpoint thread; /query validates input and returns 409 while a durable run owns the conversation. Reconnectable chat (durable runs) - POST /runs creates an idempotent, owner-scoped run; GET .../events replays an ordered SSE log by cursor, so generation survives refresh and device switches. Stop cancels; disconnect only detaches. /stream-query is a compatibility adapter. - Coalesce streamed deltas (150 ms) and wake same-process subscribers instead of polling; keep buffered text when a run is stopped. - Drain runs on shutdown; serve via Hypercorn with a shutdown timeout covering the drain (and no longer in debug mode). CHAT_RUN_* settings are forwarded. Scheduled messages and cron - Atomic delivery claims, attempt history and explicit retries; ambiguous sends become "uncertain" and are never resent automatically. - Cron triggers need a run_id/Idempotency-Key; legacy jobs need an explicit owner. - Cancelling never rewrites a started delivery; series stop via tombstones. Legacy sent rows are not forked into new series. Malformed bodies return 400. History, integrations and retrieval - Message-content search, pinned conversations, JSON/Markdown export and a memory editor. - Integration status panel and YNAB data-freshness provenance. - Date-filtered document/note search with stable citations and a source viewer. Migration 20 is generated by aerich. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
1.8 KiB
Python
70 lines
1.8 KiB
Python
import time
|
|
from contextlib import contextmanager
|
|
from prometheus_client import (
|
|
CollectorRegistry,
|
|
Counter,
|
|
Histogram,
|
|
generate_latest,
|
|
)
|
|
|
|
REGISTRY = CollectorRegistry(auto_describe=True)
|
|
|
|
AGENT_REQUESTS_TOTAL = Counter(
|
|
"raggr_agent_requests_total",
|
|
"Agent generation requests by channel and outcome.",
|
|
labelnames=("channel", "result"),
|
|
registry=REGISTRY,
|
|
)
|
|
AGENT_RESPONSE_SECONDS = Histogram(
|
|
"raggr_agent_response_seconds",
|
|
"Agent generation time by channel, excluding delivery.",
|
|
labelnames=("channel",),
|
|
buckets=(0.5, 1, 2, 5, 10, 30, 60, 120, 300),
|
|
registry=REGISTRY,
|
|
)
|
|
|
|
CHAT_RESPONSE_SECONDS = Histogram(
|
|
"raggr_chat_response_seconds",
|
|
"End-to-end chat response time in seconds.",
|
|
labelnames=("stream",),
|
|
buckets=(0.5, 1, 2, 5, 10, 30, 60, 120, 300),
|
|
registry=REGISTRY,
|
|
)
|
|
|
|
CHAT_REQUESTS_TOTAL = Counter(
|
|
"raggr_chat_requests_total",
|
|
"Total chat requests by stream mode and result.",
|
|
labelnames=("stream", "result"),
|
|
registry=REGISTRY,
|
|
)
|
|
|
|
|
|
def _s(stream: bool) -> str:
|
|
return "true" if stream else "false"
|
|
|
|
|
|
@contextmanager
|
|
def time_response(stream: bool):
|
|
t0 = time.monotonic()
|
|
result = "success"
|
|
try:
|
|
yield
|
|
except Exception:
|
|
result = "error"
|
|
raise
|
|
finally:
|
|
CHAT_RESPONSE_SECONDS.labels(stream=_s(stream)).observe(time.monotonic() - t0)
|
|
CHAT_REQUESTS_TOTAL.labels(stream=_s(stream), result=result).inc()
|
|
|
|
|
|
def observe_response(stream: bool, seconds: float) -> None:
|
|
CHAT_RESPONSE_SECONDS.labels(stream=_s(stream)).observe(seconds)
|
|
|
|
|
|
def count_result(stream: bool, result: str) -> None:
|
|
CHAT_REQUESTS_TOTAL.labels(stream=_s(stream), result=result).inc()
|
|
|
|
|
|
def render_metrics() -> bytes:
|
|
return generate_latest(REGISTRY)
|