agenteval-toolkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. agenteval/__init__.py +5 -0
  2. agenteval/_starter/certification/policy.yaml +15 -0
  3. agenteval/_starter/certification/systems/northwind-cs-staging.yaml +16 -0
  4. agenteval/_starter/certification/systems/northwind-cs.yaml +17 -0
  5. agenteval/_starter/examples/agents/demo-good.yaml +41 -0
  6. agenteval/_starter/examples/agents/demo-weak.yaml +34 -0
  7. agenteval/_starter/examples/agents/openai-compatible.example.yaml +15 -0
  8. agenteval/_starter/examples/agents/webhook.example.yaml +21 -0
  9. agenteval/_starter/examples/attestations-certification.yaml +16 -0
  10. agenteval/_starter/examples/attestations.yaml +19 -0
  11. agenteval/_starter/examples/snapshots/sample-cluster.json +323 -0
  12. agenteval/_starter/exams/customer-service/cs-ecommerce-v1.yaml +222 -0
  13. agenteval/_starter/rules/industry-pci-dss/i18n/zh.yaml +27 -0
  14. agenteval/_starter/rules/industry-pci-dss/pack.yaml +9 -0
  15. agenteval/_starter/rules/industry-pci-dss/rules.yaml +83 -0
  16. agenteval/_starter/rules/us-ca/i18n/zh.yaml +21 -0
  17. agenteval/_starter/rules/us-ca/pack.yaml +9 -0
  18. agenteval/_starter/rules/us-ca/rules.yaml +72 -0
  19. agenteval/_starter/rules/us-federal/governance.yaml +216 -0
  20. agenteval/_starter/rules/us-federal/i18n/zh.yaml +107 -0
  21. agenteval/_starter/rules/us-federal/pack.yaml +8 -0
  22. agenteval/_starter/rules/us-federal/security.yaml +164 -0
  23. agenteval/certification/__init__.py +16 -0
  24. agenteval/certification/assess.py +137 -0
  25. agenteval/certification/issuer.py +165 -0
  26. agenteval/certification/models.py +113 -0
  27. agenteval/certification/render.py +100 -0
  28. agenteval/certification/signing.py +86 -0
  29. agenteval/cli.py +533 -0
  30. agenteval/collectors/__init__.py +30 -0
  31. agenteval/collectors/kubernetes.py +70 -0
  32. agenteval/collectors/normalize.py +247 -0
  33. agenteval/config.py +49 -0
  34. agenteval/engine/__init__.py +12 -0
  35. agenteval/engine/conditions.py +109 -0
  36. agenteval/engine/evaluator.py +133 -0
  37. agenteval/engine/loader.py +131 -0
  38. agenteval/evaluation/__init__.py +17 -0
  39. agenteval/evaluation/adapters.py +276 -0
  40. agenteval/evaluation/assertions.py +73 -0
  41. agenteval/evaluation/judges.py +214 -0
  42. agenteval/evaluation/messages.py +56 -0
  43. agenteval/evaluation/models.py +174 -0
  44. agenteval/evaluation/papers.py +103 -0
  45. agenteval/evaluation/report.py +80 -0
  46. agenteval/evaluation/runner.py +154 -0
  47. agenteval/i18n.py +118 -0
  48. agenteval/models.py +136 -0
  49. agenteval/reports/__init__.py +86 -0
  50. agenteval/reports/templates/certificate.html.j2 +178 -0
  51. agenteval/reports/templates/eval_report.html.j2 +191 -0
  52. agenteval/reports/templates/report.html.j2 +75 -0
  53. agenteval/schema.py +66 -0
  54. agenteval/schemas/attestations.schema.json +76 -0
  55. agenteval/schemas/exam.schema.json +85 -0
  56. agenteval/schemas/pack.schema.json +48 -0
  57. agenteval/schemas/policy.schema.json +67 -0
  58. agenteval/schemas/rule.schema.json +298 -0
  59. agenteval/schemas/system.schema.json +76 -0
  60. agenteval/schemas/translations.schema.json +32 -0
  61. agenteval_toolkit-0.1.0.dist-info/METADATA +307 -0
  62. agenteval_toolkit-0.1.0.dist-info/RECORD +67 -0
  63. agenteval_toolkit-0.1.0.dist-info/WHEEL +5 -0
  64. agenteval_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
  65. agenteval_toolkit-0.1.0.dist-info/licenses/LICENSE +202 -0
  66. agenteval_toolkit-0.1.0.dist-info/licenses/NOTICE +19 -0
  67. agenteval_toolkit-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,113 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 The AgentEval Authors
