Spaces:
Running
Running
File size: 7,535 Bytes
10ff0f9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | #!/usr/bin/env python3
"""Focused bugcheck for Customer Agent domain + package consistency."""
from __future__ import annotations
import importlib.util
import sqlite3
import sys
import tempfile
from pathlib import Path
def load_ca():
path = Path(__file__).resolve().parents[2] / "scripts" / "customer_agent.py"
spec = importlib.util.spec_from_file_location("ca_bugcheck", path)
assert spec and spec.loader
mod = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = mod
spec.loader.exec_module(mod)
return mod
def main() -> int:
ca = load_ca()
failures: list[str] = []
def check(cond: bool, msg: str) -> None:
if not cond:
failures.append(msg)
print(f"FAIL {msg}")
else:
print(f"PASS {msg}")
# Brand validation
check(ca.normalize_brand_color("") == "", "empty brand color ok")
check(ca.normalize_brand_color("#2e4a86") == "#2e4a86", "hex color ok")
try:
ca.normalize_brand_color("navy")
check(False, "invalid color rejected")
except ValueError:
check(True, "invalid color rejected")
# Origins
check(ca.origin_allowed(["*"], "https://a.com"), "star allows")
check(not ca.origin_allowed(["https://a.com"], "https://b.com"), "deny foreign")
check(ca.origin_allowed(["https://Shop.Example.com"], "https://shop.example.com"), "origin case normalize")
# Retrieval
chunks = [
{"title": "Shipping", "text": "Free shipping over EUR 60 across the EU in 3-5 days."},
{"title": "Hours", "text": "Open Monday to Saturday 9-18."},
]
hits = ca.retrieve_chunks(chunks, "free shipping to Germany")
check(bool(hits) and "ship" in hits[0]["title"].casefold(), "shipping ranks first")
check(ca.knowledge_is_hit(hits), "shipping is knowledge hit")
miss = ca.retrieve_chunks(chunks, "wifi password for cafe guests")
check(not ca.knowledge_is_hit(miss) or (miss and miss[0].get("score", 0) < 0.35), "wifi is not a solid hit")
# Guardrails
check(ca.looks_hard_refuse("diagnose my illness"), "hard refuse medical")
check(ca.looks_sensitive("I want to speak to a human"), "sensitive human")
ans = ca.hard_refuse_answer("help@x.com")
check("cannot help" in ans.casefold() or "cannot" in ans.casefold(), "hard refuse text")
# Schema + create roundtrip
with tempfile.TemporaryDirectory() as tmp:
db = Path(tmp) / "t.sqlite3"
conn = sqlite3.connect(db)
conn.row_factory = sqlite3.Row
conn.execute("CREATE TABLE registered_users (customer_id TEXT PRIMARY KEY, email TEXT NOT NULL)")
conn.execute("INSERT INTO registered_users(customer_id, email) VALUES (?, ?)", ("c1", "a@example.com"))
ca.ensure_customer_agent_schema(conn)
agent, key = ca.create_agent(
conn,
customer_id="c1",
name="Shop",
escalate_email="help@example.com",
allowed_origins=["*"],
brand_position="left",
brand_primary_color="#112233",
)
check(key.startswith("pk_live_"), "site key prefix")
check(agent.brand_position == "left", "brand position stored")
loaded = ca.get_agent_by_site_key(conn, key)
check(loaded is not None and loaded.name == "Shop", "lookup by site key")
try:
ca.add_knowledge_doc(conn, agent_id=agent.id, title="x", body=" ")
check(False, "empty knowledge rejected")
except ValueError:
check(True, "empty knowledge rejected")
doc = ca.add_knowledge_doc(conn, agent_id=agent.id, title="FAQ", body="Open 9-5 Monday to Friday.")
check(doc["chars"] > 0, "knowledge added")
try:
ca.update_agent(conn, customer_id="c1", agent_id=agent.id, escalate_webhook_url="http://x.com")
check(False, "http webhook rejected")
except ValueError:
check(True, "http webhook rejected")
# Use a public host that resolves; webhook delivery may still fail HTTP-wise.
ca.update_agent(
conn,
customer_id="c1",
agent_id=agent.id,
escalate_webhook_url="https://example.com/hooks/synderesis",
allowed_origins=["https://shop.example.com"],
)
updated = ca.get_agent_for_customer(conn, "c1", agent.id)
assert updated is not None
ready = ca.readiness_checklist(updated, knowledge_count=1)
check(ready["ready_for_production"] is True, "ready for production when locked + FAQ + email")
# URL validation
try:
ca.validate_public_https_url("http://example.com")
check(False, "http url rejected")
except ValueError:
check(True, "http url rejected")
try:
ca.validate_public_https_url("https://127.0.0.1/x")
check(False, "loopback url rejected")
except ValueError:
check(True, "loopback url rejected")
# Escalation + delivery skip without SMTP
esc = ca.create_escalation(
conn,
agent_id=agent.id,
visitor_name="A",
visitor_email="a@b.com",
message="Help",
transcript=[{"role": "user", "content": "hi"}],
)
delivery = ca.deliver_escalation_notifications(
agent=updated,
escalation={**esc, "visitor_name": "A", "visitor_email": "a@b.com", "message": "Help", "transcript": []},
mail=ca.EscalationMailConfig(enabled=False),
webhook_signing_secret="secret",
)
check(delivery["email_status"] == "skipped", "email skipped when smtp off")
# webhook to example.com may fail DNS/HTTP - status should be sent or failed not empty
check(delivery["webhook_status"] in ("sent", "failed:http_404", "failed:http_403") or delivery["webhook_status"].startswith("failed:"), "webhook attempted")
# Chat event + coverage
ca.log_chat_event(
conn,
agent_id=agent.id,
question="wifi password?",
answer="I don't know",
knowledge_hit=False,
escalate_recommended=True,
hard_refuse=False,
retrieved_count=0,
top_score=0.0,
)
cov = ca.coverage_report(conn, agent.id)
check(cov["total_chats"] == 1, "coverage counts chats")
check(len(cov["unknown_questions"]) >= 1, "unknown questions listed")
# Export
bundle = ca.export_agent_bundle(conn, updated)
check(bundle["object"].endswith("export"), "export object")
check("site_key" not in bundle["agent"], "export omits raw site key")
conn.close()
# Package consistency
root = Path(__file__).resolve().parents[1]
version = (root / "VERSION").read_text(encoding="utf-8").strip()
check(bool(version) and version[0].isdigit(), f"VERSION present (got {version})")
php = (root / "integrations/wordpress/synderesis-customer-agent/synderesis-customer-agent.php").read_text(encoding="utf-8")
check("1.4.0" in php, "WP plugin version present")
check("synderesis_ca_test_connection" in php, "WP has test connection")
check((root / "integrations/wordpress/synderesis-customer-agent/uninstall.php").is_file(), "uninstall.php exists")
if failures:
print(f"\n{len(failures)} failure(s)")
return 1
print("\nBUGCHECK PASSED")
return 0
if __name__ == "__main__":
raise SystemExit(main())
|