diff --git a/README.md b/README.md index e5ebcec..e00345a 100644 --- a/README.md +++ b/README.md @@ -50,22 +50,32 @@ _or alternatively:_ python3.12 -m pip install anthropic mattermostdriver ssl certifi beautifulsoup4 pillow httpx ``` -3. Set the following environment variables with your own values: - -| Parameter | Description | -| --- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `AI_API_KEY` | Your Anthropic API key | -| `AI_MODEL` | The Anthropic model to use. Default: "claude-3-opus-20240229" | -| `AI_TIMEOUT` | The timeout for the AI API call in seconds. Default: "120" | -| `MAX_RESPONSE_SIZE_MB` | The maximum size of the website content to extract (in megabytes). Default: "100" | -| `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" | -| `MATTERMOST_URL` | The URL of your Mattermost server | -| `MATTERMOST_TOKEN` | The bot token (alternatively personal access token) with relevant permissions created specifically for the chatbot. Don't forget to add the bot account to the team. | -| `MATTERMOST_USERNAME` | The username of the dedicated Mattermost user account for the chatbot (if using username/password login) | -| `MATTERMOST_PASSWORD` | The password of the dedicated Mattermost user account for the chatbot (if using username/password login) | -| `MATTERMOST_MFA_TOKEN` | The MFA token of the dedicated Mattermost user account for the chatbot (if using MFA) | -| `MATTERMOST_IGNORE_SENDER_ID` | The user ID of a user to ignore (optional, useful if you have multiple chatbots to prevent endless loops) | +3. Set the following environment variables with your own values (most are optional): + +| Parameter | Description | +|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `AI_API_KEY` | Required. Your Anthropic API key | +| `AI_MODEL` | The Anthropic model to use. Default: "claude-3-opus-20240229" | +| `MATTERMOST_URL` | Required. The URL of your Mattermost server | +| `MATTERMOST_TOKEN` | Required if not using user/password. The bot token (alternatively personal access token) with relevant permissions created specifically for the chatbot. Don't forget to add the bot account to the team. | +| `MATTERMOST_USERNAME` | Required if not using token. The username of the dedicated Mattermost user account for the chatbot (if using username/password login) | +| `MATTERMOST_PASSWORD` | Required if not using token. The password of the dedicated Mattermost user account for the chatbot (if using username/password login) | +| `MATTERMOST_MFA_TOKEN` | The MFA token of the dedicated Mattermost user account for the chatbot (if using MFA) | + +#### Extended optional configuration variables: + +| Parameter | Description | +|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `AI_SYSTEM_PROMPT` | The system prompt/instructions. Default: [click](https://github.com/Elehiggle/Claude3MattermostChatbot/blob/a1e1813c40f44f5231d10ee3f25ef6eee13ae58f/chatbot.py#L88) (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" | +| `MAX_RESPONSE_SIZE_MB` | The maximum size of the website content to extract (in megabytes). Default: "100" | +| `MATTERMOST_IGNORE_SENDER_ID` | The user ID of a user to ignore (optional, useful if you have multiple chatbots to prevent endless loops) | +| `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: string path to your certificate file) | ## Usage diff --git a/chatbot.py b/chatbot.py index d237708..6642aac 100644 --- a/chatbot.py +++ b/chatbot.py @@ -40,10 +40,18 @@ def cdc(*args, **kwargs): timeout = int(os.getenv("AI_TIMEOUT", "120")) max_tokens = int(os.getenv("MAX_TOKENS", "4096")) temperature = float(os.getenv("TEMPERATURE", "0.15")) +system_prompt_unformatted = os.getenv( + "AI_SYSTEM_PROMPT", + "You are a helpful assistant. The current UTC time is {current_time}. Whenever users asks you for help you will provide them with succinct answers formatted using Markdown; do not unnecessarily greet people with their name. Do not be apologetic. You know the user's name as it is provided within [CONTEXT, from:username] bracket at the beginning of a user-role message. Never add any CONTEXT bracket to your replies (eg. [CONTEXT, from:{chatbot_username}]). The CONTEXT bracket may also include grabbed text from a website if a user adds a link to his question.", +) # Mattermost server details mattermost_url = os.environ["MATTERMOST_URL"] -mattermost_personal_access_token = os.getenv("MATTERMOST_TOKEN", "") +mattermost_scheme = os.getenv("MATTERMOST_SCHEME", "https") +mattermost_port = int(os.getenv("MATTERMOST_PORT", "443")) +mattermost_basepath = os.getenv("MATTERMOST_BASEPATH", "/api/v4") +mattermost_cert_verify = os.getenv("MATTERMOST_CERT_VERIFY", True) +mattermost_token = os.getenv("MATTERMOST_TOKEN", "") mattermost_ignore_sender_id = os.getenv("MATTERMOST_IGNORE_SENDER_ID", "") mattermost_username = os.getenv("MATTERMOST_USERNAME", "") mattermost_password = os.getenv("MATTERMOST_PASSWORD", "") @@ -59,14 +67,14 @@ def cdc(*args, **kwargs): driver = Driver( { "url": mattermost_url, - "token": mattermost_personal_access_token, + "token": mattermost_token, "login_id": mattermost_username, "password": mattermost_password, "mfa_token": mattermost_mfa_token, - "scheme": "https", - "port": 443, - "basepath": "/api/v4", - "verify": True, + "scheme": mattermost_scheme, + "port": mattermost_port, + "basepath": mattermost_basepath, + "verify": mattermost_cert_verify, } ) @@ -80,13 +88,14 @@ def cdc(*args, **kwargs): # Create a thread pool with a fixed number of worker threads thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=5) - def get_system_instructions(): current_time = datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%d %H:%M:%S.%f")[ :-3 ] - return f"You are a helpful assistant. The current UTC time is {current_time}. Whenever users asks you for help you will provide them with succinct answers formatted using Markdown; do not unnecessarily greet people with their name. Do not be apologetic. You know the user's name as it is provided within [CONTEXT, from:username] bracket at the beginning of a user-role message. Never add any CONTEXT bracket to your replies (eg. [CONTEXT, from:{chatbot_username}]). The CONTEXT bracket may also include grabbed text from a website if a user adds a link to his question." - + global chatbot_username + return system_prompt_unformatted.format( + current_time=current_time, chatbot_username=chatbot_username + ) def sanitize_username(username): if not re.match(r"^[a-zA-Z0-9_-]{1,64}$", username): @@ -191,7 +200,7 @@ def add_chunk(chunk, in_code, code_lang): return f"```{code_lang}\n" return "" - lines = msg.split('\n') + lines = msg.split("\n") for i, line in enumerate(lines): # Check if this line starts or ends a code block if line.startswith("```"): @@ -206,7 +215,9 @@ def add_chunk(chunk, in_code, code_lang): else: # Starting a new code block, capture the language in_code_block = True - code_block_lang = line[3:].strip() # Remove the backticks and get the language + code_block_lang = line[ + 3: + ].strip() # Remove the backticks and get the language current_chunk += line + "\n" else: # If adding this line exceeds the max length, we need to split here @@ -214,11 +225,15 @@ def add_chunk(chunk, in_code, code_lang): # Split here, preserve the code block state and language if necessary current_chunk = add_chunk(current_chunk, in_code_block, code_block_lang) current_chunk += line - if i < len(lines) - 1: # Avoid adding a newline at the end of the last line + if ( + i < len(lines) - 1 + ): # Avoid adding a newline at the end of the last line current_chunk += "\n" else: current_chunk += line - if i < len(lines) - 1: # Avoid adding a newline at the end of the last line + if ( + i < len(lines) - 1 + ): # Avoid adding a newline at the end of the last line current_chunk += "\n" # Don't forget to add the last chunk @@ -308,7 +323,7 @@ async def message_handler(event): sender_id == driver.client.userid or sender_id == mattermost_ignore_sender_id ): - logging.info("Ignoring post from a ignored sender ID") + logging.info("Ignoring post from an ignored sender ID") return # Check if the post is from a bot @@ -566,6 +581,8 @@ def main(): chatbot_username = driver.client.username chatbot_usernameAt = f"@{chatbot_username}" + logging.info(f"SYSTEM PROMPT: {get_system_instructions()}") + # Initialize the WebSocket connection while True: try: