Skip to content

Commit

Permalink
Stores GenAI case analysis in JSON format in the database while prese…
Browse files Browse the repository at this point in the history
…rving format on the Slack side (#5300)
  • Loading branch information
mvilanova authored Oct 8, 2024
1 parent d61fa77 commit 6796713
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 32 deletions.
20 changes: 10 additions & 10 deletions src/dispatch/case/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@
Table,
UniqueConstraint,
)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
from sqlalchemy_utils import TSVectorType, observes

from dispatch.case.priority.models import CasePriorityBase, CasePriorityCreate, CasePriorityRead
from dispatch.case.severity.models import CaseSeverityBase, CaseSeverityCreate, CaseSeverityRead
from dispatch.case.type.models import CaseTypeBase, CaseTypeCreate, CaseTypeRead
from dispatch.case_cost.models import (
CaseCostRead,
CaseCostUpdate,
)
from dispatch.case.priority.models import CasePriorityBase, CasePriorityCreate, CasePriorityRead
from dispatch.case.severity.models import CaseSeverityBase, CaseSeverityCreate, CaseSeverityRead
from dispatch.case.type.models import CaseTypeBase, CaseTypeCreate, CaseTypeRead
from dispatch.conversation.models import ConversationRead
from dispatch.database.core import Base
from dispatch.document.models import Document, DocumentRead
Expand All @@ -37,18 +38,17 @@
from dispatch.models import (
DispatchBase,
NameStr,
Pagination,
PrimaryKey,
ProjectMixin,
TimeStampMixin,
Pagination,
)
from dispatch.participant.models import (
Participant,
ParticipantRead,
ParticipantReadMinimal,
ParticipantUpdate,
)

from dispatch.storage.models import StorageRead
from dispatch.tag.models import TagRead
from dispatch.ticket.models import TicketRead
Expand Down Expand Up @@ -88,13 +88,12 @@ class Case(Base, TimeStampMixin, ProjectMixin):
visibility = Column(String, default=Visibility.open, nullable=False)
participants_team = Column(String)
participants_location = Column(String)

reported_at = Column(DateTime, default=datetime.utcnow)
triage_at = Column(DateTime)
escalated_at = Column(DateTime)
closed_at = Column(DateTime)

dedicated_channel = Column(Boolean, default=False)
genai_analysis = Column(JSONB, default={}, nullable=False, server_default="{}")

