Files
ai_site/scripts/e2e_flow.py
2026-07-31 10:19:22 +08:00

230 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""端到端AI 生成 → publish → capsule → agent 解密列表 → import + 新能力冒烟"""
from __future__ import annotations
import sys
import time
from pathlib import Path
import httpx
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "agent_sdk"))
from client import AgentClient, decrypt_capsule # noqa: E402
PLATFORM = "http://127.0.0.1:8180"
AI = "http://127.0.0.1:8180/ai"
def main() -> None:
excel = ROOT / "ai-service" / "sample_inventory.xlsx"
if not excel.exists():
import subprocess
subprocess.check_call([sys.executable, str(ROOT / "ai-service" / "make_sample_excel.py")])
with httpx.Client(timeout=60.0) as http:
# 网关无 token 应 401
denied = http.get(f"{PLATFORM}/api/v1/audit/logs")
assert denied.status_code == 401, denied.text
print("gateway jwt deny ok")
tok = http.post(
f"{PLATFORM}/api/v1/auth/login",
json={"username": "demo", "password": "demo123"},
)
tok.raise_for_status()
auth = tok.json()
token, agent_key = auth["access_token"], auth["agent_key"]
assert auth.get("role") in ("owner", "editor", "viewer"), auth
print("login ok", auth.get("username"), "role", auth.get("role"), "tenant", auth.get("tenant_id"))
headers = {"Authorization": f"Bearer {token}"}
# 对象存储
up = http.post(
f"{PLATFORM}/api/v1/storage",
headers=headers,
files={"file": ("note.txt", b"hello-storage", "text/plain")},
)
up.raise_for_status()
upj = up.json()
assert upj.get("key") and upj.get("url"), upj
print("storage upload", upj["key"])
# 多模态(无 Key 时启发式)
png = (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
b"\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00"
b"\x00\x01\x01\x00\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82"
)
files = {
"excel": (
"sample_inventory.xlsx",
excel.read_bytes(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
),
"images": ("ui_list.png", png, "image/png"),
}
data = {"prompt": "根据库存表和截图生成可筛选库存列表", "storage_mode": "schema_per_app"}
gen = http.post(f"{AI}/api/v1/apps/generate", data=data, files=files)
gen.raise_for_status()
gj = gen.json()
draft = gj["draft"]
warns = " ".join(gj.get("warnings") or [])
vision_ok = (
"看图" in warns
or "截图" in warns
or bool(draft.get("meta", {}).get("source", {}).get("vision_summary"))
)
assert vision_ok, warns
slug = draft["meta"]["slug"]
resource = draft["apis"]["resources"][0]["path"].lstrip("/")
print("generate", slug, resource, "confidence", gj["confidence"])
# 唯一 slug避免重复发布
slug = f"{slug}_{int(time.time())}"
draft["meta"]["slug"] = slug
draft["apis"]["base_path"] = f"/api/v1/apps/{slug}"
print("slug", slug)
pub = http.post(
f"{PLATFORM}/api/v1/apps/{slug}/publish",
headers=headers,
json={"blueprint": draft},
)
if pub.status_code >= 400:
print(pub.text)
pub.raise_for_status()
print("published", pub.json()["schema_name"], "memory_mode", pub.json()["memory_mode"])
# 审计
audit = http.get(f"{PLATFORM}/api/v1/audit/logs", headers=headers, params={"page": 1})
audit.raise_for_status()
actions = [x.get("action") for x in audit.json().get("items") or []]
assert any(a.startswith("publish.") for a in actions) or any(
a == "storage.upload" for a in actions
), actions
print("audit actions sample", actions[:5])
cap = http.get(f"{PLATFORM}/api/v1/apps/{slug}/agent-capsule", headers=headers)
cap.raise_for_status()
capsule = cap.json()["capsule"]
assert capsule.startswith("AJZ1."), capsule
desc = decrypt_capsule(agent_key, capsule)
assert "resources" in desc and desc["app_slug"] == slug
print("capsule decrypted resources:", [r["name"] for r in desc["resources"]])
assert "/api/v1/apps/" not in capsule
client = AgentClient(token, agent_key, capsule)
created = client.create(
resource,
{
"sku": "E2E-1",
"product_name": "测试商品",
"warehouse": "华东仓",
"qty": 3,
"unit_price": 9.9,
"status": "在售",
},
)
print("agent create id", created.get("id"))
listed = client.list(resource, warehouse="华东仓")
print("agent list total", listed.get("total"))
csv_body = "sku,product_name,warehouse,qty,unit_price,status\nE2E-2,导入商品,华南仓,5,19.9,在售\n"
try:
from openpyxl import Workbook
import io
wb = Workbook()
ws = wb.active
ws.append(["sku", "product_name", "warehouse", "qty", "unit_price", "status"])
ws.append(["E2E-2", "导入商品", "华南仓", 5, 19.9, "在售"])
buf = io.BytesIO()
wb.save(buf)
files = {
"file": (
"imp.xlsx",
buf.getvalue(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
}
except Exception:
files = {"file": ("imp.csv", csv_body.encode("utf-8"), "text/csv")}
imp = http.post(
f"{PLATFORM}/api/v1/apps/{slug}/{resource}/import",
headers=headers,
files=files,
)
imp.raise_for_status()
print("import", imp.json())
ag = http.get(
f"{PLATFORM}/api/v1/apps/{slug}/{resource}/aggregate",
headers=headers,
params={"group_by": "warehouse", "sum": "qty"},
)
ag.raise_for_status()
print("aggregate", ag.json())
# database_per_app 冒烟
data2 = {"prompt": "独立库库存台账", "storage_mode": "database_per_app"}
gen2 = http.post(
f"{AI}/api/v1/apps/generate",
data=data2,
files={
"excel": (
"sample_inventory.xlsx",
excel.read_bytes(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
)
gen2.raise_for_status()
draft2 = gen2.json()["draft"]
assert draft2["storage"]["mode"] == "database_per_app"
slug2 = f"dbapp_{int(time.time())}"
draft2["meta"]["slug"] = slug2
draft2["apis"]["base_path"] = f"/api/v1/apps/{slug2}"
pub2 = http.post(
f"{PLATFORM}/api/v1/apps/{slug2}/publish",
headers=headers,
json={"blueprint": draft2},
)
if pub2.status_code >= 400:
print("database_per_app publish:", pub2.text)
pub2.raise_for_status()
pj2 = pub2.json()
assert pj2.get("database_name"), pj2
print("database_per_app", pj2["database_name"], "schema", pj2["schema_name"])
# viewer token 不应能 publish
issued = http.post(
f"{PLATFORM}/api/v1/auth/token",
json={
"tenant_id": auth["tenant_id"],
"user_id": auth["user_id"],
"role": "viewer",
"secret": "dev-only-change-me",
},
)
if issued.status_code == 200:
vtok = issued.json()["access_token"]
forbid = http.post(
f"{PLATFORM}/api/v1/apps/{slug}/publish",
headers={"Authorization": f"Bearer {vtok}"},
json={"blueprint": draft},
)
assert forbid.status_code == 403, forbid.text
print("rbac viewer deny publish ok")
else:
print("skip rbac token issue:", issued.status_code, issued.text[:120])
print("E2E OK")
if __name__ == "__main__":
main()