Skip to main content

MVK SDK for Python - Ultralight OpenTelemetry-compatible SDK for AI Observability

Project description

MVK SDK

PyPI version Python versions Coverage

Pure Python  •  Serverless Ready  •  Zero Breaking Guarantee  •  OTEL-style Smart Auto-instrumentation  •  W3C TraceContext

The MVK SDK is a production-grade telemetry SDK with serverless auto-detection, simplified MVK-only configuration, and a non-breaking guarantee. Deploy with confidence using DIRECT mode (straight to MVK) or COLLECTOR mode (via OTEL Collector). The SDK uses OTEL-style smart auto-instrumentation with wrapt for immediate and lazy loading of AI providers and vector databases with zero risk to your application.

❗ Important — Zero Breaking Guarantee. Instrumentation never raises into your code. Any SDK-internal error is caught, logged, and swallowed — your application's execution flow is never interrupted.

Table of Contents


1. Installation

pip install mvk-sdk-py

Install providers and protocols as optional extras:

💡 Tip. Quote the package name when installing extras. Shells like zsh treat unquoted square brackets as glob patterns and the install will fail.

# AI providers
pip install "mvk-sdk-py[genai]"             # All AI providers
pip install "mvk-sdk-py[openai]"            # OpenAI only
pip install "mvk-sdk-py[anthropic]"         # Anthropic only
pip install "mvk-sdk-py[gemini]"            # Google Gemini only
pip install "mvk-sdk-py[bedrock]"           # AWS Bedrock only
pip install "mvk-sdk-py[vertexai]"          # Vertex AI only
pip install "mvk-sdk-py[azure-openai]"      # Azure OpenAI (OpenAI SDK) only
pip install "mvk-sdk-py[azure-ai]"          # Azure native AI SDK only
pip install "mvk-sdk-py[anthropic-vertex]"  # Claude on Vertex AI only
pip install "mvk-sdk-py[google-genai]"      # google.genai SDK only
pip install "mvk-sdk-py[perplexity]"        # Perplexity only
pip install "mvk-sdk-py[oci]"               # OCI Generative AI only

# AI frameworks
pip install "mvk-sdk-py[frameworks]"        # All AI frameworks
pip install "mvk-sdk-py[langchain]"         # LangChain only
pip install "mvk-sdk-py[langgraph]"         # LangGraph only
pip install "mvk-sdk-py[crewai]"            # CrewAI only
pip install "mvk-sdk-py[agno]"              # Agno only
pip install "mvk-sdk-py[semantic-kernel]"   # Semantic Kernel only
pip install "mvk-sdk-py[openai-agents]"     # OpenAI Agents SDK only
pip install "mvk-sdk-py[agent-framework]"   # Microsoft Agent Framework only

# Proxies / routers
pip install "mvk-sdk-py[litellm]"           # LiteLLM only
pip install "mvk-sdk-py[openrouter]"        # OpenRouter only

# Vector databases
pip install "mvk-sdk-py[vectordb]"          # All vector DBs
pip install "mvk-sdk-py[pinecone]"          # Pinecone only
pip install "mvk-sdk-py[weaviate]"          # Weaviate only
pip install "mvk-sdk-py[chromadb]"          # ChromaDB only
pip install "mvk-sdk-py[qdrant]"            # Qdrant only

# Export protocols
pip install "mvk-sdk-py[grpc]"              # gRPC exporter
pip install "mvk-sdk-py[compression]"       # Compression support

# Everything
pip install "mvk-sdk-py[all]"

2. Configuration & Instrumentation

mvk.instrument() is the single entry point — call it once per process, before your provider calls. All settings are passed as top-level keyword arguments (no config={...} wrapper); grouped settings (exporter, batching, failed_batch_disk, logging, serverless, wrappers) are nested dicts. Every parameter also has a MVK_* environment-variable equivalent.

Minimal setup:

import mvk_sdk as mvk

mvk.instrument(
    agent_id="agent-123",    # Required (or MVK_AGENT_ID)
    api_key="mvk_...",       # Required for DIRECT mode (or MVK_API_KEY)
    tenant_id="tenant-123",  # Required for DIRECT mode (or MVK_TENANT_ID)
)

