antonypamo commited on
Commit
193cdd8
·
verified ·
1 Parent(s): 9d33eb1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -45
app.py CHANGED
@@ -10,7 +10,7 @@ from scipy.linalg import expm
10
 
11
  from fastapi import FastAPI, HTTPException
12
  from pydantic import BaseModel, Field
13
- from pydantic import ConfigDict # para evitar warning de protected_namespaces
14
 
15
  from sentence_transformers import SentenceTransformer
16
  from huggingface_hub import hf_hub_download
@@ -25,12 +25,13 @@ import torch.nn as nn
25
  # ============================
26
 
27
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
 
28
 
29
  ENCODER_MODEL_ID = "antonypamo/RRFSAVANTMADE"
30
  META_LOGIT_REPO = "antonypamo/RRFSavantMetaLogit"
31
- META_LOGIT_FILENAME = "logreg_rrf_savant_15.joblib"
32
 
33
- # Dataset central con TODOS los artefactos
34
  RRF_DATASET_REPO = "antonypamo/savant_rrf1_curated"
35
 
36
 
@@ -81,7 +82,7 @@ except Exception as e:
81
 
82
 
83
  # ============================
84
- # Rutas a artefactos desde el dataset central
85
  # ============================
86
 
87
  def safe_hf(path_name: str) -> Optional[str]:
@@ -112,32 +113,34 @@ PHYS_ADJ_13 = safe_hf("adjacency_13.csv")
112
 
113
 
114
  # ============================
115
- # Savant CNN + nodos RRF (para futura integración)
116
  # ============================
117
 
118
  class SavantCNN(nn.Module):
119
  """
120
- CNN tal como fue entrenada originalmente:
121
  - conv1: [1 -> 32]
122
  - conv2: [32 -> 64]
123
  - conv3: [64 -> 128]
124
- - fc: [512 -> 64] (según checkpoint original)
 
125
  """
126
  def __init__(self, in_channels: int = 1, out_dim: int = 64):
127
  super().__init__()
128
  self.conv1 = nn.Conv1d(in_channels, 32, kernel_size=3, padding=1)
129
  self.conv2 = nn.Conv1d(32, 64, kernel_size=3, padding=1)
130
  self.conv3 = nn.Conv1d(64, 128, kernel_size=3, padding=1)
131
- self.pool = nn.AdaptiveAvgPool1d(1)
132
- self.fc = nn.Linear(512, out_dim) # mantiene compatibilidad con checkpoint
133
 
134
  def forward(self, x: torch.Tensor) -> torch.Tensor:
135
  # x: [batch, channels, length]
136
  x = torch.relu(self.conv1(x))
137
  x = torch.relu(self.conv2(x))
138
  x = torch.relu(self.conv3(x))
139
- x = self.pool(x).squeeze(-1) # [batch, 128] en este diseño simplificado
140
- x = self.fc(x)
 
141
  return x
142
 
143
 
@@ -181,8 +184,8 @@ else:
181
 
182
  def fuse_cnn_with_node(example_length: int = 64):
183
  """
184
- Utilidad interna: ejemplo de cómo fusionar la CNN con un nodo RRF.
185
- No se expone aún como endpoint, pero sirve para demos técnicas.
186
  """
187
  if savant_cnn is None or rrf_nodes is None:
188
  print("Fusion not available – missing CNN or RRF nodes snapshot.")
@@ -192,7 +195,7 @@ def fuse_cnn_with_node(example_length: int = 64):
192
  cnn_emb = savant_cnn(x) # [1, 64]
193
 
194
  try:
195
- # asumir que el primer nodo es algo como rrf_nodes["node_0"]
196
  node0_key = list(rrf_nodes.keys())[0]
197
  node0 = rrf_nodes[node0_key]
198
  if isinstance(node0, dict) and "linguistic" in node0:
@@ -217,13 +220,13 @@ def fuse_cnn_with_node(example_length: int = 64):
217
  # ============================
218
 
219
  phi = (1 + np.sqrt(5)) / 2
