APISAvant3 / main.py
antonypamo's picture
Update main.py
94ad995 verified
Raw
History Blame
12.6 kB
import os, time, json
from typing import Dict, List
import numpy as np
from numpy.linalg import norm
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sentence_transformers import SentenceTransformer
from huggingface_hub import hf_hub_download
import joblib
# ============================
# CONFIG
# ============================
HF_TOKEN = os.environ.get("HF_TOKEN", "")
ENCODER_MODEL_ID = "antonypamo/RRFSAVANTMADE"
META_LOGIT_REPO = "antonypamo/RRFSavantMetaLogicV2"
META_LOGIT_FILENAME = "logreg_rrf_savant.joblib"
MAX_PROMPT_CHARS = 8000
MAX_ANSWER_CHARS = 12000
MAX_DOCS = 50
MAX_DOC_CHARS = 6000
PHI_NODES = [
"Φ0_seed",
"Φ1_geometric",
"Φ2_gauge_dirac",
"Φ3_log_gravity",
"Φ4_resonance",
"Φ5_memory_symbiosis",
"Φ6_alignment",
"Φ7_meta_agi",
]
# ============================
# LOAD MODELS
# ============================
print("🔄 Loading encoder...", flush=True)
encoder = SentenceTransformer(ENCODER_MODEL_ID)
print("✅ Encoder loaded")
print("🔄 Loading meta-logit...", flush=True)
meta_path = hf_hub_download(
repo_id=META_LOGIT_REPO,
filename=META_LOGIT_FILENAME,
token=HF_TOKEN or None,
)
meta_logit = joblib.load(meta_path)
EXPECTED_FEATURES = getattr(meta_logit, "n_features_in_", 15)
if EXPECTED_FEATURES != 15:
raise RuntimeError(f"Expected 15 features, got {EXPECTED_FEATURES}")
print("✅ Meta-logit loaded")
# ============================
# CORE FUNCTIONS
# ============================
def get_embedding(text: str) -> np.ndarray:
return encoder.encode(
[text],
convert_to_numpy=True,
normalize_embeddings=True
)[0]
def cosine_sim(a, b):
return float(np.dot(a, b) / (norm(a) * norm(b) + 1e-12))
def spectral_features(emb: np.ndarray) -> Dict[str, float]:
fft = np.fft.rfft(emb)
power = np.abs(fft) ** 2
total = power.sum() + 1e-12
dominant_idx = int(np.argmax(power))
# Core features
phi = float(np.clip(total / (total + 1.0), 0.0, 1.0))
omega = float(np.clip(dominant_idx / len(power), 0.0, 1.0))
C_RRF = float(1.0 - np.std(power) / (np.mean(power) + 1e-12))
C_RRF = float(np.clip(C_RRF, 0.0, 1.0))
S_RRF = C_RRF
coherence = float(0.5 * S_RRF + 0.5 * C_RRF)
hamiltonian_energy = float(np.dot(emb, emb) / len(emb))
return {
"phi": phi,
"omega": omega,
"coherence": coherence,
"S_RRF": S_RRF,
"C_RRF": C_RRF,
"hamiltonian_energy": hamiltonian_energy,
"dominant_frequency": float(dominant_idx),
}
def closest_phi_node(feats: Dict[str, float]) -> int:
if feats["coherence"] > 0.85 and feats["phi"] > 0.6:
return 4
if feats["hamiltonian_energy"] > 0.5:
return 2
if feats["omega"] < 0.2:
return 0
if feats["coherence"] < 0.4:
return 5
if feats["phi"] < 0.3:
return 6
return 7
def rrf_vector(prompt: str, answer: str):
emb_p = get_embedding(prompt)
emb_a = get_embedding(answer)
emb = get_embedding(prompt + "\n" + answer)
feats = spectral_features(emb)
cos_pa = cosine_sim(emb_p, emb_a)
phi_idx = closest_phi_node(feats)
phi_one_hot = [1.0 if i == phi_idx else 0.0 for i in range(8)]
# 🔥 EXACTAMENTE 15 FEATURES
vector = [
feats["phi"],
feats["omega"],
feats["coherence"],
feats["S_RRF"],
feats["C_RRF"],
feats["hamiltonian_energy"],
feats["dominant_frequency"],
cos_pa,
*phi_one_hot # 8
]
return np.array(vector, dtype=float), feats, PHI_NODES[phi_idx]
def log_interaction(prompt, answer, score):
try:
with open("rrf_logs.jsonl", "a") as f:
f.write(json.dumps({
"t": time.time(),
"prompt": prompt,
"answer": answer,
"score": score
}) + "\n")
except:
pass
# ============================
# FASTAPI
# ============================
app = FastAPI(
title="Savant RRF Φ12.5 API",
version="2.1.0"
)
# ============================
# SCHEMAS
# ============================
class EvaluateRequest(BaseModel):
prompt: str
answer: str
class EvaluateResponse(BaseModel):
p_good: float
scores: Dict[str, float]
features: Dict[str, float]
phi_node: str
class ImproveRequest(BaseModel):
prompt: str
answer: str
class RerankRequest(BaseModel):
query: str
documents: List[str]
# ============================
# ROUTES
# ============================
@app.get("/")
def root():
return {
"status": "ok",
"version": "Φ12.5",
"endpoints": ["/evaluate", "/quality", "/improve", "/v1/rerank"]
}
@app.post("/evaluate", response_model=EvaluateResponse)
def evaluate(req: EvaluateRequest):
if len(req.prompt) > MAX_PROMPT_CHARS or len(req.answer) > MAX_ANSWER_CHARS:
raise HTTPException(413, "Payload too large")
x, feats, phi_node = rrf_vector(req.prompt, req.answer)
proba = meta_logit.predict_proba(x.reshape(1, -1))[0]
p_good = float(proba[1])
scores = {
"SRRF": p_good,
"CRRF": float(p_good * feats["coherence"] * feats["phi"]),
"E_phi": float(0.5 * (p_good + feats["phi"]))
}
log_interaction(req.prompt, req.answer, p_good)
return EvaluateResponse(
p_good=p_good,
scores=scores,
features=feats,
phi_node=phi_node
)
@app.post("/quality", response_model=EvaluateResponse)
def quality(req: EvaluateRequest):
return evaluate(req)
@app.post("/improve")
def improve(req: ImproveRequest):
x, _, _ = rrf_vector(req.prompt, req.answer)
p = meta_logit.predict_proba(x.reshape(1, -1))[0][1]
if p < 0.6:
improved = req.answer + " (refined for clarity, depth and coherence)"
else:
improved = req.answer
return {
"original_score": float(p),
"improved_answer": improved
}
@app.post("/v1/rerank")
def rerank(req: RerankRequest):
if len(req.documents) > MAX_DOCS:
raise HTTPException(413, "Too many documents")
texts = [req.query] + req.documents
embs = encoder.encode(
texts,
convert_to_numpy=True,
normalize_embeddings=True
)
q_emb = embs[0]
d_embs = embs[1:]
scores = d_embs @ q_emb
ranked_idx = np.argsort(-scores)
results = [
{
"id": int(i),
"score": float(scores[i]),
"rank": r + 1
}
for r, i in enumerate(ranked_idx)
]
return {
"model_id": ENCODER_MODEL_ID,
"results": results
}
]
# ============================
# STARTUP: MODELS
# ============================
print("🔄 Loading encoder...", flush=True)
encoder = SentenceTransformer(ENCODER_MODEL_ID)
print("✅ Encoder loaded", flush=True)
print("🔄 Loading meta-logit V2...", flush=True)
meta_logit_path = hf_hub_download(
repo_id=META_LOGIT_REPO,
filename=META_LOGIT_FILENAME,
token=HF_TOKEN or None,
)
meta_logit = joblib.load(meta_logit_path)
EXPECTED_FEATURES = getattr(meta_logit, "n_features_in_", 15)
if EXPECTED_FEATURES != 15:
raise RuntimeError(f"Meta-logit expects {EXPECTED_FEATURES} features, expected 15.")
print("✅ Meta-logit loaded (15D)", flush=True)
# ============================
# META-STATE FEATURE EXTRACTION
# ============================
def get_embedding(text: str) -> np.ndarray:
return encoder.encode(
[text],
convert_to_numpy=True,
normalize_embeddings=True,
)[0]
def spectral_features(emb: np.ndarray) -> Dict[str, float]:
fft = np.fft.rfft(emb)
power = np.abs(fft) ** 2
total = power.sum() + 1e-12
dominant_idx = int(np.argmax(power))
phi = float(np.clip(total / (total + 1.0), 0.0, 1.0))
omega = float(np.clip(dominant_idx / len(power), 0.0, 1.0))
S_RRF = float(1.0 - np.std(power) / (np.mean(power) + 1e-12))
S_RRF = float(np.clip(S_RRF, 0.0, 1.0))
coherence = float(0.5 * (1.0 - np.std(power) / (np.mean(power) + 1e-12)) + 0.5 * C_RRF)
hamiltonian_energy = float(np.dot(emb, emb))
dominant_frequency = float(dominant_idx)
return {
"phi": phi,
"omega": omega,
"coherence": coherence,
"S_RRF": S_RRF,
"C_RRF": C_RRF,
"hamiltonian_energy": hamiltonian_energy,
"dominant_frequency": dominant_frequency,
}
def closest_phi_node(feats: Dict[str, float]) -> int:
# Deterministic ontology mapping
if feats["coherence"] > 0.85 and feats["phi"] > 0.6:
return 4 # Φ4_resonance
if feats["hamiltonian_energy"] > 50:
return 2 # Φ2_gauge_dirac
if feats["omega"] < 0.2:
return 0 # Φ0_seed
if feats["coherence"] < 0.4:
return 5 # Φ5_memory_symbiosis
if feats["phi"] < 0.3:
return 6 # Φ6_alignment
return 7 # Φ7_meta_agi
def rrf_state_to_vector(prompt: str, answer: str) -> np.ndarray:
emb = get_embedding(prompt + "\n" + answer)
feats = spectral_features(emb)
phi_idx = closest_phi_node(feats)
phi_one_hot = [1.0 if i == phi_idx else 0.0 for i in range(8)]
vector = [
feats["phi"],
feats["omega"],
feats["coherence"],
feats["S_RRF"],
feats["C_RRF"],
feats["hamiltonian_energy"],
feats["dominant_frequency"],
*phi_one_hot,
]
return np.array(vector, dtype=float), feats, PHI_NODES[phi_idx]
# ============================
# FASTAPI
# ============================
app = FastAPI(
title="Savant RRF Φ12.0 API",
version="2.0.0",
description="Meta-state RRF quality evaluation + rerank",
)
# ============================
# SCHEMAS
# ============================
class EvaluateRequest(BaseModel):
prompt: str
answer: str
class EvaluateResponse(BaseModel):
p_good: float
scores: Dict[str, float]
features: Dict[str, float]
phi_node: str
class RerankRequest(BaseModel):
query: str
documents: List[str]
alpha: float = 0.2
class RerankDocument(BaseModel):
id: int
score: float
rank: int
class RerankResponse(BaseModel):
model_id: str
results: List[RerankDocument]
# ============================
# MANIFEST / HEALTH
# ============================
@app.get("/")
def root():
return {
"status": "ok",
"model": "RRFSavantMetaLogicV2",
"version": "Φ12.0",
"docs": "/docs",
"endpoints": ["/manifest", "/health", "/evaluate", "/quality", "/v1/rerank"],
}
@app.get("/manifest")
def manifest():
return {
"model": "RRFSavantMetaLogicV2",
"version": "Φ12.0",
"encoder": ENCODER_MODEL_ID,
"features": 15,
"phi_nodes": PHI_NODES,
}
@app.get("/health")
def health():
return {"status": "ok"}
# ============================
# /EVALUATE
# ============================
@app.post("/evaluate", response_model=EvaluateResponse)
def evaluate(req: EvaluateRequest):
if len(req.prompt) > MAX_PROMPT_CHARS or len(req.answer) > MAX_ANSWER_CHARS:
raise HTTPException(413, "Payload too large")
x, feats, phi_node = rrf_state_to_vector(req.prompt, req.answer)
proba = meta_logit.predict_proba(x.reshape(1, -1))[0]
p_good = float(proba[1])
scores = {
"SRRF": p_good,
"CRRF": p_good * feats["coherence"],
"E_phi": 0.5 * (p_good + feats["phi"]),
}
return EvaluateResponse(
p_good=p_good,
scores=scores,
features=feats,
phi_node=phi_node,
)
@app.post("/quality", response_model=EvaluateResponse)
def quality_alias(req: EvaluateRequest):
return evaluate(req)
# ============================
# /v1/rerank (BATCHED)
# ============================
@app.post("/v1/rerank", response_model=RerankResponse)
def rerank(req: RerankRequest):
if len(req.documents) > MAX_DOCS:
raise HTTPException(413, "Too many documents")
texts = [req.query] + req.documents
for d in req.documents:
if len(d) > MAX_DOC_CHARS:
raise HTTPException(413, "Document too large")
embs = encoder.encode(
texts,
convert_to_numpy=True,
normalize_embeddings=True,
)
q_emb = embs[0]
d_embs = embs[1:]
scores = d_embs @ q_emb
ranked_idx = np.argsort(-scores)
results = [
RerankDocument(
id=int(i),
score=float(scores[i]),
rank=r + 1,
)
for r, i in enumerate(ranked_idx)
]
return RerankResponse(
model_id=ENCODER_MODEL_ID,
results=results,
)