-
-
Notifications
You must be signed in to change notification settings - Fork 268
Nestbot MVP #2113
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
Merged
Nestbot MVP #2113
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
34c5451
Sync www-repopsitories (#2164)
Dishant1804 db6a6f2
Consolidate code commits
arkid15r 17db04d
Update cspell/custom-dict.txt
arkid15r d6b4a85
Update docker-compose/local.yaml
arkid15r 5cba678
Merge branch 'feature/nestbot-ai-assistant' into MVP
Dishant1804 a565d10
local yaml worder volume fix
Dishant1804 e2ebd91
instance check
Dishant1804 7207ddf
Merge branch 'feature/nestbot-ai-assistant' into MVP
Dishant1804 0354f7b
poetry file updated
Dishant1804 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
Some comments aren't visible on the classic Files Changed page.
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
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
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
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 |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| from apps.slack.commands.command import CommandBase | ||
|
|
||
| from . import ( | ||
| ai, | ||
| board, | ||
| chapters, | ||
| committees, | ||
|
|
||
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,23 @@ | ||
| """Slack bot AI command.""" | ||
|
|
||
| from apps.slack.commands.command import CommandBase | ||
|
|
||
|
|
||
| class Ai(CommandBase): | ||
| """Slack bot /ai command.""" | ||
|
|
||
| def render_blocks(self, command: dict): | ||
| """Get the rendered blocks. | ||
|
|
||
| Args: | ||
| command (dict): The Slack command payload. | ||
|
|
||
| Returns: | ||
| list: A list of Slack blocks representing the AI response. | ||
|
|
||
| """ | ||
| from apps.slack.common.handlers.ai import get_blocks | ||
|
|
||
| return get_blocks( | ||
| query=command["text"].strip(), | ||
| ) |
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,61 @@ | ||
| """Handler for AI-powered Slack functionality.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
|
|
||
| from apps.ai.agent.tools.rag.rag_tool import RagTool | ||
| from apps.slack.blocks import markdown | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def get_blocks(query: str) -> list[dict]: | ||
| """Get AI response blocks. | ||
|
|
||
| Args: | ||
| query (str): The user's question. | ||
| presentation (EntityPresentation | None): Configuration for entity presentation. | ||
|
|
||
| Returns: | ||
| list: A list of Slack blocks representing the AI response. | ||
|
|
||
| """ | ||
| ai_response = process_ai_query(query.strip()) | ||
|
|
||
| if ai_response: | ||
| return [markdown(ai_response)] | ||
| return get_error_blocks() | ||
|
|
||
|
|
||
| def process_ai_query(query: str) -> str | None: | ||
| """Process the AI query using the RAG tool. | ||
|
|
||
| Args: | ||
| query (str): The user's question. | ||
|
|
||
| Returns: | ||
| str | None: The AI response or None if error occurred. | ||
|
|
||
| """ | ||
| rag_tool = RagTool( | ||
| chat_model="gpt-4o", | ||
| embedding_model="text-embedding-3-small", | ||
| ) | ||
|
|
||
| return rag_tool.query(question=query) | ||
|
|
||
|
|
||
| def get_error_blocks() -> list[dict]: | ||
| """Get error response blocks. | ||
|
|
||
| Returns: | ||
| list: A list of Slack blocks with error message. | ||
|
|
||
| """ | ||
| return [ | ||
| markdown( | ||
| "⚠️ Unfortunately, I'm unable to answer your question at this time.\n" | ||
| "Please try again later or contact support if the issue persists." | ||
| ) | ||
| ] |
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,141 @@ | ||
| """Question detection utilities for Slack OWASP bot.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| import os | ||
| import re | ||
|
|
||
| import openai | ||
|
|
||
| from apps.slack.constants import OWASP_KEYWORDS | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class QuestionDetector: | ||
| """Utility class for detecting OWASP-related questions.""" | ||
|
|
||
| MAX_TOKENS = 50 | ||
| TEMPERATURE = 0.1 | ||
| CHAT_MODEL = "gpt-4o" | ||
|
|
||
| SYSTEM_PROMPT = """ | ||
| You are an expert in cybersecurity and OWASP (Open Web Application Security Project). | ||
| Your task is to determine if a given question is related to OWASP, cybersecurity, | ||
| web application security, or similar topics. | ||
| Key OWASP-related terms: {keywords} | ||
| Respond with only "YES" if the question is related to OWASP/cybersecurity, | ||
| or "NO" if it's not. | ||
| Do not provide any explanation or additional text. | ||
| """ | ||
|
|
||
| def __init__(self): | ||
| """Initialize the question detector. | ||
| Raises: | ||
| ValueError: If the OpenAI API key is not set. | ||
| """ | ||
| if not (openai_api_key := os.getenv("DJANGO_OPEN_AI_SECRET_KEY")): | ||
| error_msg = "DJANGO_OPEN_AI_SECRET_KEY environment variable not set" | ||
| raise ValueError(error_msg) | ||
|
|
||
| self.owasp_keywords = OWASP_KEYWORDS | ||
| self.openai_client = openai.OpenAI(api_key=openai_api_key) | ||
|
|
||
| question_patterns = [ | ||
| r"\?", | ||
| r"^(what|how|why|when|where|which|who|can|could|would|should|is|are|does|do|did)", | ||
| r"(help|explain|tell me|show me|guide|tutorial|example)", | ||
| r"(recommend|suggest|advice|opinion)", | ||
| ] | ||
|
|
||
| self.compiled_patterns = [ | ||
| re.compile(pattern, re.IGNORECASE) for pattern in question_patterns | ||
| ] | ||
|
|
||
| def is_owasp_question(self, text: str) -> bool: | ||
| """Check if the input text is an OWASP-related question. | ||
| This is the main public method that orchestrates the detection logic. | ||
| """ | ||
| if not text or not text.strip(): | ||
| return False | ||
|
|
||
| if not self.is_question(text): | ||
| return False | ||
|
|
||
| openai_result = self.is_owasp_question_with_openai(text) | ||
|
|
||
| if openai_result is None: | ||
| logger.warning( | ||
| "OpenAI detection failed. Falling back to keyword matching", | ||
| ) | ||
| return self.contains_owasp_keywords(text) | ||
|
|
||
| if openai_result: | ||
| return True | ||
| if self.contains_owasp_keywords(text): | ||
| logger.info( | ||
| "OpenAI classified as non-OWASP, but keywords were detected. Overriding to TRUE." | ||
| ) | ||
| return True | ||
| return False | ||
|
|
||
| def is_question(self, text: str) -> bool: | ||
| """Check if text appears to be a question.""" | ||
| return any(pattern.search(text) for pattern in self.compiled_patterns) | ||
|
|
||
| def is_owasp_question_with_openai(self, text: str) -> bool | None: | ||
| """Determine if the text is an OWASP-related question. | ||
| Returns: | ||
| - True: If the model responds with "YES". | ||
| - False: If the model responds with "NO". | ||
| - None: If the API call fails or the response is unexpected. | ||
| """ | ||
| system_prompt = self.SYSTEM_PROMPT.format(keywords=", ".join(self.owasp_keywords)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What content / knowledge base is used to determine the answer? |
||
| user_prompt = f'Question: "{text}"' | ||
|
|
||
| try: | ||
| response = self.openai_client.chat.completions.create( | ||
| model=self.CHAT_MODEL, | ||
| messages=[ | ||
| {"role": "system", "content": system_prompt}, | ||
| {"role": "user", "content": user_prompt}, | ||
| ], | ||
| temperature=self.TEMPERATURE, | ||
| max_tokens=self.MAX_TOKENS, | ||
| ) | ||
| except openai.OpenAIError: | ||
| logger.exception("OpenAI API error during question detection") | ||
| return None | ||
| else: | ||
| answer = response.choices[0].message.content | ||
| if not answer: | ||
| logger.error("OpenAI returned an empty response") | ||
| return None | ||
|
|
||
| clean_answer = answer.strip().upper() | ||
|
|
||
| if "YES" in clean_answer: | ||
| return True | ||
| if "NO" in clean_answer: | ||
| return False | ||
| logger.warning("Unexpected OpenAI response") | ||
| return None | ||
|
|
||
| def contains_owasp_keywords(self, text: str) -> bool: | ||
| """Check if text contains OWASP-related keywords.""" | ||
| words = re.findall(r"\b\w+\b", text) | ||
| text_words = set(words) | ||
|
|
||
| intersection = self.owasp_keywords.intersection(text_words) | ||
| if intersection: | ||
| return True | ||
|
|
||
| return any(" " in keyword and keyword in text for keyword in self.owasp_keywords) | ||
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 |
|---|---|---|
|
|
@@ -22,6 +22,64 @@ | |
| OWASP_SPONSORSHIP_CHANNEL_ID = "#C08EGFDD9L2" | ||
| OWASP_THREAT_MODELING_CHANNEL_ID = "#C1CS3C6AF" | ||
|
|
||
| OWASP_KEYWORDS = { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This approach doesn't scale well with OWASP content growing. |
||
| "api security", | ||
| "appsec", | ||
| "application security", | ||
| "assessment", | ||
| "authentication", | ||
| "authorization", | ||
| "cheat sheet series", | ||
| "chapter", | ||
| "code review", | ||
| "committee", | ||
| "cryptography", | ||
| "csrf", | ||
| "defectdojo", | ||
| "dependency", | ||
| "devops", | ||
| "devsecops", | ||
| "dynamic analysis", | ||
| "encryption", | ||
| "event", | ||
| "firewall", | ||
| "injection", | ||
| "juice shop", | ||
| "mobile security", | ||
| "nest", | ||
| "nettacker", | ||
| "owasp", | ||
| "penetration", | ||
| "project", | ||
| "rasp", | ||
| "red team", | ||
| "risk", | ||
| "sbom", | ||
| "secure", | ||
| "secure coding", | ||
| "security", | ||
| "security best practice", | ||
| "security bug", | ||
| "security fix", | ||
| "security framework", | ||
| "security guideline", | ||
| "security patch", | ||
| "security policy", | ||
| "security standard", | ||
| "security testing", | ||
| "security tools", | ||
| "static analysis", | ||
| "threat", | ||
| "threat modeling", | ||
| "top 10", | ||
| "top10", | ||
| "vulnerabilities", | ||
| "vulnerability", | ||
| "web security", | ||
| "webgoat", | ||
| "xss", | ||
| } | ||
|
|
||
| OWASP_WORKSPACE_ID = "T04T40NHX" | ||
|
|
||
| VIEW_PROJECTS_ACTION = "view_projects_action" | ||
|
|
||
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have Prompt models for this.