220
- nodes = np.array([
221
  [0, 1, phi], [0, -1, phi], [0, 1, -phi], [0, -1, -phi],
222
  [1, phi, 0], [-1, phi, 0], [1, -phi, 0], [-1, -phi, 0],
223
  [phi, 0, 1], [phi, 0, -1], [-phi, 0, 1], [-phi, 0, -1]
224
  ], dtype=float)
225
- nodes /= norm(nodes, axis=1, keepdims=True)
226
- N = nodes.shape[0] # 12 nodos
227
 
228
  sigma_x = np.array([[0, 1], [1, 0]], dtype=complex)
229
  sigma_y = np.array([[0, -1j], [1j, 0]], dtype=complex)
@@ -244,12 +247,11 @@ def geodesic_kernel(nodes, sigma=0.618, alpha_log=0.10):
244
  diff = nodes[:, None, :] - nodes[None, :, :]
245
  dist = norm(diff, axis=-1)
246
 
247
- W = np.exp(-(dist ** 2) / (sigma ** 2))
248
  np.fill_diagonal(W, 0.0)
249
 
250
  if alpha_log > 0.0:
251
- corr = 1.0 + alpha_log * np.log1p(dist ** 2)
252
- # mantener diagonal en 1 para evitar log(0)
253
  corr[range(N), range(N)] = 1.0
254
  W = W / corr
255
 
@@ -275,10 +277,10 @@ def build_dirac_hamiltonian(
275
  flux_vector=(0.0, 0.0, 0.0),
276
  gauge_scale=0.0,
277
  ):
278
- W = geodesic_kernel(nodes, sigma=sigma, alpha_log=alpha_log)
279
 
280
  if gauge_scale != 0.0 and any(flux_vector):
281
- theta = u1_edge_phases(nodes, flux_vector=flux_vector,
282
  q=q, gauge_scale=gauge_scale)
283
  U = np.exp(1j * theta)
284
  else:
@@ -286,7 +288,7 @@ def build_dirac_hamiltonian(
286
 
287
  H = np.kron(np.eye(N, dtype=complex), m * sigma_z)
288
 
289
- diff = nodes[:, None, :] - nodes[None, :, :]
290
  dist = norm(diff, axis=-1) + 1e-12
291
  d_hat = diff / dist[..., None]
292
 
@@ -308,7 +310,7 @@ def site_probs(psi):
308
  N2 = psi.shape[0]
309
  n = N2 // 2
310
  psi_mat = psi.reshape(n, 2)
311
- return np.sum(np.abs(psi_mat) ** 2, axis=1).real
312
 
313
 
314
  def chirality(psi):
@@ -325,7 +327,12 @@ def spatial_entropy(p):
325
  return float(-np.sum(p * np.log(p)).real)
326
 
327
 
328
- def evolve_dirac_shell(psi0, H, dt=0.05, steps=100, record_every=25):
 
 
 
 
 
329
  U = expm(-1j * dt * H)
330
  psi = psi0.copy()
331
 
@@ -365,14 +372,20 @@ def get_embedding(text: str) -> np.ndarray:
365
 
366
 
367
  def compute_rrf_features(prompt: str, answer: str) -> Dict[str, float]:
 
 
 
 
 
368
  e_p = get_embedding(prompt)
369
  e_a = get_embedding(answer)
370
 
371
  cosine_pa = float(np.dot(e_p, e_a))
372
  len_ratio = len(answer) / (len(prompt) + 1.0)
373
 
374
- rng = np.random.default_rng(abs(hash(prompt + answer)) % (2 ** 32))
375
- vec = rng.normal(0, 1, (2 * N,)) + 1j * rng.normal(0, 1, (2 * N,))
 
376
  vec /= np.sqrt(np.vdot(vec, vec))
377
  psi0 = vec
378
 
@@ -406,6 +419,7 @@ def compute_rrf_features(prompt: str, answer: str) -> Dict[str, float]:
406
  "dirac_energy_std": E_std,
407
  }
408
 
 
409
  S_max = math.log(N)
410
  feats["entropy_norm"] = feats["dirac_entropy_final"] / S_max
411
  feats["entropy_abs_delta"] = abs(feats["dirac_entropy_delta"])
