Skip to content

Commit

Permalink
assistant builder Slackbot channel based routing (#1999)
Browse files Browse the repository at this point in the history
* WIP assistant builder Slackbot channel based routing

* remove disabled

* honor channel-based routing

* fix bugs

* permissions filter

* ui improvment

* wrap in tx

* fix comment
  • Loading branch information
fontanierh authored Oct 9, 2023
1 parent a9c8f9f commit 40847de
Show file tree
Hide file tree
Showing 10 changed files with 809 additions and 114 deletions.
86 changes: 0 additions & 86 deletions connectors/src/api/slack_channel_link_with_agent.ts

This file was deleted.

160 changes: 160 additions & 0 deletions connectors/src/api/slack_channels_linked_with_agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { Request, Response } from "express";
import { isLeft } from "fp-ts/lib/Either";
import * as t from "io-ts";
import * as reporter from "io-ts-reporters";
import { Op } from "sequelize";

import { APIErrorWithStatusCode } from "@connectors/lib/error";
import { sequelize_conn, SlackChannel } from "@connectors/lib/models";
import { apiError, withLogging } from "@connectors/logger/withlogging";

const PatchSlackChannelsLinkedWithAgentReqBodySchema = t.type({
agent_configuration_id: t.string,
slack_channel_ids: t.array(t.string),
connector_id: t.string,
});

type PatchSlackChannelsLinkedWithAgentReqBody = t.TypeOf<
typeof PatchSlackChannelsLinkedWithAgentReqBodySchema
>;

type PatchSlackChannelsLinkedWithAgentResBody =
| { success: true }
| APIErrorWithStatusCode;

const _patchSlackChannelsLinkedWithAgentHandler = async (
req: Request<
Record<string, string>,
PatchSlackChannelsLinkedWithAgentResBody,
PatchSlackChannelsLinkedWithAgentReqBody
>,
res: Response<PatchSlackChannelsLinkedWithAgentResBody>
) => {
const bodyValidation = PatchSlackChannelsLinkedWithAgentReqBodySchema.decode(
req.body
);

if (isLeft(bodyValidation)) {
const pathError = reporter.formatValidationErrors(bodyValidation.left);
return apiError(req, res, {
api_error: {
type: "invalid_request_error",
message: `Invalid request body: ${pathError}`,
},
status_code: 400,
});
}

const {
connector_id: connectorId,
agent_configuration_id: agentConfigurationId,
slack_channel_ids: slackChannelIds,
} = bodyValidation.right;

const slackChannels = await SlackChannel.findAll({
where: {
slackChannelId: slackChannelIds,
connectorId,
},
});

const foundSlackChannelIds = new Set(
slackChannels.map((c) => c.slackChannelId)
);

const missingSlackChannelIds = Array.from(
new Set(slackChannelIds.filter((id) => !foundSlackChannelIds.has(id)))
);

if (missingSlackChannelIds.length) {
return apiError(req, res, {
api_error: {
type: "not_found",
message: `Slack channel(s) not found: ${missingSlackChannelIds.join(
", "
)}`,
},
status_code: 404,
});
}

await sequelize_conn.transaction(async (t) => {
await SlackChannel.update(
{ agentConfigurationId: null },
{
where: {
connectorId,
agentConfigurationId,
},
transaction: t,
}
);
await Promise.all(
slackChannels.map((slackChannel) =>
slackChannel.update({ agentConfigurationId }, { transaction: t })
)
);
});

res.status(200).json({
success: true,
});
};

export const patchSlackChannelsLinkedWithAgentHandler = withLogging(
_patchSlackChannelsLinkedWithAgentHandler
);

type GetSlackChannelsLinkedWithAgentResBody =
| {
slackChannels: {
slackChannelId: string;
slackChannelName: string;
agentConfigurationId: string;
}[];
}
| APIErrorWithStatusCode;

const _getSlackChannelsLinkedWithAgentHandler = async (
req: Request<
Record<string, string>,
{ slackChannelIds: string[] } | APIErrorWithStatusCode,
undefined
>,
res: Response<GetSlackChannelsLinkedWithAgentResBody>
) => {
const { connector_id: connectorId } = req.query;

if (!connectorId || typeof connectorId !== "string") {
return apiError(req, res, {
api_error: {
type: "invalid_request_error",
message: `Missing required parameters: connector_id`,
},
status_code: 400,
});
}

const slackChannels = await SlackChannel.findAll({
where: {
connectorId,
agentConfigurationId: {
[Op.not]: null,
},
},
});

res.status(200).json({
slackChannels: slackChannels.map((c) => ({
slackChannelId: c.slackChannelId,
slackChannelName: c.slackChannelName,
// We know that agentConfigurationId is not null because of the where clause above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
agentConfigurationId: c.agentConfigurationId!,
})),
});
};

export const getSlackChannelsLinkedWithAgentHandler = withLogging(
_getSlackChannelsLinkedWithAgentHandler
);
20 changes: 13 additions & 7 deletions connectors/src/api_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ import { createConnectorAPIHandler } from "@connectors/api/create_connector";
import { deleteConnectorAPIHandler } from "@connectors/api/delete_connector";
import { getConnectorAPIHandler } from "@connectors/api/get_connector";
import { getConnectorPermissionsAPIHandler } from "@connectors/api/get_connector_permissions";
import { getResourcesParentsAPIHandler } from "@connectors/api/get_resources_parents";
import { getResourcesTitlesAPIHandler } from "@connectors/api/get_resources_titles";
import { resumeConnectorAPIHandler } from "@connectors/api/resume_connector";
import { setConnectorPermissionsAPIHandler } from "@connectors/api/set_connector_permissions";
import {
getSlackChannelsLinkedWithAgentHandler,
patchSlackChannelsLinkedWithAgentHandler,
} from "@connectors/api/slack_channels_linked_with_agent";
import { stopConnectorAPIHandler } from "@connectors/api/stop_connector";
import { syncConnectorAPIHandler } from "@connectors/api/sync_connector";
import { getConnectorUpdateAPIHandler } from "@connectors/api/update_connector";
Expand All @@ -20,10 +26,6 @@ import { webhookSlackAPIHandler } from "@connectors/api/webhooks/webhook_slack";
import logger from "@connectors/logger/logger";
import { authMiddleware } from "@connectors/middleware/auth";

import { getResourcesParentsAPIHandler } from "./api/get_resources_parents";
import { getResourcesTitlesAPIHandler } from "./api/get_resources_titles";
import { linkSlackChannelWithAgentHandler } from "./api/slack_channel_link_with_agent";

export function startServer(port: number) {
const app = express();

Expand Down Expand Up @@ -72,9 +74,13 @@ export function startServer(port: number) {
setConnectorPermissionsAPIHandler
);

app.post(
"/slack/channels/:slackChannelId/link_with_agent",
linkSlackChannelWithAgentHandler
app.patch(
"/slack/channels/linked_with_agent",
patchSlackChannelsLinkedWithAgentHandler
);
app.get(
"/slack/channels/linked_with_agent",
getSlackChannelsLinkedWithAgentHandler
);

app.post("/webhooks/:webhook_secret/slack", webhookSlackAPIHandler);
Expand Down
38 changes: 33 additions & 5 deletions connectors/src/connectors/slack/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {
Connector,
ModelId,
SlackChannel,
SlackChatBotMessage,
SlackConfiguration,
} from "@connectors/lib/models";
Expand Down Expand Up @@ -258,10 +259,38 @@ async function botAnswerMessage(
);
}
}
}

