antonypamo commited on
Commit
6a1eded
·
verified ·
1 Parent(s): 0b79761

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +15 -574
app.py CHANGED
@@ -1,580 +1,21 @@
1
- # ======================================================
2
- # Savant RRF Φ12.0 — app.py (AGIRRFCore-aligned, HARDENED)
3
- # Uses the same AGIRRFCore logic as RRFSavant_AGI_Core_Colab
4
- # ======================================================
5
 
6
- from __future__ import annotations
7
-
8
- from dataclasses import dataclass, field
9
- from pathlib import Path
10
- import os, json, math, time
11
- from typing import Optional, Dict, Any, List, Tuple
12
-
13
- import numpy as np
14
- import torch
15
- import torch.nn as nn
16
-
17
- from fastapi import FastAPI, HTTPException
18
- from pydantic import BaseModel, Field, ConfigDict
19
-
20
- from sentence_transformers import SentenceTransformer
21
- from huggingface_hub import hf_hub_download
22
- import joblib
23
-
24
-
25
- # ======================================================
26
- # 0) Hardening limits
27
- # ======================================================
28
-
29
- MAX_PROMPT_CHARS = int(os.environ.get("MAX_PROMPT_CHARS", "8000"))
30
- MAX_ANSWER_CHARS = int(os.environ.get("MAX_ANSWER_CHARS", "12000"))
31
- MAX_DOCS = int(os.environ.get("MAX_DOCS", "50"))
32
- MAX_DOC_CHARS = int(os.environ.get("MAX_DOC_CHARS", "6000"))
33
-
34
-
35
- # ======================================================
36
- # 1) MANIFEST
37
- # ======================================================
38
-
39
- DEFAULT_MANIFEST = {
40
- "version": "Φ12.0",
41
- "project": "Savant RRF API & Meta-Logic Suite",
42
- "owner": "Antony Padilla Morales",
43
- "status": "fallback_default",
44
- }
45
-
46
- MANIFEST_PATH = Path(__file__).parent / "savant_rrf_api_manifest_phi12.json"
47
-
48
- def load_manifest_file() -> Dict[str, Any]:
49
- if MANIFEST_PATH.exists():
50
- try:
51
- print(f"[Manifest] Loading from {MANIFEST_PATH}", flush=True)
52
- return json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
53
- except Exception as e:
54
- print(f"[Manifest] Invalid JSON: {e}", flush=True)
55
- print("[Manifest] Using DEFAULT_MANIFEST", flush=True)
56
- return DEFAULT_MANIFEST
57
-
58
- manifest_data = load_manifest_file()
59
- print("[Manifest] version:", manifest_data.get("version"), flush=True)
60
-
61
-
62
- # ======================================================
63
- # 2) Global config
64
- # ======================================================
65
-
66
- HF_TOKEN = os.environ.get("HF_TOKEN", "") # set in Spaces secrets
67
- if HF_TOKEN:
68
- os.environ["HF_TOKEN"] = HF_TOKEN
69
-
70
- ENCODER_MODEL_ID = "antonypamo/RRFSAVANTMADE"
71
- META_LOGIT_REPO = "antonypamo/RRFSavantMetaLogicV2"
72
- META_LOGIT_FILENAME = "logreg_rrf_savant.joblib"
73
-
74
- RRF_DATASET_REPO = "antonypamo/savant_rrf1_curated"
75
-
76
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
77
- st_device = "cuda" if torch.cuda.is_available() else "cpu"
78
-
79
-
80
- def _hf_download_safe(
81
- repo_id: str,
82
- filename: str,
83
- *,
84
- repo_type: Optional[str] = None,
85
- token: Optional[str] = None,
86
- ) -> Optional[str]:
87
- """
88
- Robust HF download:
89
- - returns local path or None
90
- - prints actionable errors (401/private/gated/missing)
91
- """
92
- try:
93
- return hf_hub_download(
94
- repo_id=repo_id,
95
- filename=filename,
96
- repo_type=repo_type,
97
- token=token or None,
98
- )
99
- except Exception as e:
100
- msg = str(e)
101
- if "401" in msg or "Unauthorized" in msg:
102
- print(f"❌ [HF] 401 Unauthorized downloading {repo_id}/{filename}. "
103
- f"Repo may be private/gated or HF_TOKEN missing/invalid.", flush=True)
104
- elif "RepositoryNotFoundError" in msg or "404" in msg:
105
- print(f"❌ [HF] Repo or file not found: {repo_id}/{filename}", flush=True)
106
- else:
107
- print(f"⚠️ [HF] Download failed: {repo_id}/{filename} | {e}", flush=True)
108
- return None
109
-
110
-
111
- def hf_dataset_path(filename: str) -> Optional[str]:
112
- return _hf_download_safe(
113
- repo_id=RRF_DATASET_REPO,
114
- filename=filename,
115
- repo_type="dataset",
116
- token=HF_TOKEN if HF_TOKEN else None,
117
- )
118
-
119
-
120
- # ======================================================
121
- # 3) Optional artifacts (dataset assets)
122
- # ======================================================
123
-
124
- SAVANT_CNN_PATH = hf_dataset_path("savant_cnn.pt")
125
- RRF_NODES_PATH = hf_dataset_path("rrf_nodes.pt")
126
- RRF_TUTOR_JSONL = hf_dataset_path("rrf_tutor_curated.jsonl")
127
-
128
-
129
- # ======================================================
130
- # 4) Savant CNN (optional)
131
- # ======================================================
132
-
133
- class SavantCNN(nn.Module):
134
- def __init__(self):
135
- super().__init__()
136
- self.conv1 = nn.Conv1d(1, 32, 3, padding=1)
137
- self.conv2 = nn.Conv1d(32, 64, 3, padding=1)
138
- self.conv3 = nn.Conv1d(64, 128, 3, padding=1)
139
- self.pool = nn.AdaptiveAvgPool1d(4)
140
- self.fc = nn.Linear(512, 64)
141
-
142
- def forward(self, x):
143
- x = torch.relu(self.conv1(x))
144
- x = torch.relu(self.conv2(x))
145
- x = torch.relu(self.conv3(x))
146
- x = self.pool(x)
147
- x = x.view(x.size(0), -1)
148
- return self.fc(x)
149
-
150
-
151
- savant_cnn = None
152
- if SAVANT_CNN_PATH:
153
- try:
154
- savant_cnn = SavantCNN()
155
- savant_cnn.load_state_dict(torch.load(SAVANT_CNN_PATH, map_location=device))
156
- savant_cnn.to(device).eval()
157
- print("✅ Savant CNN loaded", flush=True)
158
- except Exception as e:
159
- print(f"⚠️ CNN load failed: {e}", flush=True)
160
-
161
- rrf_nodes = None
162
- if RRF_NODES_PATH:
163
- try:
164
- rrf_nodes = torch.load(RRF_NODES_PATH, map_location=device)
165
- print("✅ RRF nodes loaded", flush=True)
166
- except Exception as e:
167
- print(f"⚠️ RRF nodes load failed: {e}", flush=True)
168
-
169
-
170
- # ======================================================
171
- # 5) Φ-node ontology (8 nodes -> one-hot 8)
172
- # ======================================================
173
-
174
- @dataclass
175
- class PhiNode:
176
- name: str
177
- description: str
178
- tags: List[str] = field(default_factory=list)
179
- embedding: Optional[np.ndarray] = None # runtime only
180
-
181
- PHI_NODES: List[PhiNode] = [
182
- PhiNode("Φ0_seed", "Genesis seed, core identity and origin.", ["genesis","identity","anchor"]),
183
- PhiNode("Φ1_relation", "Relational bonding, dialogue, social meaning.", ["relation","dialogue"]),
184
- PhiNode("Φ2_resonance", "Signal resonance, harmonic alignment, coherence lift.", ["resonance","harmonics"]),
185
- PhiNode("Φ3_memory", "Memory consolidation, retrieval, indexing.", ["memory","retrieval"]),
186
- PhiNode("Φ4_logic", "Logical rigor, constraints, verification.", ["logic","verification"]),
187
- PhiNode("Φ5_creative", "Creative synthesis, metaphor, generative jumps.", ["creative","synthesis"]),
188
- PhiNode("Φ6_alignment", "Ethical alignment and safety constraints.", ["alignment","ethics"]),
189
- PhiNode("Φ7_meta_agi", "Meta-orchestrator that evaluates and routes flows.", ["meta","orchestration"]),
190
- ]
191
- PHI_NAME_TO_IDX = {n.name: i for i, n in enumerate(PHI_NODES)}
192
-
193
-
194
- def phi_nodes_public() -> List[Dict[str, Any]]:
195
- # JSON-safe version (no embeddings)
196
- return [{"name": n.name, "description": n.description, "tags": n.tags} for n in PHI_NODES]
197
-
198
-
199
- # ======================================================
200
- # 6) CoherenceModel (stable S_RRF + C_RRF)
201
- # ======================================================
202
-
203
- class CoherenceModel:
204
- def __init__(self, eps: float = 1e-9):
205
- self.eps = eps
206
-
207
- def compute(self, vec: np.ndarray) -> Tuple[float, float]:
208
- v = np.asarray(vec, dtype=float).ravel()
209
- n = len(v)
210
- if n < 4:
211
- return 0.0, 0.0
212
-
213
- spectrum = np.fft.rfft(v)
214
- power = (np.abs(spectrum) ** 2).astype(float)
215
- freqs = np.fft.rfftfreq(n, d=1.0).astype(float)
216
-
217
- total_power = float(power.sum()) + self.eps
218
-
219
- # C_RRF: concentration in dominant frequency
220
- C_RRF = float(power.max() / total_power)
221
-
222
- # S_RRF: prefer lower average frequency
223
- f_mean = float((freqs * power).sum() / total_power)
224
- f_max = float(freqs.max()) + self.eps
225
- S_RRF = float(1.0 - min(1.0, f_mean / f_max))
226
-
227
- return S_RRF, C_RRF
228
-
229
- coherence_model = CoherenceModel()
230
-
231
-
232
- # ======================================================
233
- # 7) AGIRRFCore (aligned)
234
- # ======================================================
235
-
236
- class AGIRRFCore:
237
- def __init__(
238
- self,
239
- phi_nodes: List[PhiNode],
240
- coherence_model: Optional[CoherenceModel] = None,
241
- st_model_name: str = ENCODER_MODEL_ID,
242
- ):
243
- self.phi_nodes = phi_nodes
244
- self.coherence_model = coherence_model
245
-
246
- print(f"🔄 Loading sentence-transformer: {st_model_name} on {st_device} ...", flush=True)
247
- self.embedder = SentenceTransformer(st_model_name, device=st_device)
248
- print("✅ Embedder loaded", flush=True)
249
-
250
- self._embed_phi_nodes()
251
-
252
- def _embed_text(self, text: str) -> np.ndarray:
253
- return self.embedder.encode([text], convert_to_numpy=True)[0]
254
-
255
- def _embed_phi_nodes(self):
256
- texts = [f"{n.name}: {n.description} | tags: {', '.join(n.tags)}" for n in self.phi_nodes]
257
- embs = self.embedder.encode(texts, convert_to_numpy=True)
258
- for node, emb in zip(self.phi_nodes, embs):
259
- node.embedding = emb
260
- print(f"✅ Embedded {len(self.phi_nodes)} Φ-nodes.", flush=True)
261
-
262
- def _dominant_frequency(self, vec: np.ndarray) -> float:
263
- v = np.asarray(vec, dtype=float).ravel()
264
- if len(v) < 4:
265
- return 0.0
266
- spectrum = np.fft.rfft(v)
267
- power = np.abs(spectrum) ** 2
268
- freqs = np.fft.rfftfreq(len(v), d=1.0)
269
- idx = int(np.argmax(power))
270
- return float(freqs[idx])
271
-
272
- def _phi_omega(self, energy: float, dom_freq: float) -> Tuple[float, float]:
273
- phi = 1.0 - math.exp(-float(energy)) # saturating
274
- omega = math.tanh(dom_freq * 10.0) # saturating
275
- return float(phi), float(omega)
276
-
277
- def _closest_phi_node(self, vec: np.ndarray) -> Tuple[str, float]:
278
- if not self.phi_nodes or self.phi_nodes[0].embedding is None:
279
- return "unknown", 0.0
280
- v = np.asarray(vec, dtype=float).ravel()
281
- v_norm = np.linalg.norm(v) + 1e-9
282
- best_name, best_cos = "unknown", -1.0
283
- for node in self.phi_nodes:
284
- e = node.embedding
285
- if e is None:
286
- continue
287
- cos = float(np.dot(v, e) / (v_norm * (np.linalg.norm(e) + 1e-9)))
288
- if cos > best_cos:
289
- best_cos = cos
290
- best_name = node.name
291
- return best_name, best_cos
292
-
293
- def analyze(self, text: str, context_label: str = "query") -> Dict[str, Any]:
294
- vec = self._embed_text(text)
295
-
296
- energy = float(np.dot(vec, vec))
297
- dom_freq = self._dominant_frequency(vec)
298
- phi, omega = self._phi_omega(energy, dom_freq)
299
-
300
- if self.coherence_model is not None:
301
- S_RRF, C_RRF = self.coherence_model.compute(vec)
302
- else:
303
- S_RRF, C_RRF = 0.0, 0.0
304
-
305
- coherence = 0.5 * float(S_RRF) + 0.5 * float(C_RRF)
306
- closest_name, closest_cos = self._closest_phi_node(vec)
307
-
308
- return {
309
- "context": context_label,
310
- "phi": phi,
311
- "omega": omega,
312
- "coherence": float(coherence),
313
- "S_RRF": float(S_RRF),
314
- "C_RRF": float(C_RRF),
315
- "hamiltonian_energy": float(energy),
316
- "dominant_frequency": float(dom_freq),
317
- "closest_phi_node": closest_name,
318
- "closest_phi_cos": float(closest_cos),
319
- "timestamp": float(time.time()),
320
- }
321
-
322
-
323
- agirrf_core = AGIRRFCore(
324
- phi_nodes=PHI_NODES,
325
- coherence_model=coherence_model,
326
- st_model_name=ENCODER_MODEL_ID,
327
- )
328
-
329
-
330
- # ======================================================
331
- # 8) Load Meta-Logit (15D)
332
- # ======================================================
333
-
334
- print("🔄 Loading meta-logit...", flush=True)
335
- meta_logit_path = _hf_download_safe(
336
- repo_id=META_LOGIT_REPO,
337
- filename=META_LOGIT_FILENAME,
338
- token=HF_TOKEN if HF_TOKEN else None,
339
- )
340
- if not meta_logit_path:
341
- raise RuntimeError(
342
- f"Meta-logit not available. Check repo_id={META_LOGIT_REPO}, "
343
- f"filename={META_LOGIT_FILENAME}, and HF_TOKEN if private."
344
- )
345
- meta_logit = joblib.load(meta_logit_path)
346
-
347
- EXPECTED_FEATURES = getattr(meta_logit, "n_features_in_", 15)
348
- if EXPECTED_FEATURES != 15:
349
- raise RuntimeError(f"Meta-logit expects {EXPECTED_FEATURES} features, expected 15.")
350
- print("✅ Meta-logit ready (15D)", flush=True)
351
-
352
-
353
- # ======================================================
354
- # 9) Feature mapping (7 + one-hot 8 = 15)
355
- # ======================================================
356
-
357
- def rrf_state_to_features(state: Dict[str, Any]) -> np.ndarray:
358
- phi = float(state.get("phi", 0.0))
359
- omega = float(state.get("omega", 0.0))
360
- coh = float(state.get("coherence", 0.0))
361
- S_RRF = float(state.get("S_RRF", 0.0))
362
- C_RRF = float(state.get("C_RRF", 0.0))
363
- E_H = float(state.get("hamiltonian_energy", 0.0))
364
- dom_f = float(state.get("dominant_frequency", 0.0))
365
-
366
- phi_name = state.get("closest_phi_node", "unknown")
367
- phi_onehot = np.zeros(len(PHI_NODES), dtype=float)
368
- idx = PHI_NAME_TO_IDX.get(phi_name)
369
- if idx is not None:
370
- phi_onehot[idx] = 1.0
371
-
372
- base = np.array([phi, omega, coh, S_RRF, C_RRF, E_H, dom_f], dtype=float)
373
- return np.concatenate([base, phi_onehot], axis=0)
374
-
375
-
376
- # ======================================================
377
- # 10) Core scoring (prompt, answer)
378
- # ======================================================
379
-
380
- def _embed_norm(text: str) -> np.ndarray:
381
- return agirrf_core.embedder.encode([text], convert_to_numpy=True, normalize_embeddings=True)[0]
382
-
383
- def compute_scores(prompt: str, answer: str) -> Dict[str, Any]:
384
- prompt = prompt or ""
385
- answer = answer or ""
386
- if not prompt.strip() or not answer.strip():
387
- raise ValueError("Empty prompt/answer")
388
-
389
- if len(prompt) > MAX_PROMPT_CHARS or len(answer) > MAX_ANSWER_CHARS:
390
- raise HTTPException(status_code=413, detail="Payload too large")
391
-
392
- # extra signal: cosine(prompt, answer)
393
- e_p = _embed_norm(prompt)
394
- e_a = _embed_norm(answer)
395
- cosine = float(np.dot(e_p, e_a))
396
-
397
- # stable single-state features on combined QA text
398
- qa_text = f"Q: {prompt}\nA: {answer}"
399
- state = agirrf_core.analyze(qa_text, context_label="qa")
400
- feats = rrf_state_to_features(state).reshape(1, -1)
401
-
402
- p_good = float(meta_logit.predict_proba(feats)[0][1])
403
-
404
- SRRF = p_good
405
- CRRF = p_good * cosine
406
- E_phi = 0.5 * (p_good + abs(cosine))
407
-
408
- return {
409
- "p_good": p_good,
410
- "SRRF": SRRF,
411
- "CRRF": CRRF,
412
- "E_phi": E_phi,
413
- "cosine": cosine,
414
-
415
- # debug/state exposure (key for Savant)
416
- "phi": float(state["phi"]),
417
- "omega": float(state["omega"]),
418
- "coherence": float(state["coherence"]),
419
- "S_RRF": float(state["S_RRF"]),
420
- "C_RRF": float(state["C_RRF"]),
421
- "hamiltonian_energy": float(state["hamiltonian_energy"]),
422
- "dominant_frequency": float(state["dominant_frequency"]),
423
- "closest_phi_node": state["closest_phi_node"],
424
- "closest_phi_cos": float(state["closest_phi_cos"]),
425
- }
426
-
427
-
428
- # ======================================================
429
- # 11) FastAPI models
430
- # ======================================================
431
-
432
- class EvaluateRequest(BaseModel):
433
- model_config = ConfigDict(protected_namespaces=())
434
- prompt: str
435
- answer: str
436
- model_label: Optional[str] = None # reserved for future routing
437
-
438
- class EvaluateResponse(BaseModel):
439
- scores: Dict[str, Any]
440
- manifest_version: str
441
 