@@ -440,7 +454,11 @@ def features_to_vector(feats: Dict[str, float]) -> np.ndarray:
440
  return np.array([feats[k] for k in keys], dtype=float)
441
 
442
 
443
- def compute_scores_srff_crff_ephi(prompt: str, answer: str):
 
 
 
 
444
  feats = compute_rrf_features(prompt, answer)
445
  x = features_to_vector(feats).reshape(1, -1)
446
 
@@ -464,7 +482,7 @@ def compute_scores_srff_crff_ephi(prompt: str, answer: str):
464
 
465
 
466
  # ============================
467
- # Role profiles
468
  # ============================
469
 
470
  ROLE_PROFILES: Dict[str, Dict[str, float]] = {
@@ -487,6 +505,9 @@ ROLE_PROFILES: Dict[str, Dict[str, float]] = {
487
 
488
 
489
  def apply_role_profile(scores: Dict[str, float], role_name: Optional[str]) -> Dict[str, Any]:
 
 
 
490
  if not role_name:
491
  role_name = "default"
492
 
@@ -600,9 +621,11 @@ def rrf_tutor_build_answer(query: str, retrieved_examples):
600
  # ============================
601
 
602
  class EvaluateRequest(BaseModel):
603
- prompt: str
604
- answer: str
605
- model_label: Optional[str] = None
 
 
606
 
607
 
608
  class EvaluateResponse(BaseModel):
@@ -613,6 +636,7 @@ class EvaluateResponse(BaseModel):
613
 
614
 
615
  class QualityRemoteRequest(EvaluateRequest):
 
616
  pass
617
 
618
 
@@ -626,6 +650,9 @@ class RoleProfilesResponse(BaseModel):
626
 
627
 
628
  class RerankRequest(BaseModel):
 
 
 
629
  query: str = Field(..., description="Query de búsqueda o pregunta del usuario.")
630
  documents: List[str] = Field(..., description="Lista de documentos candidatos a rerankear.")
631
  alpha: float = Field(
@@ -685,7 +712,7 @@ class RRFTutorResponse(BaseModel):
685
 
686
  app = FastAPI(
687
  title="Savant RRF Φ12.0 API",
688
- description="Dirac-Resonant conceptual quality layer + reranking + RRF Tutor (+ CNN/nodes listos).",
689
  version="1.2.0",
690
  )
691
 
@@ -763,9 +790,9 @@ def list_roles():
763
 
764
 
765
  @app.post("/evaluate", response_model=EvaluateResponse)
766
- def evaluate(req: EvaluateRequest):
767
  try:
768
- scores, feats = compute_scores_srff_crff_ephi(req.prompt, req.answer)
769
 
770
  role_profile = apply_role_profile(scores, req.model_label)
771
 
@@ -775,10 +802,8 @@ def evaluate(req: EvaluateRequest):
775
  flux_vector=(0.0, 0.0, 0.0),
776
  gauge_scale=0.0,
777
  )
778
- rng = np.random.default_rng(
779
- abs(hash(req.prompt + req.answer + "sim")) % (2 ** 32)
780
- )
781
- vec = rng.normal(0, 1, (2 * N,)) + 1j * rng.normal(0, 1, (2 * N,))
782
  vec /= np.sqrt(np.vdot(vec, vec))
783
  psi0 = vec
784
  sim = evolve_dirac_shell(psi0, H, dt=0.05, steps=60, record_every=20)
@@ -800,25 +825,34 @@ def evaluate(req: EvaluateRequest):
800
  role_profile=role_profile,
801
  )
802
  except Exception as e:
803
- print(f"❌ [Runtime] Error en /evaluate: {e}", file=sys.stderr, flush=True)
804
  raise HTTPException(status_code=500, detail="Internal server error")
805
 
806
 
807
  @app.post("/quality_remote", response_model=EvaluateResponse)
808
  def quality_remote(req: QualityRemoteRequest):
809
- return evaluate(req)
 
810
 
811
 
812
  @app.post("/quality", response_model=EvaluateResponse)
813
  def quality_alias(req: QualityRemoteRequest):
814
- """
815
- Alias directo de /evaluate para compatibilidad con clientes previos.
816
- """
817
- return evaluate(req)
818
 
819
 
820
  @app.post("/v1/rerank", response_model=RerankResponse)
821
  def rerank_endpoint(req: RerankRequest):
 
 
 
 
 
 
 
 
 
 
822
  results = _compute_rerank_scores(
823
  query=req.query,
824
  docs=req.documents,
 
10
 
11
  from fastapi import FastAPI, HTTPException
12
  from pydantic import BaseModel, Field
13
+ from pydantic import ConfigDict # para evitar warning con model_id
14
 
15
  from sentence_transformers import SentenceTransformer
16
  from huggingface_hub import hf_hub_download
 
25
  # ============================
26
 
27
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
28
+ os.environ["HF_TOKEN"] = HF_TOKEN # por si algún cliente interno lo espera
29
 
30
  ENCODER_MODEL_ID = "antonypamo/RRFSAVANTMADE"
31
  META_LOGIT_REPO = "antonypamo/RRFSavantMetaLogit"
32
+ META_LOGIT_FILENAME = "logreg_rrf_savant_15.joblib" # versión 15 features
33
 
34
+ # Dataset central con TODOS los artefactos RRF/Savant
35
  RRF_DATASET_REPO = "antonypamo/savant_rrf1_curated"
36
 
37
 
 
82
 
83
 
84
  # ============================
85
+ # Artefactos desde savant_rrf1_curated
86
  # ============================
87
 
88
  def safe_hf(path_name: str) -> Optional[str]:
 
113
 
114
 
115
  # ============================
116
+ # Savant CNN + nodos RRF (demo futura)
117
  # ============================
118
 
119
  class SavantCNN(nn.Module):
120
  """
121
+ CNN compatible con el checkpoint:
122
  - conv1: [1 -> 32]
123
  - conv2: [32 -> 64]
124
  - conv3: [64 -> 128]
125
+ - pool: AdaptiveAvgPool1d(4) => 128 * 4 = 512
126
+ - fc: [512 -> 64]
127
  """
128
  def __init__(self, in_channels: int = 1, out_dim: int = 64):
129
  super().__init__()
130
  self.conv1 = nn.Conv1d(in_channels, 32, kernel_size=3, padding=1)
131
  self.conv2 = nn.Conv1d(32, 64, kernel_size=3, padding=1)
132
  self.conv3 = nn.Conv1d(64, 128, kernel_size=3, padding=1)
133
+ self.pool = nn.AdaptiveAvgPool1d(4)
134
+ self.fc = nn.Linear(512, out_dim)
135
 
136
  def forward(self, x: torch.Tensor) -> torch.Tensor:
137
  # x: [batch, channels, length]
138
  x = torch.relu(self.conv1(x))
139
  x = torch.relu(self.conv2(x))
140
  x = torch.relu(self.conv3(x))
141
+ x = self.pool(x) # [batch, 128, 4]
142
+ x = x.view(x.size(0), -1) # [batch, 512]
143
+ x = self.fc(x) # [batch, out_dim]
144
  return x
145
 
146
 
 
184
 
185
  def fuse_cnn_with_node(example_length: int = 64):
186
  """
187
+ Utilidad interna de demo: fusionar embedding CNN + nodo RRF.
188
+ No expuesta aún como endpoint público.
189
  """
190
  if savant_cnn is None or rrf_nodes is None:
191
  print("Fusion not available – missing CNN or RRF nodes snapshot.")
 
195
  cnn_emb = savant_cnn(x) # [1, 64]
196
 
197
  try:
198
+ # asumir primer nodo tipo rrf_nodes["node_0"]
199
  node0_key = list(rrf_nodes.keys())[0]
200
  node0 = rrf_nodes[node0_key]
201
  if isinstance(node0, dict) and "linguistic" in node0:
 
220
  # ============================
221
 
222
  phi = (1 + np.sqrt(5)) / 2
223
+ nodes_geom = np.array([
224
  [0, 1, phi], [0, -1, phi], [0, 1, -phi], [0, -1, -phi],
225
  [1, phi, 0], [-1, phi, 0], [1, -phi, 0], [-1, -phi, 0],
226
  [phi, 0, 1], [phi, 0, -1], [-phi, 0, 1], [-phi, 0, -1]
227
  ], dtype=float)
228
+ nodes_geom /= norm(nodes_geom, axis=1, keepdims=True)
229
+ N = nodes_geom.shape[0] # 12 nodos
230
 
231
  sigma_x = np.array([[0, 1], [1, 0]], dtype=complex)
232
  sigma_y = np.array([[0, -1j], [1j, 0]], dtype=complex)
 
247
  diff = nodes[:, None, :] - nodes[None, :, :]
248
  dist = norm(diff, axis=-1)
249
 
250
+ W = np.exp(-(dist**2) / (sigma**2))
251
  np.fill_diagonal(W, 0.0)
252
 
253
  if alpha_log > 0.0:
254
+ corr = 1.0 + alpha_log * np.log1p(dist**2)
 
255
  corr[range(N), range(N)] = 1.0
256
  W = W / corr
257
 
 
277
  flux_vector=(0.0, 0.0, 0.0),
278
  gauge_scale=0.0,
279
  ):
280
+ W = geodesic_kernel(nodes_geom, sigma=sigma, alpha_log=alpha_log)
281
 
282
  if gauge_scale != 0.0 and any(flux_vector):
283
+ theta = u1_edge_phases(nodes_geom, flux_vector=flux_vector,
284
  q=q, gauge_scale=gauge_scale)
285
  U = np.exp(1j * theta)
286
  else:
 
288
 
289
  H = np.kron(np.eye(N, dtype=complex), m * sigma_z)
290
 
291
+ diff = nodes_geom[:, None, :] - nodes_geom[None, :, :]
292
  dist = norm(diff, axis=-1) + 1e-12
293
  d_hat = diff / dist[..., None]
294
 
 
310
  N2 = psi.shape[0]
311
  n = N2 // 2
312
  psi_mat = psi.reshape(n, 2)
313
+ return np.sum(np.abs(psi_mat)**2, axis=1).real
314
 
315
 
316
  def chirality(psi):
 
327
  return float(-np.sum(p * np.log(p)).real)
328
 
329
 
330
+ def evolve_dirac_shell(psi0, H, dt=0.05, steps=200, record_every=20):
331
+ """
332
+ Evolución unitaria sobre la red icosaédrica.
333
+ steps=200 da buena resolución para el feature set; los endpoints
334
+ pueden usar menos pasos si se quiere.
335
+ """
336
  U = expm(-1j * dt * H)
337
  psi = psi0.copy()
338
 
 
372
 
373
 
374
  def compute_rrf_features(prompt: str, answer: str) -> Dict[str, float]:
375
+ """
376
+ Genera las 15 features que espera el meta-logit:
377
+ - embeddings + Dirac shell + derivadas (entropía, energía, longitud, etc.)
378
+ """
379
+ # Embeddings
380
  e_p = get_embedding(prompt)
381
  e_a = get_embedding(answer)
382
 
383
  cosine_pa = float(np.dot(e_p, e_a))
384
  len_ratio = len(answer) / (len(prompt) + 1.0)
385
 
386
+ # Simulación Dirac shell determinista (semilla por prompt+answer)
387
+ rng = np.random.default_rng(abs(hash(prompt + answer)) % (2**32))
388
+ vec = rng.normal(0, 1, (2*N,)) + 1j * rng.normal(0, 1, (2*N,))
389
  vec /= np.sqrt(np.vdot(vec, vec))
390
  psi0 = vec
391
 
 
419
  "dirac_energy_std": E_std,
420
  }
