본문으로 건너뛰기

내 프로젝트에서 Contexta 검증하기

이 문서는 Contexta 라이브러리 자체를 개발하는 사람을 위한 테스트 매트릭스가 아닙니다.

Contexta를 자신의 ML, 딥러닝, LLM 프로젝트에 붙인 사용자가 다음 질문에 답할 수 있도록 돕는 문서입니다.

  • 내 코드가 실행 증거를 실제로 남기고 있나요?
  • 기록된 증거를 다른 프로세스나 다음 실행에서 다시 읽을 수 있나요?
  • 리뷰, 배포, 감사에 필요한 메트릭과 이벤트가 빠지지 않았나요?
  • 문서나 노트북에 적은 예시가 실제 실행 결과와 같은가요?

Contexta를 테스트한다는 것은 모델 정확도를 테스트한다는 뜻이 아닙니다. 모델 품질은 여러분의 평가 코드가 검증합니다. Contexta 쪽 테스트는 그 평가 결과와 실행 맥락이 나중에 다시 확인 가능한 증거로 남는지를 확인합니다.

언제 검증해야 하나요?

다음 상황에서는 작은 검증을 추가하는 것이 좋습니다.

상황확인할 것
처음으로 Contexta를 붙일 때실행, 단계, 메트릭, 이벤트가 원하는 workspace에 남는지
학습 또는 평가 스크립트를 바꿀 때기존에 보던 핵심 메트릭 이름이 계속 기록되는지
실험 비교를 자동화할 때비교 대상 실행을 다시 조회할 수 있고 delta가 계산되는지
배포 게이트를 만들 때기준 실행과 후보 실행의 메트릭, 아티팩트, 이벤트가 모두 있는지
감사 또는 고객 보고서를 만들 때리포트가 실제 저장된 실행 증거에서 생성되는지
문서, 노트북, 튜토리얼을 공유할 때화면에 적은 출력이 실제 실행 결과와 맞는지

검증의 목표는 “Contexta 내부 테스트를 대신 돌리는 것”이 아니라, 여러분의 프로젝트에서 중요한 evidence가 빠지지 않는지 확인하는 것입니다.

가장 작은 Smoke Test

새 프로젝트에 Contexta를 붙였다면 먼저 캡처가 실제 파일로 남는지 확인하세요.

아래 예제는 임시 workspace를 만들고, 하나의 실행과 학습 단계를 기록한 뒤, 로컬 캡처 파일에 레코드가 쓰였는지 확인합니다.

runtime_capture_preview.py
"""Runtime capture preview example for Contexta."""

from __future__ import annotations

import argparse
import tempfile
from pathlib import Path
from typing import Any

from contexta import Contexta
from contexta.config import UnifiedConfig, WorkspaceConfig


PROJECT_NAME = "capture-proj"
RUN_NAME = "demo-run"


def _resolve_workspace(workspace: Path | str | None) -> Path:
if workspace is None:
root = Path(tempfile.mkdtemp(prefix="contexta-capture-preview-"))
return root / ".contexta"
return Path(workspace)


def run_example(workspace: Path | str | None = None) -> dict[str, Any]:
"""Exercise the runtime scope API and record local capture output."""

workspace_path = _resolve_workspace(workspace)
ctx = Contexta(
config=UnifiedConfig(
project_name=PROJECT_NAME,
workspace=WorkspaceConfig(root_path=workspace_path),
)
)

with ctx.run(RUN_NAME) as run:
run.event("dataset.loaded", message="dataset prepared")
with run.stage("train") as stage:
stage.metric("accuracy", 0.93, unit="ratio")
stage.metric("loss", 0.12)

record_capture_path = ctx.config.workspace.cache_path / "capture" / "record.jsonl"
captured_record_count = 0
if record_capture_path.exists():
captured_record_count = sum(1 for line in record_capture_path.read_text(encoding="utf-8").splitlines() if line)

return {
"workspace": str(workspace_path),
"run_ref": run.ref,
"record_capture_path": str(record_capture_path),
"record_capture_exists": record_capture_path.exists(),
"captured_record_count": captured_record_count,
}


