Merge pull request #1672 from danswer-ai/add-groups-to-slack-bot-responses

add slack groups to user response list
This commit is contained in:
hagen-danswer
2024-06-24 15:10:12 -07:00
committed by GitHub
8 changed files with 64 additions and 13 deletions

View File

@@ -37,6 +37,7 @@ from danswer.danswerbot.slack.constants import SLACK_CHANNEL_ID
from danswer.danswerbot.slack.models import SlackMessageInfo from danswer.danswerbot.slack.models import SlackMessageInfo
from danswer.danswerbot.slack.utils import ChannelIdAdapter 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_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 respond_in_thread
from danswer.danswerbot.slack.utils import slack_usage_report from danswer.danswerbot.slack.utils import slack_usage_report
from danswer.danswerbot.slack.utils import SlackRateLimiter 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_tag_only = channel_conf.get("respond_tag_only") or False
respond_team_member_list = channel_conf.get("respond_team_member_list") or None 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: if respond_tag_only and not bypass_filters:
logger.info( logger.info(
@@ -272,10 +274,15 @@ def handle_message(
if respond_team_member_list: if respond_team_member_list:
send_to, _ = fetch_userids_from_emails(respond_team_member_list, client) 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
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 # If configured to respond to team members only, then cannot be used with a /DanswerBot command
# which would just respond to the sender # 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: if sender_id:
respond_in_thread( respond_in_thread(
client=client, client=client,
@@ -448,7 +455,7 @@ def handle_message(
# For DM (ephemeral message), we need to create a thread via a normal message so the user can see # 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. # 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( respond_in_thread(
client=client, client=client,
channel=channel, channel=channel,
@@ -593,7 +600,7 @@ def handle_message(
# For DM (ephemeral message), we need to create a thread via a normal message so the user can see # 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. # 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( respond_in_thread(
client=client, client=client,
channel=channel, channel=channel,

View File

@@ -308,6 +308,31 @@ def fetch_userids_from_emails(
return user_ids, failed_to_find 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( def fetch_groupids_from_names(
names: list[str], client: WebClient names: list[str], client: WebClient
) -> tuple[list[str], list[str]]: ) -> tuple[list[str], list[str]]:

View File

@@ -1061,6 +1061,7 @@ class ChannelConfig(TypedDict):
respond_tag_only: NotRequired[bool] # defaults to False respond_tag_only: NotRequired[bool] # defaults to False
respond_to_bots: NotRequired[bool] # defaults to False respond_to_bots: NotRequired[bool] # defaults to False
respond_team_member_list: NotRequired[list[str]] respond_team_member_list: NotRequired[list[str]]
respond_slack_group_list: NotRequired[list[str]]
answer_filters: NotRequired[list[AllowedAnswerFilters]] answer_filters: NotRequired[list[AllowedAnswerFilters]]
# If None then no follow up # If None then no follow up
# If empty list, follow up with no tags # If empty list, follow up with no tags

View File

@@ -104,6 +104,7 @@ class SlackBotConfigCreationRequest(BaseModel):
respond_to_bots: bool = False respond_to_bots: bool = False
# If no team members, assume respond in the channel to everyone # If no team members, assume respond in the channel to everyone
respond_team_member_list: list[str] = [] respond_team_member_list: list[str] = []
respond_slack_group_list: list[str] = []
answer_filters: list[AllowedAnswerFilters] = [] answer_filters: list[AllowedAnswerFilters] = []
# list of user emails # list of user emails
follow_up_tags: list[str] | None = None follow_up_tags: list[str] | None = None

View File

@@ -37,6 +37,9 @@ def _form_channel_config(
respond_team_member_list = ( respond_team_member_list = (
slack_bot_config_creation_request.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 answer_filters = slack_bot_config_creation_request.answer_filters
follow_up_tags = slack_bot_config_creation_request.follow_up_tags follow_up_tags = slack_bot_config_creation_request.follow_up_tags
@@ -58,7 +61,7 @@ def _form_channel_config(
detail=str(e), 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( raise ValueError(
"Cannot set DanswerBot to only respond to tags only and " "Cannot set DanswerBot to only respond to tags only and "
"also respond to a predetermined set of users." "also respond to a predetermined set of users."
@@ -71,6 +74,8 @@ def _form_channel_config(
channel_config["respond_tag_only"] = respond_tag_only channel_config["respond_tag_only"] = respond_tag_only
if respond_team_member_list: if respond_team_member_list:
channel_config["respond_team_member_list"] = 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: if answer_filters:
channel_config["answer_filters"] = answer_filters channel_config["answer_filters"] = answer_filters
if follow_up_tags is not None: if follow_up_tags is not None:

View File

@@ -71,9 +71,13 @@ export const SlackBotCreationForm = ({
existingSlackBotConfig?.channel_config?.respond_tag_only || false, existingSlackBotConfig?.channel_config?.respond_tag_only || false,
respond_to_bots: respond_to_bots:
existingSlackBotConfig?.channel_config?.respond_to_bots || false, existingSlackBotConfig?.channel_config?.respond_to_bots || false,
respond_team_member_list: respond_member_group_list: (
existingSlackBotConfig?.channel_config existingSlackBotConfig?.channel_config
?.respond_team_member_list || ([] as string[]), ?.respond_team_member_list ?? []
).concat(
existingSlackBotConfig?.channel_config
?.respond_slack_group_list ?? []
),
still_need_help_enabled: still_need_help_enabled:
existingSlackBotConfig?.channel_config?.follow_up_tags !== existingSlackBotConfig?.channel_config?.follow_up_tags !==
undefined, undefined,
@@ -101,7 +105,7 @@ export const SlackBotCreationForm = ({
questionmark_prefilter_enabled: Yup.boolean().required(), questionmark_prefilter_enabled: Yup.boolean().required(),
respond_tag_only: Yup.boolean().required(), respond_tag_only: Yup.boolean().required(),
respond_to_bots: Yup.boolean().required(), respond_to_bots: Yup.boolean().required(),
respond_team_member_list: Yup.array().of(Yup.string()).required(), respond_member_group_list: Yup.array().of(Yup.string()).required(),
still_need_help_enabled: Yup.boolean().required(), still_need_help_enabled: Yup.boolean().required(),
follow_up_tags: Yup.array().of(Yup.string()), follow_up_tags: Yup.array().of(Yup.string()),
document_sets: Yup.array().of(Yup.number()), document_sets: Yup.array().of(Yup.number()),
@@ -116,8 +120,13 @@ export const SlackBotCreationForm = ({
channel_names: values.channel_names.filter( channel_names: values.channel_names.filter(
(channelName) => channelName !== "" (channelName) => channelName !== ""
), ),
respond_team_member_list: values.respond_team_member_list.filter( respond_team_member_list: values.respond_member_group_list.filter(
(teamMemberEmail) => teamMemberEmail !== "" (teamMemberEmail) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(teamMemberEmail)
),
respond_slack_group_list: values.respond_member_group_list.filter(
(slackGroupName) =>
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(slackGroupName)
), ),
usePersona: usingPersonas, usePersona: usingPersonas,
}; };
@@ -227,14 +236,14 @@ export const SlackBotCreationForm = ({
subtext="If not set, DanswerBot will always ignore messages from Bots" subtext="If not set, DanswerBot will always ignore messages from Bots"
/> />
<TextArrayField <TextArrayField
name="respond_team_member_list" name="respond_member_group_list"
label="Team Members Emails" label="Team Member Emails Or Slack Group Names"
subtext={`If specified, DanswerBot responses will only be subtext={`If specified, DanswerBot responses will only be
visible to members in this list. This is visible to the members or groups in this list. This is
useful if you want DanswerBot to operate in an useful if you want DanswerBot to operate in an
"assistant" mode, where it helps the team members find "assistant" mode, where it helps the team members find
answers, but let's them build on top of DanswerBot's response / throw answers, but let's them build on top of DanswerBot's response / throw
out the occasional incorrect answer.`} out the occasional incorrect answer. Group names are case sensitive.`}
values={values} values={values}
/> />
<Divider /> <Divider />

View File

@@ -14,6 +14,7 @@ interface SlackBotConfigCreationRequest {
respond_tag_only: boolean; respond_tag_only: boolean;
respond_to_bots: boolean; respond_to_bots: boolean;
respond_team_member_list: string[]; respond_team_member_list: string[];
respond_slack_group_list: string[];
follow_up_tags?: string[]; follow_up_tags?: string[];
usePersona: boolean; usePersona: boolean;
response_type: SlackBotResponseType; response_type: SlackBotResponseType;
@@ -40,6 +41,7 @@ const buildRequestBodyFromCreationRequest = (
respond_tag_only: creationRequest.respond_tag_only, respond_tag_only: creationRequest.respond_tag_only,
respond_to_bots: creationRequest.respond_to_bots, respond_to_bots: creationRequest.respond_to_bots,
respond_team_member_list: creationRequest.respond_team_member_list, respond_team_member_list: creationRequest.respond_team_member_list,
respond_slack_group_list: creationRequest.respond_slack_group_list,
answer_filters: buildFiltersFromCreationRequest(creationRequest), answer_filters: buildFiltersFromCreationRequest(creationRequest),
follow_up_tags: creationRequest.follow_up_tags?.filter((tag) => tag !== ""), follow_up_tags: creationRequest.follow_up_tags?.filter((tag) => tag !== ""),
...(creationRequest.usePersona ...(creationRequest.usePersona

View File

@@ -475,6 +475,7 @@ export interface ChannelConfig {
respond_tag_only?: boolean; respond_tag_only?: boolean;
respond_to_bots?: boolean; respond_to_bots?: boolean;
respond_team_member_list?: string[]; respond_team_member_list?: string[];
respond_slack_group_list?: string[];
answer_filters?: AnswerFilterOption[]; answer_filters?: AnswerFilterOption[];
follow_up_tags?: string[]; follow_up_tags?: string[];
} }