Skip to content

Commit

Permalink
Solving connection issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Elehiggle committed Dec 14, 2024
1 parent aed3f2f commit fca3427
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 24 deletions.
42 changes: 21 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,27 +76,27 @@ This project is a chatbot for Mattermost that integrates with the Anthropic API
### Extended optional configuration variables:
| Parameter | Description |
|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `AI_SYSTEM_PROMPT` | The system prompt/instructions. Default: [click](https://github.com/Elehiggle/Claude3MattermostChatbot/blob/5d6d441a2c422a779371415ea91598f4e015e8ab/chatbot.py#L101) (Subject to change. current_time and CHATBOT_USERNAME variables inside the prompt will be auto-formatted and substituted. |
| `AI_TIMEOUT` | The timeout for the AI API call in seconds. Default: "120" |
| `MAX_TOKENS` | The maximum number of tokens to generate in the response. Default: "4096" (max) |
| `TEMPERATURE` | The temperature value for controlling the randomness of the generated responses (0.0 = analytical, 1.0 = fully random). Default: "0.15" |
| `TYPE_INDICATOR_MODE` | "FULL" = typing indicator will be sent to main thread/channel and the subthread; "THREAD" = only to the subthread, unless there is none (this is kinda expected behaviour, but I prefer full). Default: "FULL" |
| `MAX_RESPONSE_SIZE_MB` | The maximum size of the website or file content to extract (in megabytes, per URL/file). Default: "100" |
| `FLARESOLVERR_ENDPOINT` | Endpoint URL to your [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) instance (eg. "<http://192.168.1.55:8191/v1>"). If you use this, MAX_RESPONSE_SIZE_MB won't be honored since it can't stream content. For most effectiveness, use a residential IP endpoint |
| `BROWSER_EXECUTABLE_PATH` | Path to a Chromium binary which is used for the raw_html_to_image function call capability. Fully optional. Chromium is auto installed on the docker image. Default: "/usr/bin/chromium" |
| `DISABLE_SPECIFIC_TOOL_CALLS` | Disable certain function call tools like create_custom_emoji_by_url or get_exchange_rates. Supports multiple, separated by comma |
| `KEEP_ALL_URL_CONTENT` | Whether to feed the AI all URL content from the whole conversation thread. The website result is cached in memory. If you only want it to know about the current message's URL content (due to context size or cost), set to "FALSE". Default: "TRUE" |
| `MATTERMOST_IGNORE_SENDER_ID` | The user ID of a user to ignore (optional, useful if you have multiple chatbots that are not real bot accounts to prevent endless loops). Supports multiple, separated by comma |
| `MATTERMOST_PORT` | The port of your Mattermost server. Default: "443" |
| `MATTERMOST_SCHEME` | The scheme of the connection. Default: "https" |
| `MATTERMOST_BASEPATH` | The basepath of your Mattermost server. Default: "/api/v4" |
| `MATTERMOST_CERT_VERIFY` | Cert verification. Default: "TRUE" (also: path to your certificate file) |
| `MATTERMOST_TIMEOUT` | The timeout for the Mattermost server in seconds. Default: "2" |
| `AI_API_BASEURL` | AI API Base URL. Default: None (which will use "<https://api.anthropic.com>"). Useful if you want to use a different AI with Anthropic compatible endpoint |
| `LOG_LEVEL` | The log level. Default: "INFO" |
| `LOG_LEVEL_ROOT` | The root log level (for other modules than this chatbot). Default: "INFO" |
| Parameter | Description |
|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `AI_SYSTEM_PROMPT` | The system prompt/instructions. Default: [click](https://github.com/Elehiggle/Claude3MattermostChatbot/blob/master/config.py#L27) (Subject to change. current_time and CHATBOT_USERNAME variables inside the prompt will be auto-formatted and substituted. |
| `AI_TIMEOUT` | The timeout for the AI API call in seconds. Default: "120" |
| `MAX_TOKENS` | The maximum number of tokens to generate in the response. Default: "4096" (max) |
| `TEMPERATURE` | The temperature value for controlling the randomness of the generated responses (0.0 = analytical, 1.0 = fully random). Default: "0.15" |
| `TYPE_INDICATOR_MODE` | "FULL" = typing indicator will be sent to main thread/channel and the subthread; "THREAD" = only to the subthread, unless there is none (this is kinda expected behaviour, but I prefer full). Default: "FULL" |
| `MAX_RESPONSE_SIZE_MB` | The maximum size of the website or file content to extract (in megabytes, per URL/file). Default: "100" |
| `FLARESOLVERR_ENDPOINT` | Endpoint URL to your [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) instance (eg. "<http://192.168.1.55:8191/v1>"). If you use this, MAX_RESPONSE_SIZE_MB won't be honored since it can't stream content. For most effectiveness, use a residential IP endpoint |
| `BROWSER_EXECUTABLE_PATH` | Path to a Chromium binary which is used for the raw_html_to_image function call capability. Fully optional. Chromium is auto installed on the docker image. Default: "/usr/bin/chromium" |
| `DISABLE_SPECIFIC_TOOL_CALLS` | Disable certain function call tools like create_custom_emoji_by_url or get_exchange_rates. Supports multiple, separated by comma |
| `KEEP_ALL_URL_CONTENT` | Whether to feed the AI all URL content from the whole conversation thread. The website result is cached in memory. If you only want it to know about the current message's URL content (due to context size or cost), set to "FALSE". Default: "TRUE" |
| `MATTERMOST_IGNORE_SENDER_ID` | The user ID of a user to ignore (optional, useful if you have multiple chatbots that are not real bot accounts to prevent endless loops). Supports multiple, separated by comma |
| `MATTERMOST_PORT` | The port of your Mattermost server. Default: "443" |
| `MATTERMOST_SCHEME` | The scheme of the connection. Default: "https" |
| `MATTERMOST_BASEPATH` | The basepath of your Mattermost server. Default: "/api/v4" |
| `MATTERMOST_CERT_VERIFY` | Cert verification. Default: "TRUE" (also: path to your certificate file) |
| `MATTERMOST_TIMEOUT` | The timeout for the Mattermost server in seconds. Default: "10" |
| `AI_API_BASEURL` | AI API Base URL. Default: None (which will use "<https://api.anthropic.com>"). Useful if you want to use a different AI with Anthropic compatible endpoint |
| `LOG_LEVEL` | The log level. Default: "INFO" |
| `LOG_LEVEL_ROOT` | The root log level (for other modules than this chatbot). Default: "INFO" |

## Usage

Expand Down
4 changes: 2 additions & 2 deletions chatbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import pymupdf
import pymupdf4llm
import httpx
from mattermostdriver.driver import Driver
from mattermostdriver_patched import Driver
from bs4 import BeautifulSoup
from youtube_transcript_api import YouTubeTranscriptApi
from yt_dlp import YoutubeDL
Expand Down Expand Up @@ -137,7 +137,7 @@
"basepath": mattermost_basepath,
"verify": MATTERMOST_CERT_VERIFY,
"timeout": mattermost_timeout,
"websocket_kw_args": {"ping_interval": None},
# "websocket_kw_args": {"ping_interval": None},
}
)