3
+ """Certification: AI system registry, issuance policy, gate decisions and certificate data structures."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from datetime import datetime
8
+ from typing import Any, Literal
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ class AISystem(BaseModel):
14
+ """An AI system to certify: binds a scanned asset to an evaluated agent."""
15
+
16
+ id: str
17
+ name: str
18
+ name_zh: str = ""
19
+ organization: str
20
+ industry: str = ""
21
+ description: str = ""
22
+ description_zh: str = ""
23
+ asset_id: str # scanned asset ID, e.g. deployment/ml-prod/llama-chat
24
+ model: str = ""
25
+ agent: str = "" # config file name in the agents directory (used by the API)
26
+ agent_name: str # agent name in evaluation records
27
+ exam: str # exam paper ID
28
+ rule_packs: list[str] # rule pack IDs that must be covered
29
+ contact: str = ""
30
+
31
+
32
+ class CertPolicy(BaseModel):
33
+ min_security_score: float = 90.0
34
+ min_quality_score: float = 90.0
35
+ block_on_severities: list[str] = Field(default_factory=lambda: ["critical", "high"])
36
+ require_no_pending_attestations: bool = True
37
+ require_zero_critical_eval_failures: bool = True
38
+ require_no_eval_errors: bool = True
39
+ require_llm_judge: bool = True
40
+ allow_demo_certificates: bool = True
41
+ max_evidence_age_days: int = 30
42
+ validity_months: int = 6
43
+ auto_revoke: bool = True
44
+ issuer_name: str = "AgentEval Assurance Service"
45
+ issuer_name_zh: str = "AgentEval 认证服务"
46
+
47
+
48
+ class Gate(BaseModel):
49
+ code: str
50
+ ok: bool
51
+ kind: Literal["blocking", "freshness", "demo"] = "blocking"
52
+ params: dict[str, Any] = Field(default_factory=dict)
53
+
54
+
55
+ class PackResult(BaseModel):
56
+ id: str
57
+ name: str
58
+ name_zh: str = ""
59
+ tier: str
60
+ jurisdiction: str
61
+ passed: int = 0
62
+ failed: int = 0
63
+ manual: int = 0
64
+ not_applicable: int = 0
65
+ score: float | None = None
66
+ status: Literal["passed", "failed", "not_applicable", "not_assessed"] = "not_assessed"
67
+
68
+
69
+ class SecurityAssessment(BaseModel):
70
+ scan_id: str | None = None
71
+ scanned_at: datetime | None = None
72
+ rules_version: str | None = None
73
+ score: float | None = None
74
+ passed: int = 0
75
+ failed: int = 0
76
+ manual: int = 0
77
+ blocking_findings: list[str] = Field(default_factory=list) # "RULE@asset"
78
+ pending: list[str] = Field(default_factory=list)
79
+ packs: list[PackResult] = Field(default_factory=list)
80
+
81
+
82
+ class QualityAssessment(BaseModel):
83
+ run_id: str | None = None
84
+ evaluated_at: datetime | None = None
85
+ exam: str | None = None
86
+ exam_version: str | None = None
87
+ agent_name: str | None = None
88
+ judge: str | None = None
89
+ score: float | None = None
90
+ grade: str | None = None
91
+ critical_failures: int = 0
92
+ errors: int = 0
93
+ cases: int = 0
94
+ cases_passed: int = 0
95
+ dimensions: list[dict[str, Any]] = Field(default_factory=list)
96
+
97
+
98
+ class Decision(BaseModel):
99
+ system_id: str
100
+ evaluated_at: datetime
101
+ eligible: bool
102
+ demo: bool
103
+ gates: list[Gate]
104
+ security: SecurityAssessment
105
+ quality: QualityAssessment
106
+
107
+ @property
108
+ def revocation_failures(self) -> list[Gate]:
109
+ """Failed gates that trigger revocation: substantive gates only; stale evidence does not revoke (certificate validity handles that)."""
110
+ return [g for g in self.gates if not g.ok and g.kind == "blocking"]
111
+
112
+
113
+ CertStatus = Literal["active", "revoked", "expired", "superseded"]
@@ -0,0 +1,100 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 The AgentEval Authors
3
+ """Rendering of certificates and public verification pages (zh/en), plus optional PDF export."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from datetime import datetime
8
+ from importlib import resources
9
+ from pathlib import Path
10
+
11
+ from jinja2 import Environment, select_autoescape
12
+ from markupsafe import Markup
13
+
14
+ UI = {
15
+ "zh": {
16
+ "html_lang": "zh-CN", "title": "AI 系统安全与质量认证证书", "cert_no": "证书编号",
17
+ "certifies": "兹证明", "statement": "已通过 AgentEval 工具的安全合规扫描与标准化质量评测,满足本认证全部颁证条件。",
18
+ "security": "安全", "security_sub": "国家、区域及行业安全规范", "quality": "质量", "quality_sub": "标准化评测指标",
19
+ "score": "得分", "threshold": "阈值", "tier": "层级", "pack": "规则包", "checks": "通过 / 适用", "status": "结论",
20
+ "national": "国家", "regional": "区域", "industry": "行业", "unknown": "其他",
21
+ "st_passed": "通过", "st_failed": "未通过", "st_not_applicable": "不适用", "st_not_assessed": "未评估",
22
+ "exam": "试卷", "grade": "等级", "judge": "裁判", "cases": "样本通过", "dimension": "维度",
23
+ "issued": "颁发日期", "expires": "有效期至", "evidence": "证据", "scan": "扫描", "rules": "规则集", "run": "评测",
24
+ "subject": "被认证系统", "asset": "部署资产", "model": "模型", "agent": "评测对象",
25
+ "verify": "扫码或访问以下地址验证证书状态", "verify_offline": "离线验证:使用签发方公钥运行", "signature": "Ed25519 数字签名 · 公钥指纹",
26
+ "demo": "演示证书 · 不作正式认证用途", "demo_reason": "评测使用离线启发式裁判,正式颁证需使用 LLM 裁判。",
27
+ "state_revoked": "已吊销", "state_expired": "已过期", "state_superseded": "已被新证书替代",
28
+ "print": "打印 / 另存为 PDF",
29
+ # verification page
30
+ "v_title": "证书验证", "v_valid": "有效", "v_invalid_sig": "签名无效", "v_not_found": "未找到该证书",
31
+ "v_sig_ok": "签名校验通过", "v_sig_bad": "签名校验失败", "v_view": "查看证书", "v_reason": "原因",
32
+ "v_status": "状态", "v_org": "所属组织",
33
+ },
34
+ "en": {
35
+ "html_lang": "en", "title": "AI System Assurance Certificate", "cert_no": "Certificate No.",
36
+ "certifies": "This certifies that",
37
+ "statement": "has passed the AgentEval security compliance scan and standardized quality evaluation, and meets every certification criterion.",
38
+ "security": "Security", "security_sub": "National, regional and industry security requirements",
39
+ "quality": "Quality", "quality_sub": "Standardized evaluation metrics",
40
+ "score": "Score", "threshold": "Threshold", "tier": "Tier", "pack": "Rule pack", "checks": "Passed / applicable",
41
+ "status": "Result", "national": "National", "regional": "Regional", "industry": "Industry", "unknown": "Other",
42
+ "st_passed": "Passed", "st_failed": "Failed", "st_not_applicable": "Not applicable", "st_not_assessed": "Not assessed",
43
+ "exam": "Exam", "grade": "Grade", "judge": "Judge", "cases": "Cases passed", "dimension": "Dimension",
44
+ "issued": "Issued", "expires": "Valid until", "evidence": "Evidence", "scan": "Scan", "rules": "Rule set", "run": "Eval run",
45
+ "subject": "Certified system", "asset": "Deployment", "model": "Model", "agent": "Agent evaluated",
46
+ "verify": "Scan or visit to verify the certificate status", "verify_offline": "Verify offline with the issuer public key:", "signature": "Ed25519 signature · key fingerprint",
47
+ "demo": "DEMO certificate · not valid for certification",
48
+ "demo_reason": "The evaluation used the offline heuristic judge; a formal certificate requires an LLM judge.",
49
+ "state_revoked": "Revoked", "state_expired": "Expired", "state_superseded": "Superseded by a newer certificate",
50
+ "print": "Print / Save as PDF",
51
+ "v_title": "Certificate verification", "v_valid": "Valid", "v_invalid_sig": "Invalid signature",
52
+ "v_not_found": "Certificate not found", "v_sig_ok": "Signature verified", "v_sig_bad": "Signature check failed",
53
+ "v_view": "View certificate", "v_reason": "Reason", "v_status": "Status", "v_org": "Organization",
54
+ },
55
+ }
56
+
57
+
58
+ def qr_svg(url: str) -> Markup:
59
+ import qrcode
60
+ import qrcode.image.svg
61
+
62
+ img = qrcode.make(url, image_factory=qrcode.image.svg.SvgPathImage, box_size=8, border=1)
63
+ svg = img.to_string(encoding="unicode")
64
+ return Markup(svg)
65
+
66
+
67
+ def _env() -> Environment:
68
+ env = Environment(autoescape=select_autoescape(default=True, default_for_string=True))
69
+ env.filters["date"] = lambda s: datetime.fromisoformat(s).strftime("%Y-%m-%d") if s else "—"
70
+ return env
71
+
72
+
73
+ def _tpl(name: str) -> str:
74
+ return resources.files("agenteval").joinpath(f"reports/templates/{name}").read_text(encoding="utf-8")
75
+
76
+
77
+ def render_certificate(envelope: dict, lang: str = "en", state: str = "active", printable_button: bool = True) -> str:
78
+ if lang not in UI:
79
+ raise ValueError(f"Unsupported language: {lang}")
80
+ c = envelope["certificate"]
81
+ return _env().from_string(_tpl("certificate.html.j2")).render(
82
+ c=c, sig=envelope["signature"], t=UI[lang], lang=lang, state=state,
83
+ qr=qr_svg(c["verify_url"]) if c.get("verify_url") else None, printable_button=printable_button)
84
+
85
+
86
+ def to_pdf(html: str, out: Path) -> Path:
87
+ """Print an A4 landscape PDF with Chromium (requires playwright and its browser)."""
88
+ try:
89
+ from playwright.sync_api import sync_playwright
90
+ except ImportError as e: # pragma: no cover
91
+ raise RuntimeError("PDF export requires playwright: pip install playwright && playwright install chromium") from e
92
+ out = Path(out)
93
+ with sync_playwright() as p:
94
+ b = p.chromium.launch()
95
+ page = b.new_page()
96
+ page.set_content(html, wait_until="load")
97
+ page.emulate_media(media="print")
98
+ page.pdf(path=str(out), format="A4", landscape=True, print_background=True, prefer_css_page_size=True)
99
+ b.close()
100
+ return out
@@ -0,0 +1,86 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 The AgentEval Authors
3
+ """Ed25519 signing and verification.
4
+
5
+ In production, keep the private key in a KMS/HSM and point AGENTEVAL_SIGNING_KEY at the mounted PEM file.
6
+ In development, if no key exists, a local key is generated under ./.agenteval/ (mode 0600) for demo use only.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import base64
12
+ import hashlib
13
+ import json
14
+ import os
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from cryptography.exceptions import InvalidSignature
19
+ from cryptography.hazmat.primitives import serialization
20
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
21
+
22
+ ALG = "Ed25519"
23
+
24
+
25
+ def canonical(payload: dict[str, Any]) -> bytes:
26
+ return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
27
+
28
+
29
+ def default_key_path() -> Path:
30
+ return Path(os.environ.get("AGENTEVAL_SIGNING_KEY", Path.cwd() / ".agenteval" / "signing-key.pem"))
31
+
32
+
33
+ def generate_key(path: Path) -> Ed25519PrivateKey:
34
+ key = Ed25519PrivateKey.generate()
35
+ path.parent.mkdir(parents=True, exist_ok=True)
36
+ pem = key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8,
37
+ serialization.NoEncryption())
38
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
39
+ with os.fdopen(fd, "wb") as fh:
40
+ fh.write(pem)
41
+ return key
42
+
43
+
44
+ def load_or_create_key(path: Path | None = None) -> Ed25519PrivateKey:
45
+ path = Path(path or default_key_path())
46
+ if not path.exists():
47
+ return generate_key(path)
48
+ key = serialization.load_pem_private_key(path.read_bytes(), password=None)
49
+ if not isinstance(key, Ed25519PrivateKey):
50
+ raise ValueError(f"{path} is not an Ed25519 private key")
51
+ return key
52
+
53
+
54
+ def public_pem(key: Ed25519PrivateKey | Ed25519PublicKey) -> str:
55
+ pub = key.public_key() if isinstance(key, Ed25519PrivateKey) else key
56
+ return pub.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo).decode()
57
+
58
+
59
+ def key_id(key: Ed25519PrivateKey | Ed25519PublicKey) -> str:
60
+ pub = key.public_key() if isinstance(key, Ed25519PrivateKey) else key
61
+ raw = pub.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
62
+ return hashlib.sha256(raw).hexdigest()[:16]
63
+
64
+
65
+ def load_public_key(pem: str | bytes) -> Ed25519PublicKey:
66
+ key = serialization.load_pem_public_key(pem.encode() if isinstance(pem, str) else pem)
67
+ if not isinstance(key, Ed25519PublicKey):
68
+ raise ValueError("Not an Ed25519 public key")
69
+ return key
70
+
71
+
72
+ def sign(payload: dict[str, Any], key: Ed25519PrivateKey) -> dict[str, Any]:
73
+ sig = key.sign(canonical(payload))
74
+ return {"certificate": payload,
75
+ "signature": {"alg": ALG, "key_id": key_id(key), "value": base64.b64encode(sig).decode()}}
76
+
77
+
78
+ def verify(envelope: dict[str, Any], public_key: Ed25519PublicKey) -> bool:
79
+ try:
80
+ s = envelope["signature"]
81
+ if s.get("alg") != ALG or s.get("key_id") != key_id(public_key):
82
+ return False
83
+ public_key.verify(base64.b64decode(s["value"]), canonical(envelope["certificate"]))
84
+ return True
85
+ except (KeyError, TypeError, ValueError, InvalidSignature):
86
+ return False