search_vector = Column(
TSVectorType(
Expand Down Expand Up @@ -302,20 +301,21 @@ class CaseRead(CaseBase):
case_severity: CaseSeverityRead
case_type: CaseTypeRead
closed_at: Optional[datetime] = None
conversation: Optional[ConversationRead] = None
created_at: Optional[datetime] = None
documents: Optional[List[DocumentRead]] = []
duplicates: Optional[List[CaseReadMinimal]] = []
escalated_at: Optional[datetime] = None
events: Optional[List[EventRead]] = []
genai_analysis: Optional[dict[str, Any]] = {}
groups: Optional[List[GroupRead]] = []
incidents: Optional[List[IncidentReadMinimal]] = []
conversation: Optional[ConversationRead] = None
name: Optional[NameStr]
participants: Optional[List[ParticipantRead]] = []
project: ProjectRead
related: Optional[List[CaseReadMinimal]] = []
reporter: Optional[ParticipantRead]
reported_at: Optional[datetime] = None
participants: Optional[List[ParticipantRead]] = []
reporter: Optional[ParticipantRead]
signal_instances: Optional[List[SignalInstanceRead]] = []
storage: Optional[StorageRead] = None
tags: Optional[List[TagRead]] = []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Adds genai_analysis to case model
Revision ID: b057c079c2d5
Revises: f5107ce190fc
Create Date: 2024-10-08 10:38:39.668625
"""

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "b057c079c2d5"
down_revision = "f5107ce190fc"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"case",
sa.Column(
"genai_analysis",
postgresql.JSONB(astext_type=sa.Text()),
server_default="{}",
nullable=False,
),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("case", "genai_analysis")
# ### end Alembic commands ###
70 changes: 51 additions & 19 deletions src/dispatch/plugins/dispatch_slack/case/messages.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import logging
from typing import NamedTuple
from typing import NamedTuple, Tuple

from blockkit import (
Actions,
Expand Down Expand Up @@ -292,9 +293,37 @@ def create_action_buttons_message(
return Message(blocks=signal_metadata_blocks).build()["blocks"]


def json_to_slack_format(json_message: dict[str, str]) -> str:
"""
Converts a JSON dictionary to Slack markup format.
Args:
json_dict (dict): The JSON dictionary to convert.
Returns:
str: A string formatted with Slack markup.
"""
slack_message = ""
for key, value in json_message.items():
slack_message += f"*{key}*\n{value}\n\n"
return slack_message.strip()


def create_genai_signal_message_metadata_blocks(
signal_metadata_blocks: list[Block], message: str
signal_metadata_blocks: list[Block], message: str | dict[str, str]
) -> list[Block]:
"""
Appends a GenAI signal analysis section to the signal metadata blocks.
Args:
signal_metadata_blocks (list[Block]): The list of existing signal metadata blocks.
message (str | dict[str, str]): The GenAI analysis message, either as a string or a dictionary.
Returns:
list[Block]: The updated list of signal metadata blocks with the GenAI analysis section appended.
"""
if isinstance(message, dict):
message = json_to_slack_format(message)
signal_metadata_blocks.append(
Section(text=f":magic_wand: *GenAI Alert Analysis*\n\n{message}"),
)
Expand All @@ -308,23 +337,19 @@ def create_genai_signal_analysis_message(
db_session: Session,
client: WebClient,
config: SlackConversationConfiguration,
) -> list[Block]:
) -> Tuple[str, list[Block]]:
"""
Creates a signal analysis using a generative AI plugin.
This function generates an analysis for a given case by leveraging historical context and
a generative AI plugin. It fetches related cases, their resolutions, and relevant Slack
messages to provide a comprehensive analysis.
Creates a GenAI signal analysis message for a given case.
Args:
case (Case): The case object containing details to be included in the analysis.
channel_id (str): The ID of the Slack channel where the analysis will be sent.
db_session (Session): The database session to use for querying signal instances and related cases.
client (WebClient): The Slack WebClient to fetch threaded messages.
case (Case): The case object containing details to be included in the message.
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.
client (WebClient): The Slack WebClient to use for interacting with the Slack API.
config (SlackConversationConfiguration): The Slack conversation configuration.
Returns:
list[Block]: A list of Block objects representing the structure of the Slack message.
Tuple[str, list[Block]]: A tuple containing the GenAI analysis message and a list of Block objects representing the structure of the Slack message.
"""
signal_metadata_blocks: list[Block] = []

Expand All @@ -341,7 +366,7 @@ def create_genai_signal_analysis_message(
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)
return message, create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)

# we fetch related cases
related_cases = []
Expand Down Expand Up @@ -388,7 +413,9 @@ def create_genai_signal_analysis_message(
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)
return message, create_genai_signal_message_metadata_blocks(
signal_metadata_blocks, message
)

historical_context.append("</case>")

Expand All @@ -405,13 +432,13 @@ def create_genai_signal_analysis_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)
return message, 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)
return message, create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)

# we generate the analysis
response = genai_plugin.instance.chat_completion(
Expand All @@ -435,14 +462,19 @@ def create_genai_signal_analysis_message(
"""
)
message = response["choices"][0]["message"]["content"]
message = json.loads(
response["choices"][0]["message"]["content"]
.replace("```json", "")
.replace("```", "")
.strip()
)

# 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)

return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)
return message, create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)


def create_signal_engagement_message(
Expand Down
10 changes: 7 additions & 3 deletions src/dispatch/plugins/dispatch_slack/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,22 @@ def create_threaded(self, case: Case, conversation_id: str, db_session: Session)

# we try to generate a GenAI signal analysis message
try:
if message := create_genai_signal_analysis_message(
message, message_blocks = create_genai_signal_analysis_message(
case=case,
channel_id=conversation_id,
db_session=db_session,
client=client,
config=self.configuration,
):
)
if message:
case.genai_analysis = message

if message_blocks:
signal_response = send_message(
client=client,
conversation_id=conversation_id,
ts=response_timestamp,
blocks=message,
blocks=message_blocks,
)
except Exception as e:
logger.exception(f"Error generating GenAI signal analysis message: {e}")
Expand Down

0 comments on commit 6796713

Please sign in to comment.