# That's it — all provider calls are now traced
import openai
response = openai.ChatCompletion.create(...)  # Automatically traced

DIRECT mode (default) sends traces straight to Mavvrik; the endpoint is auto-set if omitted. COLLECTOR mode sends to your own OTEL Collector:

# DIRECT — to the Mavvrik backend
mvk.instrument(
    agent_id="agent-123", api_key="mvk_...", tenant_id="tenant-123",
    exporter={"mode": "DIRECT", "type": "otlp_http"},  # endpoint auto-set
)

# COLLECTOR — to your OTEL Collector
mvk.instrument(
    agent_id="agent-123",
    exporter={"mode": "COLLECTOR", "type": "otlp_grpc", "endpoint": "localhost:4317"},
)

2.1 Configuration Reference — all values

Every setting below can be supplied as an instrument() parameter or the matching environment variable. Nested instrument() keys are shown as group.key (e.g. exporter.timeoutexporter={"timeout": 30}). 🔒 marks sensitive values — see §2.3.

Core / identity

instrument() Environment variable Default Notes
agent_id MVK_AGENT_ID Required. Agent identifier
api_key 🔒 MVK_API_KEY Required in DIRECT mode
tenant_id MVK_TENANT_ID Required in DIRECT mode; sent as X-Tenant-ID
enabled MVK_ENABLED true Master on/off switch

Exporter / transport (exporter={...})

instrument() Environment variable Default Notes
exporter.mode MVK_MODE DIRECT DIRECT or COLLECTOR
exporter.type MVK_EXPORTER_TYPE otlp_http otlp_http, otlp_grpc, console, file
exporter.endpoint MVK_ENDPOINT auto URL or host:port (auto-set in DIRECT)
exporter.headers MVK_HEADERS JSON string or k1=v1,k2=v2
exporter.insecure MVK_EXPORTER_INSECURE false HTTP instead of HTTPS (local only)
exporter.compression MVK_EXPORTER_COMPRESSION gzip gzip or none
exporter.timeout MVK_EXPORTER_TIMEOUT 10 Seconds (1–300)
exporter.max_retries MVK_EXPORTER_MAX_RETRIES 6 0–20
exporter.retry_timeout MVK_EXPORTER_RETRY_TIMEOUT 60 Total retry seconds (1–600)
exporter.file_path MVK_EXPORTER_FILE_PATH Directory for file exporter
exporter.format MVK_EXPORTER_FORMAT simple simple or json (console/file)

Batching (batching={...})

instrument() Environment variable Default Notes
batching.max_items MVK_BATCH_MAX_ITEMS 2000 Spans/batch (1–10000)
batching.max_bytes MVK_BATCH_MAX_BYTES 2097152 2 MiB (1 KB–10 MB)
batching.max_interval_ms MVK_BATCH_MAX_INTERVAL_MS 3000 ms (100–60000)

Failed-batch disk recovery (failed_batch_disk={...})

instrument() Environment variable Default Notes
failed_batch_disk.enabled MVK_FAILED_BATCH_DISK_ENABLED false
failed_batch_disk.path MVK_FAILED_BATCH_DISK_PATH Required when enabled
failed_batch_disk.max_size_mb MVK_FAILED_BATCH_DISK_MAX_SIZE_MB 1000 10–100000
failed_batch_disk.retry_interval MVK_FAILED_BATCH_DISK_RETRY_INTERVAL 60 Seconds (1–3600)

Logging (logging={...}) — see §2.4

instrument() Environment variable Default Notes
logging.level MVK_LOG_LEVEL OFF OFF, INFO, DEBUG
logging.wrapper_level MVK_WRAPPER_LOG_LEVEL Provider wrappers only
logging.prompts_responses 🔒 MVK_LOG_PROMPTS_RESPONSES false Logs raw prompts/responses
logging.prompts_storage_mode MVK_PROMPTS_STORAGE_MODE truncate truncate, compress, envelope
logging.prompts_max_length MVK_PROMPTS_MAX_LENGTH 1000 Truncation length (100–100000)
logging.prompts_masking MVK_PROMPTS_MASKING true Mask PII/PHI/PCI (on by default)

