Skip to content
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

Improves error handling, logging, and messaging in Slack plugin #5288

Merged
merged 6 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 51 additions & 21 deletions src/dispatch/plugins/dispatch_slack/case/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from slack_sdk.web.client import WebClient
from sqlalchemy.orm import Session

from dispatch.case import service as case_service
from dispatch.case.enums import CaseResolutionReason, CaseStatus
from dispatch.case.models import Case
from dispatch.config import DISPATCH_UI_URL
Expand Down Expand Up @@ -201,7 +202,7 @@ def create_signal_message(case_id: int, channel_id: str, db_session: Session) ->
associated with the case and creating metadata blocks for the message.

Args:
case_id (int): The ID of the case for which to create the signal message.
case (int): The case id for which to create the signal message.
wssheldon marked this conversation as resolved.
Show resolved Hide resolved
channel_id (str): The ID of the Slack channel where the message will be sent.
db_session (Session): The database session to use for querying signal instances.

Expand All @@ -212,21 +213,21 @@ def create_signal_message(case_id: int, channel_id: str, db_session: Session) ->
instances = signal_service.get_instances_in_case(db_session=db_session, case_id=case_id)
(first_instance_id, first_instance_signal) = instances.first()

organization_slug = first_instance_signal.project.organization.slug
case = case_service.get(db_session=db_session, case_id=case_id)

# we create the signal metadata blocks
signal_metadata_blocks = [
Section(text="*Alerts*"),
Section(
text=f"We observed <{DISPATCH_UI_URL}/{organization_slug}/cases/{first_instance_signal.case.name}/signal/{first_instance_signal.id}|{instances.count()} alerts> in this case. The first alert for this case can be seen below."
text=f"We observed <{DISPATCH_UI_URL}/{first_instance_signal.project.organization.slug}/cases/{case.name}/signal/{first_instance_id}|{instances.count()} alerts> in this case. The first alert for this case can be seen below."
),
]

return Message(blocks=signal_metadata_blocks).build()["blocks"]


def create_action_buttons_message(
case_id: int, channel_id: str, db_session: Session
case: Case, channel_id: str, db_session: Session
) -> list[Message]:
"""
Creates a message with action buttons for a given case.
Expand All @@ -243,7 +244,7 @@ def create_action_buttons_message(
list[Message]: A list of Message objects representing the structure of the Slack messages.
"""
# we fetch the first instance to get the organization slug and project id
instances = signal_service.get_instances_in_case(db_session=db_session, case_id=case_id)
instances = signal_service.get_instances_in_case(db_session=db_session, case_id=case.id)
(first_instance_id, first_instance_signal) = instances.first()

