185 lines
5.8 KiB
Python
185 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""One-command smoke check for ordinary AI-app / skill invocation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _run(args: list[str], *, timeout: int = 120, env: dict[str, str] | None = None) -> dict[str, Any]:
|
|
completed = subprocess.run(
|
|
args,
|
|
cwd=ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=timeout,
|
|
check=False,
|
|
env=env,
|
|
)
|
|
return {
|
|
"command": " ".join(args),
|
|
"returncode": completed.returncode,
|
|
"stdout": completed.stdout,
|
|
"stderr": completed.stderr,
|
|
}
|
|
|
|
|
|
def _entrypoint_env() -> dict[str, str]:
|
|
env = os.environ.copy()
|
|
env.update(
|
|
{
|
|
"JYOTISH_SKIP_LOCAL_ENV": "1",
|
|
"VEDASTRO_API_ENDPOINT": "",
|
|
"VEDASTRO_ENABLE_NETWORK": "",
|
|
"VEDASTRO_TIMEOUT_SECONDS": "",
|
|
}
|
|
)
|
|
return env
|
|
|
|
|
|
def _engine_summary(report: dict[str, Any]) -> dict[str, Any]:
|
|
summary: dict[str, Any] = {}
|
|
engines = report.get("engines") if isinstance(report.get("engines"), dict) else {}
|
|
for name, details in engines.items():
|
|
if not isinstance(details, dict):
|
|
continue
|
|
summary[name] = {
|
|
"status": details.get("status"),
|
|
"readiness_blockers": details.get("readiness_blockers")
|
|
or ([details["missing_dependency"]] if details.get("missing_dependency") else []),
|
|
"license_boundary": details.get("license_boundary"),
|
|
}
|
|
return summary
|
|
|
|
|
|
def _runtime_summary(report: dict[str, Any]) -> dict[str, Any]:
|
|
runtime = report.get("runtime_mode") if isinstance(report.get("runtime_mode"), dict) else {}
|
|
return {
|
|
"mode": runtime.get("mode"),
|
|
"official_ready": runtime.get("official_ready"),
|
|
"expected_fallback_status": runtime.get("expected_fallback_status"),
|
|
}
|
|
|
|
|
|
def _strict_summary(report: dict[str, Any]) -> dict[str, Any]:
|
|
strict = report.get("strict_workflow") if isinstance(report.get("strict_workflow"), dict) else {}
|
|
return {
|
|
"triggered": strict.get("triggered"),
|
|
"primary_route": strict.get("primary_route"),
|
|
"routes_available": strict.get("routes_available"),
|
|
"source": strict.get("source"),
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
pytest_check = _run(
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"pytest",
|
|
"-q",
|
|
"tests/test_user_invocation_acceptance_contract.py",
|
|
"-k",
|
|
"not one_command_user_invocation_acceptance_check",
|
|
],
|
|
timeout=180,
|
|
)
|
|
entrypoint = _run(
|
|
[
|
|
sys.executable,
|
|
"scripts/vedastro_user_entrypoint.py",
|
|
"--year",
|
|
"2000",
|
|
"--month",
|
|
"1",
|
|
"--day",
|
|
"1",
|
|
"--hour",
|
|
"12",
|
|
"--minute",
|
|
"0",
|
|
"--lat",
|
|
"0.0",
|
|
"--lon",
|
|
"0.0",
|
|
"--tz",
|
|
"0",
|
|
"--question",
|
|
"请先生成 guided_topics 并推荐我最值得看的问题",
|
|
"--themes",
|
|
"career,marriage,wealth",
|
|
"--reference-date",
|
|
"2026-07-06",
|
|
"--format",
|
|
"json",
|
|
],
|
|
env=_entrypoint_env(),
|
|
)
|
|
adapters = _run([sys.executable, "scripts/diagnose_external_engine_adapters.py", "--json"], timeout=120)
|
|
|
|
errors: list[str] = []
|
|
entrypoint_report: dict[str, Any] = {}
|
|
adapter_report: dict[str, Any] = {}
|
|
if pytest_check["returncode"] != 0:
|
|
errors.append("user_invocation_pytest_failed")
|
|
if entrypoint["returncode"] != 0:
|
|
errors.append("user_entrypoint_failed")
|
|
else:
|
|
try:
|
|
entrypoint_report = json.loads(entrypoint["stdout"])
|
|
except json.JSONDecodeError:
|
|
errors.append("user_entrypoint_invalid_json")
|
|
if adapters["returncode"] != 0:
|
|
errors.append("external_adapter_diagnostic_failed")
|
|
else:
|
|
try:
|
|
adapter_report = json.loads(adapters["stdout"])
|
|
except json.JSONDecodeError:
|
|
errors.append("external_adapter_diagnostic_invalid_json")
|
|
|
|
required_entrypoint_paths = [
|
|
("strict_workflow", "triggered"),
|
|
("strict_workflow", "routes_available"),
|
|
("runtime_mode", "expected_fallback_status"),
|
|
("honesty_boundary", "all_641_methods_executed"),
|
|
]
|
|
for path in required_entrypoint_paths:
|
|
cursor: Any = entrypoint_report
|
|
for key in path:
|
|
cursor = cursor.get(key) if isinstance(cursor, dict) else None
|
|
if cursor in (None, [], ""):
|
|
errors.append(f"missing_entrypoint_{'_'.join(path)}")
|
|
|
|
usable_adapter_statuses = {"pass", "partial", "complete"}
|
|
if adapter_report.get("status") not in usable_adapter_statuses:
|
|
errors.append("external_adapter_status_unusable")
|
|
|
|
result = {
|
|
"scope": "user_invocation_acceptance_check",
|
|
"status": "pass" if not errors else "fail",
|
|
"errors": errors,
|
|
"checks": {
|
|
"user_invocation_tests": pytest_check["returncode"] == 0,
|
|
"guided_topics_entrypoint": entrypoint["returncode"] == 0 and not any(e.startswith("missing_entrypoint_") for e in errors),
|
|
"external_adapter_diagnostics": adapter_report.get("status") in usable_adapter_statuses,
|
|
},
|
|
"entrypoint_runtime_mode": _runtime_summary(entrypoint_report),
|
|
"entrypoint_strict_workflow": _strict_summary(entrypoint_report),
|
|
"external_adapter_status": adapter_report.get("status"),
|
|
"external_engines": _engine_summary(adapter_report),
|
|
}
|
|
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
|
return 0 if not errors else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|