Validation, tags & wrappers

instrument() Environment variable Default Notes
strict_validation MVK_STRICT_VALIDATION false Raise on schema violations
tag_limit MVK_TAG_LIMIT 10 Max tags/span (1–10)
tags={"env": "prod"} MVK_TAG_ENV=prod One env var per tag: MVK_TAG_<KEY>
wrappers={"include": [...]} MVK_WRAPPERS genai,vectordb Comma-separated in env
wrappers.http.exclusions MVK_HTTP_EXCLUSIONS JSON array or comma-separated

Serverless & diagnostics

instrument() Environment variable Default Notes
serverless.force MVK_SERVERLESS false Force serverless optimizations
system_alerts_enabled MVK_SYSTEM_ALERTS_ENABLED true SDK self-diagnostics telemetry
collect_cloud_metadata MVK_COLLECT_CLOUD_METADATA true Auto-skipped in local/CI
auto_middleware MVK_AUTO_MIDDLEWARE true Auto W3C context for Flask/FastAPI/Django

Full instrument() call with every group:

mvk.instrument(
    agent_id="agent-123",
    api_key="mvk_...",        # Required in DIRECT mode
    tenant_id="tenant-123",   # Required in DIRECT mode
    exporter={
        "mode": "DIRECT",          # DIRECT or COLLECTOR
        "type": "otlp_http",       # otlp_http | otlp_grpc | console | file
        "endpoint": "https://ingest.mavvrik.ai/v1/traces",  # auto-set if omitted
        "timeout": 10,
        "max_retries": 6,
        "compression": "gzip",     # gzip | none
    },
    batching={"max_items": 2000, "max_bytes": 2097152, "max_interval_ms": 3000},
    failed_batch_disk={
        "enabled": False,
        "path": "/tmp/mvk/failed_batches",  # required when enabled
        "max_size_mb": 1000,
        "retry_interval": 60,
    },
    logging={"level": "INFO", "prompts_responses": False},  # ⚠️ see §2.4
    serverless={"force": False},
    strict_validation=False,
    wrappers={"include": ["genai", "vectordb"]},
    tags={"env": "prod"},
)

2.2 Precedence — defaults < parameters < environment

When the same setting is supplied in more than one place, the SDK resolves it in this order (lowest → highest priority):

Schema default  <  instrument() parameter  <  MVK_* environment variable

Environment variables always win. This is deliberate: it lets ops/platform teams override application code at deploy time without a code change. Example:

mvk.instrument(agent_id="agent-123", logging={"level": "INFO"})
export MVK_LOG_LEVEL=DEBUG   # effective level is DEBUG, not INFO

Tags merge the same way — MVK_TAG_<KEY> overrides the same key passed in tags={...}, while other keys are preserved.

2.3 Secrets & environment (recommended)

The only secret value is api_key (MVK_API_KEY); tenant_id is sensitive-but-identifying. Do not hard-code these in source. Because environment variables take precedence (§2.2), the recommended production pattern is:

  • Keep agent_id and non-secret config in code or env as convenient.
  • Inject MVK_API_KEY (and MVK_TENANT_ID) from a secrets manager at runtime — AWS Secrets Manager / SSM, GCP Secret Manager, Azure Key Vault, or Vault — never from a committed .env or the image.
import mvk_sdk as mvk

# api_key / tenant_id resolved from MVK_API_KEY / MVK_TENANT_ID,
# which your platform injects from the secret manager.
mvk.instrument(agent_id="agent-123")
# Example: hydrate env from a secret manager before the process starts
export MVK_API_KEY="$(aws secretsmanager get-secret-value \
  --secret-id mvk/api-key --query SecretString --output text)"
export MVK_TENANT_ID=tenant-123

⚠️ Warning. Avoid passing api_key= as a literal in code or logging it. Keep logging.prompts_responses disabled in production — it logs raw LLM prompts/responses, which may contain regulated data.

2.4 Logging

