162 lines
6.7 KiB
Python
162 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Read-only live acceptance for audio search and transcript APIs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
from zhconv import convert
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--base-url", required=True)
|
|
auth = parser.add_mutually_exclusive_group(required=True)
|
|
auth.add_argument("--password-stdin", action="store_true")
|
|
auth.add_argument("--token-file", type=Path)
|
|
args = parser.parse_args()
|
|
password = sys.stdin.readline().rstrip("\r\n") if args.password_stdin else ""
|
|
headers = {}
|
|
if args.token_file is not None:
|
|
headers["Authorization"] = f"Bearer {args.token_file.read_text().strip()}"
|
|
client = httpx.Client(
|
|
base_url=args.base_url.rstrip("/"), headers=headers, timeout=httpx.Timeout(30, connect=10)
|
|
)
|
|
try:
|
|
if args.password_stdin:
|
|
login = client.post(
|
|
"/api/v1/auth/login", json={"password": password, "remember_device": False}
|
|
)
|
|
login.raise_for_status()
|
|
csrf = str(login.json().get("csrf_token") or "")
|
|
if not csrf:
|
|
raise AssertionError("login response omitted CSRF token")
|
|
client.headers["X-CSRF-Token"] = csrf
|
|
password = ""
|
|
|
|
status = client.get("/api/v1/status")
|
|
status.raise_for_status()
|
|
if status.json().get("version") != "0.5.45":
|
|
raise AssertionError(f"unexpected version: {status.json().get('version')}")
|
|
|
|
coverage_response = client.get("/api/v1/search/coverage")
|
|
coverage_response.raise_for_status()
|
|
coverage = coverage_response.json()
|
|
required = {
|
|
"total",
|
|
"indexed",
|
|
"searchable",
|
|
"empty",
|
|
"queued",
|
|
"running",
|
|
"failed",
|
|
"percent",
|
|
"current_model_percent",
|
|
}
|
|
if not required <= coverage.keys():
|
|
raise AssertionError(f"coverage fields missing: {sorted(required - coverage.keys())}")
|
|
|
|
videos_response = client.get("/api/v1/videos?limit=200")
|
|
videos_response.raise_for_status()
|
|
videos = videos_response.json()
|
|
selected = None
|
|
transcript = None
|
|
selected_item = None
|
|
fallback = None
|
|
for video in videos:
|
|
response = client.get(f"/api/v1/videos/{video['id']}/transcript?page=1&page_size=30")
|
|
response.raise_for_status()
|
|
candidate = response.json()
|
|
if candidate.get("items"):
|
|
fallback = fallback or (video, candidate, candidate["items"][0])
|
|
chinese_item = next(
|
|
(
|
|
item
|
|
for item in candidate["items"]
|
|
if re.search(r"[\u3400-\u9fff]{2,}", str(item.get("raw_text") or ""))
|
|
),
|
|
None,
|
|
)
|
|
if chinese_item:
|
|
selected, transcript, selected_item = video, candidate, chinese_item
|
|
break
|
|
if selected is None and fallback:
|
|
selected, transcript, selected_item = fallback
|
|
|
|
checks: dict[str, object] = {
|
|
"version": status.json()["version"],
|
|
"coverage": {key: coverage[key] for key in sorted(required)},
|
|
"transcript_video_found": bool(selected),
|
|
}
|
|
resources_response = client.get("/api/v1/system/resources")
|
|
resources_response.raise_for_status()
|
|
resources = resources_response.json()
|
|
diagnostics_response = client.get("/api/v1/system/diagnostics")
|
|
diagnostics_response.raise_for_status()
|
|
diagnostics = diagnostics_response.json()
|
|
models_response = client.get("/api/v1/models")
|
|
models_response.raise_for_status()
|
|
models = models_response.json()
|
|
checks["runtime"] = {
|
|
"cpu_percent": resources.get("cpu_percent"),
|
|
"memory_available_bytes": resources.get("memory_available_bytes"),
|
|
"io": resources.get("io"),
|
|
"ai_lane": (resources.get("lanes") or {}).get("ai"),
|
|
"database": diagnostics.get("database"),
|
|
"event_loop_lag_ms": diagnostics.get("event_loop_lag_ms"),
|
|
"request_p95_ms": diagnostics.get("request_p95_ms"),
|
|
"audio_accelerator": ((models.get("accelerator") or {}).get("components") or {}).get("audio"),
|
|
"audio_health": (models.get("component_health") or {}).get("audio"),
|
|
}
|
|
if selected and transcript:
|
|
first = selected_item or transcript["items"][0]
|
|
cjk_runs = re.findall(r"[\u3400-\u9fff]{2,}", str(first.get("raw_text") or ""))
|
|
if cjk_runs:
|
|
query = cjk_runs[0][: min(6, len(cjk_runs[0]))]
|
|
variants = list(dict.fromkeys((query, convert(query, "zh-cn"), convert(query, "zh-tw"))))
|
|
results = []
|
|
for variant in variants:
|
|
audio = client.post(
|
|
"/api/v1/search",
|
|
json={"text": variant, "recognition_types": ["audio"], "limit": 50},
|
|
)
|
|
audio.raise_for_status()
|
|
audio_ids = {item.get("video_id") for item in audio.json().get("items", [])}
|
|
combined = client.post("/api/v1/search", json={"text": variant, "limit": 50})
|
|
combined.raise_for_status()
|
|
combined_ids = {item.get("video_id") for item in combined.json().get("items", [])}
|
|
results.append(
|
|
{
|
|
"variant_changed": variant != query,
|
|
"audio_match": selected["id"] in audio_ids,
|
|
"combined_match": selected["id"] in combined_ids,
|
|
}
|
|
)
|
|
if not all(item["audio_match"] and item["combined_match"] for item in results):
|
|
raise AssertionError(f"live Chinese audio search mismatch: {results}")
|
|
checks["chinese_query_length"] = len(query)
|
|
checks["variant_checks"] = results
|
|
checks["transcript"] = {
|
|
"status": transcript.get("status"),
|
|
"total": transcript.get("total"),
|
|
"pages": transcript.get("pages"),
|
|
"items_returned": len(transcript.get("items", [])),
|
|
}
|
|
print(json.dumps(checks, ensure_ascii=False, indent=2), flush=True)
|
|
finally:
|
|
password = ""
|
|
try:
|
|
if args.password_stdin:
|
|
client.post("/api/v1/auth/logout")
|
|
finally:
|
|
client.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|