-
Notifications
You must be signed in to change notification settings - Fork 592
feat: Add Cisco AI Defense integration #1433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Pouyanpi
merged 6 commits into
NVIDIA-NeMo:develop
from
rucpande:feature/cisco-ai-defense-integration-1420
Oct 16, 2025
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
87621aa
feat: Add Cisco AI Defense integration
rucpande 0d9e2a4
Address PR review comments:
rucpande 5338a9d
Address review comments. Add configurable timeout and fail_open setti…
rucpande 74caa0b
Addressed review comments, improved error handling, added fixes and u…
rucpande 15960f8
Minor doc edits
rucpande 8c53797
Docs changes - add link to API docs, added a diagram showing the flow.
rucpande File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Cisco AI Defense Integration | ||
|
|
||
| [Cisco AI Defense](https://www.cisco.com/site/us/en/products/security/ai-defense/index.html?utm_medium=github&utm_campaign=nemo-guardrails) allows you to protect LLM interactions. This integration enables NeMo Guardrails to use Cisco AI Defense to protect input and output flows. | ||
|
|
||
| You'll need to set the following env variables to work with Cisco AI Defense: | ||
|
|
||
| 1. AI_DEFENSE_API_ENDPOINT - This is the URL for the Cisco AI Defense inspection API endpoint. This will look like https://[REGION].api.inspect.aidefense.security.cisco.com/api/v1/inspect/chat where REGION is us, ap, eu, etc. | ||
| 2. AI_DEFENSE_API_KEY - This is the API key for Cisco AI Defense. It is used to authenticate the API request. It can be generated from the Cisco Security Cloud Control UI at https://security.cisco.com | ||
|
|
||
| ## Setup | ||
|
|
||
| 1. Ensure that you have access to the Cisco AI Defense endpoints (SaaS or in your private deployment) | ||
| 2. Enable Cisco AI Defense flows in your `config.yml` file: | ||
|
|
||
| ```yaml | ||
| rails: | ||
| input: | ||
| flows: | ||
| - ai defense inspect prompt | ||
|
|
||
| output: | ||
| flows: | ||
| - ai defense inspect response | ||
| ``` | ||
|
|
||
| Don't forget to set the `AI_DEFENSE_API_ENDPOINT` and `AI_DEFENSE_API_KEY` environment variables. | ||
|
|
||
| ## Usage | ||
|
|
||
| Once configured, the Cisco AI Defense integration will automatically: | ||
|
|
||
| 1. Protect prompts before they are processed by the LLM. | ||
| 2. Protect LLM outputs before they are sent back to the user. | ||
|
|
||
| The `ai_defense_inspect` action in `nemoguardrails/library/ai_defense/actions.py` handles the protection process. | ||
|
|
||
| ## Error Handling | ||
|
|
||
| If the Cisco AI Defense API request fails, it will operate in a fail-open mode (not blocking the prompt/response). | ||
|
|
||
| ## Notes | ||
|
|
||
| For more information on Cisco AI Defense capabilities and configuration, please refer to the [Cisco AI Defense documentation](https://securitydocs.cisco.com/docs/scc/admin/108321.dita?utm_medium=github&utm_campaign=nemo-guardrails). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # Cisco AI Defense Configuration Example | ||
|
|
||
| This example contains configuration files for using Cisco AI Defense in your NeMo Guardrails project. | ||
|
|
||
| For more details on the Cisco AI Defense integration, see [Cisco AI Defense Integration User Guide](../../../docs/user-guides/community/ai-defense.md). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| models: | ||
| - type: main | ||
| engine: openai | ||
| model: gpt-4o-mini | ||
|
|
||
| rails: | ||
| input: | ||
| flows: | ||
| - ai defense inspect prompt | ||
|
|
||
| output: | ||
| flows: | ||
| - ai defense inspect response |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """Prompt/Response protection using Cisco AI Defense.""" | ||
|
|
||
| import logging | ||
| import os | ||
| from typing import Any, Dict, Optional | ||
|
|
||
| import httpx | ||
|
|
||
| from nemoguardrails.actions import action | ||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def ai_defense_text_mapping(result: dict) -> bool: | ||
| """ | ||
| Mapping for inspect API response | ||
| Expects result to be a dict with: | ||
| - "is_blocked": a boolean indicating if the prompt or response passed sent to AI Defense should be blocked. | ||
|
|
||
| Returns: | ||
Pouyanpi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| True if the response should be blocked (i.e. if "is_safe" is False), | ||
rucpande marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| False otherwise. | ||
| """ | ||
| # If the provider does not return "is_safe", default to safe (not blocked) | ||
| is_blocked = result.get("is_blocked", True) | ||
| return is_blocked | ||
|
|
||
|
|
||
| @action(is_system_action=True, output_mapping=ai_defense_text_mapping) | ||
| async def ai_defense_inspect( | ||
| user_prompt: Optional[str] = None, bot_response: Optional[str] = None, **kwargs | ||
| ): | ||
| api_key = os.environ.get("AI_DEFENSE_API_KEY") | ||
| if api_key is None: | ||
rucpande marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| msg = "AI_DEFENSE_API_KEY environment variable not set." | ||
| log.error(msg) | ||
| raise ValueError(msg) | ||
|
|
||
| api_endpoint = os.environ.get("AI_DEFENSE_API_ENDPOINT") | ||
| if api_endpoint is None: | ||
rucpande marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| msg = "AI_DEFENSE_API_ENDPOINT environment variable not set." | ||
| log.error(msg) | ||
| raise ValueError(msg) | ||
|
|
||
| headers = { | ||
| "X-Cisco-AI-Defense-API-Key": api_key, | ||
| "Content-Type": "application/json", | ||
| "Accept": "application/json", | ||
| } | ||
|
|
||
| if bot_response is not None: | ||
| role = "assistant" | ||
| text = str(bot_response) | ||
| elif user_prompt is not None: | ||
| role = "user" | ||
| text = str(user_prompt) | ||
| else: | ||
| msg = "Either user_prompt or bot_response must be provided" | ||
| log.error(msg) | ||
| raise ValueError(msg) | ||
Pouyanpi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| messages = [{"role": role, "content": text}] | ||
|
|
||
| metadata = None | ||
| user = kwargs.get("user") | ||
| if user is not None: | ||
| metadata = {"user": user} | ||
|
|
||
| payload: Dict[str, Any] = {"messages": messages} | ||
| if metadata: | ||
| payload["metadata"] = metadata | ||
Pouyanpi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| async with httpx.AsyncClient() as client: | ||
| try: | ||
| resp = await client.post(api_endpoint, headers=headers, json=payload) | ||
| resp.raise_for_status() | ||
| data = resp.json() | ||
| except httpx.HTTPStatusError as e: | ||
| msg = f"Error calling AI Defense API: {e}" | ||
| log.error(msg) | ||
| raise ValueError(msg) | ||
|
|
||
| # Compose a consistent return structure for flows | ||
| is_safe = bool(data.get("is_safe", True)) | ||
rucpande marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| rules = data.get("rules") or [] | ||
| if not is_safe and rules: | ||
| entries = [ | ||
| f"{r.get('rule_name')} ({r.get('classification')})" | ||
| for r in rules | ||
| if isinstance(r, dict) | ||
| ] | ||
| if entries: | ||
| log.info("AI Defense matched rules: %s", ", ".join(entries)) | ||
rucpande marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # Ensure flows can check explicit block flag | ||
| result: Dict[str, Any] = { | ||
| "is_blocked": (not is_safe), | ||
| "is_safe": is_safe, | ||
| } | ||
|
|
||
| return result | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # INPUT RAILS | ||
|
|
||
| flow ai defense inspect prompt | ||
| """Check if the prompt is safe according to AI Defense.""" | ||
| $result = await AiDefenseInspectAction(user_prompt=$user_message) | ||
| if $result["is_blocked"] | ||
| if $system.config.enable_rails_exceptions | ||
| send AIDefenseRailException(message="Prompt not allowed. The prompt was blocked by the 'ai defense inspect prompt' flow.") | ||
| else | ||
| bot refuse to respond | ||
| abort | ||
|
|
||
|
|
||
| # OUTPUT RAILS | ||
|
|
||
| flow ai defense inspect response | ||
| """Check if the response is safe according to AI Defense.""" | ||
| $result = await AiDefenseInspectAction(bot_response=$bot_message) | ||
| if $result["is_blocked"] | ||
| if $system.config.enable_rails_exceptions | ||
| send AIDefenseRailException(message="Response not allowed. The response was blocked by the 'ai defense inspect response' flow.") | ||
| else | ||
| bot refuse to respond | ||
| abort |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # INPUT RAILS | ||
|
|
||
| define subflow ai defense inspect prompt | ||
| """Check if the prompt is safe according to AI Defense.""" | ||
| $result = execute ai_defense_inspect(user_prompt=$user_message) | ||
| if $result["is_blocked"] | ||
| if $config.enable_rails_exceptions | ||
| create event AIDefenseRailException(message="Prompt not allowed. The prompt was blocked by the 'ai defense inspect prompt' flow.") | ||
| else | ||
| bot refuse to respond | ||
| stop | ||
|
|
||
|
|
||
| # OUTPUT RAILS | ||
|
|
||
| define subflow ai defense inspect response | ||
| """Check if the response is safe according to AI Defense.""" | ||
| $result = execute ai_defense_inspect(bot_response=$bot_message) | ||
| if $result["is_blocked"] | ||
| if $config.enable_rails_exceptions | ||
| create event AIDefenseRailException(message="Response not allowed. The response was blocked by the 'ai defense inspect response' flow.") | ||
| else | ||
| bot refuse to respond | ||
| stop |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.