swiftapi-python 1.2.2__py3-none-any.whl → 1.3.1__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.
- swiftapi/__init__.py +75 -96
- swiftapi/__main__.py +2 -0
- swiftapi/anthropic.py +236 -222
- swiftapi/brake/__init__.py +45 -0
- swiftapi/brake/benchmarks.py +111 -0
- swiftapi/brake/broker.py +274 -0
- swiftapi/brake/core.py +420 -0
- swiftapi/brake/install.py +268 -0
- swiftapi/brake/mcp.py +160 -0
- swiftapi/brake/observations.py +61 -0
- swiftapi/brake/qualification.py +116 -0
- swiftapi/brake/remote.py +90 -0
- swiftapi/cli.py +150 -0
- swiftapi/client.py +328 -309
- swiftapi/enforcement.py +281 -280
- swiftapi/openai.py +243 -225
- swiftapi/py.typed +0 -0
- swiftapi/verifier.py +182 -130
- swiftapi_python-1.3.1.dist-info/METADATA +144 -0
- swiftapi_python-1.3.1.dist-info/RECORD +26 -0
- {swiftapi_python-1.2.2.dist-info → swiftapi_python-1.3.1.dist-info}/WHEEL +1 -1
- swiftapi_python-1.3.1.dist-info/entry_points.txt +2 -0
- swiftapi_python-1.2.2.dist-info/METADATA +0 -255
- swiftapi_python-1.2.2.dist-info/RECORD +0 -13
- {swiftapi_python-1.2.2.dist-info → swiftapi_python-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {swiftapi_python-1.2.2.dist-info → swiftapi_python-1.3.1.dist-info}/top_level.txt +0 -0
swiftapi/openai.py
CHANGED
|
@@ -1,225 +1,243 @@
|
|
|
1
|
-
"""
|
|
2
|
-
SwiftAPI Attested OpenAI -
|
|
3
|
-
|
|
4
|
-
# Before (vibe coding):
|
|
5
|
-
from openai import OpenAI
|
|
6
|
-
client = OpenAI(api_key="sk-...")
|
|
7
|
-
|
|
8
|
-
# After (attested):
|
|
9
|
-
from swiftapi import OpenAI
|
|
10
|
-
client = OpenAI(swiftapi_key="swiftapi_live_...", openai_key="sk-...")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
from . import
|
|
21
|
-
from .
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
self.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
"""
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
1
|
+
"""
|
|
2
|
+
SwiftAPI Attested OpenAI - Compatibility wrapper.
|
|
3
|
+
|
|
4
|
+
# Before (vibe coding):
|
|
5
|
+
from openai import OpenAI
|
|
6
|
+
client = OpenAI(api_key="sk-...")
|
|
7
|
+
|
|
8
|
+
# After (attested):
|
|
9
|
+
from swiftapi import OpenAI
|
|
10
|
+
client = OpenAI(swiftapi_key="swiftapi_live_...", openai_key="sk-...")
|
|
11
|
+
|
|
12
|
+
Compatibility wrapper for the documented nonstreaming text subset. Authorization is attested.
|
|
13
|
+
Provider keys are sent only to the provider. This wrapper does not establish native EOS.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import requests
|
|
17
|
+
from typing import List, Dict, Optional, Any
|
|
18
|
+
|
|
19
|
+
from . import __version__
|
|
20
|
+
from .exceptions import SwiftAPIError, AuthenticationError, NetworkError
|
|
21
|
+
from .verifier import (
|
|
22
|
+
verify_signature,
|
|
23
|
+
claim_attestation,
|
|
24
|
+
_action_fingerprint,
|
|
25
|
+
_snapshot_action_data,
|
|
26
|
+
_validate_action_binding,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Message:
|
|
31
|
+
"""OpenAI-compatible message object."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, role: str, content: str):
|
|
34
|
+
self.role = role
|
|
35
|
+
self.content = content
|
|
36
|
+
|
|
37
|
+
def __repr__(self):
|
|
38
|
+
truncated = self.content[:80] if self.content else ""
|
|
39
|
+
return f"Message(role='{self.role}', content='{truncated}')"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Choice:
|
|
43
|
+
"""OpenAI-compatible choice object."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, index: int, message: Message, finish_reason: str):
|
|
46
|
+
self.index = index
|
|
47
|
+
self.message = message
|
|
48
|
+
self.finish_reason = finish_reason
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ChatCompletion:
|
|
52
|
+
"""OpenAI-compatible chat completion response."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, content: str, model: str, attested: bool = False, jti: str = None):
|
|
55
|
+
self.choices = [
|
|
56
|
+
Choice(
|
|
57
|
+
index=0,
|
|
58
|
+
message=Message(role="assistant", content=content),
|
|
59
|
+
finish_reason=None,
|
|
60
|
+
)
|
|
61
|
+
]
|
|
62
|
+
self.model = model
|
|
63
|
+
self.classification = "GATE_BLOCK" if not attested else "UNCLASSIFIED_PROVIDER_OUTPUT"
|
|
64
|
+
self.native_eos_verified = False
|
|
65
|
+
self.attested = attested
|
|
66
|
+
self.jti = jti
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def content(self):
|
|
70
|
+
"""Convenience: response.content instead of response.choices[0].message.content"""
|
|
71
|
+
return self.choices[0].message.content
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Completions:
|
|
75
|
+
"""Handles chat.completions.create() calls."""
|
|
76
|
+
|
|
77
|
+
def __init__(self, swiftapi_key: str, openai_key: str, authority_url: str, timeout: int):
|
|
78
|
+
self._swiftapi_key = swiftapi_key
|
|
79
|
+
self._openai_key = openai_key
|
|
80
|
+
from urllib.parse import urlparse
|
|
81
|
+
parsed = urlparse(authority_url)
|
|
82
|
+
if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password or parsed.query or parsed.fragment:
|
|
83
|
+
raise SwiftAPIError("Authority URL must use HTTPS without embedded credentials")
|
|
84
|
+
self._authority_url = authority_url.rstrip("/")
|
|
85
|
+
self._timeout = timeout
|
|
86
|
+
self._session = requests.Session()
|
|
87
|
+
|
|
88
|
+
def create(
|
|
89
|
+
self,
|
|
90
|
+
model: str,
|
|
91
|
+
messages: List[Dict[str, Any]],
|
|
92
|
+
temperature: float = 0.7,
|
|
93
|
+
max_completion_tokens: int = 300,
|
|
94
|
+
max_tokens: Optional[int] = None,
|
|
95
|
+
**kwargs,
|
|
96
|
+
) -> ChatCompletion:
|
|
97
|
+
"""
|
|
98
|
+
Create an attested chat completion.
|
|
99
|
+
|
|
100
|
+
Same interface as openai.chat.completions.create().
|
|
101
|
+
Step 1: Attest with SwiftAPI (governance).
|
|
102
|
+
Step 2: Call OpenAI directly with your key (execution).
|
|
103
|
+
|
|
104
|
+
If governance denies, content is empty and classification is GATE_BLOCK.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
model: Model name (e.g. "gpt-4o")
|
|
108
|
+
messages: List of message dicts with role and content
|
|
109
|
+
temperature: Sampling temperature
|
|
110
|
+
max_completion_tokens: Max tokens in response
|
|
111
|
+
max_tokens: Alias for max_completion_tokens (OpenAI compat)
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
ChatCompletion with .choices[0].message.content
|
|
115
|
+
"""
|
|
116
|
+
effective_max = max_tokens or max_completion_tokens
|
|
117
|
+
|
|
118
|
+
payload = {
|
|
119
|
+
"model": model,
|
|
120
|
+
"messages": messages,
|
|
121
|
+
"temperature": temperature,
|
|
122
|
+
"max_completion_tokens": effective_max,
|
|
123
|
+
}
|
|
124
|
+
if kwargs.get("stream"):
|
|
125
|
+
raise SwiftAPIError("Streaming is unsupported by this compatibility wrapper")
|
|
126
|
+
payload.update(kwargs)
|
|
127
|
+
payload = _snapshot_action_data(payload)
|
|
128
|
+
expected_fingerprint = _action_fingerprint(payload, truncated=True)
|
|
129
|
+
|
|
130
|
+
# Step 1: Attest with SwiftAPI — governance check
|
|
131
|
+
try:
|
|
132
|
+
attest_resp = self._session.post(
|
|
133
|
+
f"{self._authority_url}/attest",
|
|
134
|
+
json={"action_type": "chat_completion", "action_data": payload},
|
|
135
|
+
headers={
|
|
136
|
+
"X-SwiftAPI-Authority": self._swiftapi_key,
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
"User-Agent": f"swiftapi-python/{__version__}",
|
|
139
|
+
},
|
|
140
|
+
timeout=self._timeout,
|
|
141
|
+
allow_redirects=False,
|
|
142
|
+
)
|
|
143
|
+
except requests.exceptions.RequestException:
|
|
144
|
+
raise NetworkError("Attestation request failed") from None
|
|
145
|
+
|
|
146
|
+
if attest_resp.status_code == 403:
|
|
147
|
+
raise AuthenticationError("Invalid SwiftAPI authority key")
|
|
148
|
+
if attest_resp.status_code == 429:
|
|
149
|
+
raise SwiftAPIError("Rate limit exceeded")
|
|
150
|
+
if attest_resp.status_code != 200:
|
|
151
|
+
raise SwiftAPIError("Attestation failed")
|
|
152
|
+
|
|
153
|
+
attestation = attest_resp.json()
|
|
154
|
+
verify_signature(attestation)
|
|
155
|
+
if not _validate_action_binding(attestation, "chat_completion", expected_fingerprint):
|
|
156
|
+
return ChatCompletion(content="", model=model)
|
|
157
|
+
|
|
158
|
+
claim_attestation(attestation)
|
|
159
|
+
|
|
160
|
+
# Step 2: Call OpenAI directly — user's own key, user's own credits
|
|
161
|
+
openai_payload = payload
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
openai_resp = self._session.post(
|
|
165
|
+
"https://api.openai.com/v1/chat/completions",
|
|
166
|
+
json=openai_payload,
|
|
167
|
+
headers={
|
|
168
|
+
"Authorization": f"Bearer {self._openai_key}",
|
|
169
|
+
"Content-Type": "application/json",
|
|
170
|
+
"User-Agent": f"swiftapi-python/{__version__}",
|
|
171
|
+
},
|
|
172
|
+
timeout=self._timeout,
|
|
173
|
+
allow_redirects=False,
|
|
174
|
+
)
|
|
175
|
+
except requests.exceptions.RequestException:
|
|
176
|
+
raise NetworkError("OpenAI request failed") from None
|
|
177
|
+
|
|
178
|
+
if openai_resp.status_code == 401:
|
|
179
|
+
raise AuthenticationError("Invalid OpenAI API key")
|
|
180
|
+
if openai_resp.status_code != 200:
|
|
181
|
+
raise SwiftAPIError("OpenAI error")
|
|
182
|
+
|
|
183
|
+
data = openai_resp.json()
|
|
184
|
+
content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
|
|
185
|
+
|
|
186
|
+
result = ChatCompletion(
|
|
187
|
+
content=content,
|
|
188
|
+
model=model,
|
|
189
|
+
attested=True,
|
|
190
|
+
jti=attestation.get("jti"),
|
|
191
|
+
)
|
|
192
|
+
result.choices[0].finish_reason = data.get("choices", [{}])[0].get("finish_reason")
|
|
193
|
+
result.raw_response = data
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class _ChatNamespace:
|
|
198
|
+
"""Namespace for client.chat.completions"""
|
|
199
|
+
|
|
200
|
+
def __init__(self, swiftapi_key: str, openai_key: str, authority_url: str, timeout: int):
|
|
201
|
+
self.completions = Completions(swiftapi_key, openai_key, authority_url, timeout)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class OpenAI:
|
|
205
|
+
"""
|
|
206
|
+
Compatibility wrapper for openai.OpenAI.
|
|
207
|
+
|
|
208
|
+
Every completion is cryptographically attested via SwiftAPI.
|
|
209
|
+
SwiftAPI governs. OpenAI executes. You bring both keys.
|
|
210
|
+
|
|
211
|
+
Usage:
|
|
212
|
+
from swiftapi import OpenAI
|
|
213
|
+
|
|
214
|
+
client = OpenAI(
|
|
215
|
+
swiftapi_key="swiftapi_live_...",
|
|
216
|
+
openai_key="sk-..."
|
|
217
|
+
)
|
|
218
|
+
r = client.chat.completions.create(
|
|
219
|
+
model="gpt-4o",
|
|
220
|
+
messages=[{"role": "user", "content": "Hello"}]
|
|
221
|
+
)
|
|
222
|
+
print(r.choices[0].message.content)
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def __init__(
|
|
226
|
+
self,
|
|
227
|
+
swiftapi_key: str = None,
|
|
228
|
+
openai_key: str = None,
|
|
229
|
+
api_key: str = None,
|
|
230
|
+
authority_url: str = "https://swiftapi.ai",
|
|
231
|
+
timeout: int = 60,
|
|
232
|
+
**kwargs,
|
|
233
|
+
):
|
|
234
|
+
if not swiftapi_key:
|
|
235
|
+
raise AuthenticationError("swiftapi_key is required")
|
|
236
|
+
if not swiftapi_key.startswith("swiftapi_live_"):
|
|
237
|
+
raise AuthenticationError("Invalid key format: must start with 'swiftapi_live_'")
|
|
238
|
+
|
|
239
|
+
oai_key = openai_key or api_key
|
|
240
|
+
if not oai_key:
|
|
241
|
+
raise AuthenticationError("openai_key is required (your OpenAI API key)")
|
|
242
|
+
|
|
243
|
+
self.chat = _ChatNamespace(swiftapi_key, oai_key, authority_url, timeout)
|
swiftapi/py.typed
ADDED
|
File without changes
|