Logging is off by default (level: "OFF") for security and performance. When enabled, the SDK auto-configures Python logging — no manual setup needed.

mvk.instrument(agent_id="agent-123", api_key="mvk_...",
               logging={"level": "DEBUG"})           # controls all components

wrapper_level overrides the level for the AI provider wrappers only:

mvk.instrument(agent_id="agent-123", api_key="mvk_...",
               logging={"level": "INFO", "wrapper_level": "DEBUG"})
export MVK_LOG_LEVEL=INFO
export MVK_WRAPPER_LOG_LEVEL=DEBUG

Levels: OFF (default) · INFO (lifecycle — config, batch flushes, shutdown) · DEBUG (per-span detail — instrumentation, token extraction, queueing; enabling DEBUG also enables INFO).

Log lines are formatted as TIMESTAMP - LOGGER - LEVEL - MESSAGE, where the timestamp is YYYY-MM-DD HH:MM:SS,mmm and loggers are namespaced mvk.* (e.g. mvk.instrumentation.openai, mvk.processors.writer). Example DEBUG output for a single auto-traced OpenAI chat completion:

2026-06-29 18:37:28,807 - mvk.instrumentation.wrapper_base - DEBUG - Wrapper invoked for span_name=openai.chat.completion, method=create, instance=Completions
2026-06-29 18:37:29,044 - mvk.instrumentation.wrapper_base - DEBUG - Sync wrapper: Created span openai.chat.completion with trace_id=6eeb9c79..., operation_subtype=sync
2026-06-29 18:37:29,061 - mvk.instrumentation.openai - DEBUG - [OpenAI Token Extraction] Extracted tokens - prompt: 12, completion: 8, total: 20
2026-06-29 18:37:29,061 - mvk.processors.writer - DEBUG - Span queued for export: openai.chat.completion (queue size: 1)
2026-06-29 18:37:29,062 - mvk.processors.writer - INFO - Flushing batch of 1 span(s) to exporter

3. @mvk.context() — enriching spans (decorator vs. context manager)

mvk.instrument() only knows process-wide defaults (agent, environment, global tags). It cannot know who the current user is, which session a request belongs to, or what business workflow is executing. mvk.context() layers that runtime identity and business context onto every span produced inside its scope — auto-traced LLM calls, vector DB queries, and manual @mvk.signal / create_signal() operations all inherit it automatically.

Dual purpose — when to use which

mvk.context() has the same signature in both forms; pick by scope:

Form Use when
Context managerwith mvk.context(...): Scoping a block at runtime: an HTTP handler, message consumer, or job; per-request identity that varies per call; or wrapping an async generator (the decorator does not support async generators).
Decorator@mvk.context(...) A whole function should share one (usually static) context. Works on sync functions and async def coroutines (context is preserved across await within the same task).

Why it matters

  • Cost attributioncustomer_id, application_id, and tags let the Mavvrik backend split AI spend by tenant, product, team, or feature for chargeback / showback reporting.
  • Trace correlationuser_id, session_id, and request_id stitch multi-step agent workflows back to a single end-user request.
  • Business groupinguse_case groups related operations under a named business process (e.g. "fraud_detection_v2", "customer_onboarding").
  • Distributed tracingtraceparent / tracestate accept incoming W3C Trace Context headers so spans link across service boundaries.
  • Nearest-wins inheritance — context layered closer to the call overrides outer layers (see Context inheritance).

Signature

mvk.context(
    name: str | None = None,                 # Logical context name
    user_id: str | None = None,              # End-user identity
    session_id: str | None = None,           # Session / conversation ID
    application_id: str | None = None,       # Calling application
    customer_id: str | None = None,          # Your customer (multi-tenant)
    request_id: str | None = None,           # External request correlation ID
    region: str | None = None,               # Geographic region
    cloud_provider_code: str | None = None,  # e.g. "aws", "gcp", "azure"
    use_case: str | None = None,             # Business workflow (snake_case)
    tags: dict[str, str] | None = None,      # Up to 10 custom key/value tags
    traceparent: str | None = None,          # W3C traceparent header
    tracestate: str | None = None,           # W3C tracestate header
)

