Added Slacktoolkit (#14012)

- **Description:** 
This PR introduces the Slack toolkit to LangChain, which allows users to
read and write to Slack using the Slack API. Specifically, we've added
the following tools.
1. get_channel: Provides a summary of all the channels in a workspace.
2. get_message: Gets the message history of a channel.
3. send_message: Sends a message to a channel.
4. schedule_message: Sends a message to a channel at a specific time and
date.

- **Issue:** This pull request addresses [Add Slack Toolkit
#11747](https://github.com/langchain-ai/langchain/issues/11747)
  - **Dependencies:** package`slack_sdk`
Note: For this toolkit to function you will need to add a Slack app to
your workspace. Additional info can be found
[here](https://slack.com/help/articles/202035138-Add-apps-to-your-Slack-workspace).

---------

Co-authored-by: Bagatur <baskaryan@gmail.com>
Co-authored-by: ArianneLavada <ariannelavada@gmail.com>
Co-authored-by: ArianneLavada <84357335+ArianneLavada@users.noreply.github.com>
Co-authored-by: ariannelavada@gmail.com <you@example.com>
This commit is contained in:
gzyJoy 2023-12-03 13:25:38 -05:00 committed by GitHub
parent 99e5ee6a84
commit 32d4bb4590
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 480 additions and 0 deletions

View File

@ -0,0 +1,147 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Slack\n",
"\n",
"This notebook walks through connecting LangChain to your `Slack` account.\n",
"\n",
"To use this toolkit, you will need to get a token explained in the [Slack API docs](https://api.slack.com/tutorials/tracks/getting-a-token). Once you've received a SLACK_USER_TOKEN, you can input it as an environmental variable below."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install --upgrade slack_sdk > /dev/null\n",
"!pip install beautifulsoup4 > /dev/null # This is optional but is useful for parsing HTML messages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Assign Environmental Variables\n",
"\n",
"The toolkit will read the SLACK_USER_TOKEN environmental variable to authenticate the user so you need to set them here. You will also need to set your OPENAI_API_KEY to use the agent later."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Set environmental variables here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create the Toolkit and Get Tools\n",
"\n",
"To start, you need to create the toolkit, so you can access its tools later."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.agents.agent_toolkits import SlackToolkit\n",
"\n",
"toolkit = SlackToolkit()\n",
"tools = toolkit.get_tools()\n",
"tools"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Use within an Agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.agents import AgentType, initialize_agent\n",
"from langchain.llms import OpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"llm = OpenAI(temperature=0)\n",
"agent = initialize_agent(\n",
" tools=toolkit.get_tools(),\n",
" llm=llm,\n",
" verbose=False,\n",
" agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"agent.run(\"Send a greeting to my coworkers in the #general channel.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"agent.run(\"How many channels are in the workspace? Please list out their names.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"agent.run(\n",
" \"Tell me the number of messages sent in the #introductions channel from the past month.\"\n",
")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -42,6 +42,7 @@ from langchain.agents.agent_toolkits.playwright.toolkit import PlayWrightBrowser
from langchain.agents.agent_toolkits.powerbi.base import create_pbi_agent from langchain.agents.agent_toolkits.powerbi.base import create_pbi_agent
from langchain.agents.agent_toolkits.powerbi.chat_base import create_pbi_chat_agent from langchain.agents.agent_toolkits.powerbi.chat_base import create_pbi_chat_agent
from langchain.agents.agent_toolkits.powerbi.toolkit import PowerBIToolkit from langchain.agents.agent_toolkits.powerbi.toolkit import PowerBIToolkit
from langchain.agents.agent_toolkits.slack.toolkit import SlackToolkit
from langchain.agents.agent_toolkits.spark_sql.base import create_spark_sql_agent from langchain.agents.agent_toolkits.spark_sql.base import create_spark_sql_agent
from langchain.agents.agent_toolkits.spark_sql.toolkit import SparkSQLToolkit from langchain.agents.agent_toolkits.spark_sql.toolkit import SparkSQLToolkit
from langchain.agents.agent_toolkits.sql.base import create_sql_agent from langchain.agents.agent_toolkits.sql.base import create_sql_agent
@ -96,6 +97,7 @@ __all__ = [
"OpenAPIToolkit", "OpenAPIToolkit",
"PlayWrightBrowserToolkit", "PlayWrightBrowserToolkit",
"PowerBIToolkit", "PowerBIToolkit",
"SlackToolkit",
"SQLDatabaseToolkit", "SQLDatabaseToolkit",
"SparkSQLToolkit", "SparkSQLToolkit",
"VectorStoreInfo", "VectorStoreInfo",

View File

@ -0,0 +1 @@
"""Slack toolkit."""

View File

@ -0,0 +1,35 @@
from __future__ import annotations
from typing import TYPE_CHECKING, List
from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.pydantic_v1 import Field
from langchain.tools import BaseTool
from langchain.tools.slack.get_channel import SlackGetChannel
from langchain.tools.slack.get_message import SlackGetMessage
from langchain.tools.slack.schedule_message import SlackScheduleMessage
from langchain.tools.slack.send_message import SlackSendMessage
from langchain.tools.slack.utils import login
if TYPE_CHECKING:
from slack_sdk import WebClient
class SlackToolkit(BaseToolkit):
"""Toolkit for interacting with Slack."""
client: WebClient = Field(default_factory=login)
class Config:
"""Pydantic config."""
arbitrary_types_allowed = True
def get_tools(self) -> List[BaseTool]:
"""Get the tools in the toolkit."""
return [
SlackGetChannel(),
SlackGetMessage(),
SlackScheduleMessage(),
SlackSendMessage(),
]

View File

@ -558,6 +558,30 @@ def _import_shell_tool() -> Any:
return ShellTool return ShellTool
def _import_slack_get_channel() -> Any:
from langchain.tools.slack.get_channel import SlackGetChannel
return SlackGetChannel
def _import_slack_get_message() -> Any:
from langchain.tools.slack.get_message import SlackGetMessage
return SlackGetMessage
def _import_slack_schedule_message() -> Any:
from langchain.tools.slack.schedule_message import SlackScheduleMessage
return SlackScheduleMessage
def _import_slack_send_message() -> Any:
from langchain.tools.slack.send_message import SlackSendMessage
return SlackSendMessage
def _import_sleep_tool() -> Any: def _import_sleep_tool() -> Any:
from langchain.tools.sleep.tool import SleepTool from langchain.tools.sleep.tool import SleepTool
@ -871,6 +895,14 @@ def __getattr__(name: str) -> Any:
return _import_searx_search_tool_SearxSearchRun() return _import_searx_search_tool_SearxSearchRun()
elif name == "ShellTool": elif name == "ShellTool":
return _import_shell_tool() return _import_shell_tool()
elif name == "SlackGetChannel":
return _import_slack_get_channel
elif name == "SlackGetMessage":
return _import_slack_get_message
elif name == "SlackScheduleMessage":
return _import_slack_schedule_message
elif name == "SlackSendMessage":
return _import_slack_send_message
elif name == "SleepTool": elif name == "SleepTool":
return _import_sleep_tool() return _import_sleep_tool()
elif name == "BaseSparkSQLTool": elif name == "BaseSparkSQLTool":
@ -1016,6 +1048,10 @@ __all__ = [
"SearxSearchResults", "SearxSearchResults",
"SearxSearchRun", "SearxSearchRun",
"ShellTool", "ShellTool",
"SlackGetChannel",
"SlackGetMessage",
"SlackScheduleMessage",
"SlackSendMessage",
"SleepTool", "SleepTool",
"StdInInquireTool", "StdInInquireTool",
"StackExchangeTool", "StackExchangeTool",

View File

@ -0,0 +1,15 @@
"""Slack tools."""
from langchain.tools.slack.get_channel import SlackGetChannel
from langchain.tools.slack.get_message import SlackGetMessage
from langchain.tools.slack.schedule_message import SlackScheduleMessage
from langchain.tools.slack.send_message import SlackSendMessage
from langchain.tools.slack.utils import login
__all__ = [
"SlackGetChannel",
"SlackGetMessage",
"SlackScheduleMessage",
"SlackSendMessage",
"login",
]

View File

@ -0,0 +1,18 @@
"""Base class for Slack tools."""
from __future__ import annotations
from typing import TYPE_CHECKING
from langchain.pydantic_v1 import Field
from langchain.tools.base import BaseTool
from langchain.tools.slack.utils import login
if TYPE_CHECKING:
from slack_sdk import WebClient
class SlackBaseTool(BaseTool):
"""Base class for Slack tools."""
client: WebClient = Field(default_factory=login)
"""The WebClient object."""

View File

@ -0,0 +1,33 @@
import json
import logging
from typing import Optional
from langchain.callbacks.manager import CallbackManagerForToolRun
from langchain.tools.slack.base import SlackBaseTool
class SlackGetChannel(SlackBaseTool):
name: str = "get_channelid_name_dict"
description: str = "Use this tool to get channelid-name dict."
def _run(
self,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
try:
logging.getLogger(__name__)
result = self.client.conversations_list()
channels = result["channels"]
filtered_result = [
{key: channel[key] for key in ("id", "name", "created", "num_members")}
for channel in channels
if "id" in channel
and "name" in channel
and "created" in channel
and "num_members" in channel
]
return json.dumps(filtered_result)
except Exception as e:
return "Error creating conversation: {}".format(e)

View File

@ -0,0 +1,41 @@
import json
import logging
from typing import Optional, Type
from langchain.callbacks.manager import CallbackManagerForToolRun
from langchain.pydantic_v1 import BaseModel, Field
from langchain.tools.slack.base import SlackBaseTool
class SlackGetMessageSchema(BaseModel):
"""Input schema for SlackGetMessages."""
channel_id: str = Field(
...,
description="The channel id, private group, or IM channel to send message to.",
)
class SlackGetMessage(SlackBaseTool):
name: str = "get_messages"
description: str = "Use this tool to get messages from a channel."
args_schema: Type[SlackGetMessageSchema] = SlackGetMessageSchema
def _run(
self,
channel_id: str,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
logging.getLogger(__name__)
try:
result = self.client.conversations_history(channel=channel_id)
messages = result["messages"]
filtered_messages = [
{key: message[key] for key in ("user", "text", "ts")}
for message in messages
if "user" in message and "text" in message and "ts" in message
]
return json.dumps(filtered_messages)
except Exception as e:
return "Error creating conversation: {}".format(e)

View File

@ -0,0 +1,59 @@
import logging
from datetime import datetime as dt
from typing import Optional, Type
from langchain.callbacks.manager import CallbackManagerForToolRun
from langchain.pydantic_v1 import BaseModel, Field
from langchain.tools.slack.base import SlackBaseTool
from langchain.tools.slack.utils import UTC_FORMAT
logger = logging.getLogger(__name__)
class ScheduleMessageSchema(BaseModel):
"""Input for ScheduleMessageTool."""
message: str = Field(
...,
description="The message to be sent.",
)
channel: str = Field(
...,
description="The channel, private group, or IM channel to send message to.",
)
timestamp: str = Field(
...,
description="The datetime for when the message should be sent in the "
' following format: YYYY-MM-DDTHH:MM:SS±hh:mm, where "T" separates the date '
" and time components, and the time zone offset is specified as ±hh:mm. "
' For example: "2023-06-09T10:30:00+03:00" represents June 9th, '
" 2023, at 10:30 AM in a time zone with a positive offset of 3 "
" hours from Coordinated Universal Time (UTC).",
)
class SlackScheduleMessage(SlackBaseTool):
"""Tool for scheduling a message in Slack."""
name: str = "schedule_message"
description: str = (
"Use this tool to schedule a message to be sent on a specific date and time."
)
args_schema: Type[ScheduleMessageSchema] = ScheduleMessageSchema
def _run(
self,
message: str,
channel: str,
timestamp: str,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
try:
unix_timestamp = dt.timestamp(dt.strptime(timestamp, UTC_FORMAT))
result = self.client.chat_scheduleMessage(
channel=channel, text=message, post_at=unix_timestamp
)
output = "Message scheduled: " + str(result)
return output
except Exception as e:
return "Error scheduling message: {}".format(e)

View File

@ -0,0 +1,41 @@
from typing import Optional, Type
from langchain.callbacks.manager import CallbackManagerForToolRun
from langchain.pydantic_v1 import BaseModel, Field
from langchain.tools.slack.base import SlackBaseTool
class SendMessageSchema(BaseModel):
"""Input for SendMessageTool."""
message: str = Field(
...,
description="The message to be sent.",
)
channel: str = Field(
...,
description="The channel, private group, or IM channel to send message to.",
)
class SlackSendMessage(SlackBaseTool):
"""Tool for sending a message in Slack."""
name: str = "send_message"
description: str = (
"Use this tool to send a message with the provided message fields."
)
args_schema: Type[SendMessageSchema] = SendMessageSchema
def _run(
self,
message: str,
channel: str,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
try:
result = self.client.chat_postMessage(channel=channel, text=message)
output = "Message sent: " + str(result)
return output
except Exception as e:
return "Error creating conversation: {}".format(e)

View File

@ -0,0 +1,42 @@
"""Slack tool utils."""
from __future__ import annotations
import logging
import os
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slack_sdk import WebClient
logger = logging.getLogger(__name__)
def login() -> WebClient:
"""Authenticate using the Slack API."""
try:
from slack_sdk import WebClient
except ImportError as e:
raise ImportError(
"Cannot import slack_sdk. Please install the package with \
`pip install slack_sdk`."
) from e
if "SLACK_BOT_TOKEN" in os.environ:
token = os.environ["SLACK_BOT_TOKEN"]
client = WebClient(token=token)
logger.info("slack login success")
return client
elif "SLACK_USER_TOKEN" in os.environ:
token = os.environ["SLACK_USER_TOKEN"]
client = WebClient(token=token)
logger.info("slack login success")
return client
else:
logger.error(
"Error: The SLACK_BOT_TOKEN or SLACK_USER_TOKEN \
environment variable have not been set."
)
UTC_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
"""UTC format for datetime objects."""

View File

@ -94,6 +94,10 @@ EXPECTED_ALL = [
"SearxSearchResults", "SearxSearchResults",
"SearxSearchRun", "SearxSearchRun",
"ShellTool", "ShellTool",
"SlackGetChannel",
"SlackGetMessage",
"SlackScheduleMessage",
"SlackSendMessage",
"SleepTool", "SleepTool",
"StackExchangeTool", "StackExchangeTool",
"StdInInquireTool", "StdInInquireTool",

View File

@ -96,6 +96,10 @@ _EXPECTED = [
"SearxSearchResults", "SearxSearchResults",
"SearxSearchRun", "SearxSearchRun",
"ShellTool", "ShellTool",
"SlackGetChannel",
"SlackGetMessage",
"SlackScheduleMessage",
"SlackSendMessage",
"SleepTool", "SleepTool",
"StdInInquireTool", "StdInInquireTool",
"StackExchangeTool", "StackExchangeTool",

View File

@ -12,6 +12,7 @@ from langchain.tools.base import BaseTool
from langchain.tools.gmail.base import GmailBaseTool from langchain.tools.gmail.base import GmailBaseTool
from langchain.tools.office365.base import O365BaseTool from langchain.tools.office365.base import O365BaseTool
from langchain.tools.playwright.base import BaseBrowserTool from langchain.tools.playwright.base import BaseBrowserTool
from langchain.tools.slack.base import SlackBaseTool
def get_non_abstract_subclasses(cls: Type[BaseTool]) -> List[Type[BaseTool]]: def get_non_abstract_subclasses(cls: Type[BaseTool]) -> List[Type[BaseTool]]:
@ -20,6 +21,7 @@ def get_non_abstract_subclasses(cls: Type[BaseTool]) -> List[Type[BaseTool]]:
BaseBrowserTool, BaseBrowserTool,
GmailBaseTool, GmailBaseTool,
O365BaseTool, O365BaseTool,
SlackBaseTool,
} # Abstract but not recognized } # Abstract but not recognized
subclasses = [] subclasses = []
for subclass in cls.__subclasses__(): for subclass in cls.__subclasses__():