442
  class PredictRequest(BaseModel):
443
- features: List[float] = Field(..., min_length=15, max_length=15)
444
-
445
- class PredictResponse(BaseModel):
446
- p_good: float
447
-
448
- class RerankRequest(BaseModel):
449
- query: str
450
- documents: List[str]
451
- alpha: float = 0.2 # kept for compatibility (not used in cosine rerank)
452
-
453
- class RerankDocument(BaseModel):
454
- id: int
455
- score: float
456
- rank: int
457
-
458
- class RerankResponse(BaseModel):
459
- model_config = ConfigDict(protected_namespaces=())
460
- model_id: str
461
- results: List[RerankDocument]
462
-
463
-
464
- # ======================================================
465
- # 12) FastAPI app
466
- # ======================================================
467
-
468
- app = FastAPI(
469
- title="Savant RRF Φ12.0 API",
470
- version="1.2.1",
471
- description="AGIRRFCore-aligned Meta-Logic, Reranking & Quality Evaluation",
472
- )
473
-
474
-
475
- # --------------------------
476
- # Root (avoid 404 in Spaces)
477
- # --------------------------
478
-
479
- @app.get("/")
480
- def root():
481
- return {
482
- "status": "ok",
483
- "project": manifest_data.get("project"),
484
- "version": manifest_data.get("version"),
485
- "model": "RRFSavantMetaLogicV2",
486
- "docs": "/docs",
487
- "endpoints": ["/manifest", "/health", "/evaluate", "/predict", "/v1/rerank"],
488
- }
489
-
490
-
491
- # --------------------------
492
- # Manifest (no naming clash)
493
- # --------------------------
494
-
495
- @app.get("/manifest")
496
- def get_manifest():
497
- return {
498
- "model": "RRFSavantMetaLogicV2",
499
- "version": manifest_data.get("version"),
500
- "encoder": ENCODER_MODEL_ID,
501
- "meta_logit": f"{META_LOGIT_REPO}/{META_LOGIT_FILENAME}",
502
- "features": 15,
503
- "phi_nodes": phi_nodes_public(),
504
- "limits": {
505
- "MAX_PROMPT_CHARS": MAX_PROMPT_CHARS,
506
- "MAX_ANSWER_CHARS": MAX_ANSWER_CHARS,
507
- "MAX_DOCS": MAX_DOCS,
508
- "MAX_DOC_CHARS": MAX_DOC_CHARS,
509
- }
510
- }
511
-
512
-
513
- @app.get("/health")
514
- def health():
515
- return {
516
- "status": "ok",
517
- "encoder_loaded": True,
518
- "meta_logit_loaded": True,
519
- "cnn_loaded": savant_cnn is not None,
520
- "rrf_nodes_loaded": rrf_nodes is not None,
521
- "manifest_version": manifest_data.get("version"),
522
- "phi_nodes": len(PHI_NODES),
523
- "device": str(device),
524
- }
525
-
526
-
527
- @app.post("/evaluate", response_model=EvaluateResponse)
528
- def evaluate(req: EvaluateRequest):
529
- try:
530
- scores = compute_scores(req.prompt, req.answer)
531
- return EvaluateResponse(scores=scores, manifest_version=str(manifest_data.get("version")))
532
- except HTTPException:
533
- raise
534
- except Exception as e:
535
- print(f"[Evaluate] Error: {e}", flush=True)
536
- raise HTTPException(status_code=500, detail="Evaluation failed")
537
-
538
-
539
- @app.post("/predict", response_model=PredictResponse)
540
- def predict(req: PredictRequest):
541
- try:
542
- x = np.array([req.features], dtype=float)
543
- p_good = float(meta_logit.predict_proba(x)[0][1])
544
- return PredictResponse(p_good=p_good)
545
- except Exception as e:
546
- print(f"[Predict] Error: {e}", flush=True)
547
- raise HTTPException(status_code=500, detail="Predict failed")
548
-
549
-
550
- @app.post("/v1/rerank", response_model=RerankResponse)
551
- def rerank(req: RerankRequest):
552
- try:
553
- if not req.query or not req.query.strip():
554
- raise HTTPException(status_code=400, detail="query is empty")
555
-
556
- if len(req.documents) > MAX_DOCS:
557
- raise HTTPException(status_code=413, detail="Too many documents")
558
-
559
- for d in req.documents:
560
- if len(d) > MAX_DOC_CHARS:
561
- raise HTTPException(status_code=413, detail="Document too large")
562
-
563
- texts = [req.query] + req.documents
564
- embs = agirrf_core.embedder.encode(texts, convert_to_numpy=True, normalize_embeddings=True)
565
-
566
- q_emb = embs[0]
567
- d_embs = embs[1:]
568
- scores = (d_embs @ q_emb).astype(float).tolist()
569
 
570
- results = [{"id": i, "score": float(s)} for i, s in enumerate(scores)]
571
- results.sort(key=lambda x: x["score"], reverse=True)
 
572
 
573
- ranked = [RerankDocument(id=r["id"], score=r["score"], rank=i + 1) for i, r in enumerate(results)]
574
- return RerankResponse(model_id=ENCODER_MODEL_ID, results=ranked)
 
575
 
576
- except HTTPException:
577
- raise
578
- except Exception as e:
579
- print(f"[Rerank] Error: {e}", flush=True)
580
- raise HTTPException(status_code=500, detail="Rerank failed")
 
1
+ from fastapi import FastAPI
2
+ from pydantic import BaseModel
 
 
3
 
4
+ app = FastAPI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  class PredictRequest(BaseModel):
7
+ example_input: str
8
+ parameter1: int
9
+ parameter2: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ @app.get('/')
12
+ def root():
13
+ return {'status':'ok'}
14
 
15
+ @app.post('/predict')
16
+ def predict(data: PredictRequest):
17
+ return {'status': 'prediction_received', 'data': data}
18
 
19
+ @app.get('/evaluate')
20
+ def evaluate():
21
+ return {'status': 'evaluation_ready'}