study-buddy / tests /observability /test_contracts.py
GitHub Actions
deploy d092bea3608b7a29952f16357fda39b7a29e399b
2e818da
Raw
History Blame Contribute Delete
6.52 kB
from __future__ import annotations
import typing
import pytest
from pydantic import ValidationError
from app.observability.contracts import (
METRIC_ATTRIBUTE_KEYS,
ExperimentIdentity,
OperationObservation,
RetrievalOutcome,
TelemetryMode,
metric_attributes,
)
# --- RetrievalOutcome -------------------------------------------------
def test_retrieval_outcome_distinguishes_empty_from_error():
empty = RetrievalOutcome.success([])
failed = RetrievalOutcome.failure("collection_unavailable")
assert empty.status == "success_empty"
assert empty.empty_result is True
assert empty.retrieval_error is False
assert failed.status == "error_fallback"
assert failed.empty_result is False
assert failed.retrieval_error is True
def test_retrieval_outcome_success_with_candidates_is_not_empty():
outcome = RetrievalOutcome.success([{"chunk_id": "c1"}, {"chunk_id": "c2"}])
assert outcome.status == "success"
assert outcome.empty_result is False
assert outcome.retrieval_error is False
assert outcome.candidates == [{"chunk_id": "c1"}, {"chunk_id": "c2"}]
def test_retrieval_outcome_failure_carries_error_type_and_no_candidates():
outcome = RetrievalOutcome.failure("timeout")
assert outcome.error_type == "timeout"
assert outcome.candidates == []
# --- metric_attributes / METRIC_ATTRIBUTE_KEYS -------------------------
def test_metric_attributes_remove_high_cardinality_values():
safe = metric_attributes(
{
"pipeline.version": "rag-naive-v1",
"query.category": "methodology",
"query.id": "q-001",
"project.id": "private-project",
}
)
assert safe == {
"pipeline.version": "rag-naive-v1",
"query.category": "methodology",
}
def test_metric_attributes_drops_non_scalar_values_even_for_safe_keys():
safe = metric_attributes(
{
"pipeline.version": "rag-naive-v1",
"operation": ["retrieval", "context_assembly"],
"status": {"nested": "dict"},
}
)
assert safe == {"pipeline.version": "rag-naive-v1"}
def test_metric_attributes_handles_empty_input():
assert metric_attributes({}) == {}
def test_metric_attributes_never_mutates_input():
original = {"pipeline.version": "v1", "query.id": "q-1"}
snapshot = dict(original)
metric_attributes(original)
assert original == snapshot
# --- TelemetryMode -------------------------------------------------
def test_telemetry_mode_allows_only_the_three_canonical_values():
assert typing.get_args(TelemetryMode) == ("disabled", "local", "full")
# --- ExperimentIdentity -------------------------------------------------
def test_experiment_identity_defaults_to_all_unset():
identity = ExperimentIdentity()
assert identity.experiment_id is None
assert identity.run_id is None
assert identity.pipeline_version is None
assert identity.query_set_version is None
assert identity.corpus_version is None
assert identity.query_id is None
assert identity.query_category is None
assert identity.repetition is None
assert identity.cache_state is None
assert identity.git_commit is None
assert identity.embedding_model is None
assert identity.cerebras_model is None
assert identity.chroma_version is None
assert identity.cognee_version is None
def test_experiment_identity_accepts_all_canonical_fields():
identity = ExperimentIdentity(
experiment_id="exp-1",
run_id="run-1",
pipeline_version="rag-naive-v1",
query_set_version="qs-1",
corpus_version="corpus-1",
query_id="q-001",
query_category="methodology",
repetition=3,
cache_state="warm",
git_commit="abc123",
embedding_model="all-MiniLM-L6-v2",
cerebras_model="gemma-4-31b",
chroma_version="0.5.0",
cognee_version="1.2.2",
)
assert identity.pipeline_version == "rag-naive-v1"
assert identity.repetition == 3
def test_experiment_identity_rejects_unknown_fields():
with pytest.raises(ValidationError):
ExperimentIdentity(unknown_field="nope")
# --- OperationObservation -------------------------------------------------
def test_operation_observation_requires_operation_and_status():
with pytest.raises(ValidationError):
OperationObservation()
def test_operation_observation_minimal_construction_has_safe_defaults():
obs = OperationObservation(operation="retrieval", status="success")
assert obs.operation == "retrieval"
assert obs.status == "success"
assert obs.subsystem is None
assert obs.consumer is None
assert obs.duration_ms is None
assert obs.stage_durations_ms == {}
assert obs.counts == {}
assert obs.trace_id is None
assert obs.experiment is None
assert obs.evaluation_run is False
assert obs.attributes == {}
def test_operation_observation_carries_stage_durations_counts_and_trace_id():
obs = OperationObservation(
operation="retrieval",
subsystem="chroma",
consumer="chat",
status="success",
duration_ms=123.4,
stage_durations_ms={"vector_search_ms": 88.0, "collection_lookup_ms": 12.0},
counts={"raw_candidate_count": 20, "selected_candidate_count": 5},
trace_id="trace-abc",
)
assert obs.stage_durations_ms["vector_search_ms"] == 88.0
assert obs.counts["selected_candidate_count"] == 5
assert obs.trace_id == "trace-abc"
def test_operation_observation_can_embed_experiment_identity_and_retrieval_outcome():
identity = ExperimentIdentity(pipeline_version="rag-naive-v1", query_category="methodology")
outcome = RetrievalOutcome.success([{"chunk_id": "c1"}])
obs = OperationObservation(
operation="retrieval",
status=outcome.status,
experiment=identity,
retrieval=outcome,
evaluation_run=True,
)
assert obs.experiment.pipeline_version == "rag-naive-v1"
assert obs.retrieval.status == "success"
assert obs.evaluation_run is True
def test_operation_observation_missing_data_uses_sentinel_not_zero():
# Per the canonical contract: missing observations must never be silently
# coerced to zero. A "not instrumented" duration must stay None, not 0.
obs = OperationObservation(operation="memory", status="not_instrumented")
assert obs.status == "not_instrumented"
assert obs.duration_ms is None