organization_slug = first_instance_signal.project.organization.slug
Expand Down Expand Up @@ -321,16 +322,32 @@ def create_genai_signal_analysis_message(
"""
signal_metadata_blocks: list[Block] = []

# we fetch the first instance id and signal
(first_instance_id, first_instance_signal) = signal_service.get_instances_in_case(
db_session=db_session, case_id=case.id
).first()

if not first_instance_id or not first_instance_signal:
message = "Unable to generate GenAI signal analysis. No signal instances found."
signal_instance = signal_service.get_signal_instance(
db_session=db_session, signal_instance_id=first_instance_id
)

# we check if the signal instance exists and if GenAI is enabled
if not signal_instance:
message = (
"Unable to generate GenAI signal analysis. No signal instances found for this case."
)
log.warning(message)
return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)
mvilanova marked this conversation as resolved.
Show resolved Hide resolved

print(f"Signal GenAI Enabled: {signal_instance.signal.genai_enabled}")
mvilanova marked this conversation as resolved.
Show resolved Hide resolved

# we check if GenAI is enabled for the detection
if not signal_instance.signal.genai_enabled:
message = "Unable to generate GenAI signal analysis. GenAI feature not enabled for this detection."
log.warning(message)
return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)

# Fetch related cases
# we fetch related cases
related_cases = []
for resolution_reason in CaseResolutionReason:
related_cases.extend(
Expand All @@ -343,7 +360,7 @@ def create_genai_signal_analysis_message(
.filter(Case.id != case.id)
)

# Prepare historical context
# we prepare historical context
historical_context = []
for related_case in related_cases:
historical_context.append("<case>")
Expand All @@ -353,45 +370,54 @@ def create_genai_signal_analysis_message(
f"<resolution_reason>{related_case.resolution_reason}</resolution_reason>"
)

# Fetch Slack messages for the related case
# we fetch Slack messages for the related case
if related_case.conversation and related_case.conversation.channel_id:
try:
# Fetch threaded messages
# we fetch threaded messages
thread_messages = client.conversations_replies(
channel=related_case.conversation.channel_id,
ts=related_case.conversation.thread_id,
)

# Add relevant messages to the context (e.g., first 5 messages)
for message in thread_messages["messages"][:5]:
# we add relevant messages to the context (e.g., first 10 messages)
for message in thread_messages["messages"][:10]:
historical_context.append(f"<slack_message>{message['text']}</slack_message>")
except SlackApiError as e:
log.error(f"Error fetching Slack messages for case {related_case.name}: {e}")
log.error(
f"Unable to generate GenAI signal analysis. Error fetching Slack messages for case {related_case.name}: {e}"
)
message = "Unable to generate GenAI signal analysis. Error fetching Slack messages."
return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)

historical_context.append("</case>")

historical_context_str = "\n".join(historical_context)

signal_instance = signal_service.get_signal_instance(
db_session=db_session, signal_instance_id=first_instance_id
)

# we fetch the GenAI plugin
genai_plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=case.project.id, plugin_type="artificial-intelligence"
)

# we check if the GenAI plugin is enabled
if not genai_plugin:
message = (
"Unable to generate GenAI signal analysis. No artificial-intelligence plugin enabled."
)
log.warning(message)
return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)

# we check if the GenAI plugin has a prompt
if not signal_instance.signal.genai_prompt:
message = f"Unable to generate GenAI signal analysis. No GenAI prompt defined for {signal_instance.signal.name}"
log.warning(message)
return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)

print(f"Prompt: {signal_instance.signal.genai_prompt}")
print(f"Current Event: {str(signal_instance.raw)}")
print(f"Runbook: {signal_instance.signal.runbook}")
print(f"Historical Context: {historical_context_str}")

# we generate the analysis
response = genai_plugin.instance.chat_completion(
prompt=f"""

Expand All @@ -403,17 +429,21 @@ def create_genai_signal_analysis_message(
{str(signal_instance.raw)}
</current_event>

<runbook>
{signal_instance.signal.runbook}
</runbook>

<historical_context>
{historical_context_str}
</historical_context>

<runbook>
{first_instance_signal.runbook}
</runbook>
"""
)
message = response["choices"][0]["message"]["content"]

print(f"Response: {message}")

# we check if the response is empty
if not message:
message = "Unable to generate GenAI signal analysis. We received an empty response from the artificial-intelligence plugin."
log.warning(message)
Expand Down
11 changes: 8 additions & 3 deletions src/dispatch/plugins/dispatch_slack/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,11 @@ def create_threaded(self, case: Case, conversation_id: str, db_session: Session)
client = create_slack_client(self.configuration)
blocks = create_case_message(case=case, channel_id=conversation_id)
response = send_message(client=client, conversation_id=conversation_id, blocks=blocks)
response_timestamp = response["timestamp"]

if case.signal_instances:
signal_response = None

# we try to generate a GenAI signal analysis message
try:
if message := create_genai_signal_analysis_message(
Expand All @@ -106,13 +109,15 @@ def create_threaded(self, case: Case, conversation_id: str, db_session: Session)
signal_response = send_message(
client=client,
conversation_id=conversation_id,
ts=response["timestamp"],
ts=response_timestamp,
blocks=message,
)
except Exception as e:
logger.exception(f"Error generating GenAI signal analysis message: {e}")

case.signal_thread_ts = signal_response.get("timestamp")
case.signal_thread_ts = (
signal_response.get("timestamp") if signal_response else response_timestamp
)

# we try to generate a signal message
try:
Expand Down Expand Up @@ -151,7 +156,7 @@ def create_threaded(self, case: Case, conversation_id: str, db_session: Session)
# we try to generate action buttons
try:
message = create_action_buttons_message(
case_id=case.id, channel_id=conversation_id, db_session=db_session
case=case, channel_id=conversation_id, db_session=db_session
)
send_message(
client=client,
Expand Down
Loading