antonypamo commited on
Commit
360b154
·
verified ·
1 Parent(s): a78af69

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +18 -0
  2. app.py +324 -0
  3. requirements.txt +8 -8
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim-buster
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies if any (none for now)
6
+
7
+ # Copy and install Python dependencies
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Copy the application code
12
+ COPY app.py .
13
+
14
+ # Expose the port FastAPI runs on
15
+ EXPOSE 8000
16
+
17
+ # Command to run the application
18
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
app.py ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ from numpy.linalg import norm
4
+ from scipy.linalg import expm
5
+ from sentence_transformers import SentenceTransformer
6
+ from huggingface_hub import hf_hub_download
7
+ import joblib
8
+
9
+ from fastapi import FastAPI
10
+ from pydantic import BaseModel, Field
11
+ from typing import Optional, Dict, Any
12
+
13
+
14
+ # NOTE: HF_TOKEN is expected to be set as an environment variable in a real deployment
15
+ # For local testing, you might set it here or pass it directly
16
+ HF_TOKEN = os.environ.get("HF_TOKEN", "") # Use environment variable, default to empty
17
+ os.environ["HF_TOKEN"] = HF_TOKEN
18
+
19
+ ENCODER_MODEL_ID = "antonypamo/RRFSAVANTMADE" # encoder RRF
20
+ META_LOGIT_REPO = "antonypamo/RRFSavantMetaLogit" # repo del meta-logit
21
+ META_LOGIT_FILENAME = "logreg_rrf_savant_v2.joblib" # NUEVO archivo del meta-logit en HF
22
+
23
+ print("🔄 Cargando encoder RRFSAVANTMADE...")
24
+ encoder = SentenceTransformer(ENCODER_MODEL_ID)
25
+
26
+ print("🔄 Descargando meta-logit v2 desde HF Hub...")
27
+ meta_logit_path = hf_hub_download(
28
+ repo_id=META_LOGIT_REPO,
29
+ filename=META_LOGIT_FILENAME,
30
+ token=os.environ.get("HF_TOKEN")
31
+ )
32
+
33
+ print("🔄 Cargando modelo meta-logit v2...")
34
+ meta_logit = joblib.load(meta_logit_path)
35
+
36
+ print("✅ Encoder y meta-logit v2 cargados correctamente.")
37
+
38
+
39
+ # =========================
40
+ # Geometría icosaédrica
41
+ # (Copied from cell lyVrwdhgIOlq)
42
+ # =========================
43
+
44
+ phi = (1 + np.sqrt(5)) / 2
45
+ nodes = np.array([
46
+ [0, 1, phi], [0, -1, phi], [0, 1, -phi], [0, -1, -phi],
47
+ [1, phi, 0], [-1, phi, 0], [1, -phi, 0], [-1, -phi, 0],
48
+ [phi, 0, 1], [phi, 0, -1], [-phi, 0, 1], [-phi, 0, -1]
49
+ ], dtype=float)
50
+ nodes /= norm(nodes, axis=1, keepdims=True)
51
+ N = nodes.shape[0] # 12 nodos
52
+
53
+ # Pauli
54
+ sigma_x = np.array([[0, 1], [1, 0]], dtype=complex)
55
+ sigma_y = np.array([[0, -1j], [1j, 0]], dtype=complex)
56
+ sigma_z = np.array([[1, 0], [0, -1]], dtype=complex)
57
+
58
+ def kron_IN(M, N_sites):
59
+ return np.kron(M, np.eye(N_sites, dtype=complex))
60
+
61
+ def site_op(block_2x2, i, j, N_sites):
62
+ K = np.zeros((N_sites, N_sites), dtype=complex)
63
+ K[i, j] = 1.0
64
+ return np.kron(K, block_2x2)
65
+
66
+ def geodesic_kernel(nodes, sigma=0.618, alpha_log=0.10):
67
+ diff = nodes[:, None, :] - nodes[None, :, :]
68
+ dist = norm(diff, axis=-1)
69
+
70
+ W = np.exp(-(dist**2) / (sigma**2))
71
+ np.fill_diagonal(W, 0.0)
72
+
73
+ if alpha_log > 0.0:
74
+ corr = 1.0 + alpha_log * np.log1p(dist**2)
75
+ corr[range(N), range(N)] = 1.0
76
+ W = W / corr
77
+
78
+ row_sums = W.sum(axis=1, keepdims=True)
79
+ row_sums[row_sums == 0] = 1.0
80
+ return W / row_sums
81
+
82
+ def u1_edge_phases(nodes, flux_vector=(0.0, 0.0, 0.0), q=1.0, gauge_scale=1.0):
83
+ A = gauge_scale * np.asarray(flux_vector, dtype=float)
84
+ midpoints = (nodes[:, None, :] + nodes[None, :, :]) / 2.0
85
+ theta = (midpoints @ A).astype(float)
86
+ theta = 0.5 * (theta - theta.T)
87
+ return theta * q
88
+
89
+ def build_dirac_hamiltonian(
90
+ m=0.25,
91
+ v=1.0,
92
+ sigma=0.618,
93
+ alpha_log=0.10,
94
+ q=1.0,
95
+ flux_vector=(0.0, 0.0, 0.0),
96
+ gauge_scale=0.0
97
+ ):
98
+ W = geodesic_kernel(nodes, sigma=sigma, alpha_log=alpha_log)
99
+
100
+ if gauge_scale != 0.0 and any(flux_vector):
101
+ theta = u1_edge_phases(nodes, flux_vector=flux_vector,
102
+ q=q, gauge_scale=gauge_scale)
103
+ U = np.exp(1j * theta)
104
+ else:
105
+ U = np.ones((N, N), dtype=complex)
106
+
107
+ # Término de masa
108
+ H = np.kron(np.eye(N, dtype=complex), m * sigma_z)
109
+
110
+ # Término cinético acoplado
111
+ diff = nodes[:, None, :] - nodes[None, :, :]
112
+ dist = norm(diff, axis=-1) + 1e-12
113
+ d_hat = diff / dist[..., None]
114
+
115
+ for i in range(N):
116
+ for j in range(N):
117
+ if i == j or W[i, j] == 0:
118
+ continue
119
+ nvec = d_hat[i, j]
120
+ S = (nvec[0] * sigma_x +
121
+ nvec[1] * sigma_y +
122
+ nvec[2] * sigma_z)
123
+ H += v * W[i, j] * U[i, j] * site_op(S, i, j, N)
124
+
125
+ # Hermitizar por seguridad numérica
126
+ H = 0.5 * (H + H.conj().T)
127
+ return H
128
+
129
+ def site_probs(psi):
130
+ N2 = psi.shape[0]
131
+ n = N2 // 2
132
+ psi_mat = psi.reshape(n, 2)
133
+ return np.sum(np.abs(psi_mat)**2, axis=1).real
134
+
135
+ def chirality(psi):
136
+ S = kron_IN(sigma_z, N)
137
+ return float(np.vdot(psi, S @ psi).real)
138
+
139
+ def energy_expectation(psi, H):
140
+ return float(np.vdot(psi, H @ psi).real)
141
+
142
+ def spatial_entropy(p):
143
+ p = np.clip(p, 1e-12, 1.0)
144
+ return float(-np.sum(p * np.log(p)).real)
145
+
146
+ def evolve_dirac_shell(psi0, H, dt=0.05, steps=200, record_every=20):
147
+ U = expm(-1j * dt * H)
148
+ psi = psi0.copy()
149
+
150
+ probs_hist = []
151
+ energy_hist = []
152
+ chir_hist = []
153
+ ent_hist = []
154
+
155
+ for t in range(steps + 1):
156
+ if t % record_every == 0:
157
+ p = site_probs(psi)
158
+ probs_hist.append(p)
159
+ energy_hist.append(energy_expectation(psi, H))
160
+ chir_hist.append(chirality(psi))
161
+ ent_hist.append(spatial_entropy(p))
162
+
163
+ psi = U @ psi
164
+ psi /= np.sqrt(np.vdot(psi, psi))
165
+
166
+ return {
167
+ "probs": np.array(probs_hist, dtype=float),
168
+ "energy": np.array(energy_hist, dtype=float),
169
+ "chirality": np.array(chir_hist, dtype=float),
170
+ "entropy": np.array(ent_hist, dtype=float),
171
+ "dt": dt,
172
+ "record_every": record_every,
173
+ }
174
+
175
+
176
+ # =========================
177
+ # Feature extraction and scoring
178
+ # (Copied from cell DiknqWJZIZ5q)
179
+ # =========================
180
+
181
+ def get_embedding(text: str) -> np.ndarray:
182
+ emb = encoder.encode([text], convert_to_numpy=True, normalize_embeddings=True)
183
+ return emb[0]
184
+
185
+ def compute_rrf_features(prompt: str, answer: str) -> dict:
186
+ # Embeddings RRF
187
+ e_p = get_embedding(prompt)
188
+ e_a = get_embedding(answer)
189
+
190
+ cosine_pa = float(np.dot(e_p, e_a))
191
+ len_ratio = len(answer) / (len(prompt) + 1.0)
192
+
193
+ # Estado inicial ligado al texto (seed reproducible)
194
+ rng = np.random.default_rng(abs(hash(prompt + answer)) % (2**32))
195
+ vec = rng.normal(0, 1, (2*N,)) + 1j * rng.normal(0, 1, (2*N,))
196
+ vec /= np.sqrt(np.vdot(vec, vec))
197
+ psi0 = vec
198
+
199
+ # Hamiltoniano Dirac Φ12.0
200
+ H = build_dirac_hamiltonian(
201
+ m=0.25, v=1.0, sigma=0.618,
202
+ alpha_log=0.10, q=1.0,
203
+ flux_vector=(0.0, 0.0, 0.0),
204
+ gauge_scale=0.0
205
+ )
206
+
207
+ out = evolve_dirac_shell(psi0, H, dt=0.05, steps=200, record_every=20)
208
+
209
+ probs = out["probs"]
210
+ energy = out["energy"]
211
+ chir = out["chirality"]
212
+ entropy = out["entropy"]
213
+
214
+ S_initial = float(entropy[0])
215
+ S_final = float(entropy[-1])
216
+ S_delta = S_final - S_initial
217
+ C_final = float(chir[-1])
218
+ E_mean = float(np.mean(energy))
219
+ E_std = float(np.std(energy))
220
+
221
+ return {
222
+ "cosine_pa": cosine_pa,
223
+ "len_ratio": len_ratio,
224
+ "dirac_entropy_final": S_final,
225
+ "dirac_entropy_delta": S_delta,
226
+ "dirac_chirality_final": C_final,
227
+ "dirac_energy_mean": E_mean,
228
+ "dirac_energy_std": E_std,
229
+ }
230
+
231
+ def features_to_vector(feats: dict) -> np.ndarray:
232
+ keys = [
233
+ "cosine_pa",
234
+ "len_ratio",
235
+ "dirac_entropy_final",
236
+ "dirac_entropy_delta",
237
+ "dirac_chirality_final",
238
+ "dirac_energy_mean",
239
+ "dirac_energy_std",
240
+ ]
241
+ return np.array([feats[k] for k in keys], dtype=float)
242
+
243
+ def compute_scores_srff_crrf_ephi(prompt: str, answer: str):
244
+ feats = compute_rrf_features(prompt, answer)
245
+ x = features_to_vector(feats).reshape(1, -1)
246
+
247
+ # meta-logit v2: pipeline (scaler + logistic regression)
248
+ proba = meta_logit.predict_proba(x)[0]
249
+ p_good = float(proba[1])
250
+
251
+ SRRF = p_good
252
+ CRRF = p_good * feats["cosine_pa"]
253
+
254
+ S_final = feats["dirac_entropy_final"]
255
+ S_max = np.log(N)
256
+ norm_entropy = float(S_final / S_max)
257
+
258
+ E_phi = 0.5 * (SRRF + norm_entropy)
259
+
260
+ scores = {
261
+ "SRRF": SRRF,
262
+ "CRRF": CRRF,
263
+ "E_phi": E_phi,
264
+ "p_good": p_good,
265
+ }
266
+ return scores, feats
267
+
268
+
269
+ # =========================
270
+ # FastAPI App
271
+ # (Copied from cell LwlyX4-LIgKK)
272
+ # =========================
273
+
274
+ app = FastAPI(
275
+ title="Savant RRF Φ12.0 API",
276
+ description="Evaluación conceptual resonante para texto generado por LLMs (SRRF / CRRF / E_phi).",
277
+ version="1.0.0",
278
+ )
279
+
280
+ class EvaluateRequest(BaseModel):
281
+ prompt: str = Field(..., description="Pregunta / instrucción original.")
282
+ answer: str = Field(..., description="Respuesta generada por un LLM.")
283
+ model_label: Optional[str] = Field(
284
+ None, description="Etiqueta opcional del modelo que generó la respuesta."
285
+ )
286
+
287
+ class EvaluateResponse(BaseModel):
288
+ scores: Dict[str, float]
289
+ features: Dict[str, float]
290
+ sim_summary: Dict[str, Any]
291
+
292
+ @app.post("/evaluate", response_model=EvaluateResponse)
293
+ def evaluate_endpoint(req: EvaluateRequest):
294
+ scores, feats = compute_scores_srff_crrf_ephi(req.prompt, req.answer)
295
+
296
+ # mini-sim extra para resumen diagnóstico simple
297
+ H = build_dirac_hamiltonian(
298
+ m=0.25, v=1.0, sigma=0.618,
299
+ alpha_log=0.10, q=1.0,
300
+ flux_vector=(0.0, 0.0, 0.0),
301
+ gauge_scale=0.0
302
+ )
303
+ rng = np.random.default_rng(abs(hash(req.prompt + req.answer)) % (2**32))
304
+ vec = rng.normal(0, 1, (2*N,)) + 1j * rng.normal(0, 1, (2*N,))
305
+ vec /= np.sqrt(np.vdot(vec, vec))
306
+ psi0 = vec
307
+
308
+ sim = evolve_dirac_shell(psi0, H, dt=0.05, steps=100, record_every=25)
309
+
310
+ sim_summary = {
311
+ "entropy_initial": float(sim["entropy"][0]),
312
+ "entropy_final": float(sim["entropy"][-1]),
313
+ "chirality_initial": float(sim["chirality"][0]),
314
+ "chirality_final": float(sim["chirality"][-1]),
315
+ "energy_mean": float(np.mean(sim["energy"])),
316
+ "energy_std": float(np.std(sim["energy"])),
317
+ "N_sites": int(N),
318
+ }
319
+
320
+ return EvaluateResponse(
321
+ scores=scores,
322
+ features=feats,
323
+ sim_summary=sim_summary,
324
+ )
requirements.txt CHANGED
@@ -1,8 +1,8 @@
1
- fastapi==0.115.0
2
- uvicorn[standard]==0.30.6
3
- sentence-transformers==3.0.1
4
- huggingface_hub==0.24.6
5
- joblib==1.4.2
6
- scipy==1.13.1
7
- numpy==1.26.4
8
-
 
1
+ sentence-transformers
2
+ huggingface_hub
3
+ joblib
4
+ fastapi
5
+ uvicorn
6
+ scipy
7
+ numpy
8
+ pydantic # Explicitly add pydantic as it's used by FastAPI