-
-
Notifications
You must be signed in to change notification settings - Fork 209
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
base: feature/nestbot-assistant
Are you sure you want to change the base?
Nestbot MVP #2113
Changes from all commits
bff36bd
f254af8
a1bba29
ad3f3b4
c1334a6
c9d4a27
ff45de1
4b38f5a
b2c5b59
9b94aed
3038f32
e120962
f24453a
e876a0c
981277a
8b46f08
16fabcf
532be09
77203b8
9e03b53
ed44239
41f8126
c5aba9c
697a406
46cd884
a3255ff
64c079a
7affa22
55132d7
3d7bd48
7d0731b
ff3e61a
c709b9e
1c7fe1c
948c529
1455083
1e8d65e
3f15d7a
742a15e
bd8f280
8610dde
a9da28b
a0ed311
9646366
2d86dcb
011e843
466bca3
9c2556c
c9f260d
197c0ff
4dc3800
346d324
baae5eb
f6bb1bd
6c353d1
506ad46
18937c3
7792ab5
6bc4443
f35bf4c
2a8bbb6
1cbcc28
9c6fdd6
929989d
9580d97
7370b16
337e9d4
4509003
647edba
b7628a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
|
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(), | ||
) |
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() | ||
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
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) | ||
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
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." | ||
) | ||
] |
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. Both 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. can you suggest alternative for it? 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. I have some ideas, however it'd be great to see your suggestions first |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
"""Question detection utilities for Slack OWASP bot.""" | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
import re | ||
|
||
from apps.slack.constants import OWASP_KEYWORDS | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class QuestionDetector: | ||
"""Utility class for detecting OWASP-related questions.""" | ||
|
||
def __init__(self): | ||
"""Initialize the question detector.""" | ||
self.owasp_keywords = OWASP_KEYWORDS | ||
|
||
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.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 self.question_patterns | ||
] | ||
|
||
def is_owasp_question(self, text: str) -> bool: | ||
"""Check if text contains an OWASP-related question.""" | ||
if not text or not text.strip(): | ||
return False | ||
|
||
text_lower = text.lower().strip() | ||
|
||
is_a_question = self.is_question(text_lower) | ||
if not is_a_question: | ||
return False | ||
|
||
return self.contains_owasp_keywords(text_lower) | ||
|
||
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 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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,20 @@ | ||
from apps.slack.apps import SlackConfig | ||
from apps.slack.events import app_home_opened, team_join, url_verification | ||
from apps.slack.events.event import EventBase | ||
from apps.slack.events.member_joined_channel import catch_all, contribute, gsoc, project_nest | ||
def configure_slack_events(): | ||
"""Configure Slack events after Django apps are ready.""" | ||
from apps.slack.apps import SlackConfig | ||
from apps.slack.events import ( | ||
app_home_opened, | ||
app_mention, | ||
message_posted, | ||
team_join, | ||
url_verification, | ||
) | ||
from apps.slack.events.event import EventBase | ||
from apps.slack.events.member_joined_channel import ( | ||
catch_all, | ||
contribute, | ||
gsoc, | ||
project_nest, | ||
) | ||
|
||
if SlackConfig.app: | ||
EventBase.configure_events() | ||
if SlackConfig.app: | ||
EventBase.configure_events() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
"""Slack app mention event handler.""" | ||
|
||
import logging | ||
|
||
from apps.slack.common.handlers.ai import get_blocks | ||
from apps.slack.events.event import EventBase | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class AppMention(EventBase): | ||
"""Handles app mention events when the bot is mentioned in a channel.""" | ||
|
||
event_type = "app_mention" | ||
|
||
def handle_event(self, event, client): | ||
"""Handle an incoming app mention event.""" | ||
channel_id = event.get("channel") | ||
text = event.get("text", "") | ||
|
||
query = text | ||
for mention in event.get("blocks", []): | ||
if mention.get("type") == "rich_text": | ||
for element in mention.get("elements", []): | ||
if element.get("type") == "rich_text_section": | ||
for text_element in element.get("elements", []): | ||
if text_element.get("type") == "text": | ||
query = text_element.get("text", "").strip() | ||
break | ||
|
||
if not query: | ||
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
logger.warning("No query found in app mention") | ||
return | ||
|
||
logger.info("Handling app mention") | ||
|
||
reply_blocks = get_blocks(query=query) | ||
client.chat_postMessage( | ||
channel=channel_id, | ||
blocks=reply_blocks, | ||
text=query, | ||
thread_ts=event.get("thread_ts") or event.get("ts"), | ||
) |
Uh oh!
There was an error while loading. Please reload this page.