gnai-creator commited on
Commit
6dabffc
·
1 Parent(s): 8107ace

last update

Browse files
__pycache__/app_fixed.cpython-310.pyc ADDED
Binary file (8.89 kB). View file
 
app.py.backup ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Copyright (c) 2024-2025 Felipe Maya Muniz
3
+
4
+ """
5
+ Reference Hugging Face Space for AletheionGuard BYO-HF mode.
6
+
7
+ This is a minimal FastAPI endpoint that clients can deploy on Hugging Face Spaces
8
+ to use with AletheionGuard's BYO-HF mode.
9
+
10
+ Deploy this Space as PRIVATE and use your HF token + Space URL with AletheionGuard.
11
+ """
12
+
13
+ from fastapi import FastAPI, HTTPException, Header
14
+ from pydantic import BaseModel
15
+ from typing import Optional
16
+ import logging
17
+ import math
18
+
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+ app = FastAPI(
23
+ title="AletheionGuard HF Space",
24
+ description="Reference endpoint for BYO-HF mode",
25
+ version="1.0.0"
26
+ )
27
+
28
+
29
+ class PredictRequest(BaseModel):
30
+ """Request model for /predict endpoint."""
31
+ text: str
32
+ context: Optional[str] = None
33
+
34
+
35
+ class PredictResponse(BaseModel):
36
+ """Response model for /predict endpoint."""
37
+ q1: float
38
+ q2: float
39
+ height: float
40
+ message: str
41
+ verdict: Optional[str] = None # Optional debug field - NOT used by API
42
+
43
+
44
+ def get_verdict(q1: float, q2: float, height: float) -> str:
45
+ """
46
+ Calculate verdict for debug purposes only.
47
+
48
+ NOTE: This is NOT the official verdict. The official verdict is always
49
+ calculated by the AletheionGuard API using the same rule.
50
+
51
+ Official epistemic rule:
52
+ - u = 1.0 - height (total uncertainty)
53
+ - If q2 >= 0.35 OR u >= 0.60 → REFUSED
54
+ - If q1 >= 0.35 OR (0.30 <= u < 0.60) → MAYBE
55
+ - Otherwise → ACCEPT
56
+ """
57
+ u = 1.0 - height # Total uncertainty
58
+
59
+ if q2 >= 0.35 or u >= 0.60:
60
+ return "REFUSED"
61
+ if q1 >= 0.35 or (0.30 <= u < 0.60):
62
+ return "MAYBE"
63
+ return "ACCEPT"
64
+
65
+
66
+ @app.get("/")
67
+ def root():
68
+ """Root endpoint."""
69
+ return {
70
+ "name": "AletheionGuard HF Space",
71
+ "version": "1.0.0",
72
+ "status": "operational"
73
+ }
74
+
75
+
76
+ @app.post("/predict", response_model=PredictResponse)
77
+ def predict(
78
+ request: PredictRequest,
79
+ authorization: str = Header(...)
80
+ ):
81
+ """
82
+ Predict endpoint for text analysis.
83
+
84
+ Returns heuristic uncertainty metrics (q1, q2, height) and optional verdict.
85
+
86
+ NOTE: This is an MVP implementation using heuristics. For production:
87
+ 1. Load a sentence-transformer model
88
+ 2. Use trained Q1/Q2 gates to compute actual metrics
89
+ 3. Return embeddings/logits for calibration
90
+
91
+ Args:
92
+ request: Text and optional context
93
+ authorization: Bearer token (verified by HF automatically)
94
+
95
+ Returns:
96
+ Heuristic metrics with optional debug verdict
97
+
98
+ Example:
99
+ >>> POST /predict
100
+ >>> Headers: Authorization: Bearer hf_...
101
+ >>> Body: {"text": "Paris is the capital of France", "context": "geography"}
102
+ >>> Response: {"q1": 0.06, "q2": 0.18, "height": 0.81, "verdict": "ACCEPT"}
103
+ """
104
+ try:
105
+ logger.info(f"Received prediction request - text_length={len(request.text)}")
106
+
107
+ # MVP: Compute heuristic metrics (replace with actual model in production)
108
+ # Simple heuristics based on text characteristics:
109
+ text_len = len(request.text)
110
+ word_count = len(request.text.split())
111
+ has_context = request.context is not None
112
+
113
+ # Heuristic Q1 (aleatoric): based on text ambiguity indicators
114
+ # Lower for factual statements, higher for opinion/uncertain language
115
+ q1 = min(0.30, 0.05 + (word_count / 200)) # Increases with verbosity
116
+ if any(word in request.text.lower() for word in ["maybe", "possibly", "might", "could"]):
117
+ q1 += 0.15
118
+
119
+ # Heuristic Q2 (epistemic): based on model confidence indicators
120
+ # Lower for common topics, higher for rare/complex topics
121
+ q2 = 0.10 if text_len > 20 else 0.20 # More text = more context
122
+ if has_context:
123
+ q2 -= 0.05 # Context helps reduce epistemic uncertainty
124
+ if any(word in request.text.lower() for word in ["quantum", "theoretical", "hypothetical"]):
125
+ q2 += 0.20
126
+
127
+ # Ensure bounds [0, 1]
128
+ q1 = max(0.0, min(1.0, q1))
129
+ q2 = max(0.0, min(1.0, q2))
130
+
131
+ # Compute height from pyramidal formula
132
+ height = max(0.0, min(1.0, 1.0 - math.sqrt(q1**2 + q2**2)))
133
+
134
+ # Compute verdict (optional debug field)
135
+ verdict = get_verdict(q1, q2, height)
136
+
137
+ return PredictResponse(
138
+ q1=round(q1, 3),
139
+ q2=round(q2, 3),
140
+ height=round(height, 3),
141
+ message="Heuristic metrics computed successfully.",
142
+ verdict=verdict # Debug only - API ignores this
143
+ )
144
+
145
+ except Exception as e:
146
+ logger.error(f"Prediction failed: {str(e)}")
147
+ raise HTTPException(status_code=500, detail=str(e))
148
+
149
+
150
+ @app.get("/health")
151
+ def health():
152
+ """Health check endpoint."""
153
+ return {"status": "healthy"}
154
+
155
+
156
+ if __name__ == "__main__":
157
+ import uvicorn
158
+ uvicorn.run(app, host="0.0.0.0", port=7860) # HF Spaces use port 7860
app_fixed.py ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Copyright (c) 2024-2025 Felipe Maya Muniz
3
+
4
+ """
5
+ Production Hugging Face Space for AletheionGuard.
6
+
7
+ This endpoint loads the trained neural models and provides accurate
8
+ epistemic uncertainty estimation using the full AletheionGuard architecture.
9
+ """
10
+
11
+ from fastapi import FastAPI, HTTPException, Header
12
+ from pydantic import BaseModel
13
+ from typing import Optional
14
+ import logging
15
+ import math
16
+ import torch
17
+ import torch.nn as nn
18
+ from sentence_transformers import SentenceTransformer
19
+ from pathlib import Path
20
+
21
+ logging.basicConfig(level=logging.INFO)
22
+ logger = logging.getLogger(__name__)
23
+
24
+ app = FastAPI(
25
+ title="AletheionGuard HF Space",
26
+ description="Production epistemic uncertainty estimation",
27
+ version="2.0.0"
28
+ )
29
+
30
+
31
+ # ============================================================================
32
+ # Model Definitions (copied from q1q2_gates.py)
33
+ # ============================================================================
34
+
35
+ class UncertaintyNetwork(nn.Module):
36
+ """Base neural network for uncertainty estimation."""
37
+
38
+ def __init__(
39
+ self,
40
+ input_dim: int = 384,
41
+ hidden_dim: int = 256,
42
+ num_layers: int = 3,
43
+ dropout: float = 0.1
44
+ ):
45
+ super().__init__()
46
+
47
+ self.input_dim = input_dim
48
+ self.hidden_dim = hidden_dim
49
+ self.num_layers = num_layers
50
+
51
+ # Build MLP layers
52
+ layers = []
53
+
54
+ # Input layer
55
+ layers.append(nn.Linear(input_dim, hidden_dim))
56
+ layers.append(nn.ReLU())
57
+ layers.append(nn.Dropout(dropout))
58
+
59
+ # Hidden layers
60
+ for _ in range(num_layers - 1):
61
+ layers.append(nn.Linear(hidden_dim, hidden_dim))
62
+ layers.append(nn.ReLU())
63
+ layers.append(nn.Dropout(dropout))
64
+
65
+ # Output layer (single uncertainty value)
66
+ layers.append(nn.Linear(hidden_dim, 1))
67
+ layers.append(nn.Sigmoid()) # Clamp to [0, 1]
68
+
69
+ self.network = nn.Sequential(*layers)
70
+
71
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
72
+ if x.dim() == 1:
73
+ x = x.unsqueeze(0)
74
+ single_sample = True
75
+ else:
76
+ single_sample = False
77
+
78
+ output = self.network(x)
79
+
80
+ if single_sample:
81
+ output = output.squeeze(0)
82
+
83
+ return output
84
+
85
+
86
+ class Q1Gate(nn.Module):
87
+ """Aleatoric uncertainty gate (Q1)."""
88
+
89
+ def __init__(self, input_dim: int = 384, hidden_dim: int = 256):
90
+ super().__init__()
91
+ self.network = UncertaintyNetwork(
92
+ input_dim=input_dim,
93
+ hidden_dim=hidden_dim,
94
+ num_layers=3,
95
+ dropout=0.1
96
+ )
97
+
98
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
99
+ return self.network(embeddings)
100
+
101
+
102
+ class Q2Gate(nn.Module):
103
+ """Epistemic uncertainty gate (Q2) - conditioned on Q1."""
104
+
105
+ def __init__(self, input_dim: int = 384, hidden_dim: int = 256):
106
+ super().__init__()
107
+ # Q2 is conditioned on Q1, so input is embeddings + Q1 value
108
+ self.network = UncertaintyNetwork(
109
+ input_dim=input_dim + 1, # +1 for Q1 conditioning
110
+ hidden_dim=hidden_dim,
111
+ num_layers=3,
112
+ dropout=0.1
113
+ )
114
+
115
+ def forward(self, embeddings: torch.Tensor, q1: torch.Tensor) -> torch.Tensor:
116
+ # Handle single sample
117
+ if embeddings.dim() == 1:
118
+ embeddings = embeddings.unsqueeze(0)
119
+ single_sample = True
120
+ else:
121
+ single_sample = False
122
+
123
+ # Convert Q1 to tensor if needed
124
+ if isinstance(q1, float):
125
+ q1 = torch.tensor([[q1]], dtype=embeddings.dtype, device=embeddings.device)
126
+ elif q1.dim() == 0:
127
+ q1 = q1.unsqueeze(0).unsqueeze(0)
128
+ elif q1.dim() == 1:
129
+ q1 = q1.unsqueeze(1)
130
+
131
+ # Concatenate embeddings with Q1 for conditioning
132
+ combined = torch.cat([embeddings, q1], dim=1)
133
+ output = self.network(combined)
134
+
135
+ if single_sample:
136
+ output = output.squeeze(0)
137
+
138
+ return output
139
+
140
+
141
+ # ============================================================================
142
+ # Global Model State
143
+ # ============================================================================
144
+
145
+ class ModelState:
146
+ """Global state for loaded models."""
147
+
148
+ def __init__(self):
149
+ self.encoder = None
150
+ self.q1_gate = None
151
+ self.q2_gate = None
152
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
153
+ self.loaded = False
154
+
155
+ def load_models(self):
156
+ """Load all models at startup."""
157
+ if self.loaded:
158
+ return
159
+
160
+ try:
161
+ logger.info("🔧 Loading models...")
162
+
163
+ # 1. Load sentence transformer for embeddings
164
+ logger.info(" Loading sentence transformer...")
165
+ self.encoder = SentenceTransformer('all-MiniLM-L6-v2')
166
+ self.encoder.eval()
167
+ logger.info(" ✓ Sentence transformer loaded")
168
+
169
+ # 2. Load Q1 gate
170
+ logger.info(" Loading Q1 gate...")
171
+ self.q1_gate = Q1Gate(input_dim=384, hidden_dim=256)
172
+ if Path('q1_gate.pth').exists():
173
+ self.q1_gate.load_state_dict(torch.load('q1_gate.pth', map_location=self.device))
174
+ logger.info(" ✓ Q1 gate loaded from q1_gate.pth")
175
+ else:
176
+ logger.warning(" ⚠️ q1_gate.pth not found, using random weights")
177
+
178
+ self.q1_gate.to(self.device)
179
+ self.q1_gate.eval()
180
+
181
+ # 3. Load Q2 gate
182
+ logger.info(" Loading Q2 gate...")
183
+ self.q2_gate = Q2Gate(input_dim=384, hidden_dim=256)
184
+ if Path('q2_gate.pth').exists():
185
+ self.q2_gate.load_state_dict(torch.load('q2_gate.pth', map_location=self.device))
186
+ logger.info(" ✓ Q2 gate loaded from q2_gate.pth")
187
+ else:
188
+ logger.warning(" ⚠️ q2_gate.pth not found, using random weights")
189
+
190
+ self.q2_gate.to(self.device)
191
+ self.q2_gate.eval()
192
+
193
+ self.loaded = True
194
+ logger.info(f"✅ All models loaded successfully (device: {self.device})")
195
+
196
+ except Exception as e:
197
+ logger.error(f"❌ Failed to load models: {e}")
198
+ raise
199
+
200
+
201
+ # Global model state
202
+ models = ModelState()
203
+
204
+
205
+ # ============================================================================
206
+ # API Models
207
+ # ============================================================================
208
+
209
+ class PredictRequest(BaseModel):
210
+ """Request model for /predict endpoint."""
211
+ text: str
212
+ context: Optional[str] = None
213
+
214
+
215
+ class PredictResponse(BaseModel):
216
+ """Response model for /predict endpoint."""
217
+ q1: float
218
+ q2: float
219
+ height: float
220
+ message: str
221
+ verdict: Optional[str] = None
222
+
223
+
224
+ def get_verdict(q1: float, q2: float, height: float) -> str:
225
+ """
226
+ Calculate verdict using official epistemic rule.
227
+
228
+ Official epistemic rule:
229
+ - u = 1.0 - height (total uncertainty)
230
+ - If q2 >= 0.35 OR u >= 0.60 → REFUSED
231
+ - If q1 >= 0.35 OR (0.30 <= u < 0.60) → MAYBE
232
+ - Otherwise → ACCEPT
233
+ """
234
+ u = 1.0 - height # Total uncertainty
235
+
236
+ if q2 >= 0.35 or u >= 0.60:
237
+ return "REFUSED"
238
+ if q1 >= 0.35 or (0.30 <= u < 0.60):
239
+ return "MAYBE"
240
+ return "ACCEPT"
241
+
242
+
243
+ # ============================================================================
244
+ # API Endpoints
245
+ # ============================================================================
246
+
247
+ @app.on_event("startup")
248
+ async def startup_event():
249
+ """Load models on startup."""
250
+ models.load_models()
251
+
252
+
253
+ @app.get("/")
254
+ def root():
255
+ """Root endpoint."""
256
+ return {
257
+ "name": "AletheionGuard HF Space",
258
+ "version": "2.0.0",
259
+ "status": "operational",
260
+ "models_loaded": models.loaded
261
+ }
262
+
263
+
264
+ @app.post("/predict", response_model=PredictResponse)
265
+ def predict(
266
+ request: PredictRequest,
267
+ authorization: str = Header(...)
268
+ ):
269
+ """
270
+ Predict endpoint using trained neural models.
271
+
272
+ Returns epistemic uncertainty metrics (q1, q2, height) computed by
273
+ the trained AletheionGuard neural networks.
274
+
275
+ Args:
276
+ request: Text and optional context
277
+ authorization: Bearer token (verified by HF automatically)
278
+
279
+ Returns:
280
+ Neural-computed metrics with verdict
281
+
282
+ Example:
283
+ >>> POST /predict
284
+ >>> Headers: Authorization: Bearer hf_...
285
+ >>> Body: {"text": "Paris is the capital of France", "context": "geography"}
286
+ >>> Response: {"q1": 0.08, "q2": 0.12, "height": 0.86, "verdict": "ACCEPT"}
287
+ """
288
+ try:
289
+ if not models.loaded:
290
+ raise HTTPException(status_code=503, detail="Models not loaded")
291
+
292
+ logger.info(f"Received prediction request - text_length={len(request.text)}")
293
+
294
+ # Combine text and context for embedding
295
+ full_text = request.text
296
+ if request.context:
297
+ full_text = f"{request.context}: {request.text}"
298
+
299
+ # 1. Get embeddings from sentence transformer
300
+ with torch.no_grad():
301
+ embeddings = models.encoder.encode(
302
+ full_text,
303
+ convert_to_tensor=True,
304
+ device=models.device
305
+ )
306
+
307
+ # 2. Compute Q1 (aleatoric uncertainty)
308
+ q1_tensor = models.q1_gate(embeddings)
309
+ q1 = float(q1_tensor.item())
310
+
311
+ # 3. Compute Q2 (epistemic uncertainty) - conditioned on Q1
312
+ q2_tensor = models.q2_gate(embeddings, q1_tensor)
313
+ q2 = float(q2_tensor.item())
314
+
315
+ # 4. Compute height from pyramidal formula
316
+ # height = 1 - sqrt(q1^2 + q2^2)
317
+ height = max(0.0, min(1.0, 1.0 - math.sqrt(q1**2 + q2**2)))
318
+
319
+ # 5. Calculate verdict
320
+ verdict = get_verdict(q1, q2, height)
321
+
322
+ logger.info(f"Prediction: q1={q1:.3f}, q2={q2:.3f}, height={height:.3f}, verdict={verdict}")
323
+
324
+ return PredictResponse(
325
+ q1=round(q1, 3),
326
+ q2=round(q2, 3),
327
+ height=round(height, 3),
328
+ message="Neural metrics computed successfully.",
329
+ verdict=verdict
330
+ )
331
+
332
+ except HTTPException:
333
+ raise
334
+ except Exception as e:
335
+ logger.error(f"Prediction failed: {str(e)}")
336
+ raise HTTPException(status_code=500, detail=str(e))
337
+
338
+
339
+ @app.get("/health")
340
+ def health():
341
+ """Health check endpoint."""
342
+ return {
343
+ "status": "healthy",
344
+ "models_loaded": models.loaded,
345
+ "device": str(models.device)
346
+ }
347
+
348
+
349
+ if __name__ == "__main__":
350
+ import uvicorn
351
+ uvicorn.run(app, host="0.0.0.0", port=7860)
base_forces.pth CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:add0bf0c721dc6fb30fbaabcf5618d3ff53d571515df4a1e322d5e50dd13a87d
3
  size 201797
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:14adf0b070fec59b47f65b42794f5396020e3c3729bf52ae0d94728258e16361
3
  size 201797
