feat(observability): astrolabe_* metrics + traces for the document pipeline
Make per-tier bottlenecks in the document-processing pipeline
(scan -> fetch -> parse -> chunk -> embed -> Qdrant upsert) visible via
metrics, traces, and structured logs. Today the document_processors layer
emits only a logger.info line: no metric, no span, and page counts live only
inside a log string. The single processing-duration histogram is unlabeled and
whole-document, so it cannot isolate parse vs embed vs upsert.
New astrolabe_* metric family (distinct from the mcp_* protocol metrics):
- astrolabe_document_parse_{duration_seconds,total} + pages/chars/bytes counters
recorded at the ProcessorRegistry.process() boundary (covers all current and
future processors uniformly)
- astrolabe_document_escalation_total (dormant; tiered-pipeline readiness)
- astrolabe_embedding_{duration_seconds,requests_total,chunks_total,chars_total}
- astrolabe_document_chunks_total, astrolabe_documents_indexed_total{source,status}
Tracing: new document_processor.parse child span + enriched embed/chunk span
attributes (provider/model/batch_size/chunk_count). Structured logs gain a
consistent field vocabulary (doc_id, doc_type, processor, tier, pages, chars,
byte_size, chunks, duration_ms, status) so Loki can aggregate without regex.
Tier-readiness: processor/tier are labels from day one and a tier property is
added to DocumentProcessor, so adding docling/OCR/LLM tiers later is additive
(new label values, never new metrics). Tenant comes from the kube namespace
label; mime_type/model are span attributes only (cardinality). Existing
mcp_vector_sync_*/mcp_qdrant_* are left untouched.
Refs Deck #175 (superset of #173 Phase 2). Dashboard/recording-rules follow-up
tracked on #175 for homelab-argocd.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7e4b83dc94
commit
5d205fcaab
@@ -949,6 +949,50 @@ class Settings:
|
||||
|
||||
return f"simple-{self.simple_embedding_dimension}"
|
||||
|
||||
def get_embedding_provider_family(self) -> str:
|
||||
"""
|
||||
Get the active dense-embedding provider family (a low-cardinality label).
|
||||
|
||||
This is the single source of truth for the ``provider`` metric label and
|
||||
the ``embedding.provider`` span attribute. It returns the provider
|
||||
*family* (e.g. "bedrock"), never the model name, to keep metric
|
||||
cardinality bounded.
|
||||
|
||||
Priority mirrors ``get_embedding_model_name`` / ProviderRegistry:
|
||||
1. Gateway - if EMBEDDING_PROVIDER=gateway (family from the model prefix,
|
||||
e.g. "mistral/mistral-embed" -> "mistral")
|
||||
2. Bedrock - if AWS_REGION or BEDROCK_EMBEDDING_MODEL is set
|
||||
3. OpenAI - if OPENAI_API_KEY is set
|
||||
4. Mistral - if MISTRAL_API_KEY is set
|
||||
5. Ollama - if OLLAMA_BASE_URL is set
|
||||
6. Simple - fallback
|
||||
|
||||
Returns:
|
||||
Provider family: gateway-routed family | bedrock | openai | mistral
|
||||
| ollama | simple
|
||||
"""
|
||||
if self.embedding_provider == "gateway":
|
||||
model = self.embedding_gateway_model or ""
|
||||
return model.split("/", 1)[0] if "/" in model else "gateway"
|
||||
|
||||
if (
|
||||
self.aws_region
|
||||
or self.bedrock_embedding_model
|
||||
or self.bedrock_generation_model
|
||||
):
|
||||
return "bedrock"
|
||||
|
||||
if self.openai_api_key:
|
||||
return "openai"
|
||||
|
||||
if self.mistral_api_key:
|
||||
return "mistral"
|
||||
|
||||
if self.ollama_base_url:
|
||||
return "ollama"
|
||||
|
||||
return "simple"
|
||||
|
||||
def get_collection_name(self) -> str:
|
||||
"""
|
||||
Get Qdrant collection name.
|
||||
|
||||
Reference in New Issue
Block a user