#!/usr/bin/env python3 import argparse import json import os import sys import urllib.error import urllib.request TINY_PNG_DATA_URL = ( "data:image/png;base64," "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8Xw8AAoMBgQhP2YkAAAAASUVORK5CYII=" ) def http_json(method: str, url: str, payload=None, headers=None): data = None req_headers = dict(headers or {}) if payload is not None: data = json.dumps(payload).encode("utf-8") req_headers.setdefault("Content-Type", "application/json") req = urllib.request.Request(url, data=data, headers=req_headers, method=method) try: with urllib.request.urlopen(req, timeout=60) as resp: body = resp.read().decode("utf-8", errors="replace") return resp.status, json.loads(body) if body else {} except urllib.error.HTTPError as e: body = e.read().decode("utf-8", errors="replace") try: parsed = json.loads(body) if body else {} except Exception: parsed = {"raw": body} return e.code, parsed def check(cond: bool, label: str, details: str = "") -> bool: prefix = "PASS" if cond else "FAIL" tail = f" :: {details}" if details else "" print(f"[{prefix}] {label}{tail}") return cond def main() -> int: parser = argparse.ArgumentParser(description="AgroMatrix regression smoke checks") parser.add_argument("--base-url", default="http://127.0.0.1:9102") parser.add_argument("--agent-id", default="agromatrix") parser.add_argument("--chat-id", default="smoke-agromatrix") parser.add_argument("--user-id", default="smoke-user") parser.add_argument("--skip-review-404", action="store_true") parser.add_argument( "--mentor-token", default=( os.getenv("AGROMATRIX_REVIEW_BEARER_TOKEN") or (os.getenv("AGROMATRIX_REVIEW_BEARER_TOKENS", "").split(",")[0].strip()) or "" ), ) args = parser.parse_args() ok_all = True status, health = http_json("GET", f"{args.base_url}/health") ok_all &= check(status == 200 and health.get("status") == "ok", "health", str(health)) numeric_payload = { "prompt": "напиши мені яка сума була витрачена на добрива", "metadata": { "channel": "telegram", "chat_id": args.chat_id, "user_id": args.user_id, "user_name": "smoke", }, } status, infer_num = http_json("POST", f"{args.base_url}/v1/agents/{args.agent_id}/infer", numeric_payload) resp_text = str(infer_num.get("response") or "") numeric_guard = ( "Не можу підтвердити точне число" in resp_text or "value + unit + source" in resp_text or "source(sheet,row)" in resp_text ) ok_all &= check(status == 200 and numeric_guard, "numeric_contract_guard", resp_text[:180]) plant_payload = { "prompt": "Що це за рослина на фото?", "images": [TINY_PNG_DATA_URL], "metadata": { "channel": "telegram", "chat_id": args.chat_id, "user_id": args.user_id, "user_name": "smoke", }, } status, infer_plant = http_json("POST", f"{args.base_url}/v1/agents/{args.agent_id}/infer", plant_payload) plant_text = str(infer_plant.get("response") or "") plant_ok = ( "Не впевнений" in plant_text or "Надішли" in plant_text or "канд" in plant_text.lower() ) ok_all &= check(status == 200 and plant_ok, "deterministic_plant_response", plant_text[:180]) status, pending = http_json("GET", f"{args.base_url}/v1/agromatrix/shared-memory/pending") pending_shape = isinstance(pending, dict) and isinstance(pending.get("items"), list) ok_all &= check(status == 200 and pending_shape, "shared_pending_endpoint", f"total={pending.get('total')}") if not args.skip_review_404: req_headers = {} if args.mentor_token: req_headers["Authorization"] = f"Bearer {args.mentor_token}" status, review = http_json( "POST", f"{args.base_url}/v1/agromatrix/shared-memory/review", { "point_id": "11111111-1111-1111-1111-111111111111", "approve": False, "reviewer": "smoke", "note": "nonexistent id check", }, headers=req_headers, ) expected = 404 if args.mentor_token else 401 ok_all &= check(status == expected, "shared_review_not_found_contract", str(review)) return 0 if ok_all else 1 if __name__ == "__main__": sys.exit(main())