Officially support Slack DMs to DanswerBot (#556)

This commit is contained in:
Yuhong Sun 2023-10-10 18:07:29 -07:00 committed by GitHub
parent fa460f4da1
commit 31d5fc6d31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 58 additions and 34 deletions

View File

@ -12,8 +12,11 @@ VALID_SLACK_FILTERS = [
def get_slack_bot_config_for_channel(
channel_name: str, db_session: Session
channel_name: str | None, db_session: Session
) -> SlackBotConfig | None:
if not channel_name:
return None
slack_bot_configs = fetch_slack_bot_configs(db_session=db_session)
for config in slack_bot_configs:
if channel_name in config.channel_config["channel_names"]:

View File

@ -103,7 +103,7 @@ def handle_message(
if respond_team_member_list:
send_to = fetch_userids_from_emails(respond_team_member_list, client)
# 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
if respond_team_member_list and is_bot_msg:
if sender_id:

View File

@ -22,6 +22,7 @@ from danswer.bots.slack.utils import get_channel_name_from_id
from danswer.bots.slack.utils import respond_in_thread
from danswer.configs.app_configs import DANSWER_BOT_RESPOND_EVERY_CHANNEL
from danswer.configs.app_configs import DANSWER_REACT_EMOJI
from danswer.configs.app_configs import NOTIFY_SLACKBOT_NO_ANSWER
from danswer.connectors.slack.utils import make_slack_api_rate_limited
from danswer.db.engine import get_sqlalchemy_engine
from danswer.dynamic_configs.interface import ConfigNotFoundError
@ -80,7 +81,9 @@ def prefilter_requests(req: SocketModeRequest, client: SocketModeClient) -> bool
if event_type == "message":
bot_tag_id = client.web_client.auth_test().get("user_id")
if bot_tag_id and bot_tag_id in msg:
# DMs with the bot don't pick up the @DanswerBot so we have to keep the
# caught events_api
if bot_tag_id and bot_tag_id in msg and event.get("channel_type") != "im":
# Let the tag flow handle this case, don't reply twice
return False
@ -170,10 +173,13 @@ def build_request_details(
tagged = event.get("type") == "app_mention"
message_ts = event.get("ts")
thread_ts = event.get("thread_ts")
bot_tag_id = client.web_client.auth_test().get("user_id")
# Might exist even if not tagged, specifically in the case of @DanswerBot
# in DanswerBot DM channel
msg = re.sub(rf"<@{bot_tag_id}>\s", "", msg)
if tagged:
logger.info("User tagged DanswerBot")
bot_tag_id = client.web_client.auth_test().get("user_id")
msg = re.sub(rf"<@{bot_tag_id}>\s", "", msg)
return SlackMessageInfo(
msg_content=msg,
@ -248,8 +254,9 @@ def process_message(
req: SocketModeRequest,
client: SocketModeClient,
respond_every_channel: bool = DANSWER_BOT_RESPOND_EVERY_CHANNEL,
notify_no_answer: bool = NOTIFY_SLACKBOT_NO_ANSWER,
) -> None:
logger.info(f"Received Slack request of type: '{req.type}'")
logger.debug(f"Received Slack request of type: '{req.type}'")
# Throw out requests that can't or shouldn't be handled
if not prefilter_requests(req, client):
@ -267,32 +274,37 @@ def process_message(
channel_name=channel_name, db_session=db_session
)
# Be careful about this default, don't want to accidentally spam every channel
if slack_bot_config is None and not respond_every_channel:
logger.info(
"Skipping message since the channel is not configured to use DanswerBot"
# Be careful about this default, don't want to accidentally spam every channel
# Users should be able to DM slack bot in their private channels though
if (
slack_bot_config is None
and not respond_every_channel
# DMs are unnamed, don't filter those out
and channel_name is not None
# If @DanswerBot or /DanswerBot, always respond with the default configs
and not (details.is_bot_msg or details.bipass_filters)
):
return
try:
send_msg_ack_to_user(details, client)
except SlackApiError as e:
logger.error(f"Was not able to react to user message due to: {e}")
failed = handle_message(
message_info=details,
channel_config=slack_bot_config,
client=client.web_client,
)
return
try:
send_msg_ack_to_user(details, client)
except SlackApiError as e:
logger.error(f"Was not able to react to user message due to: {e}")
# Skipping answering due to pre-filtering is not considered a failure
if failed and notify_no_answer:
apologize_for_fail(details, client)
failed = handle_message(
message_info=details,
channel_config=slack_bot_config,
client=client.web_client,
)
# Skipping answering due to pre-filtering is not considered a failure
if failed:
apologize_for_fail(details, client)
try:
remove_react(details, client)
except SlackApiError as e:
logger.error(f"Failed to remove Reaction due to: {e}")
try:
remove_react(details, client)
except SlackApiError as e:
logger.error(f"Failed to remove Reaction due to: {e}")
def acknowledge_message(req: SocketModeRequest, client: SocketModeClient) -> None:

View File

@ -8,6 +8,7 @@ from typing import cast
from retry import retry
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from slack_sdk.models.blocks import Block
from slack_sdk.models.metadata import Metadata
@ -170,8 +171,12 @@ def get_channel_from_id(client: WebClient, channel_id: str) -> dict[str, Any]:
return response["channel"]
def get_channel_name_from_id(client: WebClient, channel_id: str) -> str:
return get_channel_from_id(client, channel_id)["name"]
def get_channel_name_from_id(client: WebClient, channel_id: str) -> str | None:
try:
return get_channel_from_id(client, channel_id).get("name")
except SlackApiError:
# Private channels such as DMs don't have a name
return None
def fetch_userids_from_emails(user_emails: list[str], client: WebClient) -> list[str]:

View File

@ -238,12 +238,16 @@ DANSWER_BOT_DISABLE_DOCS_ONLY_ANSWER = os.environ.get(
"DANSWER_BOT_DISABLE_DOCS_ONLY_ANSWER", ""
).lower() not in ["false", ""]
DANSWER_REACT_EMOJI = os.environ.get("DANSWER_REACT_EMOJI") or "eyes"
# Should DanswerBot send an apology message if it's not able to find an answer
# That way the user isn't confused as to why DanswerBot reacted but then said nothing
# Off by default to be less intrusive (don't want to give a notif that just says we couldnt help)
NOTIFY_SLACKBOT_NO_ANSWER = (
os.environ.get("NOTIFY_SLACKBOT_NO_ANSWER", "").lower() == "true"
)
# Default is only respond in channels that are included by a slack config set in the UI
DANSWER_BOT_RESPOND_EVERY_CHANNEL = (
os.environ.get("DANSWER_BOT_RESPOND_EVERY_CHANNEL", "").lower() == "true"
)
# Add a second LLM call post Answer to verify if the Answer is valid
# Throws out answers that don't directly or fully answer the user query
# This is the default for all DanswerBot channels unless the bot is configured individually

View File

@ -284,7 +284,6 @@ def upsert_persona(
persona.default_persona = default_persona
else:
persona = Persona(
id=persona_id,
name=name,
retrieval_enabled=retrieval_enabled,
system_text=system_text,

View File

@ -81,6 +81,7 @@ services:
- DANSWER_BOT_DISABLE_DOCS_ONLY_ANSWER=${DANSWER_BOT_DISABLE_DOCS_ONLY_ANSWER:-}
- DANSWER_BOT_DISPLAY_ERROR_MSGS=${DANSWER_BOT_DISPLAY_ERROR_MSGS:-}
- DANSWER_BOT_RESPOND_EVERY_CHANNEL=${DANSWER_BOT_RESPOND_EVERY_CHANNEL:-}
- NOTIFY_SLACKBOT_NO_ANSWER=${NOTIFY_SLACKBOT_NO_ANSWER:-}
# Don't change the NLP model configs unless you know what you're doing
- DOCUMENT_ENCODER_MODEL=${DOCUMENT_ENCODER_MODEL:-}
- NORMALIZE_EMBEDDINGS=${NORMALIZE_EMBEDDINGS:-}