diff --git a/nextcloud_mcp_server/observability/metrics.py b/nextcloud_mcp_server/observability/metrics.py index 99688f20..8e18adb8 100644 --- a/nextcloud_mcp_server/observability/metrics.py +++ b/nextcloud_mcp_server/observability/metrics.py @@ -342,6 +342,12 @@ embedding_chars_total = Counter( # measure, Deck #67). On a dedicated counter (not folded into the chunk/request # metrics above) so query embeds don't inflate indexing dashboards; labelled by # operation = index | query. Always emitted, independent of USAGE_METERING_ENABLED. +# +# Dashboard note: operation="query" is recorded at embed time (before Qdrant / +# verify-on-read), whereas the billing-store tokens_embedded row is written only +# after the search fully succeeds. So this counter can legitimately exceed the +# billing aggregate when a search fails post-embed — don't alert on that gap as +# a divergence bug. embedding_tokens_total = Counter( "astrolabe_embedding_tokens_total", "Total embedding tokens consumed (provider-reported or estimated)", diff --git a/nextcloud_mcp_server/providers/ollama.py b/nextcloud_mcp_server/providers/ollama.py index b541750d..ebd3b6bf 100644 --- a/nextcloud_mcp_server/providers/ollama.py +++ b/nextcloud_mcp_server/providers/ollama.py @@ -164,6 +164,13 @@ class OllamaProvider(Provider): if self._dimension is None and data["embeddings"]: self._dimension = len(data["embeddings"][0]) + # ``prompt_eval_count`` is assumed to be the batch-level total for a + # multi-input /api/embed call. Ollama's API docs aren't explicit + # about batch aggregation; if a version reports only the last + # input's tokens this understates the batch. Unverified against a + # live instance — Ollama isn't the Cloud billing provider (Mistral + # is). If it proves last-item-only, switch to per-item requests and + # sum. The char-based estimate covers versions that omit the field. prompt_eval = data.get("prompt_eval_count") total_tokens += ( round(prompt_eval)