antonypamo commited on
Commit
06dfe12
·
verified ·
1 Parent(s): e0fd074

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -1016
app.py CHANGED
@@ -1,404 +1,156 @@
1
- from pathlib import Path
2
- import json
3
-
4
- # ============================
5
- # 1) MANIFEST: carga desde archivo o usa fallback
6
- # ============================
7
-
8
- DEFAULT_MANIFEST = {
9
- "version": "Φ12.0",
10
- "project": "Savant RRF API & Meta-Logic Suite",
11
- "owner": "Antony Padilla Morales",
12
- "last_update": "2025-12-11",
13
- "modules": {
14
- "embedder": {
15
- "id": "antonypamo/RRFSAVANTMADE",
16
- "dimension": 384,
17
- "description": "Icosahedral-resonant embedder trained inside the RRF framework with Dirac shells, golden-ratio harmonics and resonance layers.",
18
- "baseline_comparison": ["MiniLM-L6-v2", "all-mpnet-base-v2"]
19
- },
20
- "meta_logit": {
21
- "repo": "antonypamo/RRFSavantMetaLogit",
22
- "filename": "logreg_rrf_savant_15.joblib",
23
- "expected_features": 15,
24
- "feature_description": [
25
- "semantic_margin",
26
- "cosine_prompt_answer",
27
- "token_entropy",
28
- "dirac_energy",
29
- "dirac_shell_std",
30
- "freq_low",
31
- "freq_mid",
32
- "freq_high",
33
- "coherence_ratio",
34
- "phi_ratio",
35
- "token_len_prompt",
36
- "token_len_answer",
37
- "sync_factor",
38
- "resonance_peak",
39
- "logit_bias"
40
- ]
41
- },
42
- "models": {
43
- "savant_cnn": {
44
- "filename": "savant_cnn.pt",
45
- "role": "Signal-to-resonance transformer for numeric → semantic channels.",
46
- "status": "experimental"
47
- },
48
- "rrf_nodes": {
49
- "filename": "rrf_nodes.pt",
50
- "description": "Graph-based icosahedral node memory for cross-session resonance."
51
- }
52
- }
53
- },
54
- "api": {
55
- "base_url": "https://antonypamo-apisavant2.hf.space",
56
- "routes": {
57
- "/embed": {
58
- "method": "POST",
59
- "input": ["text"],
60
- "output": ["embedding"],
61
- "use_model": "RRFSAVANTMADE"
62
- },
63
- "/rerank": {
64
- "method": "POST",
65
- "input": ["query", "documents[]"],
66
- "output": ["sorted_documents", "scores"],
67
- "logic": "semantic margin + resonance weighting"
68
- },
69
- "/quality": {
70
- "method": "POST",
71
- "input": ["prompt", "answer"],
72
- "output": ["proba", "label (0/1)", "feature_map"],
73
- "pipeline": "embed → feature_extractor → meta_logit"
74
- },
75
- "/roles_profile": {
76
- "method": "POST",
77
- "status": "planned",
78
- "description": "Maps text to RRF cognitive roles (Φ-nodes)."
79
- },
80
- "/tutor": {
81
- "method": "POST",
82
- "status": "planned",
83
- "description": "LLM-based tutor using resonant context."
84
- }
85
- }
86
- },
87
- "pipelines": {
88
- "embedding_pipeline": {
89
- "steps": [
90
- "load_encoder",
91
- "encode_text",
92
- "normalize",
93
- "output_embeddings"
94
- ]
95
- },
96
- "quality_pipeline": {
97
- "steps": [
98
- "encode(prompt)",
99
- "encode(answer)",
100
- "extract_features(15-dim)",
101
- "predict_meta_logit",
102
- "return_label_and_prob"
103
- ],
104
- "purpose": "Evaluate conceptual quality and reasoning integrity."
105
- },
106
- "rerank_pipeline": {
107
- "steps": [
108
- "encode_query",
109
- "encode_docs",
110
- "compute_semantic_margin",
111
- "compute_resonance_rank",
112
- "return_sorted_docs"
113
- ]
114
- }
115
- },
116
- "enterprise_architecture": {
117
- "layers": [
118
- "Frontend → React Landing Page",
119
- "Gateway Proxy → NGINX",
120
- "API Layer → FastAPI + Uvicorn",
121
- "Model Runtime → Embedder + Meta-Logit",
122
- "Compute Layer → GPU/CPU auto-scaling",
123
- "Monitoring → Prometheus + Grafana",
124
- "Storage → HF Hub + local persistence (rrf_nodes)"
125
- ]
126
- },
127
- "investor_highlights": {
128
- "differentiators": [
129
- "Meta-logic quality evaluator (15 feature resonant signal)",
130
- "Icosahedral embedding geometry",
131
- "Discrete Dirac resonance physics applied to NLP",
132
- "Symbiotic self-improvement protocol",
133
- "Low inference cost, scalable microservice"
134
- ],
135
- "traction": {
136
- "hf_space": "running",
137
- "models_downloads": "increasing",
138
- "api_usage": "real inference logs available"
139
- }
140
- },
141
- "savant_state": {
142
- "status": "active",
143
- "mode": "Savant RRF Simbiótico Hacker",
144
- "health": {
145
- "embedder": "OK",
146
- "meta_logit": "OK",
147
- "api_endpoints": {
148
- "/embed": "stable",
149
- "/rerank": "stable",
150
- "/quality": "error_404_needs_route_fix"
151
- }
152
- }
153
- },
154
- "todo_next_steps": [
155
- "Fix /quality endpoint routing",
156
- "Integrate CNN → feature_extractor",
157
- "Add persistent RRF node memory",
158
- "Deploy enterprise-tier version on AWS/GCP",
159
- "Present investor deck based on this JSON"
160
- ]
161
- }
162
-
163
- MANIFEST_PATH = Path(__file__).parent / "savant_rrf_api_manifest_phi12.json"
164
-
165
- try:
166
- if MANIFEST_PATH.exists():
167
- print(f"[Manifest] Loading from file: {MANIFEST_PATH}", flush=True)
168
- manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
169
- else:
170
- print(f"[Manifest] File not found at {MANIFEST_PATH}, using inline DEFAULT_MANIFEST.", flush=True)
171
- manifest = DEFAULT_MANIFEST
172
- except Exception as e:
173
- print(f"[Manifest] Error loading manifest: {e}. Using inline DEFAULT_MANIFEST.", flush=True)
174
- manifest = DEFAULT_MANIFEST
175
 
 
176
  import os
177
  import sys
178
- import math
179
  import json
 
180
  from typing import Optional, Dict, Any, List
181
 
182
  import numpy as np
183
  from numpy.linalg import norm
184
  from scipy.linalg import expm
185
 
 
 
 
186
  from fastapi import FastAPI, HTTPException
187
- from pydantic import BaseModel, Field
188
- from pydantic import ConfigDict # para evitar warning con model_id
189
 
190
  from sentence_transformers import SentenceTransformer
191
  from huggingface_hub import hf_hub_download
192
  import joblib
193
 
194
- import torch
195
- import torch.nn as nn
 
196
 
197
- import json
198
- from pathlib import Path
 
 
 
 
199
 
