multiple-changes-made
This commit is contained in:
264
main.py
Normal file
264
main.py
Normal file
@@ -0,0 +1,264 @@
|
||||
# --- main.py ---
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from collections import deque
|
||||
from typing import Dict, Any
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask, jsonify
|
||||
from slackeventsapi import SlackEventAdapter
|
||||
#import slack
|
||||
import json
|
||||
from hot_reload import ReloadableRegistry, start_hot_reload
|
||||
|
||||
|
||||
# --------------------------------------------------
|
||||
# Environment & Config
|
||||
# --------------------------------------------------
|
||||
|
||||
load_dotenv(dotenv_path=Path(".") / ".env")
|
||||
|
||||
from config import BOT_USER_ID, MAX_MESSAGE_LENGTH
|
||||
|
||||
import slack_functions
|
||||
from slack_event_validation import validate_slack_event
|
||||
import message_processor
|
||||
import conversation_history
|
||||
|
||||
import rag.qdrant_functions as qdrant_functions # Vector DB (RAG)
|
||||
from tool_loader import load_tools
|
||||
from bot_loader import load_bots
|
||||
|
||||
# --------------------------------------------------
|
||||
# Static Values
|
||||
# --------------------------------------------------
|
||||
|
||||
BASE_DIR = Path(__file__).parent
|
||||
|
||||
# --------------------------------------------------
|
||||
# Watchdog for new bots and tools
|
||||
# --------------------------------------------------
|
||||
|
||||
REGISTRY = ReloadableRegistry(BASE_DIR)
|
||||
start_hot_reload(REGISTRY)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Channel Mapping (JSON)
|
||||
# --------------------------------------------------
|
||||
|
||||
CHANNEL_MAP_FILE = BASE_DIR / "channel_map.json"
|
||||
|
||||
with open(CHANNEL_MAP_FILE, "r") as f:
|
||||
CHANNEL_ID_TO_BOT = json.load(f)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Helper Functions
|
||||
# --------------------------------------------------
|
||||
|
||||
def filter_tools_for_bot(tool_registry, bot_profile):
|
||||
allowed = set(getattr(bot_profile, "ENABLED_TOOL_NAMES", []))
|
||||
return {
|
||||
name: tool
|
||||
for name, tool in tool_registry.items()
|
||||
if name in allowed
|
||||
}
|
||||
|
||||
# --------------------------------------------------
|
||||
# Logging
|
||||
# --------------------------------------------------
|
||||
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s"
|
||||
)
|
||||
handler = logging.StreamHandler(sys.stderr)
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.handlers.clear()
|
||||
root_logger.addHandler(handler)
|
||||
root_logger.setLevel(logging.INFO)
|
||||
|
||||
logging.getLogger("slack").setLevel(logging.WARNING)
|
||||
|
||||
logging.info("main.py logging initialized")
|
||||
|
||||
# --------------------------------------------------
|
||||
# Dummy Tool (safe fallback)
|
||||
# --------------------------------------------------
|
||||
|
||||
class DummyToolModule:
|
||||
TOOL_DEFINITION = {
|
||||
"name": "dummy_tool",
|
||||
"description": "Tool failed to load",
|
||||
"input_schema": {}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def dummy_func(**kwargs):
|
||||
logging.error("Dummy tool invoked", extra={"kwargs": kwargs})
|
||||
return {"error": "Tool unavailable"}
|
||||
|
||||
# --------------------------------------------------
|
||||
# Global Tool Registry
|
||||
# --------------------------------------------------
|
||||
|
||||
GLOBAL_TOOL_REGISTRY = load_tools(BASE_DIR / "tools")
|
||||
logging.info(f"Registered tools: {list(GLOBAL_TOOL_REGISTRY.keys())}")
|
||||
|
||||
# --------------------------------------------------
|
||||
# Bot Profiles
|
||||
# --------------------------------------------------
|
||||
|
||||
BOT_PROFILES = load_bots(BASE_DIR / "bots")
|
||||
logging.info(f"Channel mappings loaded: {list(CHANNEL_ID_TO_BOT.keys())}")
|
||||
|
||||
# --------------------------------------------------
|
||||
# Flask + Slack Init
|
||||
# --------------------------------------------------
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
SIGNING_SECRET = os.getenv("SIGNING_SECRET")
|
||||
SLACK_TOKEN = os.getenv("SLACK_TOKEN")
|
||||
|
||||
if not SIGNING_SECRET or not SLACK_TOKEN:
|
||||
sys.exit("Missing Slack credentials")
|
||||
|
||||
slack_event_adapter = SlackEventAdapter(SIGNING_SECRET, "/slack/events", app)
|
||||
slack_client = slack.WebClient(token=SLACK_TOKEN)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Deduplication
|
||||
# --------------------------------------------------
|
||||
|
||||
processed_event_ids = deque(maxlen=1000)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Slack Message Handler
|
||||
# --------------------------------------------------
|
||||
|
||||
@slack_event_adapter.on("message")
|
||||
def handle_message(event_data):
|
||||
|
||||
if not validate_slack_event(event_data, MAX_MESSAGE_LENGTH):
|
||||
return jsonify({"status": "invalid"}), 400
|
||||
|
||||
event = event_data.get("event", {})
|
||||
event_id = event_data.get("event_id")
|
||||
api_app_id = event_data.get("api_app_id")
|
||||
|
||||
dedupe_key = f"{api_app_id}-{event_id}"
|
||||
if dedupe_key in processed_event_ids:
|
||||
return jsonify({"status": "duplicate"}), 200
|
||||
processed_event_ids.append(dedupe_key)
|
||||
|
||||
channel = event.get("channel")
|
||||
user = event.get("user")
|
||||
text = event.get("text", "")
|
||||
ts = event.get("ts")
|
||||
|
||||
if not all([channel, user, ts]):
|
||||
return jsonify({"status": "ignored"}), 200
|
||||
|
||||
is_bot_message = user == BOT_USER_ID
|
||||
subtype = event.get("subtype")
|
||||
|
||||
# --------------------------------------------------
|
||||
# Log message
|
||||
# --------------------------------------------------
|
||||
|
||||
if not is_bot_message:
|
||||
try:
|
||||
slack_functions.log_slack_message(
|
||||
slack_client, channel, user, text, ts, BOT_USER_ID
|
||||
)
|
||||
except Exception:
|
||||
logging.warning("Failed to log message")
|
||||
|
||||
# --------------------------------------------------
|
||||
# RAG Insert (profile controlled)
|
||||
# --------------------------------------------------
|
||||
|
||||
bot_name = CHANNEL_ID_TO_BOT.get(channel)
|
||||
|
||||
BOT_PROFILES = REGISTRY.get_bots()
|
||||
GLOBAL_TOOL_REGISTRY = REGISTRY.get_tools()
|
||||
|
||||
profile = BOT_PROFILES.get(bot_name)
|
||||
enable_insert = getattr(profile, "ENABLE_RAG_INSERT", False) if profile else False
|
||||
|
||||
if enable_insert and not is_bot_message and not subtype:
|
||||
try:
|
||||
qdrant_functions.embed_and_store_slack_message(
|
||||
slack_client, channel, user, text, ts, BOT_USER_ID
|
||||
)
|
||||
except Exception:
|
||||
logging.error("RAG insert failed", exc_info=True)
|
||||
|
||||
# --------------------------------------------------
|
||||
# File attachments
|
||||
# --------------------------------------------------
|
||||
|
||||
if "files" in event and not is_bot_message:
|
||||
try:
|
||||
slack_functions.handle_slack_attachments(
|
||||
slack_client, event, BOT_USER_ID
|
||||
)
|
||||
except Exception:
|
||||
logging.error("Attachment handling failed")
|
||||
|
||||
# --------------------------------------------------
|
||||
# Mention routing
|
||||
# --------------------------------------------------
|
||||
|
||||
if f"<@{BOT_USER_ID}>" not in text or is_bot_message:
|
||||
return jsonify({"status": "no_mention"}), 200
|
||||
|
||||
if not profile:
|
||||
slack_client.chat_postMessage(
|
||||
channel=channel,
|
||||
text="I’m not configured for this channel."
|
||||
)
|
||||
return jsonify({"status": "unmapped_channel"}), 200
|
||||
|
||||
logging.info(
|
||||
f"Routing mention to profile: {getattr(profile, 'BOT_IDENTIFIER', 'unknown')}"
|
||||
)
|
||||
|
||||
try:
|
||||
message_processor.process_mention(
|
||||
event_data=event_data,
|
||||
slack_client=slack_client,
|
||||
vector_store=qdrant_functions,
|
||||
bot_profile=profile,
|
||||
tool_registry=filter_tools_for_bot(GLOBAL_TOOL_REGISTRY, profile),
|
||||
)
|
||||
return jsonify({"status": "processed"}), 200
|
||||
|
||||
except Exception as e:
|
||||
logging.error("process_mention failed", exc_info=True)
|
||||
slack_client.chat_postMessage(
|
||||
channel=channel,
|
||||
text="⚠️ An internal error occurred."
|
||||
)
|
||||
return jsonify({"status": "error"}), 500
|
||||
|
||||
# --------------------------------------------------
|
||||
# Health Endpoint
|
||||
# --------------------------------------------------
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Slack AI Bot Router running (Qdrant + Local LLM)"
|
||||
|
||||
# --------------------------------------------------
|
||||
# Run
|
||||
# --------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = int(os.getenv("PORT", 5150))
|
||||
logging.info(f"Starting server on port {port}")
|
||||
app.run(host="0.0.0.0", port=port, debug=False)
|
||||
Reference in New Issue
Block a user