spendlens 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.
- spendlens-0.1.0/PKG-INFO +46 -0
- spendlens-0.1.0/README.md +29 -0
- spendlens-0.1.0/setup.cfg +4 -0
- spendlens-0.1.0/setup.py +14 -0
- spendlens-0.1.0/spendlens/__init__.py +87 -0
- spendlens-0.1.0/spendlens/pricing.py +7 -0
- spendlens-0.1.0/spendlens.egg-info/PKG-INFO +46 -0
- spendlens-0.1.0/spendlens.egg-info/SOURCES.txt +9 -0
- spendlens-0.1.0/spendlens.egg-info/dependency_links.txt +1 -0
- spendlens-0.1.0/spendlens.egg-info/requires.txt +1 -0
- spendlens-0.1.0/spendlens.egg-info/top_level.txt +1 -0
spendlens-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spendlens
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LLM cost attribution — track OpenAI spend by user, feature, and endpoint
|
|
5
|
+
Home-page: https://github.com/ChrisRobinT/spendlens.dev-product
|
|
6
|
+
Author: Chris Robin
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: openai>=1.0.0
|
|
10
|
+
Dynamic: author
|
|
11
|
+
Dynamic: description
|
|
12
|
+
Dynamic: description-content-type
|
|
13
|
+
Dynamic: home-page
|
|
14
|
+
Dynamic: requires-dist
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
Dynamic: summary
|
|
17
|
+
|
|
18
|
+
# SpendLens
|
|
19
|
+
|
|
20
|
+
Track your OpenAI costs by user, feature, and endpoint. Two-line integration.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
pip install spendlens
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from spendlens import SpendLensClient
|
|
32
|
+
|
|
33
|
+
client = SpendLensClient(api_key="your-spendlens-api-key")
|
|
34
|
+
|
|
35
|
+
response = client.chat.completions.create(
|
|
36
|
+
model="gpt-4o",
|
|
37
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
38
|
+
tags={"user_id": "u_123", "feature": "chat", "endpoint": "/api/chat"},
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Your costs appear on the dashboard. That's it.
|
|
43
|
+
|
|
44
|
+
- Never logs prompts or completions. Only metadata.
|
|
45
|
+
- Never modifies the OpenAI response.
|
|
46
|
+
- Never adds latency. Logging is async.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# SpendLens
|
|
2
|
+
|
|
3
|
+
Track your OpenAI costs by user, feature, and endpoint. Two-line integration.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install spendlens
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from spendlens import SpendLensClient
|
|
15
|
+
|
|
16
|
+
client = SpendLensClient(api_key="your-spendlens-api-key")
|
|
17
|
+
|
|
18
|
+
response = client.chat.completions.create(
|
|
19
|
+
model="gpt-4o",
|
|
20
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
21
|
+
tags={"user_id": "u_123", "feature": "chat", "endpoint": "/api/chat"},
|
|
22
|
+
)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Your costs appear on the dashboard. That's it.
|
|
26
|
+
|
|
27
|
+
- Never logs prompts or completions. Only metadata.
|
|
28
|
+
- Never modifies the OpenAI response.
|
|
29
|
+
- Never adds latency. Logging is async.
|
spendlens-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="spendlens",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
description="LLM cost attribution — track OpenAI spend by user, feature, and endpoint",
|
|
7
|
+
long_description=open("README.md").read(),
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
author="Chris Robin",
|
|
10
|
+
url="https://github.com/ChrisRobinT/spendlens.dev-product",
|
|
11
|
+
packages=find_packages(),
|
|
12
|
+
python_requires=">=3.8",
|
|
13
|
+
install_requires=["openai>=1.0.0"],
|
|
14
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import threading
|
|
3
|
+
import json
|
|
4
|
+
from urllib.request import Request, urlopen
|
|
5
|
+
from openai import OpenAI
|
|
6
|
+
from .pricing import PRICING
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _compute_cost(model, prompt_tokens, completion_tokens):
|
|
10
|
+
if model not in PRICING:
|
|
11
|
+
return 0.0, True
|
|
12
|
+
prices = PRICING[model]
|
|
13
|
+
cost = (prompt_tokens * prices["input"] / 1_000_000) + (completion_tokens * prices["output"] / 1_000_000)
|
|
14
|
+
return round(cost, 6), False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _send_event(api_url, api_key, payload):
|
|
18
|
+
try:
|
|
19
|
+
data = json.dumps(payload).encode("utf-8")
|
|
20
|
+
req = Request(
|
|
21
|
+
f"{api_url}/api/v1/ingest",
|
|
22
|
+
data=data,
|
|
23
|
+
headers={
|
|
24
|
+
"Authorization": f"Bearer {api_key}",
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
},
|
|
27
|
+
method="POST",
|
|
28
|
+
)
|
|
29
|
+
urlopen(req, timeout=5)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"[spendlens] Warning: failed to send event: {e}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SpendLensClient:
|
|
35
|
+
def __init__(self, api_key, api_url="https://spendlensdev-product-production.up.railway.app", **openai_kwargs):
|
|
36
|
+
self._api_key = api_key
|
|
37
|
+
self._api_url = api_url
|
|
38
|
+
self._openai = OpenAI(**openai_kwargs)
|
|
39
|
+
self.chat = _ChatNamespace(self)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _ChatNamespace:
|
|
43
|
+
def __init__(self, client):
|
|
44
|
+
self._client = client
|
|
45
|
+
self.completions = _CompletionsNamespace(client)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _CompletionsNamespace:
|
|
49
|
+
def __init__(self, client):
|
|
50
|
+
self._client = client
|
|
51
|
+
|
|
52
|
+
def create(self, **kwargs):
|
|
53
|
+
tags = kwargs.pop("tags", kwargs.pop("costlens_tags", {}))
|
|
54
|
+
start_time = time.time()
|
|
55
|
+
|
|
56
|
+
response = self._client._openai.chat.completions.create(**kwargs)
|
|
57
|
+
|
|
58
|
+
latency_ms = int((time.time() - start_time) * 1000)
|
|
59
|
+
model = response.model
|
|
60
|
+
prompt_tokens = response.usage.prompt_tokens
|
|
61
|
+
completion_tokens = response.usage.completion_tokens
|
|
62
|
+
total_tokens = response.usage.total_tokens
|
|
63
|
+
cost_usd, unknown_model = _compute_cost(model, prompt_tokens, completion_tokens)
|
|
64
|
+
|
|
65
|
+
payload = {
|
|
66
|
+
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
|
|
67
|
+
"model": model,
|
|
68
|
+
"prompt_tokens": prompt_tokens,
|
|
69
|
+
"completion_tokens": completion_tokens,
|
|
70
|
+
"total_tokens": total_tokens,
|
|
71
|
+
"cost_usd": cost_usd,
|
|
72
|
+
"latency_ms": latency_ms,
|
|
73
|
+
"status": "success",
|
|
74
|
+
"tags": {
|
|
75
|
+
"user_id": tags.get("user_id"),
|
|
76
|
+
"feature": tags.get("feature"),
|
|
77
|
+
"endpoint": tags.get("endpoint"),
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
threading.Thread(
|
|
82
|
+
target=_send_event,
|
|
83
|
+
args=(self._client._api_url, self._client._api_key, payload),
|
|
84
|
+
daemon=True,
|
|
85
|
+
).start()
|
|
86
|
+
|
|
87
|
+
return response
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spendlens
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LLM cost attribution — track OpenAI spend by user, feature, and endpoint
|
|
5
|
+
Home-page: https://github.com/ChrisRobinT/spendlens.dev-product
|
|
6
|
+
Author: Chris Robin
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: openai>=1.0.0
|
|
10
|
+
Dynamic: author
|
|
11
|
+
Dynamic: description
|
|
12
|
+
Dynamic: description-content-type
|
|
13
|
+
Dynamic: home-page
|
|
14
|
+
Dynamic: requires-dist
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
Dynamic: summary
|
|
17
|
+
|
|
18
|
+
# SpendLens
|
|
19
|
+
|
|
20
|
+
Track your OpenAI costs by user, feature, and endpoint. Two-line integration.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
pip install spendlens
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from spendlens import SpendLensClient
|
|
32
|
+
|
|
33
|
+
client = SpendLensClient(api_key="your-spendlens-api-key")
|
|
34
|
+
|
|
35
|
+
response = client.chat.completions.create(
|
|
36
|
+
model="gpt-4o",
|
|
37
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
38
|
+
tags={"user_id": "u_123", "feature": "chat", "endpoint": "/api/chat"},
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Your costs appear on the dashboard. That's it.
|
|
43
|
+
|
|
44
|
+
- Never logs prompts or completions. Only metadata.
|
|
45
|
+
- Never modifies the OpenAI response.
|
|
46
|
+
- Never adds latency. Logging is async.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openai>=1.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spendlens
|