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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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