def main() -> None:
parser = argparse.ArgumentParser(description="Run the Contexta runtime capture preview.")
parser.add_argument(
"--workspace",
type=Path,
default=None,
help="Optional workspace root. Defaults to a temporary .contexta workspace.",
)
args = parser.parse_args()

result = run_example(args.workspace)
print(f"Workspace: {result['workspace']}")
print(f"Run ref: {result['run_ref']}")
print(f"Capture file: {result['record_capture_path']}")
print(f"Capture file exists: {result['record_capture_exists']}")
print(f"Captured records: {result['captured_record_count']}")


if __name__ == "__main__":
main()

직접 실행하면 다음과 같은 내용을 확인할 수 있습니다.

uv run python examples/quickstart/runtime_capture_preview.py

확인할 핵심은 세 가지입니다.

  • Run ref가 출력됩니다.
  • Capture file existsTrue입니다.
  • Captured records가 0보다 큽니다.

이 테스트는 “내 코드가 Contexta capture API를 호출했고, 기록 파일이 만들어졌다”는 가장 낮은 수준의 확인입니다.

조회와 리포트까지 확인하기

캡처 파일이 생기는 것만으로는 충분하지 않을 때가 많습니다. 리뷰나 운영에서는 보통 다음 질문이 이어집니다.

  • 이 실행을 다시 조회할 수 있나요?
  • 실행에 기대한 단계가 들어 있나요?
  • 리포트가 생성되나요?

그럴 때는 metadata, record, report 흐름을 함께 확인하세요.

verified_quickstart.py
"""Verified quickstart example for Contexta."""

from __future__ import annotations

import argparse
import tempfile
from pathlib import Path
from typing import Any

from contexta import Contexta
from contexta.config import UnifiedConfig, WorkspaceConfig
from contexta.contract import (
MetricPayload,
MetricRecord,
Project,
RecordEnvelope,
Run,
StageExecution,
)


PROJECT_NAME = "quickstart-proj"
RUN_NAME = "demo-run"
RUN_REF = f"run:{PROJECT_NAME}.{RUN_NAME}"


def _resolve_workspace(workspace: Path | str | None) -> Path:
if workspace is None:
root = Path(tempfile.mkdtemp(prefix="contexta-quickstart-"))
return root / ".contexta"
return Path(workspace)


def run_example(workspace: Path | str | None = None) -> dict[str, Any]:
"""Create a minimal workspace, query one run, and build a report."""

workspace_path = _resolve_workspace(workspace)
ctx = Contexta(
config=UnifiedConfig(
project_name=PROJECT_NAME,
workspace=WorkspaceConfig(root_path=workspace_path),
)
)

project = Project(
project_ref=f"project:{PROJECT_NAME}",
name=PROJECT_NAME,
created_at="2024-06-01T12:00:00Z",
)
run = Run(
run_ref=RUN_REF,
project_ref=f"project:{PROJECT_NAME}",
name=RUN_NAME,
status="completed",
started_at="2024-06-01T12:00:00Z",
ended_at="2024-06-01T12:05:00Z",
)
stage = StageExecution(
stage_execution_ref=f"stage:{PROJECT_NAME}.{RUN_NAME}.train",
run_ref=RUN_REF,
stage_name="train",
status="completed",
started_at="2024-06-01T12:01:00Z",
ended_at="2024-06-01T12:04:00Z",
order_index=0,
)
metric = MetricRecord(
envelope=RecordEnvelope(
record_ref=f"record:{PROJECT_NAME}.{RUN_NAME}.m0001",
record_type="metric",
recorded_at="2024-06-01T12:03:00Z",
observed_at="2024-06-01T12:03:00Z",
producer_ref="contexta.quickstart",
run_ref=RUN_REF,
),
payload=MetricPayload(
metric_key="accuracy",
value=0.93,
value_type="float64",
),
)

store = ctx.metadata_store
try:
store.projects.put_project(project)
store.runs.put_run(run)
store.stages.put_stage_execution(stage)
ctx.record_store.append(metric)

runs = ctx.list_runs(PROJECT_NAME)
snapshot = ctx.get_run_snapshot(RUN_REF)
doc = ctx.build_snapshot_report(RUN_REF)

