observiq-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
observiq_sdk/__init__.py
ADDED
observiq_sdk/client.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import functools
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from observiq_sdk.sender import TraceSender
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ObservIQ:
|
|
8
|
+
"""
|
|
9
|
+
ObservIQ SDK — 2 lines mein AI monitoring.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from observiq_sdk import ObservIQ
|
|
13
|
+
oiq = ObservIQ(api_key="oiq_...")
|
|
14
|
+
|
|
15
|
+
# Groq wrap karo
|
|
16
|
+
response = oiq.trace(groq.chat.completions.create)(
|
|
17
|
+
model="llama-3.3-70b",
|
|
18
|
+
messages=[...]
|
|
19
|
+
)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
api_key: str,
|
|
25
|
+
base_url: str = "http://localhost:8000",
|
|
26
|
+
enabled: bool = True
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
api_key → ObservIQ ka API key (oiq_...)
|
|
30
|
+
base_url → ObservIQ server ka URL
|
|
31
|
+
Development: http://localhost:8000
|
|
32
|
+
Production: https://api.observiq.com
|
|
33
|
+
enabled → False karo toh SDK kuch nahi karega
|
|
34
|
+
Testing ke time useful
|
|
35
|
+
"""
|
|
36
|
+
self.api_key = api_key
|
|
37
|
+
self.base_url = base_url
|
|
38
|
+
self.enabled = enabled
|
|
39
|
+
self.sender = TraceSender(api_key, base_url)
|
|
40
|
+
|
|
41
|
+
def trace(
|
|
42
|
+
self,
|
|
43
|
+
func,
|
|
44
|
+
feature_name: Optional[str] = None,
|
|
45
|
+
user_identifier: Optional[str] = None
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Kisi bhi AI function ko wrap karo.
|
|
49
|
+
|
|
50
|
+
Yeh ek 'decorator factory' hai:
|
|
51
|
+
- func ko wrap karta hai
|
|
52
|
+
- Call hone pe timer start karta hai
|
|
53
|
+
- Response se data nikalta hai
|
|
54
|
+
- Background mein bhejta hai
|
|
55
|
+
- Original response return karta hai
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@functools.wraps(func)
|
|
59
|
+
def wrapper(*args, **kwargs):
|
|
60
|
+
# SDK disabled hai toh seedha call karo
|
|
61
|
+
if not self.enabled:
|
|
62
|
+
return func(*args, **kwargs)
|
|
63
|
+
|
|
64
|
+
# Timer start
|
|
65
|
+
start_time = time.time()
|
|
66
|
+
error_msg = None
|
|
67
|
+
response = None
|
|
68
|
+
status = "success"
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
# Actual AI call karo
|
|
72
|
+
response = func(*args, **kwargs)
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
# AI call fail hui — error record karo
|
|
76
|
+
status = "error"
|
|
77
|
+
error_msg = str(e)
|
|
78
|
+
raise # Error ko aage bhi jaane do
|
|
79
|
+
|
|
80
|
+
finally:
|
|
81
|
+
# Timer stop — chahe success ho ya error
|
|
82
|
+
latency_ms = int((time.time() - start_time) * 1000)
|
|
83
|
+
|
|
84
|
+
# Data extract karo response se
|
|
85
|
+
trace_data = self._extract_trace_data(
|
|
86
|
+
kwargs=kwargs,
|
|
87
|
+
response=response,
|
|
88
|
+
latency_ms=latency_ms,
|
|
89
|
+
status=status,
|
|
90
|
+
error_msg=error_msg,
|
|
91
|
+
feature_name=feature_name,
|
|
92
|
+
user_identifier=user_identifier
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Background mein bhejo
|
|
96
|
+
self.sender.send(trace_data)
|
|
97
|
+
|
|
98
|
+
return response
|
|
99
|
+
|
|
100
|
+
return wrapper
|
|
101
|
+
|
|
102
|
+
def _extract_trace_data(
|
|
103
|
+
self,
|
|
104
|
+
kwargs: dict,
|
|
105
|
+
response,
|
|
106
|
+
latency_ms: int,
|
|
107
|
+
status: str,
|
|
108
|
+
error_msg: Optional[str],
|
|
109
|
+
feature_name: Optional[str],
|
|
110
|
+
user_identifier: Optional[str]
|
|
111
|
+
) -> dict:
|
|
112
|
+
"""
|
|
113
|
+
Response object se useful data nikalo.
|
|
114
|
+
Groq aur OpenAI ka format same hai isliye ek function kaam karta hai.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
# Model kaunsa use hua
|
|
118
|
+
model = kwargs.get("model", "unknown")
|
|
119
|
+
|
|
120
|
+
# Input — messages se pehla user message nikalo
|
|
121
|
+
messages = kwargs.get("messages", [])
|
|
122
|
+
input_text = None
|
|
123
|
+
for msg in messages:
|
|
124
|
+
if msg.get("role") == "user":
|
|
125
|
+
content = msg.get("content", "")
|
|
126
|
+
# Sirf pehle 1000 chars — zyada store karna expensive
|
|
127
|
+
input_text = content[:1000] if content else None
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
# Output, tokens — response se nikalo
|
|
131
|
+
output_text = None
|
|
132
|
+
prompt_tokens = 0
|
|
133
|
+
completion_tokens = 0
|
|
134
|
+
cost_usd = 0.0
|
|
135
|
+
|
|
136
|
+
if response is not None:
|
|
137
|
+
try:
|
|
138
|
+
# Groq/OpenAI response format
|
|
139
|
+
if hasattr(response, "choices") and response.choices:
|
|
140
|
+
output_text = response.choices[0].message.content
|
|
141
|
+
if output_text:
|
|
142
|
+
output_text = output_text[:1000]
|
|
143
|
+
|
|
144
|
+
# Token usage
|
|
145
|
+
if hasattr(response, "usage") and response.usage:
|
|
146
|
+
prompt_tokens = response.usage.prompt_tokens or 0
|
|
147
|
+
completion_tokens = response.usage.completion_tokens or 0
|
|
148
|
+
|
|
149
|
+
# Cost calculate karo
|
|
150
|
+
# Groq llama pricing (approximate)
|
|
151
|
+
cost_usd = self._calculate_cost(
|
|
152
|
+
model, prompt_tokens, completion_tokens
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
except Exception:
|
|
156
|
+
pass # Data extract na ho toh bhi trace save ho
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
"model": model,
|
|
160
|
+
"input": input_text,
|
|
161
|
+
"output": output_text,
|
|
162
|
+
"prompt_tokens": prompt_tokens,
|
|
163
|
+
"completion_tokens": completion_tokens,
|
|
164
|
+
"latency_ms": latency_ms,
|
|
165
|
+
"cost_usd": cost_usd,
|
|
166
|
+
"status": status,
|
|
167
|
+
"error_message": error_msg,
|
|
168
|
+
"feature_name": feature_name,
|
|
169
|
+
"user_identifier": user_identifier,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
def _calculate_cost(
|
|
173
|
+
self,
|
|
174
|
+
model: str,
|
|
175
|
+
prompt_tokens: int,
|
|
176
|
+
completion_tokens: int
|
|
177
|
+
) -> float:
|
|
178
|
+
"""
|
|
179
|
+
Token count se cost calculate karo.
|
|
180
|
+
Pricing per million tokens mein hoti hai.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
# Groq pricing (per million tokens)
|
|
184
|
+
pricing = {
|
|
185
|
+
"llama-3.3-70b-versatile": {"input": 0.59, "output": 0.79},
|
|
186
|
+
"llama-3.1-8b-instant": {"input": 0.05, "output": 0.08},
|
|
187
|
+
"mixtral-8x7b-32768": {"input": 0.24, "output": 0.24},
|
|
188
|
+
"gemma2-9b-it": {"input": 0.20, "output": 0.20},
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# Model pricing dhundho — nahi mila toh default
|
|
192
|
+
model_pricing = pricing.get(model, {"input": 0.50, "output": 0.50})
|
|
193
|
+
|
|
194
|
+
# Cost = (tokens / 1,000,000) * price_per_million
|
|
195
|
+
input_cost = (prompt_tokens / 1_000_000) * model_pricing["input"]
|
|
196
|
+
output_cost = (completion_tokens / 1_000_000) * model_pricing["output"]
|
|
197
|
+
|
|
198
|
+
return round(input_cost + output_cost, 6)
|
observiq_sdk/sender.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
class TraceSender:
|
|
7
|
+
def __init__(self, api_key: str, base_url: str):
|
|
8
|
+
self.api_key = api_key
|
|
9
|
+
self.base_url = base_url.rstrip("/")
|
|
10
|
+
self.headers = {
|
|
11
|
+
"Authorization": f"Bearer {api_key}",
|
|
12
|
+
"Content-Type": "application/json"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def send(self, trace_data: dict):
|
|
16
|
+
try:
|
|
17
|
+
response = requests.post(
|
|
18
|
+
f"{self.base_url}/traces",
|
|
19
|
+
headers=self.headers,
|
|
20
|
+
json=trace_data,
|
|
21
|
+
timeout=10
|
|
22
|
+
)
|
|
23
|
+
print(f"ObservIQ trace sent: {response.status_code}")
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(f"ObservIQ error: {e}")
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: observiq-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Production-grade AI application observability SDK — monitor every LLM call automatically
|
|
5
|
+
Author-email: Prince <adarshgupta1@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://observiq.io
|
|
8
|
+
Project-URL: Documentation, https://docs.observiq.io
|
|
9
|
+
Project-URL: Repository, https://github.com/yourusername/observiq-sdk
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/yourusername/observiq-sdk/issues
|
|
11
|
+
Keywords: observability,llm,monitoring,ai,groq,openai,tracing
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: requests>=2.28.0
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
|
|
27
|
+
# ObservIQ SDK
|
|
28
|
+
|
|
29
|
+
> **PostHog for AI agents.** Monitor every LLM call in production — automatically.
|
|
30
|
+
|
|
31
|
+
[](https://badge.fury.io/py/observiq-sdk)
|
|
32
|
+
[](https://www.python.org/downloads/)
|
|
33
|
+
[](https://opensource.org/licenses/MIT)
|
|
34
|
+
|
|
35
|
+
## What is ObservIQ?
|
|
36
|
+
|
|
37
|
+
ObservIQ is a production-grade AI observability platform. Add **2 lines of code** to any AI application and instantly get:
|
|
38
|
+
|
|
39
|
+
- ✅ Real-time trace monitoring — every LLM call recorded
|
|
40
|
+
- ✅ Cost tracking — per feature, per user, per model
|
|
41
|
+
- ✅ Latency analytics — p50, p95, p99 breakdowns
|
|
42
|
+
- ✅ AI-powered anomaly detection — Groq detects issues automatically
|
|
43
|
+
- ✅ Multi-tenant — isolate data per team with API keys
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install observiq-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from groq import Groq
|
|
55
|
+
from observiq_sdk import ObservIQ
|
|
56
|
+
|
|
57
|
+
# Setup
|
|
58
|
+
groq_client = Groq(api_key="your-groq-key")
|
|
59
|
+
oiq = ObservIQ(
|
|
60
|
+
api_key="oiq_your_key_here",
|
|
61
|
+
base_url="https://api.observiq.io" # or http://localhost:8000
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Wrap your AI function — that's it!
|
|
65
|
+
response = oiq.trace(groq_client.chat.completions.create)(
|
|
66
|
+
model="llama-3.3-70b-versatile",
|
|
67
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
print(response.choices[0].message.content)
|
|
71
|
+
# Trace automatically saved to ObservIQ dashboard ✅
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Supported Providers
|
|
75
|
+
|
|
76
|
+
| Provider | Status |
|
|
77
|
+
|----------|--------|
|
|
78
|
+
| Groq | ✅ Supported |
|
|
79
|
+
| OpenAI | ✅ Supported |
|
|
80
|
+
| Anthropic | ✅ Supported |
|
|
81
|
+
| Any OpenAI-compatible API | ✅ Supported |
|
|
82
|
+
|
|
83
|
+
## Features
|
|
84
|
+
|
|
85
|
+
### Automatic Capture
|
|
86
|
+
ObservIQ automatically captures:
|
|
87
|
+
- Model name
|
|
88
|
+
- Input prompt (first 1000 chars)
|
|
89
|
+
- Output response (first 1000 chars)
|
|
90
|
+
- Latency (ms)
|
|
91
|
+
- Token usage (prompt + completion)
|
|
92
|
+
- Cost (USD) — calculated per model pricing
|
|
93
|
+
- Status (success/error)
|
|
94
|
+
- Error messages
|
|
95
|
+
|
|
96
|
+
### Feature Tracking
|
|
97
|
+
```python
|
|
98
|
+
response = oiq.trace(
|
|
99
|
+
groq_client.chat.completions.create,
|
|
100
|
+
feature_name="customer_support", # track by feature
|
|
101
|
+
user_identifier="user_123" # track by user
|
|
102
|
+
)(model="llama-3.3-70b-versatile", messages=[...])
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Error Tracking
|
|
106
|
+
Errors are automatically captured — no extra code needed:
|
|
107
|
+
```python
|
|
108
|
+
try:
|
|
109
|
+
response = oiq.trace(groq_client.chat.completions.create)(...)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
# ObservIQ already logged this error trace
|
|
112
|
+
raise
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Disable in Tests
|
|
116
|
+
```python
|
|
117
|
+
oiq = ObservIQ(
|
|
118
|
+
api_key="oiq_xxx",
|
|
119
|
+
enabled=False # No traces sent in test environment
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Dashboard
|
|
124
|
+
|
|
125
|
+
View all your traces at [observiq.io](https://observiq.io)
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT © 2026 ObservIQ
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
observiq_sdk/__init__.py,sha256=1NoCo-1enUPE0N-acr2SW4umugXnzMu6j0T2HTcQ8I8,89
|
|
2
|
+
observiq_sdk/client.py,sha256=H1VshvQXO2Dc8JeHBJV4cSnprFPjMbBviYR3hWvvW58,6494
|
|
3
|
+
observiq_sdk/sender.py,sha256=1XsROLcwvukIhXdlHXxEYBia33CCeJDdYbuD-LEz5nQ,757
|
|
4
|
+
observiq_sdk-0.1.0.dist-info/METADATA,sha256=S91qeF9Us2uF7U6pxnHxIh5nczU3K7r9-03cMme0uoY,4008
|
|
5
|
+
observiq_sdk-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
observiq_sdk-0.1.0.dist-info/top_level.txt,sha256=C3yUkDXkut7rgDUO7PUWfU7np4dVrYtZksYHusgngUY,13
|
|
7
|
+
observiq_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
observiq_sdk
|