if (mentions.length === 0) {
mentions.push({ assistantId: "dust", assistantName: "dust" });
} else {
// If no mention is found, we look at channel-based routing rules.
const channel = await SlackChannel.findOne({
where: {
connectorId: connector.id,
slackChannelId: slackChannel,
},
});
if (channel && channel.agentConfigurationId) {
const agentConfigurationsRes = await dustAPI.getAgentConfigurations();
if (agentConfigurationsRes.isErr()) {
return new Err(new Error(agentConfigurationsRes.error.message));
}
const agentConfigurations = agentConfigurationsRes.value;
const agentConfiguration = agentConfigurations.find(
(ac) => ac.sId === channel.agentConfigurationId
);
if (!agentConfiguration) {
return new Err(
new Error(
`Failed to find agent configuration ${channel.agentConfigurationId}`
)
);
}
mentions.push({
assistantId: channel.agentConfigurationId,
assistantName: agentConfiguration.name,
});
} else {
// If no mention is found and no channel-based routing rule is found, we use the default assistant.
mentions.push({ assistantId: "dust", assistantName: "dust" });
}
}

const messageReqBody = {
Expand Down Expand Up @@ -388,7 +417,6 @@ async function botAnswerMessage(
thread_ts: slackMessageTs,
});
return new Ok(event);
break;
}
default:
// Nothing to do on unsupported events
Expand Down
Loading

0 comments on commit 40847de

Please sign in to comment.