report_path = ctx.config.workspace.reports_path / "quickstart-report.md"
report_path.parent.mkdir(parents=True, exist_ok=True)
report_path.write_text(doc.to_markdown(), encoding="utf-8")

return {
"workspace": str(workspace_path),
"run_ref": RUN_REF,
"runs_visible": len(runs),
"snapshot_stage_count": len(snapshot.stages),
"report_title": doc.title,
"report_path": str(report_path),
}
finally:
store.close()


def main() -> None:
parser = argparse.ArgumentParser(description="Run the verified Contexta quickstart example.")
parser.add_argument(
"--workspace",
type=Path,
default=None,
help="Optional workspace root. Defaults to a temporary .contexta workspace.",
)
args = parser.parse_args()

result = run_example(args.workspace)
print(f"Workspace: {result['workspace']}")
print(f"Run ref: {result['run_ref']}")
print(f"Runs visible: {result['runs_visible']}")
print(f"Report title: {result['report_title']}")
print(f"Report path: {result['report_path']}")


if __name__ == "__main__":
main()

직접 실행합니다.

uv run python examples/quickstart/verified_quickstart.py

성공하면 실행 참조, 조회된 실행 수, 리포트 제목, 리포트 파일 경로가 출력됩니다. 여기서 중요한 것은 출력 문구 자체가 아니라 다음 의미입니다.

  • workspace가 만들어졌습니다.
  • 실행과 단계가 저장되었습니다.
  • 메트릭 레코드가 저장되었습니다.
  • list_runs()로 실행을 다시 찾을 수 있습니다.
  • get_run_snapshot()으로 실행 스냅샷을 만들 수 있습니다.
  • build_snapshot_report()로 리포트를 만들 수 있습니다.

내 테스트에 넣을 단언

프로젝트 테스트에 Contexta 검증을 넣을 때는 긴 문자열 전체를 비교하기보다, 나중에 의사결정에 필요한 의미를 확인하세요.

좋은 단언:

  • 실행 목록에 방금 만든 실행이 있음
  • 실행 스냅샷에 기대한 단계가 있음
  • accuracy, loss, auc처럼 팀이 보는 핵심 메트릭이 있음
  • 실패나 경고를 남기는 이벤트가 기록됨
  • 리포트 제목 또는 섹션이 기대한 실행을 가리킴
  • 배포 게이트가 필요한 기준 메트릭을 모두 찾음

피하는 것이 좋은 단언:

  • 출력 문자열 전체가 한 글자도 다르지 않은지 비교
  • 현재 시각이나 임시 경로처럼 매번 달라지는 값 비교
  • 테스트가 만들지 않은 기존 .contexta 상태에 의존
  • 내부 구현 모듈이나 private helper 호출 결과에 의존

사용자 프로젝트용 pytest 예시

이미 pytest를 쓰고 있다면, 임시 workspace를 사용해 Contexta 기록을 격리하세요.

from pathlib import Path

from contexta import Contexta
from contexta.config import UnifiedConfig, WorkspaceConfig
from contexta.contract import (
MetricPayload,
MetricRecord,
Project,
RecordEnvelope,
Run,
StageExecution,
)


def test_training_run_can_be_queried(tmp_path):
project_name = "my-ml-project"
run_name = "smoke-run"
run_ref = f"run:{project_name}.{run_name}"

ctx = Contexta(
config=UnifiedConfig(
project_name=project_name,
workspace=WorkspaceConfig(root_path=tmp_path / ".contexta"),
)
)

ctx.metadata_store.projects.put_project(
Project(
project_ref=f"project:{project_name}",
name=project_name,
created_at="2026-01-01T00:00:00Z",
)
)
ctx.metadata_store.runs.put_run(
Run(
run_ref=run_ref,
project_ref=f"project:{project_name}",
name=run_name,
status="completed",
started_at="2026-01-01T00:00:00Z",
ended_at="2026-01-01T00:05:00Z",
)
)
ctx.metadata_store.stages.put_stage_execution(
StageExecution(
stage_execution_ref=f"stage:{project_name}.{run_name}.evaluate",
run_ref=run_ref,
stage_name="evaluate",
status="completed",
started_at="2026-01-01T00:04:00Z",
ended_at="2026-01-01T00:05:00Z",
order_index=0,
)
)
ctx.record_store.append(
MetricRecord(
envelope=RecordEnvelope(
record_ref=f"record:{project_name}.{run_name}.accuracy",
record_type="metric",
recorded_at="2026-01-01T00:05:00Z",
observed_at="2026-01-01T00:05:00Z",
producer_ref="my-training-test",
run_ref=run_ref,
),
payload=MetricPayload(
metric_key="accuracy",
value=0.93,
value_type="float64",
),
)
)

