aicc-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.
aicc/__init__.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""aicc - thin client for AI Command Center.
|
|
2
|
+
|
|
3
|
+
The gateway does all the real work; this helper just points the official
|
|
4
|
+
provider SDKs at it via their standard environment variables.
|
|
5
|
+
|
|
6
|
+
import aicc
|
|
7
|
+
aicc.init(project="invoice-bot") # BEFORE creating any clients
|
|
8
|
+
|
|
9
|
+
from openai import OpenAI
|
|
10
|
+
client = OpenAI() # now flows through the gateway
|
|
11
|
+
|
|
12
|
+
Works with: openai (OPENAI_BASE_URL), anthropic (ANTHROPIC_BASE_URL),
|
|
13
|
+
google-genai (GOOGLE_GEMINI_BASE_URL), and anything else via aicc.url("provider").
|
|
14
|
+
Your API keys are untouched - the gateway passes them through.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import time
|
|
23
|
+
import urllib.request
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from typing import Any, Dict, Optional
|
|
26
|
+
from urllib.parse import quote
|
|
27
|
+
|
|
28
|
+
__all__ = ["init", "url", "track", "AiccSession"]
|
|
29
|
+
__version__ = "0.1.0"
|
|
30
|
+
|
|
31
|
+
DEFAULT_GATEWAY = "http://localhost:4321"
|
|
32
|
+
|
|
33
|
+
_session: Optional["AiccSession"] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class AiccSession:
|
|
38
|
+
gateway: str
|
|
39
|
+
project: str
|
|
40
|
+
key: Optional[str] = None
|
|
41
|
+
env: Dict[str, str] = field(default_factory=dict)
|
|
42
|
+
|
|
43
|
+
def _base(self) -> str:
|
|
44
|
+
# With auth enabled the gateway key both authenticates and attributes the
|
|
45
|
+
# call; without it, the project name in the path does the grouping.
|
|
46
|
+
return (
|
|
47
|
+
f"{self.gateway}/k/{quote(self.key, safe='')}"
|
|
48
|
+
if self.key
|
|
49
|
+
else f"{self.gateway}/p/{quote(self.project, safe='')}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def url(self, provider: str) -> str:
|
|
53
|
+
"""Gateway base URL for any provider, e.g. url('openai') or url('ollama')."""
|
|
54
|
+
base = f"{self._base()}/{provider}"
|
|
55
|
+
return base + "/v1" if provider not in ("anthropic", "gemini") else base
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def init(
|
|
59
|
+
project: str = "default",
|
|
60
|
+
key: Optional[str] = None,
|
|
61
|
+
gateway: Optional[str] = None,
|
|
62
|
+
check: bool = True,
|
|
63
|
+
) -> AiccSession:
|
|
64
|
+
"""Route this process's AI SDK traffic through AI Command Center.
|
|
65
|
+
|
|
66
|
+
Call BEFORE constructing OpenAI()/Anthropic()/genai.Client() - the SDKs
|
|
67
|
+
read their base-URL environment variables at construction time.
|
|
68
|
+
|
|
69
|
+
project - how calls are grouped on the dashboard.
|
|
70
|
+
key - project gateway key (required when the gateway has auth enabled;
|
|
71
|
+
defaults to $AICC_KEY). Get it from the dashboard → settings.
|
|
72
|
+
gateway - gateway origin; defaults to $AICC_GATEWAY or http://localhost:4321.
|
|
73
|
+
check - ping the gateway and print a warning if it is unreachable.
|
|
74
|
+
"""
|
|
75
|
+
global _session
|
|
76
|
+
gw = (gateway or os.environ.get("AICC_GATEWAY") or DEFAULT_GATEWAY).rstrip("/")
|
|
77
|
+
key = key or os.environ.get("AICC_KEY") or None
|
|
78
|
+
base = f"{gw}/k/{quote(key, safe='')}" if key else f"{gw}/p/{quote(project, safe='')}"
|
|
79
|
+
|
|
80
|
+
env = {
|
|
81
|
+
"OPENAI_BASE_URL": f"{base}/openai/v1",
|
|
82
|
+
"OPENAI_API_BASE": f"{base}/openai/v1", # legacy libs (openai<1, some frameworks)
|
|
83
|
+
"ANTHROPIC_BASE_URL": f"{base}/anthropic",
|
|
84
|
+
"GOOGLE_GEMINI_BASE_URL": f"{base}/gemini",
|
|
85
|
+
}
|
|
86
|
+
os.environ.update(env)
|
|
87
|
+
_session = AiccSession(gateway=gw, project=project, key=key, env=env)
|
|
88
|
+
|
|
89
|
+
if check:
|
|
90
|
+
try:
|
|
91
|
+
with urllib.request.urlopen(f"{gw}/health", timeout=1.5) as res:
|
|
92
|
+
json.loads(res.read().decode("utf-8"))
|
|
93
|
+
except Exception:
|
|
94
|
+
print(
|
|
95
|
+
f"[aicc] warning: no AI Command Center gateway at {gw} - "
|
|
96
|
+
f"calls will fail until you run: npx ai-command-center start",
|
|
97
|
+
file=sys.stderr,
|
|
98
|
+
)
|
|
99
|
+
return _session
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def url(provider: str) -> str:
|
|
103
|
+
"""Gateway base URL for a provider (requires init() first)."""
|
|
104
|
+
if _session is None:
|
|
105
|
+
raise RuntimeError("aicc.init() has not been called")
|
|
106
|
+
return _session.url(provider)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def track(
|
|
110
|
+
*,
|
|
111
|
+
project: Optional[str] = None,
|
|
112
|
+
provider: str = "custom",
|
|
113
|
+
model: Optional[str] = None,
|
|
114
|
+
tokens_in: Optional[int] = None,
|
|
115
|
+
tokens_out: Optional[int] = None,
|
|
116
|
+
cost_usd: Optional[float] = None,
|
|
117
|
+
latency_ms: Optional[float] = None,
|
|
118
|
+
ok: bool = True,
|
|
119
|
+
error: Optional[str] = None,
|
|
120
|
+
**extra: Any,
|
|
121
|
+
) -> bool:
|
|
122
|
+
"""Report usage for calls the gateway can't see (batch jobs, exotic providers).
|
|
123
|
+
|
|
124
|
+
Returns True when the record was accepted, False when the gateway was
|
|
125
|
+
unreachable (never raises - telemetry must not break the host app).
|
|
126
|
+
"""
|
|
127
|
+
gw = _session.gateway if _session else (os.environ.get("AICC_GATEWAY") or DEFAULT_GATEWAY).rstrip("/")
|
|
128
|
+
gw_key = (_session.key if _session else None) or os.environ.get("AICC_KEY")
|
|
129
|
+
record = {
|
|
130
|
+
"project": project or (_session.project if _session else "default"),
|
|
131
|
+
"provider": provider,
|
|
132
|
+
"model": model,
|
|
133
|
+
"tokensIn": tokens_in,
|
|
134
|
+
"tokensOut": tokens_out,
|
|
135
|
+
"costUsd": cost_usd,
|
|
136
|
+
"latencyMs": latency_ms,
|
|
137
|
+
"ok": ok,
|
|
138
|
+
"error": error,
|
|
139
|
+
"ts": int(time.time() * 1000),
|
|
140
|
+
**extra,
|
|
141
|
+
}
|
|
142
|
+
# Drop None-valued keys so the gateway treats them as absent (and, e.g.,
|
|
143
|
+
# prices the record itself) rather than coercing null to 0.
|
|
144
|
+
record = {k: v for k, v in record.items() if v is not None}
|
|
145
|
+
headers = {"Content-Type": "application/json"}
|
|
146
|
+
if gw_key:
|
|
147
|
+
headers["x-aicc-key"] = gw_key
|
|
148
|
+
req = urllib.request.Request(
|
|
149
|
+
f"{gw}/api/track",
|
|
150
|
+
data=json.dumps(record).encode("utf-8"),
|
|
151
|
+
headers=headers,
|
|
152
|
+
method="POST",
|
|
153
|
+
)
|
|
154
|
+
try:
|
|
155
|
+
with urllib.request.urlopen(req, timeout=3) as res:
|
|
156
|
+
return 200 <= res.status < 300
|
|
157
|
+
except Exception:
|
|
158
|
+
return False
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aicc-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Thin helper that routes your OpenAI/Anthropic/Gemini SDK calls through an AI Command Center gateway (one line: aicc.init(project=...)).
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: llm,observability,cost-tracking,openai,anthropic,gemini
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# aicc-sdk (Python)
|
|
13
|
+
|
|
14
|
+
Thin helper for [AI Command Center](../../README.md). One line routes every
|
|
15
|
+
OpenAI / Anthropic / Gemini SDK call in your process through the gateway, so
|
|
16
|
+
usage and cost land on the consolidated dashboard.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import aicc
|
|
20
|
+
aicc.init(project="invoice-bot") # BEFORE creating clients
|
|
21
|
+
|
|
22
|
+
from openai import OpenAI
|
|
23
|
+
client = OpenAI() # unchanged code, now fully tracked
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If the gateway has login enabled, pass the project's gateway key (from the
|
|
27
|
+
dashboard → settings, or `$AICC_KEY`):
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
aicc.init(project="invoice-bot", key="aicc_…") # or set AICC_KEY in the env
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The SDK only sets standard environment variables (`OPENAI_BASE_URL`,
|
|
34
|
+
`ANTHROPIC_BASE_URL`, `GOOGLE_GEMINI_BASE_URL`) - you can also set those by
|
|
35
|
+
hand and skip this package entirely. Your API keys never change hands.
|
|
36
|
+
|
|
37
|
+
Extras:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
aicc.url("mistral") # gateway base URL for any provider
|
|
41
|
+
aicc.track(model="gpt-4o-mini", # report usage the gateway can't see
|
|
42
|
+
tokens_in=1200, tokens_out=300)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Install (local, from this repo): `pip install -e packages/sdk-python`
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
aicc/__init__.py,sha256=bkVj6uSCjHZSareDb60X7xTKYw1XGLSH60QXnL3nkTs,5532
|
|
2
|
+
aicc_sdk-0.1.0.dist-info/licenses/LICENSE,sha256=nzNIbi_fwN6f21P-YoP0cLIbnvc3hdG79Api20dwqQU,1070
|
|
3
|
+
aicc_sdk-0.1.0.dist-info/METADATA,sha256=OQ18GtPmymTNa3kPEeehwr6UmmsRRfXFrlBAjhbZGa4,1520
|
|
4
|
+
aicc_sdk-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
5
|
+
aicc_sdk-0.1.0.dist-info/top_level.txt,sha256=9nYQKRw37Ozu-MAr-Wsr25o0E6dlFbQPxbsTO5peW2s,5
|
|
6
|
+
aicc_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aditya Sarade
|
|
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 @@
|
|
|
1
|
+
aicc
|