All parameters are optional — pass only what is meaningful at the call site. Unknown keyword arguments are logged and ignored; the SDK never raises into client code.

Examples

Context manager — per-request scope (HTTP handler, consumer, job):

import mvk_sdk as mvk
import openai

mvk.instrument(agent_id="agent-123", api_key="mvk_...", tenant_id="tenant-123")

def handle_chat(request):
    with mvk.context(
        user_id=request.user.id,
        session_id=request.session.id,
        customer_id=request.tenant.id,
        use_case="customer_support_chat",
        tags={"feature": "chat", "tier": "premium"},
    ):
        # Every span below inherits the identity, use_case, and tags above
        return openai.ChatCompletion.create(...)

Decorator — function-scoped enrichment:

@mvk.context(region="us-east-1", use_case="document_search",
             tags={"service": "search-api"})
def search_handler(query: str):
    return run_pipeline(query)   # all LLM / vector DB calls inherit the context

Continuing a distributed trace from an upstream service:

def grpc_handler(request, metadata):
    with mvk.context(
        traceparent=metadata.get("traceparent"),
        tracestate=metadata.get("tracestate"),
        request_id=metadata.get("x-request-id"),
    ):
        return process(request)

Nesting — innermost wins for scalars, tags merge:

with mvk.context(customer_id="acme", tags={"env": "prod"}):
    with mvk.context(user_id="u-42", tags={"feature": "summarize"}):
        # Span sees: customer_id="acme", user_id="u-42",
        #            tags={"env": "prod", "feature": "summarize"}
        summarize(doc)

Clearing an inherited attribute — pass an empty string:

with mvk.context(user_id="u-42"):
    with mvk.context(user_id=""):
        run_background_job()   # user_id removed from spans in this inner block

Notes & limits

  • Tag cap: maximum 10 tags per span after all levels merge. Excess tags are dropped (or raise if strict_validation=True). See Tag validation for key/value rules.
  • Per-request headers override everything: incoming x-mvk-* headers on a single call take precedence over decorator and context-manager values for that call only.
  • Unknown kwargs are non-fatal: a misspelled parameter (e.g. userid=...) is logged and ignored. The SDK never raises into client code.
  • All attributes are prefixed mvk.* on the wire: e.g. user_id becomes mvk.user_id, and tags={"team": "ml"} becomes mvk.tags.team.

4. Cost signals — @mvk.signal(), create_signal() & metered usage

Auto-instrumentation covers LLM providers, vector DBs, and supported frameworks out of the box. For everything else — wrapping a business workflow under a single named span, tracing an in-house LLM gateway, attributing custom tool / storage / API costs — the SDK exposes three manual APIs:

API Purpose Use as
@mvk.signal() Wrap a function so its entire execution becomes one parent span; child auto-traced calls nest under it Decorator
mvk.create_signal() Create a span for a code block with explicit step_type / operation Context manager
mvk.add_metered_usage() Attach billable quantity metrics (pages, API calls, bytes, custom tokens) to the current span Plain function call

Identity, session, customer, and tags propagate to these spans automatically from mvk.context().

@mvk.signal() — decorate a function as a named span

The function name (or name=...) becomes the span name; every auto-traced call inside (LLM, vector DB, HTTP) and every create_signal() block becomes a child span. A run_id is auto-generated at the root signal and propagated to all descendants (mvk.run_id on the wire) for trace correlation.

📝 Note. step_type, operation, operation_subtype, and model are auto-set by wrappers for child spans. @mvk.signal() also accepts step_type and tool_name when the decorated function itself represents a tool wrapper, but tool_name is only applied when step_type="TOOL". For explicit manual span classification (operation, operation_subtype), use mvk.create_signal().

@mvk.signal(
    name: str | None = None,                 # Span name (default: function name)
    user_id: str | None = None,
    session_id: str | None = None,
    application_id: str | None = None,
    customer_id: str | None = None,
    request_id: str | None = None,
    region: str | None = None,
    cloud_provider_code: str | None = None,
    use_case: str | None = None,
    step_type: str | MVKStepType | None = None,  # Optional; use with TOOL spans
    tool_name: str | None = None,            # TOOL-only label for the wrapped function
    tags: dict[str, str] | None = None,      # Custom tags (max 10 per span)
)

