LOT 1 - Optimizare script build -Instalare mono comanda
This commit is contained in:
parent
5380c3fc63
commit
42ff22bf85
127 changed files with 16163 additions and 532 deletions
271
ai_platform/local_gpu_stack/run_tests.py
Normal file
271
ai_platform/local_gpu_stack/run_tests.py
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
#!/usr/bin/env python3
|
||||
"""DiDi LOT 1 — runner automat de teste end-to-end.
|
||||
|
||||
Rulează un test funcțional pe fiecare componentă și scrie rezultatele într-un
|
||||
raport JSON cu timestamp (reports/test_report_<data>.json). Fiecare intrare
|
||||
conține: componentă, endpoint, http_code, ok (verdict funcțional), latență,
|
||||
extras din răspuns + request-ul folosit — ca dovadă vizibilă.
|
||||
|
||||
Rulare: python3 run_tests.py (HOST_IP din .env sau 10.11.10.18)
|
||||
HOST_IP=1.2.3.4 python3 run_tests.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json, os, subprocess, sys, time, datetime, pathlib
|
||||
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
ASSETS = HERE / "test_assets"
|
||||
REPORTS = HERE / "reports"
|
||||
|
||||
# --- config din .env (fallback la defaults) ---------------------------------
|
||||
def load_env() -> dict:
|
||||
env = {}
|
||||
f = HERE / ".env"
|
||||
if f.exists():
|
||||
for line in f.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
env[k.strip()] = v.strip()
|
||||
return env
|
||||
|
||||
ENV = load_env()
|
||||
H = os.environ.get("HOST_IP") or ENV.get("HOST_IP") or "10.11.10.18"
|
||||
TOKEN = ENV.get("GATEWAY_API_TOKEN", "didi-local-dev-token-123")
|
||||
WHISPER = ENV.get("WHISPER_MODEL", "large-v3-turbo")
|
||||
|
||||
# --- docker shim (pt pregatirea asset-urilor; merge si prin sg docker) -------
|
||||
def docker(args: list[str]) -> subprocess.CompletedProcess:
|
||||
if subprocess.run(["docker", "ps"], capture_output=True).returncode == 0:
|
||||
return subprocess.run(["docker", *args], capture_output=True, text=True)
|
||||
return subprocess.run(["sg", "docker", "-c", "docker " + " ".join(args)],
|
||||
capture_output=True, text=True)
|
||||
|
||||
def ensure_assets() -> None:
|
||||
ASSETS.mkdir(exist_ok=True)
|
||||
mp4 = ASSETS / "sample.mp4"
|
||||
if not mp4.exists():
|
||||
import glob
|
||||
cands = sorted(glob.glob(str(HERE.parent.parent / "artefacte_lot1/runs_deepfake/**/*.mp4"), recursive=True),
|
||||
key=lambda p: os.path.getsize(p))
|
||||
if cands:
|
||||
subprocess.run(["cp", cands[0], str(mp4)])
|
||||
if not (ASSETS / "frame.jpg").exists() or not (ASSETS / "audio.wav").exists():
|
||||
# genereaza un cadru + audio din containerul extractors (are ffmpeg)
|
||||
docker(["cp", str(mp4), "didiAI-extractors:/tmp/s.mp4"])
|
||||
docker(["exec", "didiAI-extractors", "sh", "-c",
|
||||
"ffmpeg -y -i /tmp/s.mp4 -frames:v 1 /tmp/frame.jpg -vn -ar 16000 -ac 1 -t 8 /tmp/audio.wav"])
|
||||
docker(["cp", "didiAI-extractors:/tmp/frame.jpg", str(ASSETS / "frame.jpg")])
|
||||
docker(["cp", "didiAI-extractors:/tmp/audio.wav", str(ASSETS / "audio.wav")])
|
||||
|
||||
# --- helper request prin curl -----------------------------------------------
|
||||
def run_curl(args: list[str], timeout: int) -> tuple[int, str, float]:
|
||||
"""Return (http_code, body, latency_ms)."""
|
||||
full = ["curl", "-s", "-m", str(timeout), "-w", "\n%{http_code}", *args]
|
||||
t0 = time.monotonic()
|
||||
try:
|
||||
p = subprocess.run(full, capture_output=True, text=True, timeout=timeout + 10)
|
||||
out = p.stdout
|
||||
except subprocess.TimeoutExpired:
|
||||
return (0, "TIMEOUT", round((time.monotonic() - t0) * 1000, 1))
|
||||
lat = round((time.monotonic() - t0) * 1000, 1)
|
||||
code = 0; body = out
|
||||
if "\n" in out:
|
||||
body, _, last = out.rpartition("\n")
|
||||
try: code = int(last.strip())
|
||||
except ValueError: code = 0
|
||||
return (code, body, lat)
|
||||
|
||||
def jparse(body: str):
|
||||
try: return json.loads(body)
|
||||
except Exception: return None
|
||||
|
||||
# --- definitia testelor ------------------------------------------------------
|
||||
def t_llm():
|
||||
c, b, l = run_curl([f"http://{H}:14011/v1/chat/completions", "-H", "Content-Type: application/json",
|
||||
"-d", json.dumps({"model":"qwen3.5","messages":[{"role":"user","content":"Ce este un deepfake? O propozitie."}],"max_tokens":120})], 90)
|
||||
d = jparse(b); txt = (d or {}).get("choices",[{}])[0].get("message",{}).get("content","") if d else ""
|
||||
return c, l, bool(txt) and not txt.lower().startswith("thinking"), {"reply": txt[:300]}
|
||||
def t_embeddings():
|
||||
c,b,l = run_curl([f"http://{H}:14100/v1/embeddings","-H","Content-Type: application/json","-d",json.dumps({"model":"bge-m3","input":"test"})],30)
|
||||
d=jparse(b); dim=len((d or {}).get("data",[{}])[0].get("embedding",[])) if d else 0
|
||||
return c,l,dim==1024,{"dim":dim}
|
||||
def t_rerank():
|
||||
c,b,l=run_curl([f"http://{H}:14200/v1/rerank","-H","Content-Type: application/json","-d",json.dumps({"model":"bge-reranker-v2-m3","query":"deepfake detection","documents":["o pisica","sistem detectie deepfake video"]})],30)
|
||||
d=jparse(b); r=(d or {}).get("results",[]) ; top=r[0] if r else {}
|
||||
return c,l,top.get("index")==1,{"top_index":top.get("index"),"score":round(top.get("relevance_score",0),4)}
|
||||
def t_audio():
|
||||
c,b,l=run_curl([f"http://{H}:54300/v1/audio/transcriptions","-F",f"file=@{ASSETS}/audio.wav","-F",f"model={WHISPER}"],120)
|
||||
d=jparse(b); txt=(d or {}).get("text","") if d else ""
|
||||
return c,l,bool(txt.strip()),{"text":txt[:200],"language":(d or {}).get("language")}
|
||||
def t_video():
|
||||
c,b,l=run_curl([f"http://{H}:54600/analyze/video","-F",f"file=@{ASSETS}/sample.mp4"],180)
|
||||
d=jparse(b); v=(d or {}).get("verdict")
|
||||
return c,l,v in ("REAL","FAKE","UNCERTAIN"),{"verdict":v,"frames_analyzed":(d or {}).get("frames_analyzed")}
|
||||
def t_metadata():
|
||||
c,b,l=run_curl([f"http://{H}:54400/v1/metadata","-F",f"file=@{ASSETS}/sample.mp4"],40)
|
||||
d=jparse(b); ok=bool((d or {}).get("analyses",{}).get("integrity",{}).get("ok"))
|
||||
return c,l,ok,{"media_type":(d or {}).get("media_type"),"sha256":(d or {}).get("sha256","")[:16]}
|
||||
def t_ner():
|
||||
c,b,l=run_curl([f"http://{H}:54400/v1/ner","-H","Content-Type: application/json","-d",json.dumps({"text":"Klaus Iohannis s-a intalnit cu Emmanuel Macron la Bucuresti."})],60)
|
||||
d=jparse(b); ents=(d or {}).get("results",{}).get("entities",[])
|
||||
return c,l,len(ents)>=2,{"entities":[e.get("text")+":"+e.get("label","") for e in ents][:5]}
|
||||
def t_detect():
|
||||
c,b,l=run_curl([f"http://{H}:54400/v1/detect","-F",f"file=@{ASSETS}/frame.jpg"],90)
|
||||
d=jparse(b); det=(d or {}).get("results",{}).get("detections",[])
|
||||
return c,l,len(det)>=1,{"objects":len(det),"labels":[x.get("label") for x in det][:5]}
|
||||
def t_ocr():
|
||||
c,b,l=run_curl([f"http://{H}:54400/v1/ocr","-F",f"file=@{ASSETS}/frame.jpg"],90)
|
||||
d=jparse(b); ok=bool((d or {}).get("ok")); txt=(d or {}).get("results",{}).get("text","")
|
||||
return c,l,ok,{"text":txt[:150]}
|
||||
def t_sentiment():
|
||||
c,b,l=run_curl([f"http://{H}:54400/v1/sentiment","-H","Content-Type: application/json","-d",json.dumps({"text":"Produsul este excelent, sunt foarte multumit!"})],60)
|
||||
d=jparse(b); ok=bool((d or {}).get("ok"))
|
||||
return c,l,ok,{"label":(d or {}).get("results",{}).get("label"),"score":(d or {}).get("results",{}).get("score")}
|
||||
def t_forensic():
|
||||
c,b,l=run_curl([f"http://{H}:8085/api/forensic-modules"],15)
|
||||
d=jparse(b); m=(d or {}).get("available_modules",[])
|
||||
return c,l,len(m)>=3,{"modules":[x.get("id") for x in m]}
|
||||
def t_web():
|
||||
c,b,l=run_curl([f"http://{H}:51100/v1/search","-H","Content-Type: application/json","-d",json.dumps({"queries":["deepfake detection"],"max_results":3})],40)
|
||||
d=jparse(b); r=(d or {}).get("results",[])
|
||||
return c,l,len(r)>0,{"results":len(r),"first":(r[0].get("title","")[:60] if r else None)}
|
||||
def t_catalog():
|
||||
c,b,l=run_curl([f"http://{H}:11000/catalog/v1/status","-H",f"Authorization: Bearer {TOKEN}"],15)
|
||||
d=jparse(b); st=(d or {}).get("status")
|
||||
return c,l,st in ("healthy","degraded"),{"status":st,"components":[x.get("component_id") for x in (d or {}).get("components",[])]}
|
||||
def t_gateway():
|
||||
c,b,l=run_curl([f"http://{H}:11000/health"],10)
|
||||
c2,_,_=run_curl([f"http://{H}:11000/web/v1/info"],10) # fara token -> 401
|
||||
return c,l,(c==200 and c2==401),{"health":c,"web_no_auth":c2}
|
||||
def t_brain_atoms():
|
||||
c,b,l=run_curl([f"http://{H}:8090/v1/analysis_atom/stats/extended"],15)
|
||||
d=jparse(b)
|
||||
return c,l,(d is not None and "total_atoms" in d),{"total_atoms":(d or {}).get("total_atoms"),"by_tier":(d or {}).get("by_tier")}
|
||||
def t_brain_facts():
|
||||
c,b,l=run_curl([f"http://{H}:8090/v1/fact_status/list?page=1&page_size=10"],15)
|
||||
d=jparse(b)
|
||||
return c,l,(d is not None and "total" in d),{"total":(d or {}).get("total")}
|
||||
def t_brain_vcache():
|
||||
c,b,l=run_curl([f"http://{H}:8090/v1/verification_cache/list?page=1&page_size=10"],15)
|
||||
d=jparse(b)
|
||||
return c,l,(d is not None and "total" in d),{"total":(d or {}).get("total")}
|
||||
|
||||
def t_llm_completions():
|
||||
c,b,l=run_curl([f"http://{H}:14011/v1/completions","-H","Content-Type: application/json","-d",json.dumps({"model":"qwen3.5","prompt":"Capitala Romaniei este","max_tokens":10})],60)
|
||||
d=jparse(b); txt=(d or {}).get("choices",[{}])[0].get("text","") if d else ""
|
||||
return c,l,bool(txt.strip()),{"text":txt[:120]}
|
||||
def t_llm_info():
|
||||
c,b,l=run_curl([f"http://{H}:14011/v1/info"],15); d=jparse(b)
|
||||
return c,l,isinstance(d,dict) and len(d)>0,{"keys":list(d.keys())[:6] if isinstance(d,dict) else None}
|
||||
def t_video_semantic():
|
||||
c,b,l=run_curl([f"http://{H}:54600/analyze/video/semantic","-F",f"file=@{ASSETS}/sample.mp4"],180)
|
||||
d=jparse(b); ok=bool(d) and ("final_summary" in d or "chunk_results" in d)
|
||||
return c,l,ok,{"num_chunks":(d or {}).get("num_chunks"),"summary":(str((d or {}).get("final_summary",""))[:120])}
|
||||
def t_web_fetch():
|
||||
c,b,l=run_curl([f"http://{H}:51100/v1/fetch","-H","Content-Type: application/json","-d",json.dumps({"urls":["https://en.wikipedia.org/wiki/Deepfake"]})],60)
|
||||
ok=(c==200 and len(b)>1500 and ("title" in b or "content" in b or "text" in b))
|
||||
return c,l,ok,{"bytes":len(b)}
|
||||
def t_web_gather():
|
||||
c,b,l=run_curl([f"http://{H}:51100/v1/gather","-H","Content-Type: application/json","-d",json.dumps({"claim":"Deepfakes can be detected by AI","max_search_results":5})],150)
|
||||
d=jparse(b); ev=(d or {}).get("evidence",[]); n=(d or {}).get("total_evidence_items",len(ev) if isinstance(ev,list) else 0)
|
||||
return c,l,bool(d) and (n>0 or (isinstance(ev,list) and len(ev)>0)),{"evidence_items":n,"urls_found":(d or {}).get("total_urls_found")}
|
||||
def t_forensic_evidence():
|
||||
c,b,l=run_curl([f"http://{H}:8085/api/forensic-evidence","-F",f"video=@{ASSETS}/sample.mp4","-F","modules=m29","-F","every_n_frames=30"],150)
|
||||
d=jparse(b); mr=(d or {}).get("modules_run",[])
|
||||
return c,l,bool(mr),{"modules_run":mr,"frames":(d or {}).get("n_frames_extracted")}
|
||||
def t_catalog_components():
|
||||
c,b,l=run_curl([f"http://{H}:11000/catalog/v1/components","-H",f"Authorization: Bearer {TOKEN}"],15)
|
||||
d=jparse(b); comp=(d or {}).get("components",d if isinstance(d,list) else [])
|
||||
return c,l,c==200 and len(comp)>0,{"components":len(comp)}
|
||||
def t_catalog_models():
|
||||
c,b,l=run_curl([f"http://{H}:11000/catalog/v1/models","-H",f"Authorization: Bearer {TOKEN}"],15)
|
||||
d=jparse(b)
|
||||
return c,l,c==200 and d is not None,{"models":len((d or {}).get("models",d if isinstance(d,list) else []))}
|
||||
def t_brain_search():
|
||||
c,b,l=run_curl([f"http://{H}:8090/v1/search","-H","Content-Type: application/json","-d",json.dumps({"queries":["deepfake"],"max_results":2})],60)
|
||||
d=jparse(b)
|
||||
return c,l,bool(d) and "results" in d,{"total_results":(d or {}).get("total_results"),"cache":(d or {}).get("brain_meta",{}).get("cache_status")}
|
||||
def t_brain_gather():
|
||||
c,b,l=run_curl([f"http://{H}:8090/v1/gather","-H","Content-Type: application/json","-d",json.dumps({"claim":"Deepfakes can be detected by AI"})],120)
|
||||
d=jparse(b)
|
||||
return c,l,bool(d) and "evidence" in d,{"evidence_items":(d or {}).get("total_evidence_items"),"urls":(d or {}).get("total_urls_found")}
|
||||
def t_dashboard_health():
|
||||
c,b,l=run_curl([f"http://{H}:51300/health"],10); d=jparse(b)
|
||||
return c,l,(d or {}).get("status") in ("healthy","ok"),{"status":(d or {}).get("status"),"db":(d or {}).get("db")}
|
||||
def t_dashboard_monitoring():
|
||||
c,b,l=run_curl([f"http://{H}:51300/api/monitoring/services"],20); d=jparse(b)
|
||||
s=d if isinstance(d,list) else (d or {}).get("services",[])
|
||||
h=sum(1 for x in s if x.get("status")=="healthy")
|
||||
return c,l,len(s)>0 and h==len(s),{"healthy":f"{h}/{len(s)}"}
|
||||
def t_cloak():
|
||||
c,b,l=run_curl([f"http://{H}:8770/v1/search","-H","Content-Type: application/json","-d",json.dumps({"queries":["deepfake"],"engines":["ddg"],"max_results_per_engine":3})],45)
|
||||
d=jparse(b); r=(d or {}).get("results",[])
|
||||
return c,l,len(r)>0,{"results":len(r),"first":(r[0].get("title","")[:55] if r else None)}
|
||||
|
||||
TESTS = [
|
||||
("llm","LLM (Qwen3.5-35B-A3B)","POST /v1/chat/completions",t_llm),
|
||||
("llm_completions","LLM · completions","POST /v1/completions",t_llm_completions),
|
||||
("llm_info","LLM · info","GET /v1/info",t_llm_info),
|
||||
("embeddings","Embeddings (bge-m3)","POST /v1/embeddings",t_embeddings),
|
||||
("rerank","Rerank (bge-reranker-v2-m3)","POST /v1/rerank",t_rerank),
|
||||
("audio","Audio (Whisper large-v3-turbo)","POST /v1/audio/transcriptions",t_audio),
|
||||
("video","Video/BusterX (deepfake)","POST /analyze/video",t_video),
|
||||
("video_semantic","Video · semantic","POST /analyze/video/semantic",t_video_semantic),
|
||||
("extractors_metadata","Extractors · metadata","POST /v1/metadata",t_metadata),
|
||||
("extractors_ner","Extractors · NER (GLiNER)","POST /v1/ner",t_ner),
|
||||
("extractors_detect","Extractors · detect (YOLOv8)","POST /v1/detect",t_detect),
|
||||
("extractors_ocr","Extractors · OCR (LLM vision)","POST /v1/ocr",t_ocr),
|
||||
("extractors_sentiment","Extractors · sentiment (LLM)","POST /v1/sentiment",t_sentiment),
|
||||
("forensic","Forensic · module list","GET /api/forensic-modules",t_forensic),
|
||||
("forensic_evidence","Forensic · analiza reala","POST /api/forensic-evidence",t_forensic_evidence),
|
||||
("web","Web · search (SearXNG)","POST /v1/search",t_web),
|
||||
("web_fetch","Web · fetch","POST /v1/fetch",t_web_fetch),
|
||||
("web_gather","Web · gather (evidence)","POST /v1/gather",t_web_gather),
|
||||
("catalog","Catalog · status","GET /catalog/v1/status",t_catalog),
|
||||
("catalog_components","Catalog · components","GET /catalog/v1/components",t_catalog_components),
|
||||
("catalog_models","Catalog · models","GET /catalog/v1/models",t_catalog_models),
|
||||
("gateway","Gateway (auth+rutare)","GET /health + 401",t_gateway),
|
||||
("brain_atoms","Brain · analysis_atom","GET /v1/analysis_atom/stats/extended",t_brain_atoms),
|
||||
("brain_facts","Brain · fact_status","GET /v1/fact_status/list",t_brain_facts),
|
||||
("brain_vcache","Brain · verification_cache","GET /v1/verification_cache/list",t_brain_vcache),
|
||||
("brain_search","Brain · search (RAG)","POST /v1/search",t_brain_search),
|
||||
("brain_gather","Brain · gather (RAG)","POST /v1/gather",t_brain_gather),
|
||||
("dashboard","Dashboard · health","GET /health",t_dashboard_health),
|
||||
("dashboard_monitoring","Dashboard · monitoring 11/11","GET /api/monitoring/services",t_dashboard_monitoring),
|
||||
("cloak","Cloak · SERP scraper","POST /v1/search",t_cloak),
|
||||
]
|
||||
|
||||
def main() -> int:
|
||||
ensure_assets()
|
||||
ts = datetime.datetime.now().astimezone()
|
||||
stamp = ts.strftime("%Y%m%d_%H%M%S")
|
||||
results = []
|
||||
print(f"\n DiDi LOT 1 — test run @ {ts.isoformat()} (host {H})\n")
|
||||
for tid, name, ep, fn in TESTS:
|
||||
try:
|
||||
code, lat, ok, extra = fn()
|
||||
except Exception as e: # noqa: BLE001
|
||||
code, lat, ok, extra = 0, 0.0, False, {"error": str(e)}
|
||||
mark = "\033[32mPASS\033[0m" if ok else "\033[31mFAIL\033[0m"
|
||||
print(f" [{mark}] {name:34} {ep:38} HTTP {code:>3} {lat:>7.0f}ms {json.dumps(extra, ensure_ascii=False)[:80]}")
|
||||
results.append({"id":tid,"name":name,"endpoint":ep,"http_code":code,
|
||||
"ok":ok,"latency_ms":lat,"result":extra,"checked_at":ts.isoformat()})
|
||||
passed = sum(1 for r in results if r["ok"])
|
||||
report = {
|
||||
"generated_at": ts.isoformat(),
|
||||
"host": H,
|
||||
"summary": {"total": len(results), "passed": passed, "failed": len(results) - passed},
|
||||
"tests": results,
|
||||
}
|
||||
REPORTS.mkdir(exist_ok=True)
|
||||
out = REPORTS / f"test_report_{stamp}.json"
|
||||
out.write_text(json.dumps(report, indent=2, ensure_ascii=False))
|
||||
latest = REPORTS / "latest.json"
|
||||
latest.write_text(json.dumps(report, indent=2, ensure_ascii=False))
|
||||
print(f"\n === {passed}/{len(results)} PASS ===")
|
||||
print(f" Raport JSON: {out}")
|
||||
print(f" (si copie: {latest})\n")
|
||||
return 0 if passed == len(results) else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue