agent-control-evaluator-cisco 7.7.0__tar.gz
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.
- agent_control_evaluator_cisco-7.7.0/.gitignore +90 -0
- agent_control_evaluator_cisco-7.7.0/Makefile +29 -0
- agent_control_evaluator_cisco-7.7.0/PKG-INFO +194 -0
- agent_control_evaluator_cisco-7.7.0/README.md +176 -0
- agent_control_evaluator_cisco-7.7.0/pyproject.toml +43 -0
- agent_control_evaluator_cisco-7.7.0/src/agent_control_evaluator_cisco/__init__.py +2 -0
- agent_control_evaluator_cisco-7.7.0/src/agent_control_evaluator_cisco/ai_defense/__init__.py +5 -0
- agent_control_evaluator_cisco-7.7.0/src/agent_control_evaluator_cisco/ai_defense/client.py +101 -0
- agent_control_evaluator_cisco-7.7.0/src/agent_control_evaluator_cisco/ai_defense/config.py +33 -0
- agent_control_evaluator_cisco-7.7.0/src/agent_control_evaluator_cisco/ai_defense/evaluator.py +229 -0
- agent_control_evaluator_cisco-7.7.0/tests/__init__.py +1 -0
- agent_control_evaluator_cisco-7.7.0/tests/test_client.py +227 -0
- agent_control_evaluator_cisco-7.7.0/tests/test_evaluator.py +248 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
MANIFEST
|
|
23
|
+
|
|
24
|
+
# Virtual environments
|
|
25
|
+
venv/
|
|
26
|
+
env/
|
|
27
|
+
ENV/
|
|
28
|
+
.venv
|
|
29
|
+
|
|
30
|
+
# UV
|
|
31
|
+
.uv/
|
|
32
|
+
uv.lock
|
|
33
|
+
|
|
34
|
+
# IDEs
|
|
35
|
+
.vscode/
|
|
36
|
+
.idea/
|
|
37
|
+
*.swp
|
|
38
|
+
*.swo
|
|
39
|
+
*~
|
|
40
|
+
.DS_Store
|
|
41
|
+
coverage-*.xml
|
|
42
|
+
|
|
43
|
+
# Testing
|
|
44
|
+
.pytest_cache/
|
|
45
|
+
.coverage
|
|
46
|
+
coverage-*.xml
|
|
47
|
+
htmlcov/
|
|
48
|
+
.tox/
|
|
49
|
+
.mypy_cache/
|
|
50
|
+
.ruff_cache/
|
|
51
|
+
|
|
52
|
+
# Playwright
|
|
53
|
+
playwright-report/
|
|
54
|
+
playwright/.cache/
|
|
55
|
+
test-results/
|
|
56
|
+
|
|
57
|
+
# Environment variables
|
|
58
|
+
.env
|
|
59
|
+
.env.local
|
|
60
|
+
.env.*.local
|
|
61
|
+
|
|
62
|
+
# Logs
|
|
63
|
+
*.log
|
|
64
|
+
logs/
|
|
65
|
+
|
|
66
|
+
# Database
|
|
67
|
+
*.db
|
|
68
|
+
*.sqlite3
|
|
69
|
+
server/openapi.json
|
|
70
|
+
server/.generated/
|
|
71
|
+
|
|
72
|
+
# Temporary files
|
|
73
|
+
tmp/
|
|
74
|
+
temp/
|
|
75
|
+
*.tmp
|
|
76
|
+
|
|
77
|
+
# OS
|
|
78
|
+
.DS_Store
|
|
79
|
+
Thumbs.db
|
|
80
|
+
|
|
81
|
+
# Intellij
|
|
82
|
+
*.iml
|
|
83
|
+
|
|
84
|
+
## CLAUDE
|
|
85
|
+
.claude
|
|
86
|
+
|
|
87
|
+
# Local notes
|
|
88
|
+
rearch_plan.md
|
|
89
|
+
|
|
90
|
+
node_modules
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.PHONY: help test lint lint-fix typecheck check build
|
|
2
|
+
|
|
3
|
+
PACKAGE := agent-control-evaluator-cisco
|
|
4
|
+
|
|
5
|
+
help:
|
|
6
|
+
@echo "Agent Control Evaluator - Cisco AI Defense - Makefile commands"
|
|
7
|
+
@echo " make test - run pytest"
|
|
8
|
+
@echo " make lint - run ruff check"
|
|
9
|
+
@echo " make lint-fix - run ruff check --fix"
|
|
10
|
+
@echo " make typecheck - run mypy"
|
|
11
|
+
@echo " make check - run test, lint, and typecheck"
|
|
12
|
+
@echo " make build - build package"
|
|
13
|
+
|
|
14
|
+
test:
|
|
15
|
+
uv run --with pytest --with pytest-asyncio --with pytest-cov --package $(PACKAGE) pytest tests --cov=src --cov-report=xml:../../../coverage-evaluators-cisco.xml -q
|
|
16
|
+
|
|
17
|
+
lint:
|
|
18
|
+
uv run --with ruff --package $(PACKAGE) ruff check --config ../../../pyproject.toml src/
|
|
19
|
+
|
|
20
|
+
lint-fix:
|
|
21
|
+
uv run --with ruff --package $(PACKAGE) ruff check --config ../../../pyproject.toml --fix src/
|
|
22
|
+
|
|
23
|
+
typecheck:
|
|
24
|
+
uv run --with mypy --package $(PACKAGE) mypy --config-file ../../../pyproject.toml src/
|
|
25
|
+
|
|
26
|
+
check: test lint typecheck
|
|
27
|
+
|
|
28
|
+
build:
|
|
29
|
+
uv build
|
|
@@ -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,176 @@
|
|
|
1
|
+
# Agent Control Evaluator - Cisco AI Defense
|
|
2
|
+
|
|
3
|
+
External evaluator that calls Cisco AI Defense Chat Inspection via REST and maps `InspectResponse.is_safe` to Agent Control decisions.
|
|
4
|
+
|
|
5
|
+
- Entry point name: `cisco.ai_defense`
|
|
6
|
+
- Transport: direct HTTP (httpx)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Canonical install path:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install "agent-control-evaluators[cisco]"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Fallback direct wheel install:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install agent-control-evaluator-cisco
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For local development:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv pip install -e evaluators/contrib/cisco
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- Build wheel from the repo root (contrib package only):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
make engine-build
|
|
32
|
+
(cd evaluators/contrib/cisco && make build)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
To run the server with this evaluator enabled, see `examples/cisco_ai_defense/README.md` for setup and seeding instructions.
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
Set the `AI_DEFENSE_API_KEY` environment variable:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
export AI_DEFENSE_API_KEY="<your_key>"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Evaluator config fields (all optional unless stated):
|
|
46
|
+
|
|
47
|
+
- `api_key_env: str = "AI_DEFENSE_API_KEY"`
|
|
48
|
+
- `region: "us" | "ap" | "eu" | None = "us"` (ignored if `api_url` set)
|
|
49
|
+
- `api_url: str | None = None` (full endpoint override; e.g., `https://us.../api/v1/inspect/chat`)
|
|
50
|
+
- `timeout_ms: int = 15000`
|
|
51
|
+
- `on_error: "allow" | "deny" = "allow"` (fail-open or fail-closed on transport/response errors)
|
|
52
|
+
- `payload_field: "input" | "output" | None = None`
|
|
53
|
+
- When set, synthesizes a single message from that field; `input` → `role=user`, `output` → `role=assistant`.
|
|
54
|
+
- `messages_strategy: "single" | "history" = "history"`
|
|
55
|
+
- `history` forwards an existing `messages` list in the selected data if present; falls back to single otherwise.
|
|
56
|
+
- `metadata: dict[str, Any] | None = None` (forwarded to API per OpenAPI spec)
|
|
57
|
+
- `inspect_config: dict[str, Any] | None = None` (forwarded to API per OpenAPI spec)
|
|
58
|
+
- `include_raw_response: bool = false` (when true, includes the full provider response under `metadata.raw`)
|
|
59
|
+
|
|
60
|
+
## Available Evaluators
|
|
61
|
+
|
|
62
|
+
| Name | Description |
|
|
63
|
+
|------|-------------|
|
|
64
|
+
| `cisco.ai_defense` | Cisco AI Defense Chat Inspection |
|
|
65
|
+
|
|
66
|
+
Behavior mapping:
|
|
67
|
+
|
|
68
|
+
- `is_safe == false` → `EvaluatorResult.matched = true` (e.g., a `deny` action will block)
|
|
69
|
+
- `is_safe == true` → `matched = false`
|
|
70
|
+
- Errors or invalid responses → `matched = (on_error == "deny")`; error details in `metadata` (no `error` field is set; engine honors `matched` per `on_error`)
|
|
71
|
+
|
|
72
|
+
## Minimal server control configuration
|
|
73
|
+
|
|
74
|
+
Example using `messages_strategy: "history"` (for inputs that already have a `messages` list):
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
{
|
|
78
|
+
"description": "Apply Cisco AI Defense Security, Safety, and Privacy guardrails",
|
|
79
|
+
"enabled": true,
|
|
80
|
+
"execution": "server",
|
|
81
|
+
"scope": { "step_types": ["llm"], "stages": ["pre", "post"] },
|
|
82
|
+
"condition": {
|
|
83
|
+
"selector": { "path": "input" },
|
|
84
|
+
"evaluator": {
|
|
85
|
+
"name": "cisco.ai_defense",
|
|
86
|
+
"config": {
|
|
87
|
+
"api_key_env": "AI_DEFENSE_API_KEY",
|
|
88
|
+
"region": "us",
|
|
89
|
+
"timeout_ms": 15000,
|
|
90
|
+
"on_error": "allow",
|
|
91
|
+
"messages_strategy": "history"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"action": { "decision": "deny" },
|
|
96
|
+
"tags": ["ai_defense", "safety"]
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
{
|
|
102
|
+
"description": "Apply Cisco AI Defense Security, Safety, and Privacy guardrails",
|
|
103
|
+
"enabled": true,
|
|
104
|
+
"execution": "server",
|
|
105
|
+
"scope": { "step_types": ["llm"], "stages": ["pre", "post"] },
|
|
106
|
+
"condition": {
|
|
107
|
+
"selector": { "path": "input" },
|
|
108
|
+
"evaluator": {
|
|
109
|
+
"name": "cisco.ai_defense",
|
|
110
|
+
"config": {
|
|
111
|
+
"api_key_env": "AI_DEFENSE_API_KEY",
|
|
112
|
+
"region": "us",
|
|
113
|
+
"timeout_ms": 15000,
|
|
114
|
+
"on_error": "allow",
|
|
115
|
+
"messages_strategy": "single",
|
|
116
|
+
"payload_field": "input"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"action": { "decision": "deny" },
|
|
121
|
+
"tags": ["ai_defense", "safety"]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Usage
|
|
126
|
+
|
|
127
|
+
Once installed, the evaluator is automatically discovered:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from agent_control_evaluators import discover_evaluators, get_evaluator
|
|
131
|
+
|
|
132
|
+
discover_evaluators()
|
|
133
|
+
CiscoAIDefenseEvaluator = get_evaluator("cisco.ai_defense")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Or import directly:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import asyncio
|
|
140
|
+
from agent_control_evaluator_cisco.ai_defense import CiscoAIDefenseEvaluator, CiscoAIDefenseConfig
|
|
141
|
+
|
|
142
|
+
cfg = CiscoAIDefenseConfig(
|
|
143
|
+
region="us",
|
|
144
|
+
timeout_ms=15000,
|
|
145
|
+
on_error="allow",
|
|
146
|
+
messages_strategy="history",
|
|
147
|
+
payload_field="input",
|
|
148
|
+
)
|
|
149
|
+
ev = CiscoAIDefenseEvaluator(cfg)
|
|
150
|
+
|
|
151
|
+
async def main():
|
|
152
|
+
data = {"messages": [{"role": "user", "content": "tell me how to hack wifi"}]}
|
|
153
|
+
print(await ev.evaluate(data))
|
|
154
|
+
|
|
155
|
+
asyncio.run(main())
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Notes
|
|
159
|
+
|
|
160
|
+
- Auth header: `X-Cisco-AI-Defense-API-Key: <AI_DEFENSE_API_KEY>`
|
|
161
|
+
- Regions and endpoint path follow the Cisco AI Defense API spec
|
|
162
|
+
- For custom deployments, set `api_url` to the full Chat Inspection endpoint.
|
|
163
|
+
- The evaluator validates the API key at construction and raises if missing.
|
|
164
|
+
- `is_available()` returns false if `httpx` is not installed; discovery will skip registration.
|
|
165
|
+
- `messages_strategy: "history"` forwards the full message array when present; consider `messages_strategy: "single"` if payload size is a concern.
|
|
166
|
+
|
|
167
|
+
## Documentation
|
|
168
|
+
|
|
169
|
+
- Cisco AI Defense Inspection API reference: https://developer.cisco.com/docs/ai-defense-inspection/introduction/
|
|
170
|
+
- Cisco Security Console (get API Key): https://security.cisco.com
|
|
171
|
+
- Cisco AI Defense User Guide: https://securitydocs.cisco.com/docs/ai-def/user/97384.dita
|
|
172
|
+
- Regional API base URLs used by this evaluator:
|
|
173
|
+
- US: `https://us.api.inspect.aidefense.security.cisco.com`
|
|
174
|
+
- AP: `https://ap.api.inspect.aidefense.security.cisco.com`
|
|
175
|
+
- EU: `https://eu.api.inspect.aidefense.security.cisco.com`
|
|
176
|
+
- Chat Inspection path: `/api/v1/inspect/chat`
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agent-control-evaluator-cisco"
|
|
3
|
+
version = "7.7.0"
|
|
4
|
+
description = "Cisco AI Defense evaluator for agent-control"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = { text = "Apache-2.0" }
|
|
8
|
+
authors = [{ name = "Cisco AI Defense Team" }]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"agent-control-evaluators>=7.7.0",
|
|
11
|
+
"agent-control-models>=7.7.0",
|
|
12
|
+
"httpx>=0.24.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = [
|
|
17
|
+
"pytest>=8.0.0",
|
|
18
|
+
"pytest-asyncio>=0.23.0",
|
|
19
|
+
"pytest-cov>=4.0.0",
|
|
20
|
+
"ruff>=0.1.0",
|
|
21
|
+
"mypy>=1.8.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.entry-points."agent_control.evaluators"]
|
|
25
|
+
"cisco.ai_defense" = "agent_control_evaluator_cisco.ai_defense:CiscoAIDefenseEvaluator"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["hatchling"]
|
|
29
|
+
build-backend = "hatchling.build"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/agent_control_evaluator_cisco"]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 100
|
|
36
|
+
target-version = "py312"
|
|
37
|
+
|
|
38
|
+
[tool.ruff.lint]
|
|
39
|
+
select = ["E", "F", "I"]
|
|
40
|
+
|
|
41
|
+
[tool.uv.sources]
|
|
42
|
+
agent-control-evaluators = { path = "../../builtin", editable = true }
|
|
43
|
+
agent-control-models = { path = "../../../models", editable = true }
|
|
@@ -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
|