pump-openai-radar 0.0.1__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.
- pump_openai_radar-0.0.1/.github/workflows/format.yml +21 -0
- pump_openai_radar-0.0.1/.github/workflows/test.yml +33 -0
- pump_openai_radar-0.0.1/.gitignore +9 -0
- pump_openai_radar-0.0.1/PKG-INFO +340 -0
- pump_openai_radar-0.0.1/README.md +299 -0
- pump_openai_radar-0.0.1/openai-radar +14 -0
- pump_openai_radar-0.0.1/pyproject.toml +77 -0
- pump_openai_radar-0.0.1/src/openai_radar/__init__.py +23 -0
- pump_openai_radar-0.0.1/src/openai_radar/agents/__init__.py +78 -0
- pump_openai_radar-0.0.1/src/openai_radar/agents/tools.py +251 -0
- pump_openai_radar-0.0.1/src/openai_radar/cli.py +431 -0
- pump_openai_radar-0.0.1/src/openai_radar/client.py +147 -0
- pump_openai_radar-0.0.1/src/openai_radar/exporters/__init__.py +6 -0
- pump_openai_radar-0.0.1/src/openai_radar/exporters/csv.py +79 -0
- pump_openai_radar-0.0.1/src/openai_radar/exporters/drawio.py +245 -0
- pump_openai_radar-0.0.1/src/openai_radar/findings.py +238 -0
- pump_openai_radar-0.0.1/src/openai_radar/models/__init__.py +23 -0
- pump_openai_radar-0.0.1/src/openai_radar/models/base.py +240 -0
- pump_openai_radar-0.0.1/src/openai_radar/pump_login.py +390 -0
- pump_openai_radar-0.0.1/src/openai_radar/runner.py +186 -0
- pump_openai_radar-0.0.1/src/openai_radar/scanners/__init__.py +28 -0
- pump_openai_radar-0.0.1/src/openai_radar/scanners/assistants.py +137 -0
- pump_openai_radar-0.0.1/src/openai_radar/scanners/base.py +84 -0
- pump_openai_radar-0.0.1/src/openai_radar/scanners/batch_jobs.py +18 -0
- pump_openai_radar-0.0.1/src/openai_radar/scanners/fine_tunes.py +18 -0
- pump_openai_radar-0.0.1/src/openai_radar/scanners/report.py +174 -0
- pump_openai_radar-0.0.1/src/openai_radar/scanners/usage.py +146 -0
- pump_openai_radar-0.0.1/src/openai_radar/scanners/vector_stores.py +19 -0
- pump_openai_radar-0.0.1/src/openai_radar/upload.py +124 -0
- pump_openai_radar-0.0.1/tests/__init__.py +0 -0
- pump_openai_radar-0.0.1/tests/conftest.py +18 -0
- pump_openai_radar-0.0.1/tests/fakes.py +64 -0
- pump_openai_radar-0.0.1/tests/test_agents.py +180 -0
- pump_openai_radar-0.0.1/tests/test_cli.py +613 -0
- pump_openai_radar-0.0.1/tests/test_client.py +144 -0
- pump_openai_radar-0.0.1/tests/test_exporters.py +162 -0
- pump_openai_radar-0.0.1/tests/test_findings.py +214 -0
- pump_openai_radar-0.0.1/tests/test_models.py +169 -0
- pump_openai_radar-0.0.1/tests/test_package.py +28 -0
- pump_openai_radar-0.0.1/tests/test_pump_login.py +171 -0
- pump_openai_radar-0.0.1/tests/test_relationships.py +54 -0
- pump_openai_radar-0.0.1/tests/test_report.py +210 -0
- pump_openai_radar-0.0.1/tests/test_runner.py +168 -0
- pump_openai_radar-0.0.1/tests/test_scanners.py +283 -0
- pump_openai_radar-0.0.1/tests/test_upload.py +105 -0
- pump_openai_radar-0.0.1/uv.lock +1870 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Format
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
ruff-format:
|
|
12
|
+
name: ruff format
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Check formatting
|
|
19
|
+
uses: astral-sh/ruff-action@v4.1.0
|
|
20
|
+
with:
|
|
21
|
+
args: format --check --diff
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
pytest:
|
|
12
|
+
name: pytest (${{ matrix.python-version }})
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
python -m pip install -e ".[dev]"
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: python -m pytest
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pump-openai-radar
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: OpenAI infrastructure FinOps SDK — scan, analyze, and export your OpenAI org resources
|
|
5
|
+
Project-URL: Homepage, https://github.com/gomorsmi/openai-radar
|
|
6
|
+
Project-URL: Repository, https://github.com/gomorsmi/openai-radar
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/gomorsmi/openai-radar/issues
|
|
8
|
+
Project-URL: PyPI, https://pypi.org/project/openai-radar/
|
|
9
|
+
Author: pump.co, Mor Michaeli
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: cloud-cost,finops,hyperscaler,llm,openai,radar
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: anyio>=4.0.0
|
|
26
|
+
Requires-Dist: httpx>=0.27.0
|
|
27
|
+
Requires-Dist: lxml>=5.0.0
|
|
28
|
+
Requires-Dist: openai>=1.60.0
|
|
29
|
+
Requires-Dist: pydantic>=2.0.0
|
|
30
|
+
Requires-Dist: rich>=13.0.0
|
|
31
|
+
Requires-Dist: typer>=0.12.0
|
|
32
|
+
Provides-Extra: agents
|
|
33
|
+
Requires-Dist: openai-agents>=0.22.0; extra == 'agents'
|
|
34
|
+
Provides-Extra: all
|
|
35
|
+
Requires-Dist: openai-radar[agents]; extra == 'all'
|
|
36
|
+
Provides-Extra: csv
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
39
|
+
Provides-Extra: drawio
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# openai-radar
|
|
43
|
+
|
|
44
|
+
**OpenAI infrastructure FinOps SDK** — part of the [Hyperscaler Radar](https://github.com/gomorsmi) suite.
|
|
45
|
+
|
|
46
|
+
[](https://pypi.org/project/openai-radar/)
|
|
47
|
+
[](https://pypi.org/project/openai-radar/)
|
|
48
|
+
[](LICENSE)
|
|
49
|
+
|
|
50
|
+
Scan your OpenAI org for assistants, vector stores, fine-tunes, batch jobs, and token usage.
|
|
51
|
+
Detect external service relationships in assistant instructions, flag cost anomalies via a findings engine,
|
|
52
|
+
and export inventory to CSV or a draw.io architecture diagram — all through a clean Python SDK
|
|
53
|
+
that mirrors the `openai-agents` Runner API.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install openai-radar # SDK + CLI + CSV and draw.io export
|
|
61
|
+
pip install openai-radar[agents] # + openai-agents integration
|
|
62
|
+
pip install openai-radar[all] # everything
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Requires Python 3.10+.
|
|
66
|
+
|
|
67
|
+
CSV and draw.io export are part of the base install — no extras needed, same as
|
|
68
|
+
the rest of the Radar suite. The `[csv]` and `[drawio]` extras still resolve (as
|
|
69
|
+
no-ops) so older pins keep working.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Quick-start
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from openai_radar import RadarClient, Runner
|
|
77
|
+
|
|
78
|
+
# Reads OPENAI_API_KEY from environment
|
|
79
|
+
client = RadarClient()
|
|
80
|
+
|
|
81
|
+
result = Runner.run_sync(client)
|
|
82
|
+
|
|
83
|
+
print(result.summary())
|
|
84
|
+
result.export_csv("./out/")
|
|
85
|
+
result.export_drawio("./out/openai_arch.drawio")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### With admin key (org-wide visibility)
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
client = RadarClient(
|
|
92
|
+
api_key="sk-proj-...",
|
|
93
|
+
admin_key="sk-admin-...", # unlocks cross-project usage data
|
|
94
|
+
)
|
|
95
|
+
result = Runner.run_sync(client, config)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Scoped to a project
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from openai_radar import RadarClient, Runner, RunConfig
|
|
102
|
+
|
|
103
|
+
client = RadarClient()
|
|
104
|
+
config = RunConfig(project_id="proj_xxx", usage_lookback_days=7)
|
|
105
|
+
result = Runner.run_sync(client, config)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## CLI
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Full scan — findings table to stdout
|
|
114
|
+
openai-radar run
|
|
115
|
+
|
|
116
|
+
# CSVs + draw.io diagram
|
|
117
|
+
openai-radar run --csv-dir ./out --drawio-file arch.drawio
|
|
118
|
+
|
|
119
|
+
# JSON instead of a table
|
|
120
|
+
openai-radar run --output json --out-file scan.json
|
|
121
|
+
|
|
122
|
+
# Admin key for org-wide usage data
|
|
123
|
+
openai-radar run --admin-key sk-admin-... --csv-dir ./out
|
|
124
|
+
|
|
125
|
+
# Findings only
|
|
126
|
+
openai-radar findings
|
|
127
|
+
|
|
128
|
+
# Scope to a project, 7-day lookback
|
|
129
|
+
openai-radar run --project proj_xxx --lookback 7
|
|
130
|
+
|
|
131
|
+
# Log in to Pump, then push the org cost report (admin key required)
|
|
132
|
+
openai-radar login
|
|
133
|
+
openai-radar run --admin-key sk-admin-... --upload
|
|
134
|
+
|
|
135
|
+
# Check or forget the stored Pump token
|
|
136
|
+
openai-radar status
|
|
137
|
+
openai-radar logout
|
|
138
|
+
|
|
139
|
+
# Print the version
|
|
140
|
+
openai-radar version
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Flags follow the Radar suite convention: `--output/-o` selects `table` or `json`,
|
|
144
|
+
`--out-file` writes the JSON payload, `--csv-dir` writes per-resource CSVs.
|
|
145
|
+
`--upload` writes `report.csv` (costs) and `usage.csv` (token usage) and pushes
|
|
146
|
+
them with the token from `openai-radar login`. Costs upload as role `billing`;
|
|
147
|
+
usage uploads as role `inventory`. `--upload-token` does the same with a one-shot
|
|
148
|
+
token and overrides the stored login. `--report-file` chooses the cost CSV path;
|
|
149
|
+
with `--csv-dir` and no `--report-file` it is `{csv-dir}/report.csv`. `usage.csv`
|
|
150
|
+
is written next to it.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Pump onboarding
|
|
155
|
+
|
|
156
|
+
The token is exchanged for a presigned S3 URL, and only the cost and usage CSVs leave the machine.
|
|
157
|
+
|
|
158
|
+
1. Log in. This runs the browser OAuth flow and stores an upload token locally:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
openai-radar login
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
2. Scan and upload with an OpenAI admin key:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
openai-radar run --admin-key sk-admin-... --upload
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
This scans the org, pulls daily costs from `/organization/costs`, writes
|
|
171
|
+
`report.csv` and `usage.csv`, and uploads them with the stored token. Costs
|
|
172
|
+
are role `billing`; usage is role `inventory`. `--csv-dir` and `--drawio-file`
|
|
173
|
+
still work on the same command. Pass `--report-file` to choose where the cost
|
|
174
|
+
CSV is written.
|
|
175
|
+
3. Pump detects the upload and runs its analysis.
|
|
176
|
+
|
|
177
|
+
`openai-radar status` shows whether a token is stored (not the token itself).
|
|
178
|
+
`openai-radar logout` deletes it. A one-shot token still works without logging in:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
openai-radar run --admin-key sk-admin-... --upload-token <TOKEN>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`report.csv` columns:
|
|
185
|
+
|
|
186
|
+
| Column | Meaning |
|
|
187
|
+
| --------- | ----------------------------------------- |
|
|
188
|
+
| Date | UTC day (`YYYY-MM-DD`) |
|
|
189
|
+
| ProjectID | OpenAI project, or `-` |
|
|
190
|
+
| LineItem | Cost line item (model and token category) |
|
|
191
|
+
| Amount | Non-zero cost, 6 decimal places |
|
|
192
|
+
| Currency | e.g. `USD` |
|
|
193
|
+
|
|
194
|
+
Zero-cost buckets are omitted. The token carries no company id — Pump binds the
|
|
195
|
+
company and the S3 key server-side. Login stores the API origin it used, and
|
|
196
|
+
`run --upload` sends the report there. Override it with `--api-base` or
|
|
197
|
+
`PUMP_API_BASE` (default `https://api.pump.co`):
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
openai-radar run --admin-key sk-admin-... --upload-token <TOKEN> --api-base http://localhost:8001
|
|
201
|
+
# or
|
|
202
|
+
PUMP_API_BASE=http://localhost:8001 openai-radar run --admin-key sk-admin-... --upload-token <TOKEN>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`openai_radar/upload.py` posts `{api_base}/api/v1/estimate/radar/urls` once per
|
|
206
|
+
file, with `{"token", "role", "provider": "openai"}`. Costs use role `billing`
|
|
207
|
+
and usage uses role `inventory`. Each CSV is then `PUT` as `Content-Type: text/csv`
|
|
208
|
+
(the presigned URL signs that content type).
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## openai-agents integration
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
pip install openai-radar[agents]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from openai_radar.agents import build_radar_agent
|
|
220
|
+
from agents import Runner
|
|
221
|
+
|
|
222
|
+
agent = build_radar_agent()
|
|
223
|
+
result = Runner.run_sync(agent, "Scan my org and flag any cost anomalies")
|
|
224
|
+
print(result.final_output)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Or compose individual Radar tools into your own agent:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from agents import Agent
|
|
231
|
+
from openai_radar.agents.tools import scan_assistants, run_findings, export_drawio
|
|
232
|
+
|
|
233
|
+
agent = Agent(
|
|
234
|
+
name="My FinOps Agent",
|
|
235
|
+
instructions="...",
|
|
236
|
+
tools=[scan_assistants, run_findings, export_drawio],
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Findings engine
|
|
243
|
+
|
|
244
|
+
| Rule ID | Severity | Condition |
|
|
245
|
+
| --------- | -------- | ---------------------------------------------- |
|
|
246
|
+
| ASST_001 | LOW | Assistant has zero tools |
|
|
247
|
+
| ASST_002 | INFO | Code Interpreter enabled (file storage costs) |
|
|
248
|
+
| VS_001 | MEDIUM | Vector store > 5 GB |
|
|
249
|
+
| VS_002 | HIGH | Vector store expires within 7 days |
|
|
250
|
+
| FT_001 | MEDIUM | Fine-tune job in `failed` state |
|
|
251
|
+
| BATCH_001 | HIGH | Batch job failure rate > 10% |
|
|
252
|
+
| USAGE_001 | MEDIUM | Model consumes > 10M tokens in lookback window |
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Service relationship detection
|
|
257
|
+
|
|
258
|
+
`AssistantScanner` inspects each assistant's `instructions` field for external service signals
|
|
259
|
+
and emits `ServiceRelationship` objects (visualized as edges in the draw.io diagram):
|
|
260
|
+
|
|
261
|
+
| Kind | Signals |
|
|
262
|
+
| -------- | ----------------------------------------------- |
|
|
263
|
+
| AWS | `aws`, `s3`, `ec2`, `lambda`, `dynamodb`, `sqs` |
|
|
264
|
+
| GCP | `gcp`, `bigquery`, `gcs`, `google cloud` |
|
|
265
|
+
| AZURE | `azure`, `blob.core.windows`, `cosmosdb` |
|
|
266
|
+
| DATABASE | `postgres`, `mysql`, `mongo`, `redis`, `neon` |
|
|
267
|
+
| SLACK | `slack` |
|
|
268
|
+
| EMAIL | `sendgrid`, `mailgun`, `smtp` |
|
|
269
|
+
| WEBHOOK | `webhook`, `http://` |
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## SDK structure
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
src/openai_radar/
|
|
277
|
+
├── client.py # RadarClient (auth, project vs admin key)
|
|
278
|
+
├── runner.py # Runner, RunConfig, RunResult
|
|
279
|
+
├── findings.py # FindingEngine, Finding, Severity
|
|
280
|
+
├── models/base.py # Pydantic v2 models for all resource types
|
|
281
|
+
├── scanners/ # One scanner per resource type, plus the cost report
|
|
282
|
+
├── exporters/ # CSV + draw.io exporters
|
|
283
|
+
├── pump_login.py # `login` / `logout` / `status` (OAuth + PKCE)
|
|
284
|
+
├── upload.py # Pump presigned-URL upload (billing + inventory)
|
|
285
|
+
├── agents/ # openai-agents tools + build_radar_agent()
|
|
286
|
+
└── cli.py # openai-radar CLI
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Development
|
|
292
|
+
|
|
293
|
+
Python 3.10 or newer. From a checkout:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
python3 -m venv .venv
|
|
297
|
+
source .venv/bin/activate
|
|
298
|
+
pip install -e ".[all]"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
With [uv](https://docs.astral.sh/uv/):
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
uv venv
|
|
305
|
+
source .venv/bin/activate
|
|
306
|
+
uv pip install -e ".[all]"
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The package lives in `src/openai_radar`, including `cli.py` and `pump_login.py`.
|
|
310
|
+
|
|
311
|
+
From this checkout:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
./openai-radar --help
|
|
315
|
+
./openai-radar login
|
|
316
|
+
./openai-radar status
|
|
317
|
+
./openai-radar logout
|
|
318
|
+
./openai-radar run --upload
|
|
319
|
+
./openai-radar version
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`login` opens a browser for the Pump OAuth flow and writes the token to
|
|
323
|
+
`$XDG_CONFIG_HOME/openai-radar/credentials.json`, or
|
|
324
|
+
`~/.config/openai-radar/credentials.json` when `XDG_CONFIG_HOME` is unset.
|
|
325
|
+
`OPENAI_RADAR_CONFIG_DIR` overrides that directory. `PUMP_API_BASE` and
|
|
326
|
+
`PUMP_APP_BASE` override the Pump origins, as do `--api-base` and `--app-base`.
|
|
327
|
+
`run --upload` sends `report.csv` with the stored token. Scans still read
|
|
328
|
+
`OPENAI_API_KEY` and, for org-wide usage, `OPENAI_ADMIN_KEY`.
|
|
329
|
+
|
|
330
|
+
Login tests talk to a local fake Pump:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
python -m unittest tests.test_pump_login
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Part of the Hyperscaler Radar suite
|
|
339
|
+
|
|
340
|
+
`aws-radar` · `gcp-radar` · `azure-radar` · `oci-radar` · `openai-radar` ·`claude-radar` · `gemini-radar` · `datadog-radar`
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# openai-radar
|
|
2
|
+
|
|
3
|
+
**OpenAI infrastructure FinOps SDK** — part of the [Hyperscaler Radar](https://github.com/gomorsmi) suite.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/openai-radar/)
|
|
6
|
+
[](https://pypi.org/project/openai-radar/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
Scan your OpenAI org for assistants, vector stores, fine-tunes, batch jobs, and token usage.
|
|
10
|
+
Detect external service relationships in assistant instructions, flag cost anomalies via a findings engine,
|
|
11
|
+
and export inventory to CSV or a draw.io architecture diagram — all through a clean Python SDK
|
|
12
|
+
that mirrors the `openai-agents` Runner API.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install openai-radar # SDK + CLI + CSV and draw.io export
|
|
20
|
+
pip install openai-radar[agents] # + openai-agents integration
|
|
21
|
+
pip install openai-radar[all] # everything
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Python 3.10+.
|
|
25
|
+
|
|
26
|
+
CSV and draw.io export are part of the base install — no extras needed, same as
|
|
27
|
+
the rest of the Radar suite. The `[csv]` and `[drawio]` extras still resolve (as
|
|
28
|
+
no-ops) so older pins keep working.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick-start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from openai_radar import RadarClient, Runner
|
|
36
|
+
|
|
37
|
+
# Reads OPENAI_API_KEY from environment
|
|
38
|
+
client = RadarClient()
|
|
39
|
+
|
|
40
|
+
result = Runner.run_sync(client)
|
|
41
|
+
|
|
42
|
+
print(result.summary())
|
|
43
|
+
result.export_csv("./out/")
|
|
44
|
+
result.export_drawio("./out/openai_arch.drawio")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### With admin key (org-wide visibility)
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
client = RadarClient(
|
|
51
|
+
api_key="sk-proj-...",
|
|
52
|
+
admin_key="sk-admin-...", # unlocks cross-project usage data
|
|
53
|
+
)
|
|
54
|
+
result = Runner.run_sync(client, config)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Scoped to a project
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from openai_radar import RadarClient, Runner, RunConfig
|
|
61
|
+
|
|
62
|
+
client = RadarClient()
|
|
63
|
+
config = RunConfig(project_id="proj_xxx", usage_lookback_days=7)
|
|
64
|
+
result = Runner.run_sync(client, config)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## CLI
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Full scan — findings table to stdout
|
|
73
|
+
openai-radar run
|
|
74
|
+
|
|
75
|
+
# CSVs + draw.io diagram
|
|
76
|
+
openai-radar run --csv-dir ./out --drawio-file arch.drawio
|
|
77
|
+
|
|
78
|
+
# JSON instead of a table
|
|
79
|
+
openai-radar run --output json --out-file scan.json
|
|
80
|
+
|
|
81
|
+
# Admin key for org-wide usage data
|
|
82
|
+
openai-radar run --admin-key sk-admin-... --csv-dir ./out
|
|
83
|
+
|
|
84
|
+
# Findings only
|
|
85
|
+
openai-radar findings
|
|
86
|
+
|
|
87
|
+
# Scope to a project, 7-day lookback
|
|
88
|
+
openai-radar run --project proj_xxx --lookback 7
|
|
89
|
+
|
|
90
|
+
# Log in to Pump, then push the org cost report (admin key required)
|
|
91
|
+
openai-radar login
|
|
92
|
+
openai-radar run --admin-key sk-admin-... --upload
|
|
93
|
+
|
|
94
|
+
# Check or forget the stored Pump token
|
|
95
|
+
openai-radar status
|
|
96
|
+
openai-radar logout
|
|
97
|
+
|
|
98
|
+
# Print the version
|
|
99
|
+
openai-radar version
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Flags follow the Radar suite convention: `--output/-o` selects `table` or `json`,
|
|
103
|
+
`--out-file` writes the JSON payload, `--csv-dir` writes per-resource CSVs.
|
|
104
|
+
`--upload` writes `report.csv` (costs) and `usage.csv` (token usage) and pushes
|
|
105
|
+
them with the token from `openai-radar login`. Costs upload as role `billing`;
|
|
106
|
+
usage uploads as role `inventory`. `--upload-token` does the same with a one-shot
|
|
107
|
+
token and overrides the stored login. `--report-file` chooses the cost CSV path;
|
|
108
|
+
with `--csv-dir` and no `--report-file` it is `{csv-dir}/report.csv`. `usage.csv`
|
|
109
|
+
is written next to it.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Pump onboarding
|
|
114
|
+
|
|
115
|
+
The token is exchanged for a presigned S3 URL, and only the cost and usage CSVs leave the machine.
|
|
116
|
+
|
|
117
|
+
1. Log in. This runs the browser OAuth flow and stores an upload token locally:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
openai-radar login
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
2. Scan and upload with an OpenAI admin key:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
openai-radar run --admin-key sk-admin-... --upload
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
This scans the org, pulls daily costs from `/organization/costs`, writes
|
|
130
|
+
`report.csv` and `usage.csv`, and uploads them with the stored token. Costs
|
|
131
|
+
are role `billing`; usage is role `inventory`. `--csv-dir` and `--drawio-file`
|
|
132
|
+
still work on the same command. Pass `--report-file` to choose where the cost
|
|
133
|
+
CSV is written.
|
|
134
|
+
3. Pump detects the upload and runs its analysis.
|
|
135
|
+
|
|
136
|
+
`openai-radar status` shows whether a token is stored (not the token itself).
|
|
137
|
+
`openai-radar logout` deletes it. A one-shot token still works without logging in:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
openai-radar run --admin-key sk-admin-... --upload-token <TOKEN>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`report.csv` columns:
|
|
144
|
+
|
|
145
|
+
| Column | Meaning |
|
|
146
|
+
| --------- | ----------------------------------------- |
|
|
147
|
+
| Date | UTC day (`YYYY-MM-DD`) |
|
|
148
|
+
| ProjectID | OpenAI project, or `-` |
|
|
149
|
+
| LineItem | Cost line item (model and token category) |
|
|
150
|
+
| Amount | Non-zero cost, 6 decimal places |
|
|
151
|
+
| Currency | e.g. `USD` |
|
|
152
|
+
|
|
153
|
+
Zero-cost buckets are omitted. The token carries no company id — Pump binds the
|
|
154
|
+
company and the S3 key server-side. Login stores the API origin it used, and
|
|
155
|
+
`run --upload` sends the report there. Override it with `--api-base` or
|
|
156
|
+
`PUMP_API_BASE` (default `https://api.pump.co`):
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
openai-radar run --admin-key sk-admin-... --upload-token <TOKEN> --api-base http://localhost:8001
|
|
160
|
+
# or
|
|
161
|
+
PUMP_API_BASE=http://localhost:8001 openai-radar run --admin-key sk-admin-... --upload-token <TOKEN>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`openai_radar/upload.py` posts `{api_base}/api/v1/estimate/radar/urls` once per
|
|
165
|
+
file, with `{"token", "role", "provider": "openai"}`. Costs use role `billing`
|
|
166
|
+
and usage uses role `inventory`. Each CSV is then `PUT` as `Content-Type: text/csv`
|
|
167
|
+
(the presigned URL signs that content type).
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## openai-agents integration
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
pip install openai-radar[agents]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from openai_radar.agents import build_radar_agent
|
|
179
|
+
from agents import Runner
|
|
180
|
+
|
|
181
|
+
agent = build_radar_agent()
|
|
182
|
+
result = Runner.run_sync(agent, "Scan my org and flag any cost anomalies")
|
|
183
|
+
print(result.final_output)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Or compose individual Radar tools into your own agent:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from agents import Agent
|
|
190
|
+
from openai_radar.agents.tools import scan_assistants, run_findings, export_drawio
|
|
191
|
+
|
|
192
|
+
agent = Agent(
|
|
193
|
+
name="My FinOps Agent",
|
|
194
|
+
instructions="...",
|
|
195
|
+
tools=[scan_assistants, run_findings, export_drawio],
|
|
196
|
+
)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Findings engine
|
|
202
|
+
|
|
203
|
+
| Rule ID | Severity | Condition |
|
|
204
|
+
| --------- | -------- | ---------------------------------------------- |
|
|
205
|
+
| ASST_001 | LOW | Assistant has zero tools |
|
|
206
|
+
| ASST_002 | INFO | Code Interpreter enabled (file storage costs) |
|
|
207
|
+
| VS_001 | MEDIUM | Vector store > 5 GB |
|
|
208
|
+
| VS_002 | HIGH | Vector store expires within 7 days |
|
|
209
|
+
| FT_001 | MEDIUM | Fine-tune job in `failed` state |
|
|
210
|
+
| BATCH_001 | HIGH | Batch job failure rate > 10% |
|
|
211
|
+
| USAGE_001 | MEDIUM | Model consumes > 10M tokens in lookback window |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Service relationship detection
|
|
216
|
+
|
|
217
|
+
`AssistantScanner` inspects each assistant's `instructions` field for external service signals
|
|
218
|
+
and emits `ServiceRelationship` objects (visualized as edges in the draw.io diagram):
|
|
219
|
+
|
|
220
|
+
| Kind | Signals |
|
|
221
|
+
| -------- | ----------------------------------------------- |
|
|
222
|
+
| AWS | `aws`, `s3`, `ec2`, `lambda`, `dynamodb`, `sqs` |
|
|
223
|
+
| GCP | `gcp`, `bigquery`, `gcs`, `google cloud` |
|
|
224
|
+
| AZURE | `azure`, `blob.core.windows`, `cosmosdb` |
|
|
225
|
+
| DATABASE | `postgres`, `mysql`, `mongo`, `redis`, `neon` |
|
|
226
|
+
| SLACK | `slack` |
|
|
227
|
+
| EMAIL | `sendgrid`, `mailgun`, `smtp` |
|
|
228
|
+
| WEBHOOK | `webhook`, `http://` |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## SDK structure
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
src/openai_radar/
|
|
236
|
+
├── client.py # RadarClient (auth, project vs admin key)
|
|
237
|
+
├── runner.py # Runner, RunConfig, RunResult
|
|
238
|
+
├── findings.py # FindingEngine, Finding, Severity
|
|
239
|
+
├── models/base.py # Pydantic v2 models for all resource types
|
|
240
|
+
├── scanners/ # One scanner per resource type, plus the cost report
|
|
241
|
+
├── exporters/ # CSV + draw.io exporters
|
|
242
|
+
├── pump_login.py # `login` / `logout` / `status` (OAuth + PKCE)
|
|
243
|
+
├── upload.py # Pump presigned-URL upload (billing + inventory)
|
|
244
|
+
├── agents/ # openai-agents tools + build_radar_agent()
|
|
245
|
+
└── cli.py # openai-radar CLI
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
Python 3.10 or newer. From a checkout:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
python3 -m venv .venv
|
|
256
|
+
source .venv/bin/activate
|
|
257
|
+
pip install -e ".[all]"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
With [uv](https://docs.astral.sh/uv/):
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
uv venv
|
|
264
|
+
source .venv/bin/activate
|
|
265
|
+
uv pip install -e ".[all]"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The package lives in `src/openai_radar`, including `cli.py` and `pump_login.py`.
|
|
269
|
+
|
|
270
|
+
From this checkout:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
./openai-radar --help
|
|
274
|
+
./openai-radar login
|
|
275
|
+
./openai-radar status
|
|
276
|
+
./openai-radar logout
|
|
277
|
+
./openai-radar run --upload
|
|
278
|
+
./openai-radar version
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`login` opens a browser for the Pump OAuth flow and writes the token to
|
|
282
|
+
`$XDG_CONFIG_HOME/openai-radar/credentials.json`, or
|
|
283
|
+
`~/.config/openai-radar/credentials.json` when `XDG_CONFIG_HOME` is unset.
|
|
284
|
+
`OPENAI_RADAR_CONFIG_DIR` overrides that directory. `PUMP_API_BASE` and
|
|
285
|
+
`PUMP_APP_BASE` override the Pump origins, as do `--api-base` and `--app-base`.
|
|
286
|
+
`run --upload` sends `report.csv` with the stored token. Scans still read
|
|
287
|
+
`OPENAI_API_KEY` and, for org-wide usage, `OPENAI_ADMIN_KEY`.
|
|
288
|
+
|
|
289
|
+
Login tests talk to a local fake Pump:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
python -m unittest tests.test_pump_login
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Part of the Hyperscaler Radar suite
|
|
298
|
+
|
|
299
|
+
`aws-radar` · `gcp-radar` · `azure-radar` · `oci-radar` · `openai-radar` ·`claude-radar` · `gemini-radar` · `datadog-radar`
|