snapshot = ctx.get_run_snapshot(run_ref)
metric_keys = {
record.key
for record in snapshot.records
if record.record_type == "metric"
}

assert snapshot.run.name == run_name
assert len(snapshot.stages) == 1
assert "accuracy" in metric_keys

이 테스트는 모델 학습 자체를 검증하지 않습니다. 대신 “학습 결과를 Contexta에서 다시 조회할 수 있는 형태로 남겼는가”를 검증합니다.

CI에서는 무엇을 확인하나요?

CI에 넣을 검증은 프로젝트 규모에 맞게 작게 시작하세요.

첫 단계로는 다음 정도면 충분합니다.

  • 대표 학습 또는 평가 스크립트가 Contexta workspace를 생성하는지
  • 실행이 하나 이상 조회되는지
  • 팀이 보는 핵심 메트릭이 기록되는지
  • 리포트 생성이 실패하지 않는지

예를 들어 사용자 프로젝트에서는 아래처럼 별도 테스트를 둘 수 있습니다.

pytest tests/test_contexta_smoke.py -q

Contexta 저장소 내부의 tests/contract, tests/store, tests/e2e 같은 디렉터리를 그대로 따라 할 필요는 없습니다. 그것들은 Contexta 라이브러리 자체를 배포하기 위한 테스트 구조입니다. 사용자 프로젝트에서는 여러분의 학습, 평가, 배포 흐름에서 중요한 evidence가 남는지만 확인하면 됩니다.

문서와 노트북을 검증할 때

팀 문서나 노트북에 Contexta 출력 예시를 넣는다면, 예쁜 예시 값을 손으로 적기보다 실제 실행 결과를 기준으로 작성하세요.

권장 흐름은 다음과 같습니다.

  1. 문서에 보여 줄 예제 스크립트를 실행합니다.
  2. 출력된 run ref, 메트릭, 리포트 제목을 문서에 옮깁니다.
  3. 문서에 적은 값이 스크립트의 현재 출력과 맞는지 다시 확인합니다.
  4. 예제 workspace는 실제 프로젝트 workspace와 분리합니다.

케이스 스터디나 튜토리얼을 작성한다면 seed와 analyze를 분리하는 방식이 유용합니다.

  • seed 스크립트는 예제용 Contexta workspace에 실행 증거를 만듭니다.
  • analyze 스크립트는 이미 저장된 증거를 읽고 질문에 답합니다.
  • 문서의 결과 예시는 analyze 스크립트 출력과 맞아야 합니다.

이 구조를 쓰면 설명이 코드보다 앞서가거나, 출력 예시가 실제 동작과 어긋나는 일을 줄일 수 있습니다.

실패했을 때 보는 순서

Contexta 검증이 실패하면 아래 순서로 확인하세요.

  1. workspace 경로가 기대한 곳인지 확인합니다.
  2. 실행 참조가 run:프로젝트.실행 형태로 맞는지 확인합니다.
  3. 캡처 파일 또는 레코드가 실제로 생성되었는지 확인합니다.
  4. 조회하려는 실행의 metadata가 저장되어 있는지 확인합니다.
  5. 메트릭 이름이 학습 코드와 분석 코드에서 같은지 확인합니다.
  6. 예제나 문서 출력은 스크립트를 다시 실행해 현재 값과 비교합니다.

특히 get_run_snapshot()이 실행을 찾지 못한다면, 메트릭 파일이 있다는 사실만 보지 말고 실행 metadata가 저장되어 있는지 확인해야 합니다. 캡처와 조회는 연결되어 있지만, 같은 검증은 아닙니다.

다음 읽을거리