superpenguin 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.
- superpenguin-0.1.0/.gitignore +18 -0
- superpenguin-0.1.0/LICENSE +21 -0
- superpenguin-0.1.0/PKG-INFO +289 -0
- superpenguin-0.1.0/README.md +257 -0
- superpenguin-0.1.0/pyproject.toml +53 -0
- superpenguin-0.1.0/src/superpenguin/__init__.py +79 -0
- superpenguin-0.1.0/src/superpenguin/_client.py +142 -0
- superpenguin-0.1.0/src/superpenguin/_context.py +7 -0
- superpenguin-0.1.0/src/superpenguin/_litellm.py +278 -0
- superpenguin-0.1.0/src/superpenguin/_pricing.py +77 -0
- superpenguin-0.1.0/src/superpenguin/_trace.py +135 -0
- superpenguin-0.1.0/src/superpenguin/_wrap.py +697 -0
- superpenguin-0.1.0/tests/__init__.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Carrot Labs AI, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: superpenguin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SuperPenguin Python SDK — AI cost management, attribution, and spend tracking
|
|
5
|
+
Project-URL: Homepage, https://carrotlabs.ai
|
|
6
|
+
Project-URL: Documentation, https://carrotlabs.ai
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: ai,anthropic,attribution,cost-management,litellm,llm,monitoring,observability,openai,spend-tracking
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Provides-Extra: anthropic
|
|
24
|
+
Requires-Dist: anthropic; extra == 'anthropic'
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: anthropic; extra == 'dev'
|
|
27
|
+
Requires-Dist: openai; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
29
|
+
Provides-Extra: openai
|
|
30
|
+
Requires-Dist: openai; extra == 'openai'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# SuperPenguin Python SDK
|
|
34
|
+
|
|
35
|
+
Track AI costs automatically. Wrap your OpenAI or Anthropic client — or patch litellm — and every LLM call is captured with token counts, estimated cost, latency, and attribution metadata. No proxy required.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install superpenguin
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or install from source (in the `sdk/python/` directory):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### 1. Wrap your client (one line)
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import superpenguin as sp
|
|
55
|
+
from openai import OpenAI
|
|
56
|
+
|
|
57
|
+
sp.init(api_key="sp_...") # your SuperPenguin API key
|
|
58
|
+
|
|
59
|
+
client = sp.wrap(OpenAI())
|
|
60
|
+
|
|
61
|
+
# Use the client exactly as normal — cost events are captured automatically
|
|
62
|
+
response = client.chat.completions.create(
|
|
63
|
+
model="gpt-4o",
|
|
64
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
65
|
+
)
|
|
66
|
+
print(response.choices[0].message.content)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
That's it. Every `create()` call through the wrapped client is captured with provider, model, token counts, estimated cost (USD), and latency.
|
|
70
|
+
|
|
71
|
+
### 2. Works with Anthropic too
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import superpenguin as sp
|
|
75
|
+
from anthropic import Anthropic
|
|
76
|
+
|
|
77
|
+
sp.init(api_key="sp_...")
|
|
78
|
+
|
|
79
|
+
client = sp.wrap(Anthropic())
|
|
80
|
+
|
|
81
|
+
response = client.messages.create(
|
|
82
|
+
model="claude-sonnet-4-20250514",
|
|
83
|
+
max_tokens=1024,
|
|
84
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. Streaming works transparently
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
client = sp.wrap(OpenAI())
|
|
92
|
+
|
|
93
|
+
stream = client.chat.completions.create(
|
|
94
|
+
model="gpt-4o",
|
|
95
|
+
messages=[{"role": "user", "content": "Tell me a story"}],
|
|
96
|
+
stream=True,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
for chunk in stream:
|
|
100
|
+
if chunk.choices and chunk.choices[0].delta.content:
|
|
101
|
+
print(chunk.choices[0].delta.content, end="", flush=True)
|
|
102
|
+
|
|
103
|
+
# Cost event is submitted automatically when the stream finishes
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 4. LiteLLM support
|
|
107
|
+
|
|
108
|
+
If you use [litellm](https://github.com/BerriAI/litellm) to call 100+ LLM providers through a single interface, one call patches everything:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import superpenguin as sp
|
|
112
|
+
import litellm
|
|
113
|
+
|
|
114
|
+
sp.init(api_key="sp_...")
|
|
115
|
+
sp.patch_litellm()
|
|
116
|
+
|
|
117
|
+
# Every litellm.completion() / litellm.acompletion() is now tracked
|
|
118
|
+
response = litellm.completion(
|
|
119
|
+
model="openai/gpt-4o",
|
|
120
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 5. Add attribution metadata
|
|
125
|
+
|
|
126
|
+
Attach metadata to attribute costs to customers, features, teams, or environments:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# Set defaults for all calls from this client
|
|
130
|
+
client = sp.wrap(OpenAI(), metadata={
|
|
131
|
+
"customer_id": "cust_acme_123",
|
|
132
|
+
"feature": "doc_summary",
|
|
133
|
+
"team": "product",
|
|
134
|
+
"environment": "production",
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
# Or override per-call via extra_body
|
|
138
|
+
response = client.chat.completions.create(
|
|
139
|
+
model="gpt-4o",
|
|
140
|
+
messages=[{"role": "user", "content": "Summarize this document"}],
|
|
141
|
+
extra_body={
|
|
142
|
+
"sp_metadata": {
|
|
143
|
+
"customer_id": "cust_other_456",
|
|
144
|
+
"prompt_key": "summarize_v2",
|
|
145
|
+
"prompt_version": "3",
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## `@sp.trace` Decorator
|
|
152
|
+
|
|
153
|
+
For multi-step pipelines (RAG, agents, chains), use the `@sp.trace` decorator. Any wrapped LLM calls inside the function are automatically linked as children.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import superpenguin as sp
|
|
157
|
+
from openai import OpenAI
|
|
158
|
+
|
|
159
|
+
sp.init(api_key="sp_...")
|
|
160
|
+
client = sp.wrap(OpenAI())
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@sp.trace
|
|
164
|
+
def answer_question(question: str) -> str:
|
|
165
|
+
docs = search_knowledge_base(question)
|
|
166
|
+
|
|
167
|
+
response = client.chat.completions.create(
|
|
168
|
+
model="gpt-4o",
|
|
169
|
+
messages=[
|
|
170
|
+
{"role": "system", "content": f"Context:\n{docs}"},
|
|
171
|
+
{"role": "user", "content": question},
|
|
172
|
+
],
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return response.choices[0].message.content
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
result = answer_question("How do I reset my password?")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Decorator variants
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
@sp.trace
|
|
185
|
+
def my_function(): ...
|
|
186
|
+
|
|
187
|
+
@sp.trace("my-pipeline")
|
|
188
|
+
def my_function(): ...
|
|
189
|
+
|
|
190
|
+
@sp.trace(name="my-pipeline", tags=["production"], metadata={"customer_id": "acme"})
|
|
191
|
+
def my_function(): ...
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Async support
|
|
195
|
+
|
|
196
|
+
Both `wrap()` and `@sp.trace` work with async clients and functions:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from openai import AsyncOpenAI
|
|
200
|
+
|
|
201
|
+
client = sp.wrap(AsyncOpenAI())
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@sp.trace
|
|
205
|
+
async def answer_question(question: str) -> str:
|
|
206
|
+
response = await client.chat.completions.create(
|
|
207
|
+
model="gpt-4o",
|
|
208
|
+
messages=[{"role": "user", "content": question}],
|
|
209
|
+
)
|
|
210
|
+
return response.choices[0].message.content
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Configuration
|
|
214
|
+
|
|
215
|
+
### `sp.init()`
|
|
216
|
+
|
|
217
|
+
| Parameter | Type | Default | Description |
|
|
218
|
+
|-----------|------|---------|-------------|
|
|
219
|
+
| `api_key` | `str` | `SP_API_KEY` env var | Your SuperPenguin API key |
|
|
220
|
+
| `base_url` | `str` | `https://api.carrotlabs.ai` | API endpoint |
|
|
221
|
+
| `flush_interval` | `float` | `5.0` | Seconds between background batch flushes |
|
|
222
|
+
| `batch_size` | `int` | `50` | Max events per batch POST |
|
|
223
|
+
|
|
224
|
+
### Environment variables
|
|
225
|
+
|
|
226
|
+
| Variable | Description |
|
|
227
|
+
|----------|-------------|
|
|
228
|
+
| `SP_API_KEY` | API key (used if not passed to `init()`) |
|
|
229
|
+
| `SP_BASE_URL` | API base URL override |
|
|
230
|
+
|
|
231
|
+
If `SP_API_KEY` is set, `init()` is called automatically on first use.
|
|
232
|
+
|
|
233
|
+
### `sp.wrap()`
|
|
234
|
+
|
|
235
|
+
| Parameter | Type | Default | Description |
|
|
236
|
+
|-----------|------|---------|-------------|
|
|
237
|
+
| `client` | `OpenAI \| Anthropic` | required | The LLM client to wrap |
|
|
238
|
+
| `name` | `str` | `None` | Override the default event name |
|
|
239
|
+
| `metadata` | `dict` | `None` | Default metadata for every call (customer_id, feature, team, etc.) |
|
|
240
|
+
| `tags` | `list[str]` | `None` | Tags added to every event |
|
|
241
|
+
|
|
242
|
+
### `sp.patch_litellm()`
|
|
243
|
+
|
|
244
|
+
| Parameter | Type | Default | Description |
|
|
245
|
+
|-----------|------|---------|-------------|
|
|
246
|
+
| `name` | `str` | `None` | Override the default event name |
|
|
247
|
+
| `metadata` | `dict` | `None` | Default metadata for every litellm call |
|
|
248
|
+
| `tags` | `list[str]` | `None` | Tags added to every event |
|
|
249
|
+
|
|
250
|
+
### `sp.flush()`
|
|
251
|
+
|
|
252
|
+
Force-flush any pending events. Useful before process exit in short-lived scripts:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
sp.flush()
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
An `atexit` handler also flushes automatically on normal interpreter shutdown.
|
|
259
|
+
|
|
260
|
+
## Metadata Fields
|
|
261
|
+
|
|
262
|
+
| Field | Type | Purpose |
|
|
263
|
+
|-------|------|---------|
|
|
264
|
+
| `customer_id` | string | End-customer or account consuming the AI call |
|
|
265
|
+
| `feature` | string | Product feature name (e.g., `search`, `support_agent`) |
|
|
266
|
+
| `team` | string | Internal team owning the feature |
|
|
267
|
+
| `environment` | string | `production`, `staging`, `dev`, etc. |
|
|
268
|
+
| `prompt_key` | string | Identifier for the prompt template |
|
|
269
|
+
| `prompt_version` | string | Version of the prompt template |
|
|
270
|
+
| Any other key | string | Stored as custom tags, queryable in the dashboard |
|
|
271
|
+
|
|
272
|
+
## What Gets Tracked
|
|
273
|
+
|
|
274
|
+
Each event includes:
|
|
275
|
+
|
|
276
|
+
| Field | Description |
|
|
277
|
+
|-------|-------------|
|
|
278
|
+
| `provider` | `"openai"`, `"anthropic"`, or `"litellm"` |
|
|
279
|
+
| `model` | Model name used |
|
|
280
|
+
| `input_tokens` | Prompt token count |
|
|
281
|
+
| `output_tokens` | Completion token count |
|
|
282
|
+
| `cached_tokens` | Cached prompt tokens (if applicable) |
|
|
283
|
+
| `cost_usd_micros` | Estimated cost in USD micros (1 USD = 1,000,000 micros) |
|
|
284
|
+
| `latency_ms` | End-to-end call duration |
|
|
285
|
+
| `streaming` | Whether the call was streamed |
|
|
286
|
+
| `has_tools` | Whether tool calls were used |
|
|
287
|
+
| `has_vision` | Whether image inputs were included |
|
|
288
|
+
|
|
289
|
+
**Never captured:** Prompt content, response content, images, audio, tool arguments, or function results. The SDK only captures cost-relevant metadata.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# SuperPenguin Python SDK
|
|
2
|
+
|
|
3
|
+
Track AI costs automatically. Wrap your OpenAI or Anthropic client — or patch litellm — and every LLM call is captured with token counts, estimated cost, latency, and attribution metadata. No proxy required.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install superpenguin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install from source (in the `sdk/python/` directory):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -e .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### 1. Wrap your client (one line)
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import superpenguin as sp
|
|
23
|
+
from openai import OpenAI
|
|
24
|
+
|
|
25
|
+
sp.init(api_key="sp_...") # your SuperPenguin API key
|
|
26
|
+
|
|
27
|
+
client = sp.wrap(OpenAI())
|
|
28
|
+
|
|
29
|
+
# Use the client exactly as normal — cost events are captured automatically
|
|
30
|
+
response = client.chat.completions.create(
|
|
31
|
+
model="gpt-4o",
|
|
32
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
33
|
+
)
|
|
34
|
+
print(response.choices[0].message.content)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
That's it. Every `create()` call through the wrapped client is captured with provider, model, token counts, estimated cost (USD), and latency.
|
|
38
|
+
|
|
39
|
+
### 2. Works with Anthropic too
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import superpenguin as sp
|
|
43
|
+
from anthropic import Anthropic
|
|
44
|
+
|
|
45
|
+
sp.init(api_key="sp_...")
|
|
46
|
+
|
|
47
|
+
client = sp.wrap(Anthropic())
|
|
48
|
+
|
|
49
|
+
response = client.messages.create(
|
|
50
|
+
model="claude-sonnet-4-20250514",
|
|
51
|
+
max_tokens=1024,
|
|
52
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Streaming works transparently
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
client = sp.wrap(OpenAI())
|
|
60
|
+
|
|
61
|
+
stream = client.chat.completions.create(
|
|
62
|
+
model="gpt-4o",
|
|
63
|
+
messages=[{"role": "user", "content": "Tell me a story"}],
|
|
64
|
+
stream=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
for chunk in stream:
|
|
68
|
+
if chunk.choices and chunk.choices[0].delta.content:
|
|
69
|
+
print(chunk.choices[0].delta.content, end="", flush=True)
|
|
70
|
+
|
|
71
|
+
# Cost event is submitted automatically when the stream finishes
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 4. LiteLLM support
|
|
75
|
+
|
|
76
|
+
If you use [litellm](https://github.com/BerriAI/litellm) to call 100+ LLM providers through a single interface, one call patches everything:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import superpenguin as sp
|
|
80
|
+
import litellm
|
|
81
|
+
|
|
82
|
+
sp.init(api_key="sp_...")
|
|
83
|
+
sp.patch_litellm()
|
|
84
|
+
|
|
85
|
+
# Every litellm.completion() / litellm.acompletion() is now tracked
|
|
86
|
+
response = litellm.completion(
|
|
87
|
+
model="openai/gpt-4o",
|
|
88
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 5. Add attribution metadata
|
|
93
|
+
|
|
94
|
+
Attach metadata to attribute costs to customers, features, teams, or environments:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# Set defaults for all calls from this client
|
|
98
|
+
client = sp.wrap(OpenAI(), metadata={
|
|
99
|
+
"customer_id": "cust_acme_123",
|
|
100
|
+
"feature": "doc_summary",
|
|
101
|
+
"team": "product",
|
|
102
|
+
"environment": "production",
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
# Or override per-call via extra_body
|
|
106
|
+
response = client.chat.completions.create(
|
|
107
|
+
model="gpt-4o",
|
|
108
|
+
messages=[{"role": "user", "content": "Summarize this document"}],
|
|
109
|
+
extra_body={
|
|
110
|
+
"sp_metadata": {
|
|
111
|
+
"customer_id": "cust_other_456",
|
|
112
|
+
"prompt_key": "summarize_v2",
|
|
113
|
+
"prompt_version": "3",
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## `@sp.trace` Decorator
|
|
120
|
+
|
|
121
|
+
For multi-step pipelines (RAG, agents, chains), use the `@sp.trace` decorator. Any wrapped LLM calls inside the function are automatically linked as children.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import superpenguin as sp
|
|
125
|
+
from openai import OpenAI
|
|
126
|
+
|
|
127
|
+
sp.init(api_key="sp_...")
|
|
128
|
+
client = sp.wrap(OpenAI())
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@sp.trace
|
|
132
|
+
def answer_question(question: str) -> str:
|
|
133
|
+
docs = search_knowledge_base(question)
|
|
134
|
+
|
|
135
|
+
response = client.chat.completions.create(
|
|
136
|
+
model="gpt-4o",
|
|
137
|
+
messages=[
|
|
138
|
+
{"role": "system", "content": f"Context:\n{docs}"},
|
|
139
|
+
{"role": "user", "content": question},
|
|
140
|
+
],
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return response.choices[0].message.content
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
result = answer_question("How do I reset my password?")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Decorator variants
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
@sp.trace
|
|
153
|
+
def my_function(): ...
|
|
154
|
+
|
|
155
|
+
@sp.trace("my-pipeline")
|
|
156
|
+
def my_function(): ...
|
|
157
|
+
|
|
158
|
+
@sp.trace(name="my-pipeline", tags=["production"], metadata={"customer_id": "acme"})
|
|
159
|
+
def my_function(): ...
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Async support
|
|
163
|
+
|
|
164
|
+
Both `wrap()` and `@sp.trace` work with async clients and functions:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from openai import AsyncOpenAI
|
|
168
|
+
|
|
169
|
+
client = sp.wrap(AsyncOpenAI())
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@sp.trace
|
|
173
|
+
async def answer_question(question: str) -> str:
|
|
174
|
+
response = await client.chat.completions.create(
|
|
175
|
+
model="gpt-4o",
|
|
176
|
+
messages=[{"role": "user", "content": question}],
|
|
177
|
+
)
|
|
178
|
+
return response.choices[0].message.content
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Configuration
|
|
182
|
+
|
|
183
|
+
### `sp.init()`
|
|
184
|
+
|
|
185
|
+
| Parameter | Type | Default | Description |
|
|
186
|
+
|-----------|------|---------|-------------|
|
|
187
|
+
| `api_key` | `str` | `SP_API_KEY` env var | Your SuperPenguin API key |
|
|
188
|
+
| `base_url` | `str` | `https://api.carrotlabs.ai` | API endpoint |
|
|
189
|
+
| `flush_interval` | `float` | `5.0` | Seconds between background batch flushes |
|
|
190
|
+
| `batch_size` | `int` | `50` | Max events per batch POST |
|
|
191
|
+
|
|
192
|
+
### Environment variables
|
|
193
|
+
|
|
194
|
+
| Variable | Description |
|
|
195
|
+
|----------|-------------|
|
|
196
|
+
| `SP_API_KEY` | API key (used if not passed to `init()`) |
|
|
197
|
+
| `SP_BASE_URL` | API base URL override |
|
|
198
|
+
|
|
199
|
+
If `SP_API_KEY` is set, `init()` is called automatically on first use.
|
|
200
|
+
|
|
201
|
+
### `sp.wrap()`
|
|
202
|
+
|
|
203
|
+
| Parameter | Type | Default | Description |
|
|
204
|
+
|-----------|------|---------|-------------|
|
|
205
|
+
| `client` | `OpenAI \| Anthropic` | required | The LLM client to wrap |
|
|
206
|
+
| `name` | `str` | `None` | Override the default event name |
|
|
207
|
+
| `metadata` | `dict` | `None` | Default metadata for every call (customer_id, feature, team, etc.) |
|
|
208
|
+
| `tags` | `list[str]` | `None` | Tags added to every event |
|
|
209
|
+
|
|
210
|
+
### `sp.patch_litellm()`
|
|
211
|
+
|
|
212
|
+
| Parameter | Type | Default | Description |
|
|
213
|
+
|-----------|------|---------|-------------|
|
|
214
|
+
| `name` | `str` | `None` | Override the default event name |
|
|
215
|
+
| `metadata` | `dict` | `None` | Default metadata for every litellm call |
|
|
216
|
+
| `tags` | `list[str]` | `None` | Tags added to every event |
|
|
217
|
+
|
|
218
|
+
### `sp.flush()`
|
|
219
|
+
|
|
220
|
+
Force-flush any pending events. Useful before process exit in short-lived scripts:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
sp.flush()
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
An `atexit` handler also flushes automatically on normal interpreter shutdown.
|
|
227
|
+
|
|
228
|
+
## Metadata Fields
|
|
229
|
+
|
|
230
|
+
| Field | Type | Purpose |
|
|
231
|
+
|-------|------|---------|
|
|
232
|
+
| `customer_id` | string | End-customer or account consuming the AI call |
|
|
233
|
+
| `feature` | string | Product feature name (e.g., `search`, `support_agent`) |
|
|
234
|
+
| `team` | string | Internal team owning the feature |
|
|
235
|
+
| `environment` | string | `production`, `staging`, `dev`, etc. |
|
|
236
|
+
| `prompt_key` | string | Identifier for the prompt template |
|
|
237
|
+
| `prompt_version` | string | Version of the prompt template |
|
|
238
|
+
| Any other key | string | Stored as custom tags, queryable in the dashboard |
|
|
239
|
+
|
|
240
|
+
## What Gets Tracked
|
|
241
|
+
|
|
242
|
+
Each event includes:
|
|
243
|
+
|
|
244
|
+
| Field | Description |
|
|
245
|
+
|-------|-------------|
|
|
246
|
+
| `provider` | `"openai"`, `"anthropic"`, or `"litellm"` |
|
|
247
|
+
| `model` | Model name used |
|
|
248
|
+
| `input_tokens` | Prompt token count |
|
|
249
|
+
| `output_tokens` | Completion token count |
|
|
250
|
+
| `cached_tokens` | Cached prompt tokens (if applicable) |
|
|
251
|
+
| `cost_usd_micros` | Estimated cost in USD micros (1 USD = 1,000,000 micros) |
|
|
252
|
+
| `latency_ms` | End-to-end call duration |
|
|
253
|
+
| `streaming` | Whether the call was streamed |
|
|
254
|
+
| `has_tools` | Whether tool calls were used |
|
|
255
|
+
| `has_vision` | Whether image inputs were included |
|
|
256
|
+
|
|
257
|
+
**Never captured:** Prompt content, response content, images, audio, tool arguments, or function results. The SDK only captures cost-relevant metadata.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "superpenguin"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "SuperPenguin Python SDK — AI cost management, attribution, and spend tracking"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
keywords = [
|
|
9
|
+
"llm",
|
|
10
|
+
"cost-management",
|
|
11
|
+
"observability",
|
|
12
|
+
"openai",
|
|
13
|
+
"anthropic",
|
|
14
|
+
"litellm",
|
|
15
|
+
"ai",
|
|
16
|
+
"monitoring",
|
|
17
|
+
"attribution",
|
|
18
|
+
"spend-tracking",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
"Typing :: Typed",
|
|
33
|
+
]
|
|
34
|
+
dependencies = []
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://carrotlabs.ai"
|
|
38
|
+
Documentation = "https://carrotlabs.ai"
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
openai = ["openai"]
|
|
42
|
+
anthropic = ["anthropic"]
|
|
43
|
+
dev = ["pytest>=7.0.0", "openai", "anthropic"]
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["hatchling"]
|
|
47
|
+
build-backend = "hatchling.build"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel]
|
|
50
|
+
packages = ["src/superpenguin"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ["tests"]
|