From 694e9e86794df9b4cb08d6f3c02fd3a7b47e0fcb Mon Sep 17 00:00:00 2001 From: hagen-danswer Date: Wed, 19 Jun 2024 22:11:33 -0700 Subject: [PATCH 1/3] finished first draft --- .../slack/handlers/handle_message.py | 8 +++++- backend/danswer/danswerbot/slack/utils.py | 25 +++++++++++++++++++ backend/danswer/db/models.py | 1 + backend/danswer/server/manage/models.py | 1 + backend/danswer/server/manage/slack_bot.py | 7 +++++- .../admin/bot/SlackBotConfigCreationForm.tsx | 16 ++++++++++++ web/src/app/admin/bot/lib.ts | 2 ++ web/src/lib/types.ts | 1 + 8 files changed, 59 insertions(+), 2 deletions(-) diff --git a/backend/danswer/danswerbot/slack/handlers/handle_message.py b/backend/danswer/danswerbot/slack/handlers/handle_message.py index 90b3b354c..2cd309547 100644 --- a/backend/danswer/danswerbot/slack/handlers/handle_message.py +++ b/backend/danswer/danswerbot/slack/handlers/handle_message.py @@ -37,6 +37,7 @@ from danswer.danswerbot.slack.constants import SLACK_CHANNEL_ID from danswer.danswerbot.slack.models import SlackMessageInfo from danswer.danswerbot.slack.utils import ChannelIdAdapter from danswer.danswerbot.slack.utils import fetch_userids_from_emails +from danswer.danswerbot.slack.utils import fetch_userids_from_groups from danswer.danswerbot.slack.utils import respond_in_thread from danswer.danswerbot.slack.utils import slack_usage_report from danswer.danswerbot.slack.utils import SlackRateLimiter @@ -262,6 +263,7 @@ def handle_message( respond_tag_only = channel_conf.get("respond_tag_only") or False respond_team_member_list = channel_conf.get("respond_team_member_list") or None + respond_slack_group_list = channel_conf.get("respond_slack_group_list") or None if respond_tag_only and not bypass_filters: logger.info( @@ -271,7 +273,11 @@ def handle_message( return False if respond_team_member_list: - send_to, _ = fetch_userids_from_emails(respond_team_member_list, client) + user_ids, _ = fetch_userids_from_emails(respond_team_member_list, client) + send_to = (send_to + user_ids) if send_to else user_ids + if respond_slack_group_list: + user_ids, _ = fetch_userids_from_groups(respond_slack_group_list, client) + send_to = (send_to + user_ids) if send_to else user_ids # If configured to respond to team members only, then cannot be used with a /DanswerBot command # which would just respond to the sender diff --git a/backend/danswer/danswerbot/slack/utils.py b/backend/danswer/danswerbot/slack/utils.py index 892734e99..12f7b4662 100644 --- a/backend/danswer/danswerbot/slack/utils.py +++ b/backend/danswer/danswerbot/slack/utils.py @@ -308,6 +308,31 @@ def fetch_userids_from_emails( return user_ids, failed_to_find +def fetch_userids_from_groups( + group_names: list[str], client: WebClient +) -> tuple[list[str], list[str]]: + user_ids: list[str] = [] + failed_to_find: list[str] = [] + for group_name in group_names: + try: + # First, find the group ID from the group name + response = client.usergroups_list() + groups = {group["name"]: group["id"] for group in response["usergroups"]} + group_id = groups.get(group_name) + + if group_id: + # Fetch user IDs for the group + response = client.usergroups_users_list(usergroup=group_id) + user_ids.extend(response["users"]) + else: + failed_to_find.append(group_name) + except Exception as e: + logger.error(f"Error fetching user IDs for group {group_name}: {str(e)}") + failed_to_find.append(group_name) + + return user_ids, failed_to_find + + def fetch_groupids_from_names( names: list[str], client: WebClient ) -> tuple[list[str], list[str]]: diff --git a/backend/danswer/db/models.py b/backend/danswer/db/models.py index 29ca74bfc..01cd4c6e3 100644 --- a/backend/danswer/db/models.py +++ b/backend/danswer/db/models.py @@ -1059,6 +1059,7 @@ class ChannelConfig(TypedDict): respond_tag_only: NotRequired[bool] # defaults to False respond_to_bots: NotRequired[bool] # defaults to False respond_team_member_list: NotRequired[list[str]] + respond_slack_group_list: NotRequired[list[str]] answer_filters: NotRequired[list[AllowedAnswerFilters]] # If None then no follow up # If empty list, follow up with no tags diff --git a/backend/danswer/server/manage/models.py b/backend/danswer/server/manage/models.py index cbc3251bf..1e4f44789 100644 --- a/backend/danswer/server/manage/models.py +++ b/backend/danswer/server/manage/models.py @@ -104,6 +104,7 @@ class SlackBotConfigCreationRequest(BaseModel): respond_to_bots: bool = False # If no team members, assume respond in the channel to everyone respond_team_member_list: list[str] = [] + respond_slack_group_list: list[str] = [] answer_filters: list[AllowedAnswerFilters] = [] # list of user emails follow_up_tags: list[str] | None = None diff --git a/backend/danswer/server/manage/slack_bot.py b/backend/danswer/server/manage/slack_bot.py index aade420ad..979776983 100644 --- a/backend/danswer/server/manage/slack_bot.py +++ b/backend/danswer/server/manage/slack_bot.py @@ -37,6 +37,9 @@ def _form_channel_config( respond_team_member_list = ( slack_bot_config_creation_request.respond_team_member_list ) + respond_slack_group_list = ( + slack_bot_config_creation_request.respond_slack_group_list + ) answer_filters = slack_bot_config_creation_request.answer_filters follow_up_tags = slack_bot_config_creation_request.follow_up_tags @@ -58,7 +61,7 @@ def _form_channel_config( detail=str(e), ) - if respond_tag_only and respond_team_member_list: + if respond_tag_only and (respond_team_member_list or respond_slack_group_list): raise ValueError( "Cannot set DanswerBot to only respond to tags only and " "also respond to a predetermined set of users." @@ -71,6 +74,8 @@ def _form_channel_config( channel_config["respond_tag_only"] = respond_tag_only if respond_team_member_list: channel_config["respond_team_member_list"] = respond_team_member_list + if respond_slack_group_list: + channel_config["respond_slack_group_list"] = respond_slack_group_list if answer_filters: channel_config["answer_filters"] = answer_filters if follow_up_tags is not None: diff --git a/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx b/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx index 6a5984125..cfcca211d 100644 --- a/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx +++ b/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx @@ -74,6 +74,9 @@ export const SlackBotCreationForm = ({ respond_team_member_list: existingSlackBotConfig?.channel_config ?.respond_team_member_list || ([] as string[]), + respond_slack_group_list: + existingSlackBotConfig?.channel_config + ?.respond_slack_group_list || ([] as string[]), still_need_help_enabled: existingSlackBotConfig?.channel_config?.follow_up_tags !== undefined, @@ -102,6 +105,7 @@ export const SlackBotCreationForm = ({ respond_tag_only: Yup.boolean().required(), respond_to_bots: Yup.boolean().required(), respond_team_member_list: Yup.array().of(Yup.string()).required(), + respond_slack_group_list: Yup.array().of(Yup.string()).required(), still_need_help_enabled: Yup.boolean().required(), follow_up_tags: Yup.array().of(Yup.string()), document_sets: Yup.array().of(Yup.number()), @@ -119,6 +123,9 @@ export const SlackBotCreationForm = ({ respond_team_member_list: values.respond_team_member_list.filter( (teamMemberEmail) => teamMemberEmail !== "" ), + respond_slack_group_list: values.respond_slack_group_list.filter( + (slackGroupName) => slackGroupName !== "" + ), usePersona: usingPersonas, }; if (!cleanedValues.still_need_help_enabled) { @@ -237,6 +244,15 @@ export const SlackBotCreationForm = ({ out the occasional incorrect answer.`} values={values} /> + Post Response Behavior diff --git a/web/src/app/admin/bot/lib.ts b/web/src/app/admin/bot/lib.ts index b6c6bec16..07feab85d 100644 --- a/web/src/app/admin/bot/lib.ts +++ b/web/src/app/admin/bot/lib.ts @@ -14,6 +14,7 @@ interface SlackBotConfigCreationRequest { respond_tag_only: boolean; respond_to_bots: boolean; respond_team_member_list: string[]; + respond_slack_group_list: string[]; follow_up_tags?: string[]; usePersona: boolean; response_type: SlackBotResponseType; @@ -40,6 +41,7 @@ const buildRequestBodyFromCreationRequest = ( respond_tag_only: creationRequest.respond_tag_only, respond_to_bots: creationRequest.respond_to_bots, respond_team_member_list: creationRequest.respond_team_member_list, + respond_slack_group_list: creationRequest.respond_slack_group_list, answer_filters: buildFiltersFromCreationRequest(creationRequest), follow_up_tags: creationRequest.follow_up_tags?.filter((tag) => tag !== ""), ...(creationRequest.usePersona diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 138765c5c..30c336f97 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -475,6 +475,7 @@ export interface ChannelConfig { respond_tag_only?: boolean; respond_to_bots?: boolean; respond_team_member_list?: string[]; + respond_slack_group_list?: string[]; answer_filters?: AnswerFilterOption[]; follow_up_tags?: string[]; } From 287a706e894f809bdfdf306c59bf1f7c7170f5ec Mon Sep 17 00:00:00 2001 From: hagen-danswer Date: Thu, 20 Jun 2024 11:48:14 -0700 Subject: [PATCH 2/3] combined the input fields --- .../slack/handlers/handle_message.py | 8 ++-- .../admin/bot/SlackBotConfigCreationForm.tsx | 39 ++++++++----------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/backend/danswer/danswerbot/slack/handlers/handle_message.py b/backend/danswer/danswerbot/slack/handlers/handle_message.py index 2cd309547..a8f82a4a2 100644 --- a/backend/danswer/danswerbot/slack/handlers/handle_message.py +++ b/backend/danswer/danswerbot/slack/handlers/handle_message.py @@ -278,10 +278,12 @@ def handle_message( if respond_slack_group_list: user_ids, _ = fetch_userids_from_groups(respond_slack_group_list, client) send_to = (send_to + user_ids) if send_to else user_ids + if send_to: + send_to = list(set(send_to)) # remove duplicates # If configured to respond to team members only, then cannot be used with a /DanswerBot command # which would just respond to the sender - if respond_team_member_list and is_bot_msg: + if (respond_team_member_list or respond_slack_group_list) and is_bot_msg: if sender_id: respond_in_thread( client=client, @@ -454,7 +456,7 @@ def handle_message( # For DM (ephemeral message), we need to create a thread via a normal message so the user can see # the ephemeral message. This also will give the user a notification which ephemeral message does not. - if respond_team_member_list: + if respond_team_member_list or respond_slack_group_list: respond_in_thread( client=client, channel=channel, @@ -599,7 +601,7 @@ def handle_message( # For DM (ephemeral message), we need to create a thread via a normal message so the user can see # the ephemeral message. This also will give the user a notification which ephemeral message does not. - if respond_team_member_list: + if respond_team_member_list or respond_slack_group_list: respond_in_thread( client=client, channel=channel, diff --git a/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx b/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx index cfcca211d..8be9ef6ad 100644 --- a/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx +++ b/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx @@ -71,12 +71,13 @@ export const SlackBotCreationForm = ({ existingSlackBotConfig?.channel_config?.respond_tag_only || false, respond_to_bots: existingSlackBotConfig?.channel_config?.respond_to_bots || false, - respond_team_member_list: + respond_member_group_list: ( existingSlackBotConfig?.channel_config - ?.respond_team_member_list || ([] as string[]), - respond_slack_group_list: + ?.respond_team_member_list ?? [] + ).concat( existingSlackBotConfig?.channel_config - ?.respond_slack_group_list || ([] as string[]), + ?.respond_slack_group_list ?? [] + ), still_need_help_enabled: existingSlackBotConfig?.channel_config?.follow_up_tags !== undefined, @@ -104,8 +105,7 @@ export const SlackBotCreationForm = ({ questionmark_prefilter_enabled: Yup.boolean().required(), respond_tag_only: Yup.boolean().required(), respond_to_bots: Yup.boolean().required(), - respond_team_member_list: Yup.array().of(Yup.string()).required(), - respond_slack_group_list: Yup.array().of(Yup.string()).required(), + respond_member_group_list: Yup.array().of(Yup.string()).required(), still_need_help_enabled: Yup.boolean().required(), follow_up_tags: Yup.array().of(Yup.string()), document_sets: Yup.array().of(Yup.number()), @@ -120,11 +120,13 @@ export const SlackBotCreationForm = ({ channel_names: values.channel_names.filter( (channelName) => channelName !== "" ), - respond_team_member_list: values.respond_team_member_list.filter( - (teamMemberEmail) => teamMemberEmail !== "" + respond_team_member_list: values.respond_member_group_list.filter( + (teamMemberEmail) => + /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(teamMemberEmail) ), - respond_slack_group_list: values.respond_slack_group_list.filter( - (slackGroupName) => slackGroupName !== "" + respond_slack_group_list: values.respond_member_group_list.filter( + (slackGroupName) => + !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(slackGroupName) ), usePersona: usingPersonas, }; @@ -234,23 +236,14 @@ export const SlackBotCreationForm = ({ subtext="If not set, DanswerBot will always ignore messages from Bots" /> - From b4675082b123b117d5422c88efc03e81c9866074 Mon Sep 17 00:00:00 2001 From: hagen-danswer Date: Thu, 20 Jun 2024 21:16:26 -0400 Subject: [PATCH 3/3] Update handle_message.py --- backend/danswer/danswerbot/slack/handlers/handle_message.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/danswer/danswerbot/slack/handlers/handle_message.py b/backend/danswer/danswerbot/slack/handlers/handle_message.py index a8f82a4a2..abaf094ce 100644 --- a/backend/danswer/danswerbot/slack/handlers/handle_message.py +++ b/backend/danswer/danswerbot/slack/handlers/handle_message.py @@ -273,8 +273,7 @@ def handle_message( return False if respond_team_member_list: - user_ids, _ = fetch_userids_from_emails(respond_team_member_list, client) - send_to = (send_to + user_ids) if send_to else user_ids + send_to, _ = fetch_userids_from_emails(respond_team_member_list, client) if respond_slack_group_list: user_ids, _ = fetch_userids_from_groups(respond_slack_group_list, client) send_to = (send_to + user_ids) if send_to else user_ids