antonypamo commited on
Commit
ffe689a
·
verified ·
1 Parent(s): 8d5714f

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +194 -4
main.py CHANGED
@@ -359,17 +359,207 @@ def compute_scores_srff_crrf_ephi(prompt: str, answer: str):
359
  return scores, feats
360
 
361
 
362
- # ============================================================
363
- # 5. FASTAPI APP
364
- # ============================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
  app = FastAPI(
367
  title="Savant RRF Φ12.0 API",
368
- description="Savant RRF Quality (/v1/quality) y Savant RRF Seek (/v1/rerank)",
369
  version="1.0.0",
370
  )
371
 
372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  # ----------------- MODELOS Pydantic -----------------
374
 
375
  class EvaluateRequest(BaseModel):
 
359
  return scores, feats
360
 
361
 
362
+ # ============================
363
+ # FastAPI app
364
+ # ============================
365
+
366
+ class EvaluateRequest(BaseModel):
367
+ prompt: str
368
+ answer: str
369
+ model_label: Optional[str] = None
370
+
371
+
372
+ class EvaluateResponse(BaseModel):
373
+ scores: Dict[str, float]
374
+ features: Dict[str, float]
375
+ sim_summary: Dict[str, Any]
376
+
377
+
378
+ # Para poder reutilizar EvaluateRequest en /quality_remote
379
+ class QualityRemoteRequest(EvaluateRequest):
380
+ """Mismo schema que EvaluateRequest, usado para el alias /quality_remote."""
381
+ pass
382
+
383
 
384
  app = FastAPI(
385
  title="Savant RRF Φ12.0 API",
386
+ description="Dirac-Resonant conceptual quality layer for LLM-generated text.",
387
  version="1.0.0",
388
  )
389
 
390
 
