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
commit 50f799edf4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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.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(
@ -272,10 +274,15 @@ def handle_message(
if respond_team_member_list:
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
# 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,
@ -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
# 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,
@ -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
# 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,

View File

@ -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]]:

View File

@ -1061,6 +1061,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

View File

@ -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

View File

@ -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:

View File

@ -71,9 +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_team_member_list ?? []
).concat(
existingSlackBotConfig?.channel_config
?.respond_slack_group_list ?? []
),
still_need_help_enabled:
existingSlackBotConfig?.channel_config?.follow_up_tags !==
undefined,
@ -101,7 +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_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()),
@ -116,8 +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_member_group_list.filter(
(slackGroupName) =>
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(slackGroupName)
),
usePersona: usingPersonas,
};
@ -227,14 +236,14 @@ export const SlackBotCreationForm = ({
subtext="If not set, DanswerBot will always ignore messages from Bots"
/>
<TextArrayField
name="respond_team_member_list"
label="Team Members Emails"
name="respond_member_group_list"
label="Team Member Emails Or Slack Group Names"
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
"assistant" mode, where it helps the team members find
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}
/>
<Divider />

View File

@ -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

View File

@ -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[];
}