-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.py
More file actions
309 lines (242 loc) · 11.6 KB
/
Copy pathutils.py
File metadata and controls
309 lines (242 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
70
71
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
"""
utils.py
========
MK Intel — Shared Utilities
API key management, client initialization, and session mode handling.
Every LLM call in the platform goes through _get_client(session).
──────────────────────────────────────────────────────────────────
Session modes
──────────────────────────────────────────────────────────────────
developer Key from .env / environment variable. No quota.
Used during local development and testing.
byok User-provided API key. No quota — they pay.
Default public mode for technical evaluators
and developers building on top of MK Intel.
demo Platform-funded key. Hard quota enforced.
For recruiters and non-technical evaluators.
Requires authentication (email magic link or
GitHub OAuth). See ROADMAP.md for full spec.
blocked No key, no authentication. Rejected immediately.
──────────────────────────────────────────────────────────────────
Public API
──────────────────────────────────────────────────────────────────
get_client(session)
Returns an initialized Anthropic client for the session.
Enforces quota in demo mode.
Raises if no valid key is available.
detect_session_mode(session)
Infers the session mode from available key sources.
log_api_usage(response, step_name)
Logs token usage for a completed API call.
Helps users track consumption against demo quota.
Exceptions
──────────────────────────────────────────────────────────────────
MKAuthError No valid API key found.
DemoQuotaExceededError Demo quota exhausted.
"""
from __future__ import annotations
import os
from typing import TYPE_CHECKING, Optional
import anthropic
if TYPE_CHECKING:
from mk_intel_session import MKSession
# ── Session mode constants ────────────────────────────────────────────────────
SESSION_MODE_DEVELOPER = "developer"
SESSION_MODE_BYOK = "byok"
SESSION_MODE_DEMO = "demo"
SESSION_MODE_BLOCKED = "blocked"
VALID_SESSION_MODES = {
SESSION_MODE_DEVELOPER,
SESSION_MODE_BYOK,
SESSION_MODE_DEMO,
SESSION_MODE_BLOCKED,
}
# ── Demo quota limits ─────────────────────────────────────────────────────────
# These are enforced by the demo auth backend (see ROADMAP.md).
# Defined here as constants so they are visible to all platform code.
DEMO_QUOTA_RUNS = 2 # full analysis runs per demo session
DEMO_QUOTA_TOKENS = 30_000 # hard token cap per demo session
DEMO_RECRUITER_QUOTA_RUNS = 3 # runs for recruiter code sessions
DEMO_RECRUITER_QUOTA_TOKENS = 50_000
# ── Exceptions ────────────────────────────────────────────────────────────────
class MKAuthError(Exception):
"""
Raised when no valid API key is available for the session.
"""
pass
class DemoQuotaExceededError(Exception):
"""
Raised when a demo session has exhausted its quota.
Includes a user-facing message directing to BYOK.
"""
pass
# ── Client initialization ─────────────────────────────────────────────────────
def get_client(session: "MKSession") -> anthropic.Anthropic:
"""
Returns an initialized Anthropic client for the session.
Handles all four session modes:
developer : key from ANTHROPIC_API_KEY environment variable
byok : key from session.api_key
demo : key from ANTHROPIC_API_KEY (platform-funded)
+ quota check before returning client
blocked : raises MKAuthError immediately
Args:
session : active MKSession object.
Returns:
anthropic.Anthropic client initialized with the correct key.
Raises:
MKAuthError : no valid API key available.
DemoQuotaExceededError : demo session quota exhausted.
ValueError : invalid session_mode.
"""
mode = detect_session_mode(session)
if mode == SESSION_MODE_BLOCKED:
raise MKAuthError(
"No API key provided and no demo session active.\n\n"
"To use MK Intel:\n"
" 1. Provide your own Anthropic API key (BYOK):\n"
" session.api_key = 'sk-ant-...'\n"
" 2. Or request a free demo session at: [demo URL]\n"
" 3. Or set the ANTHROPIC_API_KEY environment variable\n"
" for local development."
)
if mode == SESSION_MODE_DEMO:
_check_demo_quota(session)
key = os.environ.get("ANTHROPIC_API_KEY")
if not key:
raise MKAuthError(
"Demo mode requires ANTHROPIC_API_KEY to be set in the "
"platform environment. Contact the platform administrator."
)
return anthropic.Anthropic(api_key=key)
if mode == SESSION_MODE_BYOK:
key = session.api_key
if not key:
raise MKAuthError(
"BYOK mode requires session.api_key to be set.\n"
"Set it before making any API calls:\n"
" session.api_key = 'sk-ant-...'"
)
return anthropic.Anthropic(api_key=key)
if mode == SESSION_MODE_DEVELOPER:
key = os.environ.get("ANTHROPIC_API_KEY")
if not key:
raise MKAuthError(
"Developer mode requires ANTHROPIC_API_KEY environment variable.\n"
"Add it to your .env file:\n"
" ANTHROPIC_API_KEY=sk-ant-..."
)
return anthropic.Anthropic(api_key=key)
raise ValueError(f"Unknown session_mode: '{mode}'")
def detect_session_mode(session: "MKSession") -> str:
"""
Infers the session mode from available key sources.
Priority:
1. session.session_mode if explicitly set and valid
2. session.api_key present → byok
3. ANTHROPIC_API_KEY env var present + no demo token → developer
4. session.demo_token present → demo
5. nothing → blocked
Args:
session : active MKSession object.
Returns:
Session mode string.
"""
# Explicit mode set on session — trust it if valid
if hasattr(session, "session_mode") and session.session_mode:
mode = session.session_mode
if mode in VALID_SESSION_MODES:
return mode
# BYOK — user provided their own key
if hasattr(session, "api_key") and session.api_key:
return SESSION_MODE_BYOK
# Demo — session has a demo token
if hasattr(session, "demo_token") and session.demo_token:
return SESSION_MODE_DEMO
# Developer — env var set, no user key, no demo token
if os.environ.get("ANTHROPIC_API_KEY"):
return SESSION_MODE_DEVELOPER
# Nothing available
return SESSION_MODE_BLOCKED
# ── Usage logging ─────────────────────────────────────────────────────────────
def log_api_usage(
response: anthropic.types.Message,
step_name: str,
session: Optional["MKSession"] = None,
) -> dict:
"""
Logs token usage for a completed API call.
Prints a summary to stdout and optionally updates the session's
token usage counter (used for demo quota tracking).
Args:
response : Anthropic API response object.
step_name : human-readable name of the pipeline step.
session : active MKSession (optional — updates token counter
if session is in demo mode).
Returns:
Dict with usage stats:
{
"step": str,
"input_tokens": int,
"output_tokens": int,
"total_tokens": int,
}
"""
input_tokens = response.usage.input_tokens
output_tokens = response.usage.output_tokens
total_tokens = input_tokens + output_tokens
print(
f"[mk_intel] {step_name} — "
f"{input_tokens} in / {output_tokens} out / "
f"{total_tokens} total tokens"
)
# Update demo session token counter if applicable
if session is not None:
mode = detect_session_mode(session)
if mode == SESSION_MODE_DEMO:
if hasattr(session, "demo_tokens_used"):
session.demo_tokens_used = (
getattr(session, "demo_tokens_used", 0) + total_tokens
)
remaining = DEMO_QUOTA_TOKENS - getattr(session, "demo_tokens_used", 0)
print(f"[mk_intel] Demo quota remaining: ~{remaining:,} tokens")
return {
"step": step_name,
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"total_tokens": total_tokens,
}
# ── Demo quota enforcement ────────────────────────────────────────────────────
def _check_demo_quota(session: "MKSession") -> None:
"""
Checks whether the demo session has remaining quota.
TODO (Phase P6 — Demo Auth System):
Replace this stub with a live quota check against the
demo_sessions SQLite table. See ROADMAP.md for full spec.
The check should verify:
1. session.demo_token is valid and not expired
2. runs_used < quota_runs
3. tokens_used < quota_tokens
If any check fails, raise DemoQuotaExceededError with the
appropriate user-facing message.
Current behavior (stub):
Passes through without enforcement.
Safe for development — demo mode is not yet publicly exposed.
Args:
session : active MKSession in demo mode.
Raises:
DemoQuotaExceededError : when quota is exhausted (future).
"""
# ── TODO: implement live quota check (Phase P6) ───────────────────────────
# from demo_auth import check_quota # to be built
# check_quota(session.demo_token)
# ─────────────────────────────────────────────────────────────────────────
# Local token counter check (client-side only, not tamper-proof)
tokens_used = getattr(session, "demo_tokens_used", 0)
if tokens_used >= DEMO_QUOTA_TOKENS:
raise DemoQuotaExceededError(
f"Demo quota exhausted ({tokens_used:,} / {DEMO_QUOTA_TOKENS:,} tokens used).\n\n"
"To continue using MK Intel, provide your own Anthropic API key:\n"
" session.api_key = 'sk-ant-...'\n\n"
"Get your API key at: https://console.anthropic.com"
)