200
  MANIFEST_PATH = Path(__file__).parent / "savant_rrf_api_manifest_phi12.json"
201
- manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
202
-
203
-
204
- # ============================
205
- # Configuración general
206
- # ============================
207
-
208
- HF_TOKEN = os.environ.get("HF_TOKEN", "")
209
- os.environ["HF_TOKEN"] = HF_TOKEN # por si algún cliente interno lo espera
210
-
211
- ENCODER_MODEL_ID = "antonypamo/RRFSAVANTMADE"
212
- META_LOGIT_REPO = "antonypamo/RRFSavantMetaLogit"
213
- META_LOGIT_FILENAME = "logreg_rrf_savant_15.joblib" # versión 15 features
214
-
215
- # Dataset central con TODOS los artefactos RRF/Savant
216
- RRF_DATASET_REPO = "antonypamo/savant_rrf1_curated"
217
 
 
 
 
 
 
 
 
218
 
219
- def hf_data_path(filename: str) -> str:
220
- """
221
- Descarga un archivo desde el dataset antonypamo/savant_rrf1_curated
222
- y devuelve la ruta local en cache.
223
- """
224
- return hf_hub_download(
225
- repo_id=RRF_DATASET_REPO,
226
- filename=filename,
227
- repo_type="dataset",
228
- token=HF_TOKEN or None,
229
- )
230
 
 
231
 
232
- print("===== Application Startup =====", flush=True)
233
 
234
- # ============================
235
- # Cargar encoder y meta-logit
236
- # ============================
237
 
238
- print("🔄 [Startup] Cargando encoder RRFSAVANTMADE...", flush=True)
239
- try:
240
- encoder = SentenceTransformer(ENCODER_MODEL_ID)
241
- print("✅ [Startup] Encoder cargado.", flush=True)
242
- except Exception as e:
243
- print(f"❌ [Startup] Error al cargar encoder: {e}", file=sys.stderr, flush=True)
244
- raise
245
 
246
- print("🔄 [Startup] Descargando meta-logit desde HF Hub...", flush=True)
247
- try:
248
- meta_logit_path = hf_hub_download(
249
- repo_id=META_LOGIT_REPO,
250
- filename=META_LOGIT_FILENAME,
251
- token=HF_TOKEN or None,
252
- )
253
- print(f"🔄 [Startup] Cargando modelo meta-logit '{META_LOGIT_FILENAME}'...", flush=True)
254
- meta_logit = joblib.load(meta_logit_path)
255
- try:
256
- print(f"🔎 [Startup] Meta-logit espera {meta_logit.n_features_in_} features.", flush=True)
257
- except Exception:
258
- print("⚠️ [Startup] No se pudo leer n_features_in_.", flush=True)
259
- print("✅ [Startup] Meta-logit cargado.", flush=True)
260
- except Exception as e:
261
- print(f"❌ [Startup] Error al cargar meta-logit: {e}", file=sys.stderr, flush=True)
262
- raise
263
 
 
264
 
265
- # ============================
266
- # Artefactos desde savant_rrf1_curated
267
- # ============================
268
 
269
- def safe_hf(path_name: str) -> Optional[str]:
270
  try:
271
- p = hf_data_path(path_name)
272
- print(f"✅ [Dataset] Descargado {path_name}", flush=True)
273
- return p
 
 
 
274
  except Exception as e:
275
- print(f"⚠️ [Dataset] No se pudo descargar {path_name}: {e}", file=sys.stderr, flush=True)
276
  return None
277
 
 
 
 
278
 
279
- SAVANT_CNN_PATH = safe_hf("savant_cnn.pt")
280
- RRF_NODES_PATH = safe_hf("rrf_nodes.pt")
281
- RRF_TUTOR_JSONL_PATH = safe_hf("rrf_tutor_curated.jsonl")
282
- RRF_SEMANTIC_CORPUS_PATH = safe_hf("RRF_SAVANT_SEMANTIC_CORPUS.jsonl")
283
- RRF_CORPUS_INDEX_PATH = safe_hf("RRF_SAVANT_CORPUS.index")
284
 
285
- PHYS_RRF_RESONANCE_MATRIX = safe_hf("rrf_resonance_matrix.csv")
286
- PHYS_RRF_ENERGY_PROFILE = safe_hf("rrf_energy_profile.csv")
287
- PHYS_RRF_EIGEN_SPECTRUM = safe_hf("rrf_eigen_spectrum.csv")
288
 
289
- PHYS_RES_MATRIX_13 = safe_hf("resonance_matrix_13.csv")
290
- PHYS_NODES_13 = safe_hf("nodes_13.csv")
291
- PHYS_ENERGY_LOGPHI_13 = safe_hf("energy_logphi_13.csv")
292
- PHYS_DEGREE_13 = safe_hf("degree_13.csv")
293
- PHYS_ADJ_13 = safe_hf("adjacency_13.csv")
 
 
 
 
 
 
 
294
 
 
 
 
295
 
296
- # ============================
297
- # Savant CNN + nodos RRF (demo interna)
298
- # ============================
 
 
299
 
300
  class SavantCNN(nn.Module):
301
- """
302
- CNN compatible con el checkpoint:
303
- - conv1: [1 -> 32]
304
- - conv2: [32 -> 64]
305
- - conv3: [64 -> 128]
306
- - pool: AdaptiveAvgPool1d(4) => 128 * 4 = 512
307
- - fc: [512 -> 64]
308
- """
309
- def __init__(self, in_channels: int = 1, out_dim: int = 64):
310
  super().__init__()
311
- self.conv1 = nn.Conv1d(in_channels, 32, kernel_size=3, padding=1)
312
- self.conv2 = nn.Conv1d(32, 64, kernel_size=3, padding=1)
313
- self.conv3 = nn.Conv1d(64, 128, kernel_size=3, padding=1)
314
  self.pool = nn.AdaptiveAvgPool1d(4)
315
- self.fc = nn.Linear(512, out_dim)
316
 
317
- def forward(self, x: torch.Tensor) -> torch.Tensor:
318
- # x: [batch, channels, length]
319
  x = torch.relu(self.conv1(x))
320
  x = torch.relu(self.conv2(x))
321
  x = torch.relu(self.conv3(x))
322
- x = self.pool(x) # [batch, 128, 4]
323
- x = x.view(x.size(0), -1) # [batch, 512]
324
- x = self.fc(x) # [batch, out_dim]
325
- return x
326
-
327
-
328
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
329
 
330
- savant_cnn: Optional[SavantCNN] = None
331
- rrf_nodes: Optional[Any] = None
332
-
333
- if SAVANT_CNN_PATH is not None:
334
  try:
335
- state_dict = torch.load(SAVANT_CNN_PATH, map_location=device)
336
- print("✅ Checkpoint keys:", list(state_dict.keys()))
337
- print("ℹ️ conv3.weight shape en checkpoint:", state_dict["conv3.weight"].shape)
338
- print("ℹ️ fc.weight shape en checkpoint:", state_dict["fc.weight"].shape)
339
-
340
  savant_cnn = SavantCNN()