Works on sync functions, async def coroutines, and async generators — the SDK auto-detects the function type.

# Group a multi-step workflow under one named span
@mvk.signal(name="answer_question", use_case="customer_support_chat",
            tags={"tier": "premium"})
def answer_question(query: str):
    embedding = openai.Embedding.create(...)     # child span: EMBEDDING
    hits = pinecone.query(...)                    # child span: RETRIEVER
    return openai.ChatCompletion.create(...)      # child span: LLM


# Custom tool wrapper — attach mvk.tool_name to the decorator span
@mvk.signal(name="search_documents", step_type="TOOL",
            tool_name="knowledge_base_search")
def search_documents(query: str):
    return internal_search(query)

mvk.create_signal() — manual spans with explicit step_type

Use this when auto-instrumentation does not cover the call — a custom in-house LLM service, a paid third-party API, a parsing step, a file upload. Unlike @mvk.signal(), this is a context manager and lets you set step_type / operation explicitly so the backend classifies the cost correctly.

from mvk_sdk.schema import MVKStepType  # optional; strings also accepted

mvk.create_signal(
    name: str,                                   # Span name (required)
    step_type: MVKStepType | str | None = None,  # LLM | TOOL | RETRIEVER |
                                                 # EMBEDDING | BATCH | AGENT_CALL
    operation: str | None = None,                # e.g. "parse", "api_call"
    operation_subtype: str | None = None,        # Free-form refinement
    tool_name: str | None = None,                # TOOL-only per-tool attribution
    tags: dict[str, str] | None = None,
)

Inherits user_id, session_id, customer_id, region, use_case, and tags from the surrounding mvk.context() / @mvk.signal(). Pass tags={} to opt out of context-tag inheritance.

with mvk.create_signal(name="parse-document", step_type="TOOL",
                       operation="parse", tool_name="document_parser"):
    content = parse_pdf(file_path)

mvk.add_metered_usage() — attach billable quantities

Auto-instrumented LLM calls already populate mvk.metered_usage with token counts. For everything else with a cost dimension — pages processed, external API calls, storage bytes, image generations, characters translated — call add_metered_usage() inside the current signal.

mvk.add_metered_usage([
    {
        "metric_kind": str,    # e.g. "file.pages_processed", "api.calls", "storage.bytes"
        "quantity": float,     # Runtime-measured quantity (never hardcoded)
        "uom": str,            # Unit of measure: "page", "request", "byte", "image", ...
        "metadata": {
            "rate_per_unit": float,   # Cost per UOM unit (USD by default)
            "provider": str,          # External vendor name
            # ...any other free-form metadata
        },
    },
])

❗ Important — rate_per_unit is what turns a quantity into a cost. The backend computes a line item as quantity × rate_per_unit and rolls it up by customer_id / tags / use_case.

  • Omit it → the backend applies the configured rate template for that metric_kind. Use this when a template exists and you want central control.
  • Set it inline → overrides the template, and is required for metric_kinds that have no template. Always pair a numeric rate_per_unit with the correct uom so quantity and rate are in the same units.

Also accepts Metric instances (from mvk_sdk.metrics import Metric) for the three core fields only (metric_kind, quantity, uom); use the dict form when you need metadata / rate_per_unit. If no active span exists, the call is logged and skipped — it never raises.

End-to-end example — tools, custom costs, and auto-traced LLM together

import mvk_sdk as mvk
from openai import OpenAI

mvk.instrument(agent_id="doc-agent", api_key="mvk_...", tenant_id="tenant-123",
               wrappers={"include": ["genai"]})

client = OpenAI()


