# MetricAI Python SDK

> AI billing and metering proxy for India — route LLM calls, track costs, attribute spend, and manage budgets in INR/UPI.

[![PyPI Version](https://img.shields.io/pypi/v/metricai.svg)](https://pypi.org/project/metricai/)
[![Python Versions](https://img.shields.io/pypi/pyversions/metricai)](https://pypi.org/project/metricai/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

---

## Modes

MetricAI SDK supports two modes of operation:

| Mode | Description |
|------|-------------|
| **Cloud Mode** | Route traffic through MetricAI proxy with SaaS dashboard |
| **[Local Mode](docs/LOCAL_SDK_QUICKSTART.md)** | Zero-egress tracking with local SQLite and dashboard |

**Local Mode** is ideal for:
- Enterprise deployments
- Air-gapped environments
- Compliance requirements
- Cost-sensitive projects

Get started with Local Mode: **[Quick Start Guide](docs/LOCAL_SDK_QUICKSTART.md)**

---

## Features

| Capability | Description |
|------------|-------------|
| **Local Mode (NEW)** | Zero-egress tracking with SQLite — no data leaves your environment |
| **Multi-Provider Proxy** | Route OpenAI, Anthropic, Gemini, Grok, Groq, Perplexity, and more through a single endpoint |
| **Native SDK Compatibility** | Use official provider SDKs with zero code changes via `instrument()` |
| **Per-Agent & Per-User Attribution** | Group costs by `agent_id` and `user_id` in the dashboard |
| **Budget Caps** | Hard INR caps per agent or conversation to prevent runaway spend |
| **Outcome Billing** | Charge on success/failure outcomes, not just token usage |
| **Streaming Telemetry** | Track token counts and tool usage in real-time for streaming responses |
| **Agentic Workflows** | Built-in `MetricAIAgent` and `MetricAIPipeline` with tool calling and governance |
| **Framework Integrations** | LangChain, LangGraph, CrewAI, LlamaIndex, AutoGen, FastAPI, Django |
| **Voice AI Tracking** | Deepgram, ElevenLabs, Sarvam with voice-specific metrics |
| **LiveKit Integration** | Track LiveKit voice agents with billing and attribution |
| **Invoice payments** | Generate hosted pay links for end-customer invoices (`create_invoice_payment_link`) |
| **Fail-Open** | Requests forward directly to providers if the proxy is unreachable |

---

## Installation

```bash
pip install metricai
```

### With Optional Providers

```bash
# Just OpenAI
pip install "metricai[openai]"

# Just Anthropic
pip install "metricai[anthropic]"

# All providers at once
pip install "metricai[all]"
```

Available extras: `openai`, `anthropic`, `gemini`, `groq`, `grok`, `perplexity`, `bedrock`, `tavily`, `langchain`, `langgraph`, `crewai`, `llamaindex`, `fastapi`, `django`, `deepgram`, `elevenlabs`, `sarvam`

---

## Quickstart

### 1. Set your API key

```bash
export METRICAI_API_KEY="your_metricai_api_key"
```

For BYOK mode (bring your own provider keys):

```bash
export METRICAI_API_KEY="your_metricai_api_key"
export OPENAI_API_KEY="sk-proj-..."        # Optional — for OpenAI routes
export ANTHROPIC_API_KEY="sk-ant-..."      # Optional — for Anthropic routes
export GEMINI_API_KEY="AI..."              # Optional — for Gemini routes
```

### 2. Use native SDKs via proxy

```python
from metricai import MetricAI

client = MetricAI(api_key="your_metricai_api_key")

# OpenAI via proxy
openai_client = client.openai_sdk(agent_id="support-agent", user_id="user_42")
response = openai_client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "What's the weather in Mumbai?"}]
)
print(response.choices[0].message.content)
```

### 3. Track usage and costs

```python
from metricai import MetricAI, MetricAISession, timed_call

client = MetricAI(api_key="your_metricai_api_key")

# Session-based tracking with budget cap
with MetricAISession(client, agent_id="booking-agent", user_id="user_123", max_budget_inr=50.0) as session:
    response = client.openai_sdk().chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Book a flight to Delhi"}]
    )
    session.track(provider="openai", model="gpt-4o-mini", input_tokens=1500, output_tokens=300)
```

### 4. Outcome billing

```python
# Simulate first to estimate cost
client.track(
    provider="openai",
    model="gpt-4o-mini",
    outcome={"success": True, "outcome_type": "booking_confirmed", "charge_on_success_inr": 2.5},
    simulate=True,
)

# Live outcome billing
client.track(
    provider="openai",
    model="gpt-4o-mini",
    outcome={"success": True, "charge_on_success_inr": 2.5},
    latency_ms=850,
)
```

### 5. Standalone agent with tools

```python
from metricai import MetricAI
from metricai.workflow import MetricAIAgent

client = MetricAI(api_key="your_metricai_api_key", llm_keys={"gemini": "your_gemini_key"})

agent = MetricAIAgent(
    client,
    agent_id="research-agent",
    user_id="user_456",
    model="gemini-2.0-flash",
    system_prompt="You are a helpful research assistant.",
    budget_cap_inr=25.0,
)

response = agent.run("What are the top 5 AI news stories today?")
print(response.content)
```

---

## Invoice payments

Collect `final_usd` from an end customer (Y) with a hosted pay page. Invoice REST
calls go to the **API** host (`api.metricai.co.in` / `staging-api.metricai.co.in`),
not the LLM proxy. Override with `METRICAI_API_URL` or `MetricAI(api_base_url=...)`.

Finish dashboard onboarding first: **Settings → Billing** (Route or BYOR), then
**Payouts** (Route) or **Payments** (BYOR) until the account is ready to collect.

```python
from metricai import MetricAI

client = MetricAI(api_key="your_metricai_api_key")

invoice = client.get_invoices("cust_123", period_start="01082026", period_end="31082026")
link = client.create_invoice_payment_link(
    "cust_123",
    period_start=invoice["period_start"],
    period_end=invoice["period_end"],
)
# Redirect the customer to link["payment_url"]  →  https://pay.metricai.co.in/i/{token}

status = client.get_invoice_payment_status(
    "cust_123",
    period_start=invoice["period_start"],
    period_end=invoice["period_end"],
)
# status["payment_status"] is "unpaid" | "pending" | "paid"
```

If you collect on your own Razorpay checkout (BYOR self-collect), call
`client.mark_invoice_collected(...)` after a successful payment so MetricAI can
accrue platform fees.

In a team project only the **project owner** can create a payment link or mark
an invoice collected. A member's API key raises `MetricAIError` with that
reason; `get_invoice_payment_status` stays available to every member.

Runnable example: [`examples/invoice_payment_link.py`](examples/invoice_payment_link.py).
Guides: [Route](https://metricai.co.in/docs#invoice-payments-route),
[BYOR hosted](https://metricai.co.in/docs#invoice-payments-byor),
[BYOR self-collect](https://metricai.co.in/docs#invoice-payments-self-collect).

---

## Core Concepts

### Client Initialization

```python
from metricai import MetricAI, MetricAIConfig

# Simple initialization
client = MetricAI(api_key="your_metricai_api_key")

# With configuration object
config = MetricAIConfig(
    api_key="your_metricai_api_key",
    mode="byok",                        # "byok" or "managed"
    llm_keys={"openai": "sk-..."},     # Provider keys for BYOK mode
    proxy_base_url="https://proxy.metricai.co.in",
    default_billing_mode="hybrid",      # "usage", "outcome", or "hybrid"
    default_budget_cap_inr=100.0,       # Daily INR cap
    fail_open=True,                     # Forward to provider if proxy unreachable
)

client = MetricAI(config=config)
```

### Headers and Attribution

```python
# Get headers for any SDK call
headers = client.headers(
    agent_id="my-agent",
    user_id="user_123",
    session_id="session_abc",
    billing_mode="hybrid",
    budget_cap_inr=50.0,
)

# Pass headers to any HTTP client
response = openai_client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    extra_headers=headers,
)
```

### Routing

```python
from metricai import MetricAI, RoutingConfig, RoutingManager

client = MetricAI(api_key="your_metricai_api_key")

# Configure routing policy server-side
router = RoutingManager(client)
router.configure(
    agent_id="routing-agent",
    strategy="cost-optimized",
    config={"max_model": "gpt-4o-mini", "fallback_model": "gpt-3.5-turbo"}
)

# Or via config
client = MetricAI(
    api_key="your_metricai_api_key",
    routing=RoutingConfig(strategy="latency-optimized")
)
```

### Global Instrumentation

Instrument all provider SDK clients globally — no need to change existing code:

```python
from metricai import init, instrument

# Initialize once at app startup
init(api_key="your_metricai_api_key", auto_instrument=True)

# All subsequent OpenAI/Anthropic calls are automatically routed through MetricAI
from openai import OpenAI
client = OpenAI()  # Already patched — routes through MetricAI proxy
```

---

## Framework Integrations

### LangChain

```python
from langchain_openai import ChatOpenAI
from metricai.integrations.langchain import MetricAIChatCallbackHandler

handler = MetricAIChatCallbackHandler(agent_id="lc-agent", user_id="user_1")
llm = ChatOpenAI(callbacks=[handler])
response = llm.invoke("Hello!")
```

### LangGraph

```python
from langgraph.prebuilt import create_react_agent
from metricai.integrations.langgraph import MetricAIGraphTracer

tracer = MetricAIGraphTracer(agent_id="graph-agent", user_id="user_2")
agent = create_react_agent(model, tools, tracer=tracer)
```

### CrewAI

```python
from crewai import Agent
from metricai.integrations.crewai import MetricAICrewAIWrapper

wrapper = MetricAICrewAIWrapper(api_key="your_key")
agent = wrapper.wrap_agent(
    Agent(role="Researcher", goal="Find AI news", backstory="You are a researcher"),
    agent_id="crewai-researcher",
)
```

### FastAPI Middleware

```python
from fastapi import FastAPI
from metricai.integrations.web import MetricAIFastAPIMiddleware

app = FastAPI()
app.add_middleware(
    MetricAIFastAPIMiddleware,
    api_key="your_metricai_api_key",
    default_agent_id="fastapi-agent",
)
```

---

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `METRICAI_API_KEY` | Your MetricAI API key | Required |
| `METRICAI_PROXY_URL` | Proxy base URL | `https://proxy.metricai.co.in` |
| `METRICAI_API_URL` | REST API host for invoices, wallet, auth | Derived from the proxy host (`api.metricai.co.in`) |
| `METRICAI_ENVIRONMENT` | `production` or `sandbox` | `production` |
| `OPENAI_API_KEY` | BYOK key for OpenAI | — |
| `ANTHROPIC_API_KEY` | BYOK key for Anthropic | — |
| `GEMINI_API_KEY` | BYOK key for Gemini | — |

---

## Supported Providers

| Provider | Proxy Path | Native SDK |
|----------|-----------|------------|
| OpenAI | `/v1/proxy/openai` | ✅ `openai` |
| Anthropic | `/v1/proxy/claude` | ✅ `anthropic` |
| Gemini | `/v1/proxy/gemini` | ✅ `google-genai` |
| Grok | `/v1/proxy/grok` | ✅ `xai-sdk` |
| Groq | `/v1/proxy/groq` | ✅ `groq` |
| Perplexity | `/v1/proxy/perplexity` | ✅ `perplexityai` |
| AWS Bedrock | `/v1/proxy/bedrock` | ✅ `boto3` |
| Tavily | `/v1/proxy/tavily` | ✅ `tavily-python` |
| Deepgram | `/v1/proxy/deepgram` | ✅ `deepgram-sdk` |
| ElevenLabs | `/v1/proxy/elevenlabs` | ✅ `elevenlabs` |
| Sarvam | `/v1/proxy/sarvam` | ✅ `sarvamai` |

---

## Error Handling

```python
from metricai import (
    MetricAI,
    MetricAIError,
    MetricAIAuthError,
    MetricAIQuotaExhaustedError,
    MetricAIRateLimitError,
    MetricAIProxyError,
)

client = MetricAI(api_key="your_metricai_api_key")

try:
    response = client.openai_sdk().chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": "Hello"}]
    )
except MetricAIAuthError:
    print("Invalid API key")
except MetricAIQuotaExhaustedError:
    print("Budget cap reached — upgrade your plan")
except MetricAIRateLimitError:
    print("Rate limited — retry after a moment")
except MetricAIProxyError as e:
    print(f"Proxy error: {e}")
except MetricAIError as e:
    print(f"Unexpected error: {e}")
```

---

## API Reference

### `MetricAI`

Main client class for all operations.

```python
MetricAI(
    api_key: str,                          # Required: your MetricAI API key
    base_url: str | None = None,           # Override proxy URL
    timeout: int = 60,                     # Request timeout in seconds
    agent_id: str | None = None,           # Default agent ID
    user_id: str | None = None,            # Default user ID
    billing_mode: str | None = None,       # "usage", "outcome", or "hybrid"
    budget_cap_inr: float | None = None,   # Daily budget cap in INR
    mode: str = "byok",                    # "byok" or "managed"
    llm_keys: dict | None = None,          # Provider API keys
    config: MetricAIConfig | None = None,  # Configuration object
)
```

**Methods:**

- `openai_sdk(...)` → Returns OpenAI-compatible client routed through proxy
- `anthropic_sdk(...)` → Returns Anthropic client routed through proxy
- `gemini_sdk(...)` → Returns Gemini client routed through proxy
- `grok_sdk(...)` → Returns Grok client routed through proxy
- `headers(...)` → Returns headers dict for any SDK call
- `track(...)` → Track a billing event
- `track_stream(...)` → Track streaming responses
- `session(...)` → Context manager for session tracking
- `get_quota_status()` → Get current billing usage

### `MetricAISession`

Session context manager for grouped tracking:

```python
with MetricAISession(
    client,
    agent_id="my-agent",
    user_id="my-user",
    max_budget_inr=50.0,
) as session:
    # Your LLM calls here
    session.track(provider="openai", model="gpt-4o", input_tokens=100, output_tokens=50)
```

### `MetricAIAgent`

Tool-capable agent with built-in governance:

```python
agent = MetricAIAgent(
    client,
    agent_id="my-agent",
    user_id="my-user",
    provider="openai",          # or "anthropic", "gemini", "grok"
    model="gpt-4o",
    system_prompt="You are a helpful assistant.",
    tools=[...],                # Optional: list of tool definitions
    max_iterations=10,
    budget_cap_inr=25.0,
    billing_mode="hybrid",
)

response = agent.run("User's request here")
print(response.content)
print(f"Tokens: {response.input_tokens} + {response.output_tokens}")
```

---

## Examples

More examples are available in the [`examples/`](examples/) directory:

- [`byok_openai_sdk.py`](examples/byok_openai_sdk.py) — Using official SDKs via proxy
- [`invoice_payment_link.py`](examples/invoice_payment_link.py) — Hosted invoice payment link (staging)
- [`standalone_workflow.py`](examples/standalone_workflow.py) — Standalone agent with tools
- [`langchain_metricai_sample.py`](examples/langchain_metricai_sample.py) — LangChain integration
- [`langgraph_tracer.py`](examples/langgraph_tracer.py) — LangGraph tracing
- [`crewai_crew.py`](examples/crewai_crew.py) — CrewAI integration

---

## License

MIT License — see [LICENSE](LICENSE) for details.

---

## Support

- Documentation: [https://docs.metricai.co.in](https://docs.metricai.co.in)
- Issues: [GitHub Issues](https://github.com/your-org/metricai/issues)
- Email: support@metricai.co.in