agent-control-evaluator-cisco 7.7.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.
@@ -0,0 +1,2 @@
1
+ __all__ = []
2
+
@@ -0,0 +1,5 @@
1
+ from .config import CiscoAIDefenseConfig
2
+ from .evaluator import CiscoAIDefenseEvaluator
3
+
4
+ __all__ = ["CiscoAIDefenseEvaluator", "CiscoAIDefenseConfig"]
5
+
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ # Thin REST client for Cisco AI Defense Chat Inspection.
4
+ # Uses httpx.AsyncClient and the OpenAPI-defined endpoint/header.
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ try:
9
+ import httpx
10
+
11
+ AI_DEFENSE_HTTPX_AVAILABLE = True
12
+ except ImportError: # Narrow to import error only
13
+ httpx = None # type: ignore
14
+ AI_DEFENSE_HTTPX_AVAILABLE = False
15
+
16
+
17
+ # Regions from ai_defense_api.json "servers" section
18
+ REGION_BASE_URLS: dict[str, str] = {
19
+ "us": "https://us.api.inspect.aidefense.security.cisco.com",
20
+ "ap": "https://ap.api.inspect.aidefense.security.cisco.com",
21
+ "eu": "https://eu.api.inspect.aidefense.security.cisco.com",
22
+ }
23
+
24
+
25
+ def build_endpoint(base_url: str) -> str:
26
+ base = base_url.rstrip("/")
27
+ return f"{base}/api/v1/inspect/chat"
28
+
29
+
30
+ @dataclass
31
+ class AIDefenseClient:
32
+ """Minimal async client for Cisco AI Defense Chat Inspection.
33
+
34
+ Attributes:
35
+ api_key: API key used for authentication header
36
+ endpoint_url: Full URL to POST /api/v1/inspect/chat
37
+ timeout_s: Timeout in seconds
38
+ """
39
+
40
+ api_key: str = field(repr=False)
41
+ endpoint_url: str
42
+ timeout_s: float
43
+
44
+ _client: httpx.AsyncClient | None = field( # type: ignore[name-defined]
45
+ default=None,
46
+ repr=False,
47
+ compare=False,
48
+ )
49
+
50
+ async def _get_client(self) -> httpx.AsyncClient: # type: ignore[name-defined]
51
+ if not AI_DEFENSE_HTTPX_AVAILABLE: # pragma: no cover
52
+ raise RuntimeError("httpx not installed; cannot call Cisco AI Defense REST API")
53
+ if self._client is None or self._client.is_closed:
54
+ self._client = httpx.AsyncClient(timeout=self.timeout_s)
55
+ return self._client
56
+
57
+ async def chat_inspect(
58
+ self,
59
+ messages: list[dict[str, str]],
60
+ metadata: dict[str, Any] | None = None,
61
+ inspect_config: dict[str, Any] | None = None,
62
+ headers: dict[str, str] | None = None,
63
+ ) -> dict[str, Any]:
64
+ client = await self._get_client()
65
+
66
+ req_headers: dict[str, str] = {
67
+ "X-Cisco-AI-Defense-API-Key": self.api_key,
68
+ "Content-Type": "application/json",
69
+ "Accept": "application/json",
70
+ }
71
+ if headers:
72
+ req_headers.update(headers)
73
+
74
+ payload: dict[str, Any] = {"messages": messages}
75
+ if metadata is not None:
76
+ payload["metadata"] = metadata
77
+ if inspect_config is not None:
78
+ payload["config"] = inspect_config
79
+
80
+ resp = await client.post(self.endpoint_url, json=payload, headers=req_headers)
81
+ resp.raise_for_status()
82
+ data = resp.json()
83
+ if not isinstance(data, dict):
84
+ raise RuntimeError("Invalid response payload: not a JSON object")
85
+ return data
86
+
87
+ async def aclose(self) -> None:
88
+ if self._client and not self._client.is_closed:
89
+ await self._client.aclose()
90
+
91
+ async def close(self) -> None:
92
+ """Close the HTTP client and release resources."""
93
+ await self.aclose()
94
+
95
+ async def __aenter__(self) -> AIDefenseClient:
96
+ """Async context manager entry."""
97
+ return self
98
+
99
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
100
+ """Async context manager exit."""
101
+ await self.close()
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal
4
+
5
+ from agent_control_evaluators import EvaluatorConfig
6
+ from pydantic import Field
7
+
8
+
9
+ class CiscoAIDefenseConfig(EvaluatorConfig):
10
+ """Configuration for Cisco AI Defense evaluator (REST).
11
+
12
+ Attributes:
13
+ api_key_env: Env var name for API key
14
+ region: Optional server region (us, ap, eu); ignored if api_url set
15
+ api_url: Optional full endpoint override
16
+ timeout_ms: Request timeout (milliseconds)
17
+ on_error: Error policy (allow=fail-open, deny=fail-closed)
18
+ payload_field: Force single-message role: input→user, output→assistant
19
+ messages_strategy: "single" (synthesize) or "history" (pass-through messages)
20
+ metadata: Optional metadata object to include (OpenAPI spec)
21
+ inspect_config: Optional Inspect API config passthrough (see OpenAPI spec)
22
+ """
23
+
24
+ api_key_env: str = "AI_DEFENSE_API_KEY"
25
+ region: Literal["us", "ap", "eu"] | None = "us"
26
+ api_url: str | None = None
27
+ timeout_ms: int = Field(default=15_000, ge=1)
28
+ on_error: Literal["allow", "deny"] = "allow"
29
+ payload_field: Literal["input", "output"] | None = None
30
+ messages_strategy: Literal["single", "history"] = "history"
31
+ metadata: dict[str, Any] | None = None
32
+ inspect_config: dict[str, Any] | None = None
33
+ include_raw_response: bool = False
@@ -0,0 +1,229 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from importlib.metadata import PackageNotFoundError, version
6
+ from typing import Any
7
+
8
+ from agent_control_evaluators import (
9
+ Evaluator,
10
+ EvaluatorMetadata,
11
+ register_evaluator,
12
+ )
13
+ from agent_control_models import EvaluatorResult
14
+
15
+ from .client import AI_DEFENSE_HTTPX_AVAILABLE, REGION_BASE_URLS, AIDefenseClient, build_endpoint
16
+ from .config import CiscoAIDefenseConfig
17
+
18
+
19
+ def _resolve_package_version() -> str:
20
+ """Return the installed package version, or a dev fallback during local imports."""
21
+ try:
22
+ return version("agent-control-evaluator-cisco")
23
+ except PackageNotFoundError:
24
+ return "0.0.0.dev"
25
+
26
+
27
+ _PACKAGE_VERSION = _resolve_package_version()
28
+
29
+
30
+ def _load_api_key(env_name: str) -> str:
31
+ key = os.getenv(env_name)
32
+ if not key:
33
+ raise RuntimeError(
34
+ f"Missing Cisco AI Defense API key in env '{env_name}'. Set it on the server."
35
+ )
36
+ return key
37
+
38
+
39
+ def _build_messages(
40
+ data: Any,
41
+ strategy: str,
42
+ payload_field: str | None,
43
+ ) -> list[dict[str, str]]:
44
+ """Build Chat Inspection messages from selected data.
45
+
46
+ - history: pass-through if data has 'messages' list; else fallback to single
47
+ - single: synthesize one message with role based on payload_field
48
+ """
49
+ if strategy == "history":
50
+ if isinstance(data, dict) and isinstance(data.get("messages"), list):
51
+ msgs: list[dict[str, str]] = []
52
+ for m in data["messages"]:
53
+ if isinstance(m, dict) and "content" in m:
54
+ role = str(m.get("role", "user"))
55
+ content = str(m.get("content", ""))
56
+ msgs.append({"role": role, "content": content})
57
+ if msgs:
58
+ return msgs
59
+ # Fallback to single
60
+
61
+ role = "assistant" if payload_field == "output" else "user"
62
+ content = _coerce_message_content(data, payload_field)
63
+ return [{"role": role, "content": content}]
64
+
65
+
66
+ def _coerce_message_content(data: Any, payload_field: str | None) -> str:
67
+ if data is None:
68
+ return ""
69
+
70
+ if isinstance(data, dict):
71
+ candidate: Any = None
72
+ preferred_keys: list[str] = []
73
+ if payload_field is not None:
74
+ preferred_keys.append(payload_field)
75
+ if payload_field == "output":
76
+ preferred_keys.extend(["output", "content", "text", "message"])
77
+ else:
78
+ preferred_keys.extend(["input", "content", "text", "message"])
79
+
80
+ for key in preferred_keys:
81
+ if key in data and data[key] is not None:
82
+ candidate = data[key]
83
+ break
84
+
85
+ if candidate is None:
86
+ candidate = data
87
+ return _stringify_message_content(candidate)
88
+
89
+ return _stringify_message_content(data)
90
+
91
+
92
+ def _stringify_message_content(value: Any) -> str:
93
+ if value is None:
94
+ return ""
95
+ if isinstance(value, str):
96
+ return value
97
+ if isinstance(value, (int, float, bool)):
98
+ return str(value)
99
+ try:
100
+ return json.dumps(value, ensure_ascii=False, sort_keys=True, default=str)
101
+ except TypeError:
102
+ return str(value)
103
+
104
+
105
+ @register_evaluator
106
+ class CiscoAIDefenseEvaluator(Evaluator[CiscoAIDefenseConfig]):
107
+ """Cisco AI Defense evaluator.
108
+
109
+ Maps InspectResponse.is_safe to EvaluatorResult.matched.
110
+ """
111
+
112
+ metadata = EvaluatorMetadata(
113
+ name="cisco.ai_defense",
114
+ version=_PACKAGE_VERSION,
115
+ description="Cisco AI Defense Chat Inspection integration",
116
+ requires_api_key=True,
117
+ timeout_ms=15000,
118
+ )
119
+
120
+ config_model = CiscoAIDefenseConfig
121
+
122
+ @classmethod
123
+ def is_available(cls) -> bool:
124
+ """Evaluator is available only if httpx dependency exists."""
125
+ return AI_DEFENSE_HTTPX_AVAILABLE
126
+
127
+ def __init__(self, config: CiscoAIDefenseConfig) -> None:
128
+ self.config = config
129
+
130
+ # Validate and resolve configuration eagerly to avoid per-call work.
131
+ # API key
132
+ try:
133
+ api_key = _load_api_key(self.config.api_key_env)
134
+ except RuntimeError as e:
135
+ # Fail fast during construction so misconfiguration is caught early.
136
+ raise ValueError(str(e)) from e
137
+
138
+ # Endpoint
139
+ if self.config.api_url:
140
+ endpoint_url = self.config.api_url
141
+ else:
142
+ base_url = REGION_BASE_URLS.get(self.config.region or "us", REGION_BASE_URLS["us"])
143
+ endpoint_url = build_endpoint(base_url)
144
+
145
+ # Timeout
146
+ timeout_s = float(self.config.timeout_ms) / 1000.0
147
+
148
+ # Create a single client instance for reuse.
149
+ self._client: AIDefenseClient = AIDefenseClient(
150
+ api_key=api_key,
151
+ endpoint_url=endpoint_url,
152
+ timeout_s=timeout_s,
153
+ )
154
+
155
+ async def evaluate(self, data: Any) -> EvaluatorResult: # noqa: D401
156
+ # Null input: do not call external service; treat as no data
157
+ if data is None:
158
+ return EvaluatorResult(matched=False, confidence=1.0, message="No data")
159
+
160
+ messages = _build_messages(
161
+ data,
162
+ strategy=self.config.messages_strategy,
163
+ payload_field=self.config.payload_field,
164
+ )
165
+ if not messages:
166
+ return EvaluatorResult(matched=False, confidence=1.0, message="No data to inspect")
167
+
168
+ # Call REST API for Chat Inspection
169
+ try:
170
+ response: dict[str, Any] = await self._client.chat_inspect(
171
+ messages=messages,
172
+ metadata=self.config.metadata,
173
+ inspect_config=self.config.inspect_config,
174
+ )
175
+
176
+ # Map is_safe to matched
177
+ is_safe = response.get("is_safe")
178
+ if isinstance(is_safe, bool):
179
+ matched = not is_safe
180
+ msg = "Content is unsafe" if matched else "Content is safe"
181
+ meta: dict[str, Any] = {
182
+ "severity": response.get("severity"),
183
+ "classifications": response.get("classifications"),
184
+ "rules": response.get("rules"),
185
+ "attack_technique": response.get("attack_technique"),
186
+ "event_id": response.get("event_id"),
187
+ }
188
+ if self.config.include_raw_response:
189
+ meta["raw"] = response
190
+ return EvaluatorResult(
191
+ matched=matched,
192
+ confidence=1.0,
193
+ message=msg,
194
+ metadata=meta,
195
+ )
196
+
197
+ # If no boolean is present, consider it an evaluator error
198
+ fallback = self.config.on_error
199
+ matched = fallback == "deny"
200
+ error_message = "Cisco AI Defense response missing 'is_safe'"
201
+ meta2: dict[str, Any] = {"fallback_action": fallback}
202
+ if self.config.include_raw_response:
203
+ meta2["raw"] = response
204
+ return EvaluatorResult(
205
+ matched=matched,
206
+ confidence=0.0,
207
+ message=error_message,
208
+ metadata=meta2,
209
+ error=None if matched else error_message,
210
+ )
211
+ except Exception as e: # noqa: BLE001
212
+ fallback = self.config.on_error
213
+ matched = fallback == "deny"
214
+ error_detail = str(e)
215
+ return EvaluatorResult(
216
+ matched=matched,
217
+ confidence=0.0,
218
+ message=f"Cisco AI Defense evaluation error: {error_detail}",
219
+ metadata={
220
+ "error": error_detail,
221
+ "error_type": type(e).__name__,
222
+ "fallback_action": fallback,
223
+ },
224
+ error=None if matched else error_detail,
225
+ )
226
+
227
+ async def aclose(self) -> None:
228
+ """Close the underlying Cisco AI Defense client."""
229
+ await self._client.aclose()
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-control-evaluator-cisco
3
+ Version: 7.7.0
4
+ Summary: Cisco AI Defense evaluator for agent-control
5
+ Author: Cisco AI Defense Team
6
+ License: Apache-2.0
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: agent-control-evaluators>=7.7.0
9
+ Requires-Dist: agent-control-models>=7.7.0
10
+ Requires-Dist: httpx>=0.24.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
13
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
14
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
15
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
16
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # Agent Control Evaluator - Cisco AI Defense
20
+
21
+ External evaluator that calls Cisco AI Defense Chat Inspection via REST and maps `InspectResponse.is_safe` to Agent Control decisions.
22
+
23
+ - Entry point name: `cisco.ai_defense`
24
+ - Transport: direct HTTP (httpx)
25
+
26
+ ## Installation
27
+
28
+ Canonical install path:
29
+
30
+ ```bash
31
+ pip install "agent-control-evaluators[cisco]"
32
+ ```
33
+
34
+ Fallback direct wheel install:
35
+
36
+ ```bash
37
+ pip install agent-control-evaluator-cisco
38
+ ```
39
+
40
+ For local development:
41
+
42
+ ```bash
43
+ uv pip install -e evaluators/contrib/cisco
44
+ ```
45
+
46
+ - Build wheel from the repo root (contrib package only):
47
+
48
+ ```bash
49
+ make engine-build
50
+ (cd evaluators/contrib/cisco && make build)
51
+ ```
52
+
53
+ To run the server with this evaluator enabled, see `examples/cisco_ai_defense/README.md` for setup and seeding instructions.
54
+
55
+ ## Configuration
56
+
57
+ Set the `AI_DEFENSE_API_KEY` environment variable:
58
+
59
+ ```bash
60
+ export AI_DEFENSE_API_KEY="<your_key>"
61
+ ```
62
+
63
+ Evaluator config fields (all optional unless stated):
64
+
65
+ - `api_key_env: str = "AI_DEFENSE_API_KEY"`
66
+ - `region: "us" | "ap" | "eu" | None = "us"` (ignored if `api_url` set)
67
+ - `api_url: str | None = None` (full endpoint override; e.g., `https://us.../api/v1/inspect/chat`)
68
+ - `timeout_ms: int = 15000`
69
+ - `on_error: "allow" | "deny" = "allow"` (fail-open or fail-closed on transport/response errors)
70
+ - `payload_field: "input" | "output" | None = None`
71
+ - When set, synthesizes a single message from that field; `input` → `role=user`, `output` → `role=assistant`.
72
+ - `messages_strategy: "single" | "history" = "history"`
73
+ - `history` forwards an existing `messages` list in the selected data if present; falls back to single otherwise.
74
+ - `metadata: dict[str, Any] | None = None` (forwarded to API per OpenAPI spec)
75
+ - `inspect_config: dict[str, Any] | None = None` (forwarded to API per OpenAPI spec)
76
+ - `include_raw_response: bool = false` (when true, includes the full provider response under `metadata.raw`)
77
+
78
+ ## Available Evaluators
79
+
80
+ | Name | Description |
81
+ |------|-------------|
82
+ | `cisco.ai_defense` | Cisco AI Defense Chat Inspection |
83
+
84
+ Behavior mapping:
85
+
86
+ - `is_safe == false` → `EvaluatorResult.matched = true` (e.g., a `deny` action will block)
87
+ - `is_safe == true` → `matched = false`
88
+ - Errors or invalid responses → `matched = (on_error == "deny")`; error details in `metadata` (no `error` field is set; engine honors `matched` per `on_error`)
89
+
90
+ ## Minimal server control configuration
91
+
92
+ Example using `messages_strategy: "history"` (for inputs that already have a `messages` list):
93
+
94
+ ```
95
+ {
96
+ "description": "Apply Cisco AI Defense Security, Safety, and Privacy guardrails",
97
+ "enabled": true,
98
+ "execution": "server",
99
+ "scope": { "step_types": ["llm"], "stages": ["pre", "post"] },
100
+ "condition": {
101
+ "selector": { "path": "input" },
102
+ "evaluator": {
103
+ "name": "cisco.ai_defense",
104
+ "config": {
105
+ "api_key_env": "AI_DEFENSE_API_KEY",
106
+ "region": "us",
107
+ "timeout_ms": 15000,
108
+ "on_error": "allow",
109
+ "messages_strategy": "history"
110
+ }
111
+ }
112
+ },
113
+ "action": { "decision": "deny" },
114
+ "tags": ["ai_defense", "safety"]
115
+ }
116
+ ```
117
+
118
+ ```
119
+ {
120
+ "description": "Apply Cisco AI Defense Security, Safety, and Privacy guardrails",
121
+ "enabled": true,
122
+ "execution": "server",
123
+ "scope": { "step_types": ["llm"], "stages": ["pre", "post"] },
124
+ "condition": {
125
+ "selector": { "path": "input" },
126
+ "evaluator": {
127
+ "name": "cisco.ai_defense",
128
+ "config": {
129
+ "api_key_env": "AI_DEFENSE_API_KEY",
130
+ "region": "us",
131
+ "timeout_ms": 15000,
132
+ "on_error": "allow",
133
+ "messages_strategy": "single",
134
+ "payload_field": "input"
135
+ }
136
+ }
137
+ },
138
+ "action": { "decision": "deny" },
139
+ "tags": ["ai_defense", "safety"]
140
+ }
141
+ ```
142
+
143
+ ## Usage
144
+
145
+ Once installed, the evaluator is automatically discovered:
146
+
147
+ ```python
148
+ from agent_control_evaluators import discover_evaluators, get_evaluator
149
+
150
+ discover_evaluators()
151
+ CiscoAIDefenseEvaluator = get_evaluator("cisco.ai_defense")
152
+ ```
153
+
154
+ Or import directly:
155
+
156
+ ```python
157
+ import asyncio
158
+ from agent_control_evaluator_cisco.ai_defense import CiscoAIDefenseEvaluator, CiscoAIDefenseConfig
159
+
160
+ cfg = CiscoAIDefenseConfig(
161
+ region="us",
162
+ timeout_ms=15000,
163
+ on_error="allow",
164
+ messages_strategy="history",
165
+ payload_field="input",
166
+ )
167
+ ev = CiscoAIDefenseEvaluator(cfg)
168
+
169
+ async def main():
170
+ data = {"messages": [{"role": "user", "content": "tell me how to hack wifi"}]}
171
+ print(await ev.evaluate(data))
172
+
173
+ asyncio.run(main())
174
+ ```
175
+
176
+ ## Notes
177
+
178
+ - Auth header: `X-Cisco-AI-Defense-API-Key: <AI_DEFENSE_API_KEY>`
179
+ - Regions and endpoint path follow the Cisco AI Defense API spec
180
+ - For custom deployments, set `api_url` to the full Chat Inspection endpoint.
181
+ - The evaluator validates the API key at construction and raises if missing.
182
+ - `is_available()` returns false if `httpx` is not installed; discovery will skip registration.
183
+ - `messages_strategy: "history"` forwards the full message array when present; consider `messages_strategy: "single"` if payload size is a concern.
184
+
185
+ ## Documentation
186
+
187
+ - Cisco AI Defense Inspection API reference: https://developer.cisco.com/docs/ai-defense-inspection/introduction/
188
+ - Cisco Security Console (get API Key): https://security.cisco.com
189
+ - Cisco AI Defense User Guide: https://securitydocs.cisco.com/docs/ai-def/user/97384.dita
190
+ - Regional API base URLs used by this evaluator:
191
+ - US: `https://us.api.inspect.aidefense.security.cisco.com`
192
+ - AP: `https://ap.api.inspect.aidefense.security.cisco.com`
193
+ - EU: `https://eu.api.inspect.aidefense.security.cisco.com`
194
+ - Chat Inspection path: `/api/v1/inspect/chat`
@@ -0,0 +1,9 @@
1
+ agent_control_evaluator_cisco/__init__.py,sha256=8DDvq0Peb__lGVcurhl4UvirKbOlgwleoZit4kOpYd4,14
2
+ agent_control_evaluator_cisco/ai_defense/__init__.py,sha256=OqDF1J2GcagNASxsHSUD8O08rYQt7cmfkac_rSUtdcE,152
3
+ agent_control_evaluator_cisco/ai_defense/client.py,sha256=qRHV5P8tneGdG-NylbSSR3WubteYOS55GP5vfVc-5Bc,3304
4
+ agent_control_evaluator_cisco/ai_defense/config.py,sha256=54qIBL7QQAt5AvxildR7xPCZ6QN-Rw2ac1V_kIw-cfU,1373
5
+ agent_control_evaluator_cisco/ai_defense/evaluator.py,sha256=HlKkZRmzTdqI0Q44kgJYeP-aMLeSgefwhPHnkgQqHEg,7870
6
+ agent_control_evaluator_cisco-7.7.0.dist-info/METADATA,sha256=sACAcDeWRsVp5dZadVLRB8OD3t1gJGQfmPwBkHg3hBE,6175
7
+ agent_control_evaluator_cisco-7.7.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
8
+ agent_control_evaluator_cisco-7.7.0.dist-info/entry_points.txt,sha256=UxIxKYokYK7Bd7Cc18sAfwdKKEIrqUhaD9mZBadS5S8,111
9
+ agent_control_evaluator_cisco-7.7.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [agent_control.evaluators]
2
+ cisco.ai_defense = agent_control_evaluator_cisco.ai_defense:CiscoAIDefenseEvaluator