391
+ class RerankRequest(BaseModel):
392
+ """
393
+ Petición para /v1/rerank
394
+ """
395
+ query: str = Field(..., description="Query de búsqueda o pregunta del usuario.")
396
+ documents: List[str] = Field(..., description="Lista de documentos candidatos a rerankear.")
397
+ alpha: float = Field(
398
+ 0.2,
399
+ description="Peso de la corrección log_rdf en el score_final. 0 = solo cosine, 1 = solo log_rdf."
400
+ )
401
+ query_embedding_norm: bool = Field(
402
+ True,
403
+ description="Si True, normaliza el embedding de query (útil para cosine)."
404
+ )
405
+
406
+ class RerankDocumentResult(BaseModel):
407
+ id: int = Field(..., description="Índice del documento en la lista de entrada.")
408
+ score_cosine: float
409
+ score_log_rdf: float
410
+ score_final: float
411
+ rank: int
412
+
413
+ class RerankResponse(BaseModel):
414
+ model_id: str
415
+ alpha: float
416
+ query_embedding_norm: bool
417
+ results: List[RerankDocumentResult]
418
+
419
+
420
+ def _compute_rerank_scores(query: str, docs: List[str], alpha: float, norm_query: bool) -> List[RerankDocumentResult]:
421
+ """
422
+ Lógica base de reranking usando encoder RRF.
423
+ - score_cosine: similitud coseno query-doc
424
+ - score_log_rdf: pequeña corrección logarítmica basada en score_cosine
425
+ - score_final: mezcla convexa de ambos
426
+ """
427
+ # Embedding de query
428
+ q_emb = encoder.encode([query], convert_to_numpy=True, normalize_embeddings=norm_query)[0]
429
+
430
+ results = []
431
+ for idx, text in enumerate(docs):
432
+ d_emb = encoder.encode([text], convert_to_numpy=True, normalize_embeddings=True)[0]
433
+ score_cosine = float(np.dot(q_emb, d_emb))
434
+
435
+ # Corrección log_rdf sencilla y estable (solo para cosenos positivos)
436
+ val = max(score_cosine, 0.0) + 1e-6
437
+ score_log_rdf = float(np.log1p(val))
438
+
439
+ score_final = (1.0 - alpha) * score_cosine + alpha * score_log_rdf
440
+
441
+ results.append(
442
+ {
443
+ "id": idx,
444
+ "score_cosine": score_cosine,
445
+ "score_log_rdf": score_log_rdf,
446
+ "score_final": score_final,
447
+ }
448
+ )
449
+
450
+ # Ordenar por score_final descendente y asignar rank
451
+ results_sorted = sorted(results, key=lambda r: r["score_final"], reverse=True)
452
+ reranked = []
453
+ for rank, r in enumerate(results_sorted, start=1):
454
+ reranked.append(
455
+ RerankDocumentResult(
456
+ id=r["id"],
457
+ score_cosine=r["score_cosine"],
458
+ score_log_rdf=r["score_log_rdf"],
459
+ score_final=r["score_final"],
460
+ rank=rank,
461
+ )
462
+ )
463
+ return reranked
464
+
465
+
466
+ @app.post("/v1/rerank", response_model=RerankResponse)
467
+ def rerank_endpoint(req: RerankRequest):
468
+ """
469
+ Endpoint Savant Seek style:
470
+ POST /v1/rerank
471
+ {
472
+ "query": "...",
473
+ "documents": ["doc1", "doc2", ...],
474
+ "alpha": 0.2,
475
+ "query_embedding_norm": true
476
+ }
477
+ """
478
+ results = _compute_rerank_scores(
479
+ query=req.query,
480
+ docs=req.documents,
481
+ alpha=req.alpha,
482
+ norm_query=req.query_embedding_norm,
483
+ )
484
+
485
+ return RerankResponse(
486
+ model_id=ENCODER_MODEL_ID,
487
+ alpha=req.alpha,
488
+ query_embedding_norm=req.query_embedding_norm,
489
+ results=results,
490
+ )
491
+
492
+ @app.get("/")
493
+ def root():
494
+ return {"message": "Savant RRF Φ12.0 API running", "docs": "/docs"}
495
+
496
+
497
+ @app.get("/health")
498
+ def health():
499
+ return {"status": "ok"}
500
+
501
+
502
+ @app.post("/evaluate", response_model=EvaluateResponse)
503
+ def evaluate(req: EvaluateRequest):
504
+ try:
505
+ scores, feats = compute_scores_srff_crff_ephi(req.prompt, req.answer)
506
+
507
+ # resumen de una simulación adicional (fresca) solo para info
508
+ H = build_dirac_hamiltonian(
509
+ m=0.25, v=1.0, sigma=0.618,
510
+ alpha_log=0.10, q=1.0,
511
+ flux_vector=(0.0, 0.0, 0.0),
512
+ gauge_scale=0.0,
513
+ )
514
+ rng = np.random.default_rng(abs(hash(req.prompt + req.answer + "sim")) % (2 ** 32))
515
+ vec = rng.normal(0, 1, (2 * N,)) + 1j * rng.normal(0, 1, (2 * N,))
516
+ vec /= np.sqrt(np.vdot(vec, vec))
517
+ psi0 = vec
518
+ sim = evolve_dirac_shell(psi0, H, dt=0.05, steps=60, record_every=20)
519
+
520
+ sim_summary = {
521
+ "entropy_initial": float(sim["entropy"][0]),
522
+ "entropy_final": float(sim["entropy"][-1]),
523
+ "chirality_initial": float(sim["chirality"][0]),
524
+ "chirality_final": float(sim["chirality"][-1]),
525
+ "energy_mean": float(np.mean(sim["energy"])),
526
+ "energy_std": float(np.std(sim["energy"])),
527
+ "N_sites": int(N),
528
+ }
529
+
530
+ return EvaluateResponse(
531
+ scores=scores,
532
+ features=feats,
533
+ sim_summary=sim_summary,
534
+ )
535
+ except Exception as e:
536
+ print(f"❌ [Runtime] Error en /evaluate: {e}", file=sys.stderr, flush=True)
537
+ raise HTTPException(status_code=500, detail="Internal server error")
538
+
539
+
540
+ # === SAVANT QUALITY_REMOTE PATCH (alias local de /evaluate) ===
541
+ @app.post("/quality_remote", response_model=EvaluateResponse)
542
+ def quality_remote(req: QualityRemoteRequest):
543
+ """
544
+ Alias de /evaluate para exponer la calidad RRF como /quality_remote.
545
+ Entrada:
546
+ {
547
+ "prompt": "...",
548
+ "answer": "...",
549
+ "model_label": "..." # opcional
550
+ }
551
+ Salida:
552
+ El mismo JSON que /evaluate:
553
+ {
554
+ "scores": {...},
555
+ "features": {...},
556
+ "sim_summary": {...}
557
+ }
558
+ """
559
+ # Aquí simplemente reutilizamos la misma lógica de evaluate
560
+ return evaluate(req)
561
+
562
+
563
  # ----------------- MODELOS Pydantic -----------------
564
 
565
  class EvaluateRequest(BaseModel):