Expand Down
2 changes: 1 addition & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
mattermost_port = int(os.getenv("MATTERMOST_PORT", "443"))
mattermost_basepath = os.getenv("MATTERMOST_BASEPATH", "/api/v4")

mattermost_timeout = os.getenv("MATTERMOST_TIMEOUT", "2")
mattermost_timeout = os.getenv("MATTERMOST_TIMEOUT", "10")

mattermost_timeout = None if mattermost_timeout == "NONE" else int(mattermost_timeout)

Expand Down
20 changes: 20 additions & 0 deletions mattermostdriver_patched.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logging
import sys
import mattermostdriver
from websocket_patched import PatchedWebsocket
from config import log_level

logger = logging.getLogger(__name__)
logger.setLevel(log_level)

mattermostdriver.websocket.Websocket = PatchedWebsocket

if 'mattermostdriver.driver' in sys.modules:
del sys.modules['mattermostdriver.driver']

import mattermostdriver.driver # noqa: E402

mattermostdriver.driver.Websocket = PatchedWebsocket

# Important line for patching
from mattermostdriver.driver import Driver # noqa: E402
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
aiohttp<=3.11.10
mattermostdriver>=7.3.2 # Stay on latest
certifi>=2024.8.30 # Stay on latest
beautifulsoup4<=4.12.3
Expand Down
69 changes: 69 additions & 0 deletions websocket_patched.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import logging
import json
import typing
import asyncio
import aiohttp
from config import log_level

logger = logging.getLogger(__name__)
logger.setLevel(log_level)


class PatchedWebsocket:
"""
Simple class to override outdated mattermostdriver's websocket class, as its causing reconnection issues
"""
heartbeat: int = 5
keepalive_delay: float = 5

def __init__(self, options: typing.Dict[str, typing.Any], token: str):
self.options = options
self._token = token
self._alive = False

async def connect(
self,
event_handler: typing.Callable[[str], typing.Awaitable[None]],
) -> None:
logger.info("Connecting websocket")
scheme = "wss://" if self.options.get("scheme", "https") == "https" else "ws://"
url = f"{scheme}{self.options['url']}:{self.options['port']}{self.options['basepath']}/websocket"
kw_args = {}
if self.options["websocket_kw_args"] is not None:
kw_args = self.options["websocket_kw_args"]
proxy = self.options.get("proxy", None)
self._alive = True
while self._alive:
try:
async with aiohttp.ClientSession() as session:
async with session.ws_connect(
url,
heartbeat=self.heartbeat,
receive_timeout=self.options.get("timeout", 10),
verify_ssl=self.options["verify"],
proxy=proxy,
**kw_args,
) as websocket:
await self._authenticate(websocket)
async for message in websocket:
await event_handler(message.data)
except Exception as e:
logger.exception(
f"Failed to establish websocket connection: {type(e)} thrown",
)
await asyncio.sleep(self.keepalive_delay)

def disconnect(self) -> None:
logger.info("Disconnecting websocket")
self._alive = False

async def _authenticate(self, websocket: aiohttp.client.ClientWebSocketResponse) -> None:
logger.info("Authenticating websocket")
json_data = json.dumps(
{
"seq": 1,
"action": "authentication_challenge",
"data": {"token": self._token},
},
)
await websocket.send_str(json_data)

0 comments on commit fca3427

Please sign in to comment.