341
- savant_cnn.load_state_dict(state_dict)
342
- savant_cnn.to(device)
343
- savant_cnn.eval()
344
- print("✅ Loaded Savant CNN from", SAVANT_CNN_PATH)
345
  except Exception as e:
346
- print("⚠️ Error loading Savant CNN:", e, file=sys.stderr)
347
- savant_cnn = None
348
- else:
349
- print("⚠️ SAVANT_CNN_PATH is None, no se cargó CNN.", file=sys.stderr)
350
-
351
 
352
- if RRF_NODES_PATH is not None:
 
353
  try:
354
  rrf_nodes = torch.load(RRF_NODES_PATH, map_location=device)
355
- print("✅ Loaded RRF nodes from", RRF_NODES_PATH)
356
- print("Type of rrf_nodes:", type(rrf_nodes))
357
- if isinstance(rrf_nodes, dict):
358
- print("🔑 rrf_nodes keys:", list(rrf_nodes.keys())[:10])
359
  except Exception as e:
360
- print("⚠️ Error loading RRF nodes:", e, file=sys.stderr)
361
- rrf_nodes = None
362
- else:
363
- print("⚠️ RRF_NODES_PATH is None, no se cargaron nodos.", file=sys.stderr)
364
-
365
-
366
- def fuse_cnn_with_node(example_length: int = 64):
367
- """
368
- Utilidad interna de demo: fusionar embedding CNN + nodo RRF.
369
- No expuesta aún como endpoint público.
370
- """
371
- if savant_cnn is None or rrf_nodes is None:
372
- print("Fusion not available – missing CNN or RRF nodes snapshot.")
373
- return None
374
-
375
- x = torch.randn(1, 1, example_length, device=device)
376
- cnn_emb = savant_cnn(x) # [1, 64]
377
-
378
- try:
379
- # asumir primer nodo tipo rrf_nodes["node_0"]
380
- node0_key = list(rrf_nodes.keys())[0]
381
- node0 = rrf_nodes[node0_key]
382
- if isinstance(node0, dict) and "linguistic" in node0:
383
- linguistic_vec = node0["linguistic"]
384
- if isinstance(linguistic_vec, torch.Tensor):
385
- linguistic_vec = linguistic_vec.detach().clone().to(device)
386
- else:
387
- linguistic_vec = torch.tensor(linguistic_vec, dtype=torch.float32, device=device)
388
- else:
389
- linguistic_vec = torch.randn(cnn_emb.shape[-1], device=device)
390
- except Exception:
391
- linguistic_vec = torch.randn(cnn_emb.shape[-1], device=device)
392
-
393
- linguistic_vec = linguistic_vec.unsqueeze(0) # [1, 64]
394
- fused = torch.cat([cnn_emb, linguistic_vec], dim=-1) # [1, 128]
395
- print("Fused embedding shape (CNN + linguistic node):", fused.shape)
396
- return fused
397
 
398
-
399
- # ============================
400
- # Geometría icosaédrica Φ12.0
401
- # ============================
402
 
403
  phi = (1 + np.sqrt(5)) / 2
