symfonic.core.observability.metrics_store¶
metrics_store ¶
Pluggable persistence for per-LLM-call metrics (v8.8.0).
The in-memory :class:ConversationMetricsCollector resets on every worker
restart. This module adds a storage-agnostic durable sink so the admin
Usage dashboard and Conversation Detail view survive restarts, without
coupling the framework to any one database.
Design mirrors two existing precedents in this repo:
- Budget (
budget.py/postgres_budget_store.py): a pluggable store interface injected via a process-wide setter, with a Postgres reference impl; core issues statements against a table it does not own. - Embeddings (
memory/embeddings/factory.py): a string-identifier factory that resolvespostgres/mongo/ a dotted path to a concrete backend shipped behind an extra, raising aValueErrorwith the exactpip installhint on a missing dependency.
Public surface:
- :class:
MetricsSink/ :class:MetricsReader/ :class:MetricsStore— the storage-agnostic Protocols a backend implements. - :class:
BufferedMetricsSink— backend-agnostic hot-path buffer + background batch flusher; wraps any store soon_llm_endnever awaits DB I/O. This is where batching lives, once, for all backends. - :func:
make_metrics_store— config-string factory. - :func:
set_metrics_store/ :func:get_metrics_store— process-wide hook the collector fan-out and admin reads share.
BufferedMetricsSink ¶
BufferedMetricsSink(
store: MetricsStore,
*,
batch_size: int = 25,
flush_interval_s: float = 2.0,
max_queue: int = 10000,
)
Hot-path buffer around any :class:MetricsStore.
record() is a synchronous O(1) enqueue so the collector fan-out in
on_llm_end never awaits DB I/O. A background task flushes the queue
every flush_interval_s or once batch_size rows are pending,
calling the wrapped store's record_calls once per batch. All read /
lifecycle methods delegate to the wrapped store, so get_metrics_store()
returns a single object serving both write and read.
Source code in src/symfonic/core/observability/metrics_store.py
ensure_indexes ¶
Delegate to the wrapped store's index bootstrap, if it has one.
flush
async
¶
Drain the whole queue now (deterministic for tests / shutdown).
Source code in src/symfonic/core/observability/metrics_store.py
record ¶
Synchronous, non-blocking enqueue. Drops oldest on overflow.
Source code in src/symfonic/core/observability/metrics_store.py
MetricsReader ¶
Bases: Protocol
Rich, tenant-scoped read side backing the admin dashboards.
conversation_detail
async
¶
Return snapshot-shaped rows for one conversation, oldest-first.
The shape MUST match ConversationMetricsCollector.snapshots() so
the scaffold's build_conversation_detail() is reused unchanged.
Source code in src/symfonic/core/observability/metrics_store.py
list_conversations
async
¶
Return (rows, total) — per-conversation token rollup.
summary
async
¶
Return {conversations, total_llm_calls, total_cost_usd, total_tokens}.
token_usage
async
¶
Return DailyUsageRow / ModelUsageRow shaped dicts (group_by in {day, model}).
Source code in src/symfonic/core/observability/metrics_store.py
MetricsSink ¶
MetricsStore ¶
Bases: MetricsSink, MetricsReader, Protocol
A full metrics backend: durable writes + rich tenant-scoped reads.
get_metrics_store ¶
Return the registered metrics store, or None (in-memory only).
make_metrics_store ¶
make_metrics_store(
identifier: str | None,
*,
postgres_session_factory: Any = None,
mongo_database_factory: Any = None,
mongo_retention_days: int = 90,
) -> MetricsStore | None
Resolve a METRICS_STORE identifier to a concrete backend.
none / "" → None (in-memory collector only, today's default).
postgres → :class:PostgresMetricsStore (needs postgres_session_factory
+ symfonic-core[postgres]). mongo → :class:MongoMetricsStore
(needs mongo_database_factory + symfonic-core[mongodb]). A dotted
path (my_pkg.mod:MyStore or my_pkg.mod.MyStore) → the adopter's
zero-arg-constructible MetricsStore.
Raises ValueError with a pip install hint on a missing extra and
on an unknown identifier, so misconfiguration fails loudly at startup.
Source code in src/symfonic/core/observability/metrics_store.py
set_metrics_store ¶
Register the process-wide metrics store (or None to disable).