421
 
422
+ # Derivadas extra para llegar a 15 features
423
  S_max = math.log(N)
424
  feats["entropy_norm"] = feats["dirac_entropy_final"] / S_max
425
  feats["entropy_abs_delta"] = abs(feats["dirac_entropy_delta"])
 
454
  return np.array([feats[k] for k in keys], dtype=float)
455
 
456
 
457
+ def compute_scores_srff_crrf_ephi(prompt: str, answer: str):
458
+ """
459
+ Usa el meta-logit para obtener p_good y derivar:
460
+ - SRRF, CRRF, E_phi
461
+ """
462
  feats = compute_rrf_features(prompt, answer)
463
  x = features_to_vector(feats).reshape(1, -1)
464
 
 
482
 
483
 
484
  # ============================
485
+ # Role profiles (perfiles de evaluación)
486
  # ============================
487
 
488
  ROLE_PROFILES: Dict[str, Dict[str, float]] = {
 
505
 
506
 
507
  def apply_role_profile(scores: Dict[str, float], role_name: Optional[str]) -> Dict[str, Any]:
508
+ """
509
+ Calcula un composite_score según el perfil de rol.
510
+ """
511
  if not role_name:
512
  role_name = "default"
513
 
 
621
  # ============================
622
 
623
  class EvaluateRequest(BaseModel):
624
+ prompt: str = Field(..., description="Pregunta / instrucción original.")
625
+ answer: str = Field(..., description="Respuesta generada por un LLM.")
626
+ model_label: Optional[str] = Field(
627
+ None, description="Etiqueta opcional de rol/modelo (default/creative/precise o custom)."
628
+ )
629
 
630
 
631
  class EvaluateResponse(BaseModel):
 
636
 
637
 
638
  class QualityRemoteRequest(EvaluateRequest):
639
+ """Alias de EvaluateRequest para /quality_remote."""
640
  pass
641
 
642
 
 
650
 
651
 
652
  class RerankRequest(BaseModel):
653
+ """
654
+ Petición para /v1/rerank
655
+ """
656
  query: str = Field(..., description="Query de búsqueda o pregunta del usuario.")
657
  documents: List[str] = Field(..., description="Lista de documentos candidatos a rerankear.")
658
  alpha: float = Field(
 
712
 
713
  app = FastAPI(
714
  title="Savant RRF Φ12.0 API",
715
+ description="Dirac-Resonant conceptual quality + role profiles + reranking + RRF Tutor (+ CNN/nodes).",
716
  version="1.2.0",
717
  )
718
 
 
790
 
791
 
792
  @app.post("/evaluate", response_model=EvaluateResponse)
793
+ def evaluate_endpoint(req: EvaluateRequest):
794
  try:
795
+ scores, feats = compute_scores_srff_crrf_ephi(req.prompt, req.answer)
796
 
797
  role_profile = apply_role_profile(scores, req.model_label)
798
 
 
802
  flux_vector=(0.0, 0.0, 0.0),
803
  gauge_scale=0.0,
804
  )
805
+ rng = np.random.default_rng(abs(hash(req.prompt + req.answer + "sim")) % (2**32))
806
+ vec = rng.normal(0, 1, (2*N,)) + 1j * rng.normal(0, 1, (2*N,))
 
 
807
  vec /= np.sqrt(np.vdot(vec, vec))
808
  psi0 = vec
809
  sim = evolve_dirac_shell(psi0, H, dt=0.05, steps=60, record_every=20)
 
825
  role_profile=role_profile,
826
  )
827
  except Exception as e:
828
+ print(f"❌ [Runtime] Error en /evaluate: {e}", flush=True)
829
  raise HTTPException(status_code=500, detail="Internal server error")
830
 
831
 
832
  @app.post("/quality_remote", response_model=EvaluateResponse)
833
  def quality_remote(req: QualityRemoteRequest):
834
+ """Alias remoto de /evaluate."""
835
+ return evaluate_endpoint(req)
836
 
837
 
838
  @app.post("/quality", response_model=EvaluateResponse)
839
  def quality_alias(req: QualityRemoteRequest):
840
+ """Alias directo de /evaluate (compatibilidad hacia atrás)."""
841
+ return evaluate_endpoint(req)
 
 
842
 
843
 
844
  @app.post("/v1/rerank", response_model=RerankResponse)
845
  def rerank_endpoint(req: RerankRequest):
846
+ """
847
+ Endpoint Savant Seek:
848
+ POST /v1/rerank
849
+ {
850
+ "query": "...",
851
+ "documents": ["doc1", "doc2", ...],
852
+ "alpha": 0.2,
853
+ "query_embedding_norm": true
854
+ }
855
+ """
856
  results = _compute_rerank_scores(
857
  query=req.query,
858
  docs=req.documents,