404
  nodes_geom = np.array([
@@ -407,729 +159,124 @@ nodes_geom = np.array([
407
  [phi, 0, 1], [phi, 0, -1], [-phi, 0, 1], [-phi, 0, -1]
408
  ], dtype=float)
409
  nodes_geom /= norm(nodes_geom, axis=1, keepdims=True)
410
- N = nodes_geom.shape[0] # 12 nodos
411
-
412
- sigma_x = np.array([[0, 1], [1, 0]], dtype=complex)
413
- sigma_y = np.array([[0, -1j], [1j, 0]], dtype=complex)
414
- sigma_z = np.array([[1, 0], [0, -1]], dtype=complex)
415
-
416
-
417
- def kron_IN(M, N_sites):
418
- return np.kron(M, np.eye(N_sites, dtype=complex))
419
-
420
-
421
- def site_op(block_2x2, i, j, N_sites):
422
- K = np.zeros((N_sites, N_sites), dtype=complex)
423
- K[i, j] = 1.0
424
- return np.kron(K, block_2x2)
425
-
426
-
427
- def geodesic_kernel(nodes, sigma=0.618, alpha_log=0.10):
428
- diff = nodes[:, None, :] - nodes[None, :, :]
429
- dist = norm(diff, axis=-1)
430
-
431
- W = np.exp(-(dist**2) / (sigma**2))
432
- np.fill_diagonal(W, 0.0)
433
-
434
- if alpha_log > 0.0:
435
- corr = 1.0 + alpha_log * np.log1p(dist**2)
436
- corr[range(N), range(N)] = 1.0
437
- W = W / corr
438
-
439
- row_sums = W.sum(axis=1, keepdims=True)
440
- row_sums[row_sums == 0] = 1.0
441
- return W / row_sums
442
-
443
-
444
- def u1_edge_phases(nodes, flux_vector=(0.0, 0.0, 0.0), q=1.0, gauge_scale=1.0):
445
- A = gauge_scale * np.asarray(flux_vector, dtype=float)
446
- midpoints = (nodes[:, None, :] + nodes[None, :, :]) / 2.0
447
- theta = (midpoints @ A).astype(float)
448
- theta = 0.5 * (theta - theta.T)
449
- return theta * q
450
-
451
-
452
- def build_dirac_hamiltonian(
453
- m=0.25,
454
- v=1.0,
455
- sigma=0.618,
456
- alpha_log=0.10,
457
- q=1.0,
458
- flux_vector=(0.0, 0.0, 0.0),
459
- gauge_scale=0.0,
460
- ):
461
- W = geodesic_kernel(nodes_geom, sigma=sigma, alpha_log=alpha_log)
462
-
463
- if gauge_scale != 0.0 and any(flux_vector):
464
- theta = u1_edge_phases(nodes_geom, flux_vector=flux_vector,
465
- q=q, gauge_scale=gauge_scale)
466
- U = np.exp(1j * theta)
467
- else:
468
- U = np.ones((N, N), dtype=complex)
469
-
470
- H = np.kron(np.eye(N, dtype=complex), m * sigma_z)
471
-
472
- diff = nodes_geom[:, None, :] - nodes_geom[None, :, :]
473
- dist = norm(diff, axis=-1) + 1e-12
474
- d_hat = diff / dist[..., None]
475
-
476
- for i in range(N):
477
- for j in range(N):
478
- if i == j or W[i, j] == 0:
479
- continue
480
- nvec = d_hat[i, j]
481
- S = (nvec[0] * sigma_x +
482
- nvec[1] * sigma_y +
483
- nvec[2] * sigma_z)
484
- H += v * W[i, j] * U[i, j] * site_op(S, i, j, N)
485
-
486
- H = 0.5 * (H + H.conj().T)
487
- return H
488
-
489
-
490
- def site_probs(psi):
491
- N2 = psi.shape[0]
492
- n = N2 // 2
493
- psi_mat = psi.reshape(n, 2)
494
- return np.sum(np.abs(psi_mat)**2, axis=1).real
495
-
496
-
497
- def chirality(psi):
498
- S = kron_IN(sigma_z, N)
499
- return float(np.vdot(psi, S @ psi).real)
500
-
501
-
502
- def energy_expectation(psi, H):
503
- return float(np.vdot(psi, H @ psi).real)
504
-
505
-
506
- def spatial_entropy(p):
507
- p = np.clip(p, 1e-12, 1.0)
508
- return float(-np.sum(p * np.log(p)).real)
509
-
510
 
511
- def evolve_dirac_shell(psi0, H, dt=0.05, steps=200, record_every=20):
512
- """
513
- Evolución unitaria sobre la red icosaédrica.
514
- steps=200 da buena resolución para el feature set; los endpoints
515
- pueden usar menos pasos si se quiere.
516
- """
517
- U = expm(-1j * dt * H)
518
- psi = psi0.copy()
519
-
520
- probs_hist = []
521
- energy_hist = []
522
- chir_hist = []
523
- ent_hist = []
524
-
525
- for t in range(steps + 1):
526
- if t % record_every == 0:
527
- p = site_probs(psi)
528
- probs_hist.append(p)
529
- energy_hist.append(energy_expectation(psi, H))
530
- chir_hist.append(chirality(psi))
531
- ent_hist.append(spatial_entropy(p))
532
-
533
- psi = U @ psi
534
- psi /= np.sqrt(np.vdot(psi, psi))
535
-
536
- return {
537
- "probs": np.array(probs_hist, dtype=float),
538
- "energy": np.array(energy_hist, dtype=float),
539
- "chirality": np.array(chir_hist, dtype=float),
540
- "entropy": np.array(ent_hist, dtype=float),
541
- "dt": dt,
542
- "record_every": record_every,
543
- }
544
-
545
-
546
- # ============================
547
- # Core RRF: embeddings + features + scores
548
- # ============================
549
 
550
  def get_embedding(text: str) -> np.ndarray:
551
- emb = encoder.encode([text], convert_to_numpy=True, normalize_embeddings=True)
552
- return emb[0]
553
 
554
-
555
- def compute_rrf_features(prompt: str, answer: str) -> Dict[str, float]:
556
- """
557
- Genera las 15 features que espera el meta-logit:
558
- - embeddings + Dirac shell + derivadas (entropía, energía, longitud, etc.)
559
- """
560
- # Embeddings
561
  e_p = get_embedding(prompt)
562
  e_a = get_embedding(answer)
 
563
 
564
- cosine_pa = float(np.dot(e_p, e_a))
565
- len_ratio = len(answer) / (len(prompt) + 1.0)
566
-
567
- # Simulación Dirac shell determinista (semilla por prompt+answer)
568
- rng = np.random.default_rng(abs(hash(prompt + answer)) % (2**32))
569
- vec = rng.normal(0, 1, (2*N,)) + 1j * rng.normal(0, 1, (2*N,))
570
- vec /= np.sqrt(np.vdot(vec, vec))
571
- psi0 = vec
572
-
573
- H = build_dirac_hamiltonian(
574
- m=0.25, v=1.0, sigma=0.618,
575
- alpha_log=0.10, q=1.0,
576
- flux_vector=(0.0, 0.0, 0.0),
577
- gauge_scale=0.0,
578
- )
579
-
580
- out = evolve_dirac_shell(psi0, H, dt=0.05, steps=100, record_every=25)
581
-
582
- entropy = out["entropy"]
583
- energy = out["energy"]
584
- chir = out["chirality"]
585
-
586
- S_final = float(entropy[-1])
587
- S_initial = float(entropy[0])
588
- S_delta = S_final - S_initial
589
- C_final = float(chir[-1])
590
- E_mean = float(np.mean(energy))
591
- E_std = float(np.std(energy))
592
-
593
- feats: Dict[str, float] = {
594
- "cosine_pa": cosine_pa,
595
- "len_ratio": len_ratio,
596
- "dirac_entropy_final": S_final,
597
- "dirac_entropy_delta": S_delta,
598
- "dirac_chirality_final": C_final,
599
- "dirac_energy_mean": E_mean,
600
- "dirac_energy_std": E_std,
601
- }
602
-
603
- # Derivadas extra para llegar a 15 features
604
- S_max = math.log(N)
605
- feats["entropy_norm"] = feats["dirac_entropy_final"] / S_max
606
- feats["entropy_abs_delta"] = abs(feats["dirac_entropy_delta"])
607
- feats["chirality_abs"] = abs(feats["dirac_chirality_final"])
608
- feats["energy_abs_mean"] = abs(feats["dirac_energy_mean"])
609
- feats["energy_std_sq"] = feats["dirac_energy_std"] ** 2
610
- feats["cosine_sq"] = feats["cosine_pa"] ** 2
611
- feats["len_log"] = math.log1p(feats["len_ratio"])
612
- feats["len_inv"] = 1.0 / (1.0 + feats["len_ratio"])
613
-
614
- return feats
615
-
616
-
617
- def features_to_vector(feats: Dict[str, float]) -> np.ndarray:
618
- keys = [
619
- "cosine_pa",
620
- "len_ratio",
621
- "dirac_entropy_final",
622
- "dirac_entropy_delta",
623
- "dirac_chirality_final",
624
- "dirac_energy_mean",
625
- "dirac_energy_std",
626
- "entropy_norm",
627
- "entropy_abs_delta",
628
- "chirality_abs",
629
- "energy_abs_mean",
630
- "energy_std_sq",
631
- "cosine_sq",
632
- "len_log",
633
- "len_inv",
634
- ]
635
- return np.array([feats[k] for k in keys], dtype=float)
636
-
637
-
638
- def compute_scores_srff_crrf_ephi(prompt: str, answer: str):
639
- """
640
- Usa el meta-logit para obtener p_good y derivar:
641
- - SRRF, CRRF, E_phi
642
- """
643
- feats = compute_rrf_features(prompt, answer)
644
- x = features_to_vector(feats).reshape(1, -1)
645
-
646
- proba = meta_logit.predict_proba(x)[0]
647
- p_good = float(proba[1])
648
-
649
- SRRF = p_good
650
- CRRF = p_good * feats["cosine_pa"]
651
-
652
- S_max = math.log(N)
653
- norm_entropy = float(feats["dirac_entropy_final"] / S_max)
654
- E_phi = 0.5 * (SRRF + norm_entropy)
655
-
656
- scores = {
657
- "SRRF": SRRF,
658
- "CRRF": CRRF,
659
- "E_phi": E_phi,
660
- "p_good": p_good,
661
- }
662
- return scores, feats
663
-
664
-
665
- # ============================
666
- # Role profiles (perfiles de evaluación)
667
- # ============================
668
-
669
- ROLE_PROFILES: Dict[str, Dict[str, float]] = {
670
- "default": {
671
- "SRRF": 1.0,
672
- "CRRF": 1.0,
673
- "E_phi": 1.0,
674
- },
675
- "creative": {
676
- "SRRF": 0.5,
677
- "CRRF": 0.5,
678
- "E_phi": 1.5,
679
- },
680
- "precise": {
681
- "SRRF": 1.0,
682
- "CRRF": 1.8,
683
- "E_phi": 0.4,
684
- },
685
- }
686
-
687
-
688
- def apply_role_profile(scores: Dict[str, float], role_name: Optional[str]) -> Dict[str, Any]:
689
- """
690
- Calcula un composite_score según el perfil de rol.
691
- """
692
- if not role_name:
693
- role_name = "default"
694
-
695
- profile = ROLE_PROFILES.get(role_name, ROLE_PROFILES["default"])
696
-
697
- composite = 0.0
698
- weight_sum = 0.0
699
- for key, w in profile.items():
700
- if key in scores:
701
- composite += w * scores[key]
702
- weight_sum += abs(w)
703
-
704
- if weight_sum > 0.0:
705
- composite /= weight_sum
706
 
707
  return {
708
- "role": role_name,
709
- "weights": profile,
710
- "composite_score": composite,
 
 
711
  }
712
 
713
-
714
- # ============================
715
- # RRF Tutor desde JSONL curado
716
- # ============================
717
-
718
- rrf_corpus_texts: List[str] = []
719
- rrf_corpus_prompts: List[str] = []
720
- rrf_corpus_completions: List[str] = []
721
- rrf_corpus_embeds = None
722
- rrf_tutor_ready = False
723
-
724
-
725
- def _load_rrf_tutor_from_jsonl(path: Optional[str]):
726
- global rrf_corpus_texts, rrf_corpus_prompts, rrf_corpus_completions, rrf_corpus_embeds, rrf_tutor_ready
727
-
728
- if path is None:
729
- print("⚠️ [RRF Tutor] No se encontró ruta para rrf_tutor_curated.jsonl", flush=True)
730
- rrf_tutor_ready = False
731
- return
732
-
733
- print(f"🔄 [RRF Tutor] Cargando ejemplos desde JSONL: {path}", flush=True)
734
- try:
735
- examples = []
736
- with open(path, "r", encoding="utf-8") as f:
737
- for line in f:
738
- line = line.strip()
739
- if not line:
740
- continue
741
- try:
742
- ex = json.loads(line)
743
- except Exception:
744
- continue
745
- if "prompt" in ex and "completion" in ex and ex["prompt"] and ex["completion"]:
746
- examples.append(ex)
747
-
748
- if not examples:
749
- raise ValueError("No se encontraron ejemplos válidos con 'prompt' y 'completion' en el JSONL.")
750
-
751
- for ex in examples:
752
- p = ex["prompt"]
753
- c = ex["completion"]
754
- rrf_corpus_prompts.append(p)
755
- rrf_corpus_completions.append(c)
756
- rrf_corpus_texts.append(p + "\n\n" + c)
757
-
758
- print(f"🔄 [RRF Tutor] Construyendo embeddings para {len(rrf_corpus_texts)} ejemplos...", flush=True)
759
- embeds = encoder.encode(
760
- rrf_corpus_texts,
761
- convert_to_numpy=True,
762
- show_progress_bar=True,
763
- normalize_embeddings=True,
764
- )
765
- rrf_corpus_embeds = embeds
766
- rrf_tutor_ready = True
767
- print("✅ [RRF Tutor] Embeddings construidos y listos.", flush=True)
768
-
769
- except Exception as e:
770
- print(f"❌ [RRF Tutor] Error cargando JSONL: {e}", flush=True)
771
- rrf_corpus_texts = []
772
- rrf_corpus_prompts = []
773
- rrf_corpus_completions = []
774
- rrf_corpus_embeds = None
775
- rrf_tutor_ready = False
776
- print("⚠️ [RRF Tutor] Endpoint /v1/rrf_tutor devolverá 503 si se usa.", flush=True)
777
-
778
-
779
- # Cargar RRF Tutor en startup
780
- _load_rrf_tutor_from_jsonl(RRF_TUTOR_JSONL_PATH)
781
-
782
-
783
- def rrf_tutor_retrieve_examples(query: str, top_k: int = 3):
784
- """
785
- Recupera los ejemplos más similares desde el JSONL curado
786
- usando embeddings del encoder RRF.
787
- """
788
- if (not rrf_tutor_ready) or rrf_corpus_embeds is None or len(rrf_corpus_embeds) == 0:
789
- raise RuntimeError("Embeddings de RRF Tutor no están disponibles.")
790
-
791
- q_emb = encoder.encode([query], convert_to_numpy=True, normalize_embeddings=True)[0]
792
- sims = np.dot(rrf_corpus_embeds, q_emb)
793
-
794
- top_k = min(top_k, len(rrf_corpus_embeds))
795
- top_idx = np.argsort(-sims)[:top_k]
796
-
797
- results = []
798
- for idx in top_idx:
799
- results.append(
800
- {
801
- "idx": int(idx),
802
- "score": float(sims[idx]),
803
- "prompt": rrf_corpus_prompts[idx],
804
- "completion": rrf_corpus_completions[idx],
805
- }
806
- )
807
- return results
808
-
809
-
810
- def rrf_tutor_build_answer(query: str, retrieved_examples):
811
- """
812
- Construye una respuesta simple basada en el mejor ejemplo del corpus.
813
- """
814
- if not retrieved_examples:
815
- return (
816
- "No encontré ejemplos relevantes en el dataset RRF Tutor para tu consulta. "
817
- "Verifica que rrf_tutor_curated.jsonl contenga 'prompt' y 'completion'."
818
- )
819
-
820
- best = retrieved_examples[0]
821
- base_completion = best["completion"]
822
-
823
- answer = (
824
- "🔎 Respuesta basada en el ejemplo más cercano del corpus RRF:\n\n"
825
- f"{base_completion}\n\n"
826
- "💡 Nota: Esta es una versión mínima que reutiliza directamente la 'completion' "
827
- "del ejemplo más similar en el corpus curado. En una versión extendida, aquí "
828
- "se conectaría un LLM pequeño que combine varios ejemplos como contexto."
829
- )
830
- return answer
831
-
832
-
833
- # ============================
834
- # FastAPI models
835
- # ============================
836
 
837
  class EvaluateRequest(BaseModel):
838
- prompt: str = Field(..., description="Pregunta / instrucción original.")
839
- answer: str = Field(..., description="Respuesta generada por un LLM.")
840
- model_label: Optional[str] = Field(
841
- None, description="Etiqueta opcional de rol/modelo (default/creative/precise o custom)."
842
- )
843
-
844
 
845
  class EvaluateResponse(BaseModel):
846
  scores: Dict[str, float]
847
- features: Dict[str, float]
848
- sim_summary: Dict[str, Any]
849
- role_profile: Optional[Dict[str, Any]] = None
850
-
851
-
852
- class QualityRemoteRequest(EvaluateRequest):
853
- """Alias de EvaluateRequest para /quality_remote."""
854
- pass
855
-
856
-
857
- class RoleProfileInfo(BaseModel):
858
- name: str
859
- weights: Dict[str, float]
860
-
861
-
862
- class RoleProfilesResponse(BaseModel):
863
- roles: List[RoleProfileInfo]
864
-
865
 
866
  class RerankRequest(BaseModel):
867
- """
868
- Petición para /v1/rerank
869
- """
870
- query: str = Field(..., description="Query de búsqueda o pregunta del usuario.")
871
- documents: List[str] = Field(..., description="Lista de documentos candidatos a rerankear.")
872
- alpha: float = Field(
873
- 0.2,
874
- description="Peso de la corrección log_rdf en el score_final. 0 = solo cosine, 1 = solo log_rdf.",
875
- )
876
- query_embedding_norm: bool = Field(
877
- True,
878
- description="Si True, normaliza el embedding de query (útil para cosine).",
879
- )
880
-
881
 
882
- class RerankDocumentResult(BaseModel):
883
- id: int = Field(..., description="Índice del documento en la lista de entrada.")
884
- score_cosine: float
885
- score_log_rdf: float
886
- score_final: float
887
  rank: int
888
 
889
-
890
  class RerankResponse(BaseModel):
891
- # evitar warning con 'model_id'
892
  model_config = ConfigDict(protected_namespaces=())
893
-
894
  model_id: str
895
- alpha: float
896
- query_embedding_norm: bool
897
- results: List[RerankDocumentResult]
898
 
899
-
900
- class RRFTutorRequest(BaseModel):
901
- query: str = Field(..., description="Pregunta o fragmento de ecuación/idea RRF.")
902
- max_examples: int = Field(
903
- 3, ge=1, le=8,
904
- description="Número de ejemplos de savant_rrf1_curated a recuperar (1-8).",
905
- )
906
- include_raw_context: bool = Field(
907
- False,
908
- description="Si es true, devuelve los ejemplos recuperados.",
909
- )
910
-
911
-
912
- class RetrievedExample(BaseModel):
913
- prompt: str
914
- completion: str
915
- score: float
916
-
917
-
918
- class RRFTutorResponse(BaseModel):
919
- answer: str
920
- retrieved: Optional[List[RetrievedExample]] = None
921
-
922
-
923
- # ============================
924
- # FastAPI app
925
- # ============================
926
 
927
  app = FastAPI(
928
  title="Savant RRF Φ12.0 API",
929
- description="Dirac-Resonant conceptual quality + role profiles + reranking + RRF Tutor (+ CNN/nodes).",
930
  version="1.2.0",
 
931
  )
932
 
933
-
934
- # ============================
935
- # Utilidades /v1/rerank
936
- # ============================
937
-
938
- def _compute_rerank_scores(query: str, docs: List[str], alpha: float, norm_query: bool) -> List[RerankDocumentResult]:
939
- q_emb = encoder.encode([query], convert_to_numpy=True, normalize_embeddings=norm_query)[0]
940
-
941
- results = []
942
- for idx, text in enumerate(docs):
943
- d_emb = encoder.encode([text], convert_to_numpy=True, normalize_embeddings=True)[0]
944
- score_cosine = float(np.dot(q_emb, d_emb))
945
-
946
- val = max(score_cosine, 0.0) + 1e-6
947
- score_log_rdf = float(np.log1p(val))
948
-
949
- score_final = (1.0 - alpha) * score_cosine + alpha * score_log_rdf
950
-
951
- results.append(
952
- {
953
- "id": idx,
954
- "score_cosine": score_cosine,
955
- "score_log_rdf": score_log_rdf,
956
- "score_final": score_final,
957
- }
958
- )
959
-
960
- results_sorted = sorted(results, key=lambda r: r["score_final"], reverse=True)
961
- reranked = []
962
- for rank, r in enumerate(results_sorted, start=1):
963
- reranked.append(
964
- RerankDocumentResult(
965
- id=r["id"],
966
- score_cosine=r["score_cosine"],
967
- score_log_rdf=r["score_log_rdf"],
968
- score_final=r["score_final"],
969
- rank=rank,
970
- )
971
- )
972
- return reranked
973
-
974
-
975
- # ============================
976
- # Endpoints
977
- # ============================
978
 
979
  @app.get("/")
980
  def root():
981
- return {"message": "Savant RRF Φ12.0 API running", "docs": "/docs"}
982
-
 
 
 
983
 
984
  @app.get("/health")
985
  def health():
986
- """
987
- Endpoint de health corporativo: resume el estado de todos los módulos.
988
- """
989
  return {
990
- "status": "ok",
991
- "encoder_model_id": ENCODER_MODEL_ID,
992
- "meta_logit_filename": META_LOGIT_FILENAME,
993
- "N_sites": N,
994
- "rrf_tutor_examples": len(rrf_corpus_prompts),
995
- "rrf_tutor_ready": rrf_tutor_ready,
996
  "cnn_loaded": savant_cnn is not None,
997
  "rrf_nodes_loaded": rrf_nodes is not None,
998
- "physics_artifacts": {
999
- "rrf_resonance_matrix": PHYS_RRF_RESONANCE_MATRIX is not None,
1000
- "rrf_energy_profile": PHYS_RRF_ENERGY_PROFILE is not None,
1001
- "rrf_eigen_spectrum": PHYS_RRF_EIGEN_SPECTRUM is not None,
1002
- "resonance_matrix_13": PHYS_RES_MATRIX_13 is not None,
1003
- "nodes_13": PHYS_NODES_13 is not None,
1004
- "energy_logphi_13": PHYS_ENERGY_LOGPHI_13 is not None,
1005
- "degree_13": PHYS_DEGREE_13 is not None,
1006
- "adjacency_13": PHYS_ADJ_13 is not None,
1007
- },
1008
  }
1009
 
1010
-
1011
- @app.get("/roles", response_model=RoleProfilesResponse)
1012
- def list_roles():
1013
- roles = [
1014
- RoleProfileInfo(name=name, weights=weights)
1015
- for name, weights in ROLE_PROFILES.items()
1016
- ]
1017
- return RoleProfilesResponse(roles=roles)
1018
-
1019
-
1020
  @app.post("/evaluate", response_model=EvaluateResponse)
1021
- def evaluate_endpoint(req: EvaluateRequest):
1022
  try:
1023
- scores, feats = compute_scores_srff_crrf_ephi(req.prompt, req.answer)
1024
-
1025
- role_profile = apply_role_profile(scores, req.model_label)
1026
-
1027
- H = build_dirac_hamiltonian(
1028
- m=0.25, v=1.0, sigma=0.618,
1029
- alpha_log=0.10, q=1.0,
1030
- flux_vector=(0.0, 0.0, 0.0),
1031
- gauge_scale=0.0,
1032
- )
1033
- rng = np.random.default_rng(abs(hash(req.prompt + req.answer + "sim")) % (2**32))
1034
- vec = rng.normal(0, 1, (2*N,)) + 1j * rng.normal(0, 1, (2*N,))
1035
- vec /= np.sqrt(np.vdot(vec, vec))
1036
- psi0 = vec
1037
- sim = evolve_dirac_shell(psi0, H, dt=0.05, steps=60, record_every=20)
1038
-
1039
- sim_summary = {
1040
- "entropy_initial": float(sim["entropy"][0]),
1041
- "entropy_final": float(sim["entropy"][-1]),
1042
- "chirality_initial": float(sim["chirality"][0]),
1043
- "chirality_final": float(sim["chirility"][-1]) if "chirility" in sim else float(sim["chirality"][-1]),
1044
- "energy_mean": float(np.mean(sim["energy"])),
1045
- "energy_std": float(np.std(sim["energy"])),
1046
- "N_sites": int(N),
1047
- }
1048
-
1049
  return EvaluateResponse(
1050
  scores=scores,
1051
- features=feats,
1052
- sim_summary=sim_summary,
1053
- role_profile=role_profile,
1054
  )
1055
  except Exception as e:
1056
- print(f"[Runtime] Error en /evaluate: {e}", flush=True)
1057
- raise HTTPException(status_code=500, detail="Internal server error")
1058
-
1059
-
1060
- @app.post("/quality_remote", response_model=EvaluateResponse)
1061
- def quality_remote(req: QualityRemoteRequest):
1062
- """Alias remoto de /evaluate."""
1063
- return evaluate_endpoint(req)
1064
-
1065
 
1066
- @app.post("/quality", response_model=EvaluateResponse)
1067
- def quality_alias(req: QualityRemoteRequest):
1068
- """Alias directo de /evaluate (compatibilidad hacia atrás)."""
1069
- return evaluate_endpoint(req)
1070
 
 
 
 
 
1071
 
1072
- @app.post("/v1/rerank", response_model=RerankResponse)
1073
- def rerank_endpoint(req: RerankRequest):
1074
- """
1075
- Endpoint Savant Seek:
1076
- POST /v1/rerank
1077
- {
1078
- "query": "...",
1079
- "documents": ["doc1", "doc2", ...],
1080
- "alpha": 0.2,
1081
- "query_embedding_norm": true
1082
- }
1083
- """
1084
- results = _compute_rerank_scores(
1085
- query=req.query,
1086
- docs=req.documents,
1087
- alpha=req.alpha,
1088
- norm_query=req.query_embedding_norm,
1089
- )
1090
 
1091
  return RerankResponse(
1092
  model_id=ENCODER_MODEL_ID,
1093
- alpha=req.alpha,
1094
- query_embedding_norm=req.query_embedding_norm,
1095
- results=results,
1096
  )
1097
 
1098
-
1099
- @app.post("/v1/rrf_tutor", response_model=RRFTutorResponse)
1100
- def rrf_tutor_endpoint(body: RRFTutorRequest):
1101
- if not body.query or not body.query.strip():
1102
- raise HTTPException(status_code=400, detail="El campo 'query' no puede estar vacío.")
1103
-
1104
- if not rrf_tutor_ready:
1105
- raise HTTPException(
1106
- status_code=503,
1107
- detail=(
1108
- "RRF Tutor no está listo: embeddings no cargados. "
1109
- "Verifica rrf_tutor_curated.jsonl en antonypamo/savant_rrf1_curated y reinicia el Space."
1110
- ),
1111
- )
1112
-
1113
- try:
1114
- retrieved = rrf_tutor_retrieve_examples(body.query, top_k=body.max_examples)
1115
- except Exception as e:
1116
- raise HTTPException(
1117
- status_code=500,
1118
- detail=f"Error interno recuperando ejemplos RRF Tutor: {e}",
1119
- )
1120
-
1121
- answer = rrf_tutor_build_answer(body.query, retrieved)
1122
-
1123
- resp = RRFTutorResponse(answer=answer)
1124
-
1125
- if body.include_raw_context:
1126
- resp.retrieved = [
1127
- RetrievedExample(
1128
- prompt=ex["prompt"],
1129
- completion=ex["completion"],
1130
- score=ex["score"],
1131
- )
1132
- for ex in retrieved
1133
- ]
1134
-
1135
- return resp
 
1
+ # ======================================================
2
+ # Savant RRF Φ12.0 — app.py (FIXED & STABLE)
3
+ # ======================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
+ from pathlib import Path
6
  import os
7
  import sys
 
8
  import json
9
+ import math
10
  from typing import Optional, Dict, Any, List
11
 
12
  import numpy as np
13
  from numpy.linalg import norm
14
  from scipy.linalg import expm
15
 
16
+ import torch
17
+ import torch.nn as nn
18
+
19
  from fastapi import FastAPI, HTTPException
20
+ from pydantic import BaseModel, Field, ConfigDict
 
21
 
22
  from sentence_transformers import SentenceTransformer
23
  from huggingface_hub import hf_hub_download
24
  import joblib
25
 
26
+ # ======================================================
27
+ # 1) MANIFEST (single source of truth — FIXED)
28
+ # ======================================================
29
 
30
+ DEFAULT_MANIFEST = {
31
+ "version": "Φ12.0",
32
+ "project": "Savant RRF API & Meta-Logic Suite",
33
+ "owner": "Antony Padilla Morales",
34
+ "status": "fallback_default"
35
+ }
36
 
37
  MANIFEST_PATH = Path(__file__).parent / "savant_rrf_api_manifest_phi12.json"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ def load_manifest() -> Dict[str, Any]:
40
+ if MANIFEST_PATH.exists():
41
+ try:
42
+ print(f"[Manifest] Loading from {MANIFEST_PATH}", flush=True)
43
+ return json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
44
+ except Exception as e:
45
+ print(f"[Manifest] Invalid JSON: {e}", flush=True)
46
 
47
+ print("[Manifest] Using DEFAULT_MANIFEST", flush=True)
48
+ return DEFAULT_MANIFEST
 
 
 
 
 
 
 
 
 
49
 
50
+ manifest = load_manifest()
51
 
52
+ print("[Manifest] version:", manifest.get("version"), flush=True)
53
 
54
+ # ======================================================
55
+ # 2) Global configuration
56
+ # ======================================================
57
 
58
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
59
+ os.environ["HF_TOKEN"] = HF_TOKEN
 
 
 
 
 
60
 
61
+ ENCODER_MODEL_ID = "antonypamo/RRFSAVANTMADE"
62
+ META_LOGIT_REPO = "antonypamo/RRFSavantMetaLogit"
63
+ META_LOGIT_FILENAME = "logreg_rrf_savant_15.joblib"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ RRF_DATASET_REPO = "antonypamo/savant_rrf1_curated"
66
 
67
+ # ======================================================
68
+ # 3) Hugging Face dataset helper
69
+ # ======================================================
70
 
71
+ def hf_data_path(filename: str) -> Optional[str]:
72
  try:
73
+ return hf_hub_download(
74
+ repo_id=RRF_DATASET_REPO,
75
+ filename=filename,
76
+ repo_type="dataset",
77
+ token=HF_TOKEN or None,
78
+ )
79
  except Exception as e:
80
+ print(f"[Dataset] Missing {filename}: {e}", flush=True)
81
  return None
82
 
83
+ # ======================================================
84
+ # 4) Startup — load models
85
+ # ======================================================
86
 
87
+ print("===== Savant RRF Φ12.0 Startup =====", flush=True)
 
 
 
 
88
 
89
+ print("🔄 Loading encoder...", flush=True)
90
+ encoder = SentenceTransformer(ENCODER_MODEL_ID)
91
+ print("✅ Encoder ready", flush=True)
92
 
93
+ print("🔄 Loading meta-logit...", flush=True)
94
+ meta_logit_path = hf_hub_download(
95
+ repo_id=META_LOGIT_REPO,
96
+ filename=META_LOGIT_FILENAME,
97
+ token=HF_TOKEN or None,
98
+ )
99
+ meta_logit = joblib.load(meta_logit_path)
100
+ print("✅ Meta-logit ready", flush=True)
101
+
102
+ # ======================================================
103
+ # 5) Optional artifacts
104
+ # ======================================================
105
 
106
+ SAVANT_CNN_PATH = hf_data_path("savant_cnn.pt")
107
+ RRF_NODES_PATH = hf_data_path("rrf_nodes.pt")
108
+ RRF_TUTOR_JSONL = hf_data_path("rrf_tutor_curated.jsonl")
109
 
110
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
111
+
112
+ # ======================================================
113
+ # 6) Savant CNN
114
+ # ======================================================
115
 
116
  class SavantCNN(nn.Module):
117
+ def __init__(self):
 
 
 
 
 
 
 
 
118
  super().__init__()
119
+ self.conv1 = nn.Conv1d(1, 32, 3, padding=1)
120
+ self.conv2 = nn.Conv1d(32, 64, 3, padding=1)
121
+ self.conv3 = nn.Conv1d(64, 128, 3, padding=1)
122
  self.pool = nn.AdaptiveAvgPool1d(4)
123
+ self.fc = nn.Linear(512, 64)
124
 
125
+ def forward(self, x):
 
126
  x = torch.relu(self.conv1(x))
127
  x = torch.relu(self.conv2(x))
128
  x = torch.relu(self.conv3(x))
129
+ x = self.pool(x)
130
+ x = x.view(x.size(0), -1)
131
+ return self.fc(x)
 
 
 
 
132
 
133
+ savant_cnn = None
134
+ if SAVANT_CNN_PATH:
 
 
135
  try:
 
 
 
 
 
136
  savant_cnn = SavantCNN()
137
+ savant_cnn.load_state_dict(torch.load(SAVANT_CNN_PATH, map_location=device))
138
+ savant_cnn.to(device).eval()
139
+ print("✅ Savant CNN loaded", flush=True)
 
140
  except Exception as e:
141
+ print(f"⚠️ CNN load failed: {e}", flush=True)
 
 
 
 
142
 
143
+ rrf_nodes = None
144
+ if RRF_NODES_PATH:
145
  try:
146
  rrf_nodes = torch.load(RRF_NODES_PATH, map_location=device)
147
+ print("✅ RRF nodes loaded", flush=True)
 
 
 
148
  except Exception as e:
149
+ print(f"⚠️ RRF nodes failed: {e}", flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
+ # ======================================================
152
+ # 7) Icosahedral geometry (Φ12)
153
+ # ======================================================
 
154
 
155
  phi = (1 + np.sqrt(5)) / 2
156
  nodes_geom = np.array([
 
159
  [phi, 0, 1], [phi, 0, -1], [-phi, 0, 1], [-phi, 0, -1]
160
  ], dtype=float)
161
  nodes_geom /= norm(nodes_geom, axis=1, keepdims=True)
162
+ N = nodes_geom.shape[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
+ # ======================================================
165
+ # 8) Core embedding + scoring
166
+ # ======================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
  def get_embedding(text: str) -> np.ndarray:
169
+ return encoder.encode([text], convert_to_numpy=True, normalize_embeddings=True)[0]
 
170
 
171
+ def compute_basic_scores(prompt: str, answer: str) -> Dict[str, float]:
 
 
 
 
 
 
172
  e_p = get_embedding(prompt)
173
  e_a = get_embedding(answer)
174
+ cosine = float(np.dot(e_p, e_a))
175
 
176
+ x = np.array([[cosine] + [0.0] * 14], dtype=float)
177
+ p_good = float(meta_logit.predict_proba(x)[0][1])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
  return {
180
+ "cosine": cosine,
181
+ "p_good": p_good,
182
+ "SRRF": p_good,
183
+ "CRRF": p_good * cosine,
184
+ "E_phi": 0.5 * (p_good + abs(cosine))
185
  }
186
 
187
+ # ======================================================
188
+ # 9) FastAPI models
189
+ # ======================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
  class EvaluateRequest(BaseModel):
192
+ prompt: str
193
+ answer: str
194
+ model_label: Optional[str] = None
 
 
 
195
 
196
  class EvaluateResponse(BaseModel):
197
  scores: Dict[str, float]
198
+ manifest_version: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
  class RerankRequest(BaseModel):
201
+ query: str
202
+ documents: List[str]
203
+ alpha: float = 0.2
 
 
 
 
 
 
 
 
 
 
 
204
 
205
+ class RerankDocument(BaseModel):
206
+ id: int
207
+ score: float
 
 
208
  rank: int
209
 
 
210
  class RerankResponse(BaseModel):
 
211
  model_config = ConfigDict(protected_namespaces=())
 
212
  model_id: str
213
+ results: List[RerankDocument]
 
 
214
 
215
+ # ======================================================
216
+ # 10) FastAPI app
217
+ # ======================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
  app = FastAPI(
220
  title="Savant RRF Φ12.0 API",
 
221
  version="1.2.0",
222
+ description="Resonant Meta-Logic, Reranking & Quality Evaluation"
223
  )
224
 
225
+ # ======================================================
226
+ # 11) Endpoints
227
+ # ======================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
  @app.get("/")
230
  def root():
231
+ return {
232
+ "status": "ok",
233
+ "project": manifest.get("project"),
234
+ "version": manifest.get("version")
235
+ }
236
 
237
  @app.get("/health")
238
  def health():
 
 
 
239
  return {
240
+ "encoder": True,
241
+ "meta_logit": True,
 
 
 
 
242
  "cnn_loaded": savant_cnn is not None,
243
  "rrf_nodes_loaded": rrf_nodes is not None,
244
+ "manifest": manifest.get("version")
 
 
 
 
 
 
 
 
 
245
  }
246
 
 
 
 
 
 
 
 
 
 
 
247
  @app.post("/evaluate", response_model=EvaluateResponse)
248
+ def evaluate(req: EvaluateRequest):
249
  try:
250
+ scores = compute_basic_scores(req.prompt, req.answer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  return EvaluateResponse(
252
  scores=scores,
253
+ manifest_version=manifest.get("version")
 
 
254
  )
255
  except Exception as e:
256
+ print(f"[Evaluate] Error: {e}", flush=True)
257
+ raise HTTPException(status_code=500, detail="Evaluation failed")
 
 
 
 
 
 
 
258
 
259
+ @app.post("/v1/rerank", response_model=RerankResponse)
260
+ def rerank(req: RerankRequest):
261
+ q_emb = get_embedding(req.query)
262
+ results = []
263
 
264
+ for i, doc in enumerate(req.documents):
265
+ d_emb = get_embedding(doc)
266
+ score = float(np.dot(q_emb, d_emb))
267
+ results.append({"id": i, "score": score})
268
 
269
+ results = sorted(results, key=lambda x: x["score"], reverse=True)
270
+ ranked = [
271
+ RerankDocument(id=r["id"], score=r["score"], rank=i + 1)
272
+ for i, r in enumerate(results)
273
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
  return RerankResponse(
276
  model_id=ENCODER_MODEL_ID,
277
+ results=ranked
 
 
278
  )
279
 
280
+ # ======================================================
281
+ # END — app.py FIXED
282
+ # ======================================================