def process_document(file_path: str, user_id: str,
                     session_id: str, customer_id: str) -> str:
    with mvk.context(user_id=user_id, session_id=session_id, customer_id=customer_id):
        # 1. Manual TOOL span — custom parsing cost (per-page)
        with mvk.create_signal(name="parse-document", step_type="TOOL",
                               operation="parse", tool_name="document_parser"):
            content = parse_pdf(file_path)
            page_count = get_page_count(file_path)
            mvk.add_metered_usage([{
                "metric_kind": "file.pages_processed",
                "quantity": page_count,            # runtime value, never hardcoded
                "uom": "page",
                "metadata": {"rate_per_unit": 0.0015},   # $/page
            }])

        # 2. Manual TOOL span — paid third-party API
        with mvk.create_signal(name="extract-metadata", step_type="TOOL",
                               operation="api_call", tool_name="metadata_service"):
            metadata = call_metadata_api(content)
            mvk.add_metered_usage([{
                "metric_kind": "api.calls",
                "quantity": 1,
                "uom": "request",
                "metadata": {"rate_per_unit": 0.05, "provider": "metadata-service"},
            }])

        # 3. Auto-traced LLM call — wrapper populates token metered_usage
        summary = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"Summarize: {content[:2000]}"}],
        )
        return summary.choices[0].message.content

This single request produces one trace with three child spans (parse-document, extract-metadata, the auto-traced chat), all stamped with user_id / session_id / customer_id, each carrying its own metered_usage line items so cost is attributed end-to-end.

Notes & limits

  • Always capture quantity from runtime values (e.g. len(file_bytes), page_count, 1 per API call) — never hardcode.
  • Multiple metrics per span — pass a list in one add_metered_usage() call, or call multiple times within the same span (entries are appended).
  • No active span = no-op — calling outside a @mvk.signal() / create_signal() / auto-traced call logs a warning and skips silently.
  • Common metric_kind examples: token.prompt, token.completion, file.pages_processed, api.calls, storage.bytes, images.generated, characters.translated, email.sent, db.rows_scanned.

5. Reference

Context inheritance (Nearest Wins)

The SDK layers context from four sources, applied outermost → innermost: Global (mvk.instrument(tags=...)) → Decorator (@mvk.signal / @mvk.context) → Context manager (with mvk.context(...)) → Per-request headers (x-mvk-*).

  • Scalars (user_id, session_id, customer_id, request_id, …): nearest wins completely.
  • Tags: merge all levels; nearest wins for duplicate keys.
  • Empty values clear attributes: mvk.user_id="" removes user attribution from this scope down.

Tag validation

  • Maximum 10 tags per span after merging
  • Keys: ^[a-z0-9._-]{1,64}$ (dots allowed)
  • Values: UTF-8 strings ≤256 chars
  • Validation errors are logged (or raised if strict_validation=True)
valid, issues = mvk.validate_tags({
    "user.id": "123",               # dots allowed
    "tier": "premium",
    "INVALID!": "will-be-dropped",  # invalid characters
})

Architecture

Memory-First Architecture — best for development, QA, and production (all environments):

Producers → Memory Queue (10MB) → Writer Thread → Exporter
                                        ↓
                            (exponential backoff retry)
                                        ↓
                          (on failure after 10 attempts)
                                        ↓
                                FailedBatchDisk
  • Memory-first performance with a 10MB bounded queue, non-blocking producers
  • Exponential backoff retry (1s to 5min); persistently failed batches saved to disk
  • Fork-safe (Gunicorn, uWSGI, prefork servers); shuts down gracefully on SIGTERM/SIGINT, flushing pending spans

Step types (MVKStepType): LLM (token metrics), EMBEDDING (embedding metrics), RETRIEVER (vector/search), TOOL (tool/HTTP + manual tool spans), AGENT_CALL (orchestration), BATCH (batch ops). MEMORY is reserved for future use.

Smart auto-instrumentation uses OpenTelemetry's proven strategy: if a library is already imported → immediate instrumentation; if not → lazy hook via wrapt.when_imported().

Supported integrations

AI Providers (wrappers={"include": ["genai"]}):

