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.
- agenteval/__init__.py +5 -0
- agenteval/_starter/certification/policy.yaml +15 -0
- agenteval/_starter/certification/systems/northwind-cs-staging.yaml +16 -0
- agenteval/_starter/certification/systems/northwind-cs.yaml +17 -0
- agenteval/_starter/examples/agents/demo-good.yaml +41 -0
- agenteval/_starter/examples/agents/demo-weak.yaml +34 -0
- agenteval/_starter/examples/agents/openai-compatible.example.yaml +15 -0
- agenteval/_starter/examples/agents/webhook.example.yaml +21 -0
- agenteval/_starter/examples/attestations-certification.yaml +16 -0
- agenteval/_starter/examples/attestations.yaml +19 -0
- agenteval/_starter/examples/snapshots/sample-cluster.json +323 -0
- agenteval/_starter/exams/customer-service/cs-ecommerce-v1.yaml +222 -0
- agenteval/_starter/rules/industry-pci-dss/i18n/zh.yaml +27 -0
- agenteval/_starter/rules/industry-pci-dss/pack.yaml +9 -0
- agenteval/_starter/rules/industry-pci-dss/rules.yaml +83 -0
- agenteval/_starter/rules/us-ca/i18n/zh.yaml +21 -0
- agenteval/_starter/rules/us-ca/pack.yaml +9 -0
- agenteval/_starter/rules/us-ca/rules.yaml +72 -0
- agenteval/_starter/rules/us-federal/governance.yaml +216 -0
- agenteval/_starter/rules/us-federal/i18n/zh.yaml +107 -0
- agenteval/_starter/rules/us-federal/pack.yaml +8 -0
- agenteval/_starter/rules/us-federal/security.yaml +164 -0
- agenteval/certification/__init__.py +16 -0
- agenteval/certification/assess.py +137 -0
- agenteval/certification/issuer.py +165 -0
- agenteval/certification/models.py +113 -0
- agenteval/certification/render.py +100 -0
- agenteval/certification/signing.py +86 -0
- agenteval/cli.py +533 -0
- agenteval/collectors/__init__.py +30 -0
- agenteval/collectors/kubernetes.py +70 -0
- agenteval/collectors/normalize.py +247 -0
- agenteval/config.py +49 -0
- agenteval/engine/__init__.py +12 -0
- agenteval/engine/conditions.py +109 -0
- agenteval/engine/evaluator.py +133 -0
- agenteval/engine/loader.py +131 -0
- agenteval/evaluation/__init__.py +17 -0
- agenteval/evaluation/adapters.py +276 -0
- agenteval/evaluation/assertions.py +73 -0
- agenteval/evaluation/judges.py +214 -0
- agenteval/evaluation/messages.py +56 -0
- agenteval/evaluation/models.py +174 -0
- agenteval/evaluation/papers.py +103 -0
- agenteval/evaluation/report.py +80 -0
- agenteval/evaluation/runner.py +154 -0
- agenteval/i18n.py +118 -0
- agenteval/models.py +136 -0
- agenteval/reports/__init__.py +86 -0
- agenteval/reports/templates/certificate.html.j2 +178 -0
- agenteval/reports/templates/eval_report.html.j2 +191 -0
- agenteval/reports/templates/report.html.j2 +75 -0
- agenteval/schema.py +66 -0
- agenteval/schemas/attestations.schema.json +76 -0
- agenteval/schemas/exam.schema.json +85 -0
- agenteval/schemas/pack.schema.json +48 -0
- agenteval/schemas/policy.schema.json +67 -0
- agenteval/schemas/rule.schema.json +298 -0
- agenteval/schemas/system.schema.json +76 -0
- agenteval/schemas/translations.schema.json +32 -0
- agenteval_toolkit-0.1.0.dist-info/METADATA +307 -0
- agenteval_toolkit-0.1.0.dist-info/RECORD +67 -0
- agenteval_toolkit-0.1.0.dist-info/WHEEL +5 -0
- agenteval_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
- agenteval_toolkit-0.1.0.dist-info/licenses/LICENSE +202 -0
- agenteval_toolkit-0.1.0.dist-info/licenses/NOTICE +19 -0
- 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
|