megflow-observability 0.1.0__tar.gz
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.
- megflow_observability-0.1.0/PKG-INFO +97 -0
- megflow_observability-0.1.0/README.md +71 -0
- megflow_observability-0.1.0/megflow_observability/__init__.py +7 -0
- megflow_observability-0.1.0/megflow_observability/anthropic_wrapper.py +74 -0
- megflow_observability-0.1.0/megflow_observability/client.py +84 -0
- megflow_observability-0.1.0/megflow_observability/openai_wrapper.py +82 -0
- megflow_observability-0.1.0/megflow_observability/pricing.py +69 -0
- megflow_observability-0.1.0/megflow_observability.egg-info/PKG-INFO +97 -0
- megflow_observability-0.1.0/megflow_observability.egg-info/SOURCES.txt +12 -0
- megflow_observability-0.1.0/megflow_observability.egg-info/dependency_links.txt +1 -0
- megflow_observability-0.1.0/megflow_observability.egg-info/requires.txt +10 -0
- megflow_observability-0.1.0/megflow_observability.egg-info/top_level.txt +1 -0
- megflow_observability-0.1.0/pyproject.toml +35 -0
- megflow_observability-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: megflow-observability
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Megflow AI API observability SDK — auto-instrument OpenAI and Anthropic calls
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://observability.megflow.com
|
|
7
|
+
Project-URL: Repository, https://github.com/saleem-megflow/megflow-observe
|
|
8
|
+
Keywords: llm,observability,openai,anthropic,monitoring
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Provides-Extra: openai
|
|
20
|
+
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
21
|
+
Provides-Extra: anthropic
|
|
22
|
+
Requires-Dist: anthropic>=0.20.0; extra == "anthropic"
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: openai>=1.0.0; extra == "all"
|
|
25
|
+
Requires-Dist: anthropic>=0.20.0; extra == "all"
|
|
26
|
+
|
|
27
|
+
# megflow-observability
|
|
28
|
+
|
|
29
|
+
Python SDK for [Megflow AI Observability](https://observability.megflow.com) — track tokens, cost, latency and errors across OpenAI, Anthropic, Gemini and more.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install megflow-observability
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from megflow_observability import MegflowObserve
|
|
41
|
+
|
|
42
|
+
observe = MegflowObserve(api_key="obs_your_key_here")
|
|
43
|
+
|
|
44
|
+
observe.track(
|
|
45
|
+
provider="openai",
|
|
46
|
+
model="gpt-4o",
|
|
47
|
+
input_tokens=100,
|
|
48
|
+
output_tokens=50,
|
|
49
|
+
total_tokens=150,
|
|
50
|
+
cost_usd=0.00075,
|
|
51
|
+
latency_ms=320,
|
|
52
|
+
status_code=200,
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Auto-instrument OpenAI
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from openai import OpenAI
|
|
60
|
+
from megflow_observability import MegflowObserve, wrap_openai
|
|
61
|
+
|
|
62
|
+
client = wrap_openai(OpenAI(), MegflowObserve(api_key="obs_your_key_here"))
|
|
63
|
+
|
|
64
|
+
# All calls are tracked automatically
|
|
65
|
+
response = client.chat.completions.create(
|
|
66
|
+
model="gpt-4o",
|
|
67
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Auto-instrument Anthropic
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import anthropic
|
|
75
|
+
from megflow_observability import MegflowObserve, wrap_anthropic
|
|
76
|
+
|
|
77
|
+
client = wrap_anthropic(anthropic.Anthropic(), MegflowObserve(api_key="obs_your_key_here"))
|
|
78
|
+
|
|
79
|
+
response = client.messages.create(
|
|
80
|
+
model="claude-sonnet-4-6",
|
|
81
|
+
max_tokens=1024,
|
|
82
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Optional dependencies
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install megflow-observability[openai] # includes openai
|
|
90
|
+
pip install megflow-observability[anthropic] # includes anthropic
|
|
91
|
+
pip install megflow-observability[all] # includes both
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Links
|
|
95
|
+
|
|
96
|
+
- Dashboard: [observability.megflow.com](https://observability.megflow.com)
|
|
97
|
+
- JS SDK: [npmjs.com/package/megflow-observability](https://www.npmjs.com/package/megflow-observability)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# megflow-observability
|
|
2
|
+
|
|
3
|
+
Python SDK for [Megflow AI Observability](https://observability.megflow.com) — track tokens, cost, latency and errors across OpenAI, Anthropic, Gemini and more.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install megflow-observability
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from megflow_observability import MegflowObserve
|
|
15
|
+
|
|
16
|
+
observe = MegflowObserve(api_key="obs_your_key_here")
|
|
17
|
+
|
|
18
|
+
observe.track(
|
|
19
|
+
provider="openai",
|
|
20
|
+
model="gpt-4o",
|
|
21
|
+
input_tokens=100,
|
|
22
|
+
output_tokens=50,
|
|
23
|
+
total_tokens=150,
|
|
24
|
+
cost_usd=0.00075,
|
|
25
|
+
latency_ms=320,
|
|
26
|
+
status_code=200,
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Auto-instrument OpenAI
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from openai import OpenAI
|
|
34
|
+
from megflow_observability import MegflowObserve, wrap_openai
|
|
35
|
+
|
|
36
|
+
client = wrap_openai(OpenAI(), MegflowObserve(api_key="obs_your_key_here"))
|
|
37
|
+
|
|
38
|
+
# All calls are tracked automatically
|
|
39
|
+
response = client.chat.completions.create(
|
|
40
|
+
model="gpt-4o",
|
|
41
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Auto-instrument Anthropic
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import anthropic
|
|
49
|
+
from megflow_observability import MegflowObserve, wrap_anthropic
|
|
50
|
+
|
|
51
|
+
client = wrap_anthropic(anthropic.Anthropic(), MegflowObserve(api_key="obs_your_key_here"))
|
|
52
|
+
|
|
53
|
+
response = client.messages.create(
|
|
54
|
+
model="claude-sonnet-4-6",
|
|
55
|
+
max_tokens=1024,
|
|
56
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Optional dependencies
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install megflow-observability[openai] # includes openai
|
|
64
|
+
pip install megflow-observability[anthropic] # includes anthropic
|
|
65
|
+
pip install megflow-observability[all] # includes both
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Links
|
|
69
|
+
|
|
70
|
+
- Dashboard: [observability.megflow.com](https://observability.megflow.com)
|
|
71
|
+
- JS SDK: [npmjs.com/package/megflow-observability](https://www.npmjs.com/package/megflow-observability)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from .pricing import calc_cost
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import anthropic
|
|
11
|
+
from .client import MegflowObserve
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def wrap_anthropic(client: "anthropic.Anthropic", observe: "MegflowObserve") -> "anthropic.Anthropic":
|
|
15
|
+
original_create = client.messages.create
|
|
16
|
+
|
|
17
|
+
def patched_create(*args: Any, **kwargs: Any) -> Any:
|
|
18
|
+
start = time.monotonic()
|
|
19
|
+
model: str = kwargs.get("model", "")
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
response: Any = original_create(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
# Don't track streaming responses
|
|
25
|
+
if hasattr(response, "__iter__") and not hasattr(response, "usage"):
|
|
26
|
+
return response
|
|
27
|
+
|
|
28
|
+
latency_ms = int((time.monotonic() - start) * 1000)
|
|
29
|
+
usage = getattr(response, "usage", None)
|
|
30
|
+
input_tokens: int = getattr(usage, "input_tokens", 0) or 0
|
|
31
|
+
output_tokens: int = getattr(usage, "output_tokens", 0) or 0
|
|
32
|
+
cached_tokens: int = getattr(usage, "cache_read_input_tokens", 0) or 0
|
|
33
|
+
finish_reason: str = getattr(response, "stop_reason", "") or ""
|
|
34
|
+
|
|
35
|
+
observe.track(
|
|
36
|
+
provider="anthropic",
|
|
37
|
+
model=model,
|
|
38
|
+
input_tokens=input_tokens,
|
|
39
|
+
output_tokens=output_tokens,
|
|
40
|
+
total_tokens=input_tokens + output_tokens,
|
|
41
|
+
cached_tokens=cached_tokens,
|
|
42
|
+
cost_usd=calc_cost("anthropic", model, input_tokens, output_tokens),
|
|
43
|
+
latency_ms=latency_ms,
|
|
44
|
+
status_code=200,
|
|
45
|
+
error="",
|
|
46
|
+
finish_reason=finish_reason,
|
|
47
|
+
request_id=getattr(response, "id", "") or "",
|
|
48
|
+
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return response
|
|
52
|
+
|
|
53
|
+
except Exception as exc:
|
|
54
|
+
latency_ms = int((time.monotonic() - start) * 1000)
|
|
55
|
+
status_code: int = getattr(exc, "status_code", None) or getattr(exc, "status", 500) or 500
|
|
56
|
+
|
|
57
|
+
observe.track(
|
|
58
|
+
provider="anthropic",
|
|
59
|
+
model=model,
|
|
60
|
+
input_tokens=0,
|
|
61
|
+
output_tokens=0,
|
|
62
|
+
total_tokens=0,
|
|
63
|
+
cost_usd=0.0,
|
|
64
|
+
latency_ms=latency_ms,
|
|
65
|
+
status_code=status_code,
|
|
66
|
+
error=str(exc),
|
|
67
|
+
rate_limit_remaining=0 if status_code == 429 else -1,
|
|
68
|
+
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
raise
|
|
72
|
+
|
|
73
|
+
client.messages.create = patched_create # type: ignore[method-assign]
|
|
74
|
+
return client
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import threading
|
|
5
|
+
import urllib.request
|
|
6
|
+
import urllib.error
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
SDK_VERSION = "0.1.0"
|
|
11
|
+
DEFAULT_ENDPOINT = "https://api.observability.megflow.com"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MegflowObserve:
|
|
15
|
+
def __init__(self, api_key: str, endpoint: str = DEFAULT_ENDPOINT) -> None:
|
|
16
|
+
self._api_key = api_key
|
|
17
|
+
self._endpoint = endpoint.rstrip("/")
|
|
18
|
+
|
|
19
|
+
def track(
|
|
20
|
+
self,
|
|
21
|
+
provider: str,
|
|
22
|
+
model: str,
|
|
23
|
+
input_tokens: int,
|
|
24
|
+
output_tokens: int,
|
|
25
|
+
total_tokens: int,
|
|
26
|
+
cost_usd: float,
|
|
27
|
+
latency_ms: int,
|
|
28
|
+
status_code: int,
|
|
29
|
+
error: str = "",
|
|
30
|
+
finish_reason: str = "",
|
|
31
|
+
environment: str = "",
|
|
32
|
+
user_id: str = "",
|
|
33
|
+
cached_tokens: int = 0,
|
|
34
|
+
rate_limit_remaining: int = -1,
|
|
35
|
+
retry_count: int = 0,
|
|
36
|
+
request_id: str = "",
|
|
37
|
+
timestamp: Optional[str] = None,
|
|
38
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
payload: Dict[str, Any] = {
|
|
41
|
+
"provider": provider,
|
|
42
|
+
"model": model,
|
|
43
|
+
"input_tokens": input_tokens,
|
|
44
|
+
"output_tokens": output_tokens,
|
|
45
|
+
"total_tokens": total_tokens,
|
|
46
|
+
"cached_tokens": cached_tokens,
|
|
47
|
+
"cost_usd": cost_usd,
|
|
48
|
+
"latency_ms": latency_ms,
|
|
49
|
+
"status_code": status_code,
|
|
50
|
+
"error": error,
|
|
51
|
+
"finish_reason": finish_reason,
|
|
52
|
+
"environment": environment,
|
|
53
|
+
"user_id": user_id,
|
|
54
|
+
"rate_limit_remaining": rate_limit_remaining,
|
|
55
|
+
"retry_count": retry_count,
|
|
56
|
+
"request_id": request_id,
|
|
57
|
+
"timestamp": timestamp or datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
58
|
+
"metadata": {
|
|
59
|
+
**(metadata or {}),
|
|
60
|
+
"sdk_version": SDK_VERSION,
|
|
61
|
+
"sdk_language": "python",
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
thread = threading.Thread(target=self._send, args=(payload,), daemon=True)
|
|
66
|
+
thread.start()
|
|
67
|
+
|
|
68
|
+
def _send(self, payload: Dict[str, Any]) -> None:
|
|
69
|
+
try:
|
|
70
|
+
body = json.dumps(payload).encode()
|
|
71
|
+
req = urllib.request.Request(
|
|
72
|
+
url=f"{self._endpoint}/v1/events",
|
|
73
|
+
data=body,
|
|
74
|
+
headers={
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
"X-API-Key": self._api_key,
|
|
77
|
+
"Content-Length": str(len(body)),
|
|
78
|
+
},
|
|
79
|
+
method="POST",
|
|
80
|
+
)
|
|
81
|
+
with urllib.request.urlopen(req, timeout=5):
|
|
82
|
+
pass
|
|
83
|
+
except Exception:
|
|
84
|
+
pass # observability must never break customer code
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from .pricing import calc_cost
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from openai import OpenAI
|
|
11
|
+
from .client import MegflowObserve
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def wrap_openai(client: "OpenAI", observe: "MegflowObserve") -> "OpenAI":
|
|
15
|
+
original_create = client.chat.completions.create
|
|
16
|
+
|
|
17
|
+
def patched_create(*args: Any, **kwargs: Any) -> Any:
|
|
18
|
+
start = time.monotonic()
|
|
19
|
+
model: str = kwargs.get("model", "")
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
response: Any = original_create(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
# Don't track streaming responses — chunks have no usage data
|
|
25
|
+
if hasattr(response, "__iter__") and not hasattr(response, "usage"):
|
|
26
|
+
return response
|
|
27
|
+
|
|
28
|
+
latency_ms = int((time.monotonic() - start) * 1000)
|
|
29
|
+
input_tokens: int = getattr(getattr(response, "usage", None), "prompt_tokens", 0) or 0
|
|
30
|
+
output_tokens: int = getattr(getattr(response, "usage", None), "completion_tokens", 0) or 0
|
|
31
|
+
total_tokens: int = getattr(getattr(response, "usage", None), "total_tokens", 0) or input_tokens + output_tokens
|
|
32
|
+
|
|
33
|
+
# Cached tokens (OpenAI prompt_tokens_details)
|
|
34
|
+
details = getattr(getattr(response, "usage", None), "prompt_tokens_details", None)
|
|
35
|
+
cached_tokens: int = getattr(details, "cached_tokens", 0) or 0
|
|
36
|
+
|
|
37
|
+
# Finish reason
|
|
38
|
+
choices = getattr(response, "choices", None)
|
|
39
|
+
finish_reason: str = ""
|
|
40
|
+
if choices:
|
|
41
|
+
finish_reason = getattr(choices[0], "finish_reason", "") or ""
|
|
42
|
+
|
|
43
|
+
observe.track(
|
|
44
|
+
provider="openai",
|
|
45
|
+
model=model,
|
|
46
|
+
input_tokens=input_tokens,
|
|
47
|
+
output_tokens=output_tokens,
|
|
48
|
+
total_tokens=total_tokens,
|
|
49
|
+
cached_tokens=cached_tokens,
|
|
50
|
+
cost_usd=calc_cost("openai", model, input_tokens, output_tokens),
|
|
51
|
+
latency_ms=latency_ms,
|
|
52
|
+
status_code=200,
|
|
53
|
+
error="",
|
|
54
|
+
finish_reason=finish_reason,
|
|
55
|
+
request_id=getattr(response, "id", "") or "",
|
|
56
|
+
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return response
|
|
60
|
+
|
|
61
|
+
except Exception as exc:
|
|
62
|
+
latency_ms = int((time.monotonic() - start) * 1000)
|
|
63
|
+
status_code: int = getattr(exc, "status_code", None) or getattr(exc, "status", 500) or 500
|
|
64
|
+
|
|
65
|
+
observe.track(
|
|
66
|
+
provider="openai",
|
|
67
|
+
model=model,
|
|
68
|
+
input_tokens=0,
|
|
69
|
+
output_tokens=0,
|
|
70
|
+
total_tokens=0,
|
|
71
|
+
cost_usd=0.0,
|
|
72
|
+
latency_ms=latency_ms,
|
|
73
|
+
status_code=status_code,
|
|
74
|
+
error=str(exc),
|
|
75
|
+
rate_limit_remaining=0 if status_code == 429 else -1,
|
|
76
|
+
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
raise
|
|
80
|
+
|
|
81
|
+
client.chat.completions.create = patched_create # type: ignore[method-assign]
|
|
82
|
+
return client
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
# Cost per 1M tokens [input, output] in USD
|
|
5
|
+
_PRICING: Dict[str, Dict[str, Tuple[float, float]]] = {
|
|
6
|
+
"openai": {
|
|
7
|
+
"gpt-4o": (2.50, 10.00),
|
|
8
|
+
"gpt-4o-mini": (0.15, 0.60),
|
|
9
|
+
"gpt-4-turbo": (10.00, 30.00),
|
|
10
|
+
"gpt-4-turbo-preview": (10.00, 30.00),
|
|
11
|
+
"gpt-4": (30.00, 60.00),
|
|
12
|
+
"gpt-3.5-turbo": (0.50, 1.50),
|
|
13
|
+
"o1": (15.00, 60.00),
|
|
14
|
+
"o1-mini": (3.00, 12.00),
|
|
15
|
+
"o3-mini": (1.10, 4.40),
|
|
16
|
+
},
|
|
17
|
+
"anthropic": {
|
|
18
|
+
"claude-3-5-sonnet-20241022": (3.00, 15.00),
|
|
19
|
+
"claude-3-5-sonnet-20240620": (3.00, 15.00),
|
|
20
|
+
"claude-3-5-haiku-20241022": (0.80, 4.00),
|
|
21
|
+
"claude-3-opus-20240229": (15.00, 75.00),
|
|
22
|
+
"claude-3-sonnet-20240229": (3.00, 15.00),
|
|
23
|
+
"claude-3-haiku-20240307": (0.25, 1.25),
|
|
24
|
+
"claude-sonnet-4-6": (3.00, 15.00),
|
|
25
|
+
"claude-opus-4-8": (15.00, 75.00),
|
|
26
|
+
"claude-haiku-4-5-20251001": (0.80, 4.00),
|
|
27
|
+
},
|
|
28
|
+
"gemini": {
|
|
29
|
+
"gemini-1.5-pro": (1.25, 5.00),
|
|
30
|
+
"gemini-1.5-flash": (0.075, 0.30),
|
|
31
|
+
"gemini-2.0-flash": (0.10, 0.40),
|
|
32
|
+
"gemini-2.5-pro": (1.25, 10.00),
|
|
33
|
+
"gemini-2.5-flash": (0.15, 0.60),
|
|
34
|
+
},
|
|
35
|
+
"mistral": {
|
|
36
|
+
"mistral-large-latest": (2.00, 6.00),
|
|
37
|
+
"mistral-small-latest": (0.20, 0.60),
|
|
38
|
+
"mistral-7b-instruct": (0.25, 0.25),
|
|
39
|
+
},
|
|
40
|
+
"groq": {
|
|
41
|
+
"llama3-8b-8192": (0.05, 0.10),
|
|
42
|
+
"llama3-70b-8192": (0.59, 0.79),
|
|
43
|
+
"mixtral-8x7b-32768": (0.24, 0.24),
|
|
44
|
+
},
|
|
45
|
+
"cohere": {
|
|
46
|
+
"command-r-plus": (3.00, 15.00),
|
|
47
|
+
"command-r": (0.50, 1.50),
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def calc_cost(provider: str, model: str, input_tokens: int, output_tokens: int) -> float:
|
|
53
|
+
provider_pricing = _PRICING.get(provider.lower())
|
|
54
|
+
if not provider_pricing:
|
|
55
|
+
return 0.0
|
|
56
|
+
|
|
57
|
+
rates: Optional[Tuple[float, float]] = provider_pricing.get(model)
|
|
58
|
+
if rates is None:
|
|
59
|
+
# prefix match for versioned model names
|
|
60
|
+
for k, v in provider_pricing.items():
|
|
61
|
+
if model.startswith(k) or k.startswith(model):
|
|
62
|
+
rates = v
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
if rates is None:
|
|
66
|
+
return 0.0
|
|
67
|
+
|
|
68
|
+
input_rate, output_rate = rates
|
|
69
|
+
return (input_tokens * input_rate + output_tokens * output_rate) / 1_000_000
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: megflow-observability
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Megflow AI API observability SDK — auto-instrument OpenAI and Anthropic calls
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://observability.megflow.com
|
|
7
|
+
Project-URL: Repository, https://github.com/saleem-megflow/megflow-observe
|
|
8
|
+
Keywords: llm,observability,openai,anthropic,monitoring
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Provides-Extra: openai
|
|
20
|
+
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
21
|
+
Provides-Extra: anthropic
|
|
22
|
+
Requires-Dist: anthropic>=0.20.0; extra == "anthropic"
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: openai>=1.0.0; extra == "all"
|
|
25
|
+
Requires-Dist: anthropic>=0.20.0; extra == "all"
|
|
26
|
+
|
|
27
|
+
# megflow-observability
|
|
28
|
+
|
|
29
|
+
Python SDK for [Megflow AI Observability](https://observability.megflow.com) — track tokens, cost, latency and errors across OpenAI, Anthropic, Gemini and more.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install megflow-observability
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from megflow_observability import MegflowObserve
|
|
41
|
+
|
|
42
|
+
observe = MegflowObserve(api_key="obs_your_key_here")
|
|
43
|
+
|
|
44
|
+
observe.track(
|
|
45
|
+
provider="openai",
|
|
46
|
+
model="gpt-4o",
|
|
47
|
+
input_tokens=100,
|
|
48
|
+
output_tokens=50,
|
|
49
|
+
total_tokens=150,
|
|
50
|
+
cost_usd=0.00075,
|
|
51
|
+
latency_ms=320,
|
|
52
|
+
status_code=200,
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Auto-instrument OpenAI
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from openai import OpenAI
|
|
60
|
+
from megflow_observability import MegflowObserve, wrap_openai
|
|
61
|
+
|
|
62
|
+
client = wrap_openai(OpenAI(), MegflowObserve(api_key="obs_your_key_here"))
|
|
63
|
+
|
|
64
|
+
# All calls are tracked automatically
|
|
65
|
+
response = client.chat.completions.create(
|
|
66
|
+
model="gpt-4o",
|
|
67
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Auto-instrument Anthropic
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import anthropic
|
|
75
|
+
from megflow_observability import MegflowObserve, wrap_anthropic
|
|
76
|
+
|
|
77
|
+
client = wrap_anthropic(anthropic.Anthropic(), MegflowObserve(api_key="obs_your_key_here"))
|
|
78
|
+
|
|
79
|
+
response = client.messages.create(
|
|
80
|
+
model="claude-sonnet-4-6",
|
|
81
|
+
max_tokens=1024,
|
|
82
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Optional dependencies
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install megflow-observability[openai] # includes openai
|
|
90
|
+
pip install megflow-observability[anthropic] # includes anthropic
|
|
91
|
+
pip install megflow-observability[all] # includes both
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Links
|
|
95
|
+
|
|
96
|
+
- Dashboard: [observability.megflow.com](https://observability.megflow.com)
|
|
97
|
+
- JS SDK: [npmjs.com/package/megflow-observability](https://www.npmjs.com/package/megflow-observability)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
megflow_observability/__init__.py
|
|
4
|
+
megflow_observability/anthropic_wrapper.py
|
|
5
|
+
megflow_observability/client.py
|
|
6
|
+
megflow_observability/openai_wrapper.py
|
|
7
|
+
megflow_observability/pricing.py
|
|
8
|
+
megflow_observability.egg-info/PKG-INFO
|
|
9
|
+
megflow_observability.egg-info/SOURCES.txt
|
|
10
|
+
megflow_observability.egg-info/dependency_links.txt
|
|
11
|
+
megflow_observability.egg-info/requires.txt
|
|
12
|
+
megflow_observability.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
megflow_observability
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "megflow-observability"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Megflow AI API observability SDK — auto-instrument OpenAI and Anthropic calls"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["llm", "observability", "openai", "anthropic", "monitoring"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.8",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
openai = ["openai>=1.0.0"]
|
|
26
|
+
anthropic = ["anthropic>=0.20.0"]
|
|
27
|
+
all = ["openai>=1.0.0", "anthropic>=0.20.0"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://observability.megflow.com"
|
|
31
|
+
Repository = "https://github.com/saleem-megflow/megflow-observe"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
where = ["."]
|
|
35
|
+
include = ["megflow_observability*"]
|