Provider Library Versions Step Type Token Tracking
OpenAI 0.x, 1.x LLM, EMBEDDING ✓ metered_usage
Anthropic 0.20-0.35 LLM ✓ metered_usage
Gemini google.generativeai LLM, EMBEDDING ✓ metered_usage
AWS Bedrock boto3 bedrock-runtime LLM, EMBEDDING ✓ metered_usage
Azure OpenAI azure.ai.openai LLM, EMBEDDING ✓ metered_usage
Vertex AI vertexai/google.cloud.aiplatform LLM, EMBEDDING ✓ metered_usage

Vector Databases (wrappers={"include": ["vectordb"]}):

Provider Library Versions Step Type Metrics
Pinecone 2.x-4.x RETRIEVER vector_count, dimension
Weaviate 3.x-4.x RETRIEVER vector_count, dimension
ChromaDB 0.4.x-0.5.x RETRIEVER vector_count, dimension
Qdrant 1.x RETRIEVER vector_count, dimension

Frameworks: Semantic Kernel, LangChain, LangGraph, Agno, CrewAI, OpenAI Agents.

Routers & Proxies: OpenRouter, LiteLLM.

Batch APIs: OpenAI, Anthropic, Azure OpenAI, AWS Bedrock, Gemini, Vertex AI, LangChain, Agno, CrewAI, Semantic Kernel, and ThreadPool concurrent execution.

HTTP Clients (wrappers={"include": ["http"]}, disabled by default): HTTPX 0.25-0.27 (TOOL).

Performance characteristics

Memory-First Mode (all environments) — throughput 500–2000 spans/sec; ~10MB bounded queue; producer latency <100 µs (non-blocking); export latency p99 <100ms (network dependent); failed batches saved to disk for retry.

Batching defaults (all modes) — 2000 spans max, 2 MiB max, 3000 ms max, gzip compression (default) or none.

Serverless deployment

# AWS Lambda
from mvk_sdk.serverless import lambda_handler

@lambda_handler(flush_timeout_ms=1000)   # auto flush on completion
def handler(event, context):
    # Auto-detects Lambda; optimizes batch_size=1, flush=100ms, memory-first
    return process_request(event)
# Google Cloud Functions — auto-detected via K_SERVICE / FUNCTION_NAME
import mvk_sdk as mvk

mvk.instrument(agent_id="gcf-function")

def main(request):
    result = process_request(request)
    mvk.force_flush()    # manual flush for Cloud Functions
    return result
export MVK_SERVERLESS=true   # force serverless optimizations

6. Support

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mvk_sdk_py-1.3.14.tar.gz (841.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mvk_sdk_py-1.3.14-py3-none-any.whl (901.5 kB view details)

Uploaded Python 3

File details

Details for the file mvk_sdk_py-1.3.14.tar.gz.

File metadata

  • Download URL: mvk_sdk_py-1.3.14.tar.gz
  • Upload date:
  • Size: 841.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for mvk_sdk_py-1.3.14.tar.gz
Algorithm Hash digest
SHA256 13358466ca9988a645e4c79f5699bc13b9f9d6ae1050aff670c6020d95cbf55f
MD5 95b9895757f2b96c7825271e618c0912
BLAKE2b-256 e166372ccb21d6655d83322de5c7e7c9d0d3f0a8539ce7488cbcfa7afb501066

See more details on using hashes here.

Provenance

The following attestation bundles were made for mvk_sdk_py-1.3.14.tar.gz:

Publisher: release.yml on cloudwizio/agentic-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mvk_sdk_py-1.3.14-py3-none-any.whl.

File metadata

  • Download URL: mvk_sdk_py-1.3.14-py3-none-any.whl
  • Upload date:
  • Size: 901.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for mvk_sdk_py-1.3.14-py3-none-any.whl
Algorithm Hash digest
SHA256 d3eea0cacad32e3a30aac5c760fb831bc9dde2f9651f91233f3a3cfa0e8b94cc
MD5 fc11c66d3e70fbafca1dec268fa44dea
BLAKE2b-256 0406ab34948c045b8806781bbe006f205ea313e53e056bc49f6883516a2be2a4

See more details on using hashes here.

Provenance

The following attestation bundles were made for mvk_sdk_py-1.3.14-py3-none-any.whl:

Publisher: release.yml on cloudwizio/agentic-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page