base_forces.pth:Zone.Identifier ADDED
Binary file (25 Bytes). View file
 
height_gate.pth CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:1fb33b134d1cf6c2c1ffd457b6e76afc7daac1edc1f91991490d04e294f2bf1d
3
  size 234693
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:408d2c77f444c35731c77a9c36d79ab39f3cfb7b4bc7536196088f1dfca441ef
3
  size 234693
height_gate.pth:Zone.Identifier ADDED
Binary file (25 Bytes). View file
 
last.ckpt:Zone.Identifier ADDED
Binary file (25 Bytes). View file
 
q1_gate.pth CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:70e352e31f20203257c54e3b4fe7e582fe0e8f6aaa50fbe9552a8d685e08cd35
3
  size 925133
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a5483aa16c37700c1bdc41a49482b68a21b58924c137a057706188c36eef57a1
3
  size 925133
q1_gate.pth:Zone.Identifier ADDED
Binary file (25 Bytes). View file
 
q1q2_best.ckpt CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:c2b608721183b9f4faf75091dd8d3d609a2d65a1b23b5ebc33f5af4ae92f30c3
3
- size 6861543
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:59183ae15e753478fef778a39288467c7342a32903292cf1cf4bf8970fc195c1
3
+ size 6861735
q2_gate.pth CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:2efa25c4ac9291ce4bbecbd768e7e9850b0977119a614bab6921fd3b32aebd7a
3
  size 926157
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9a1650c58a9b4bcf78793e1d3e6ad0d7a25e003263f222cf456d23627290e4b5
3
  size 926157
q2_gate.pth:Zone.Identifier ADDED
Binary file (25 Bytes). View file