Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 74 additions & 6 deletions backend/app/classification/classification_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

logger = logging.getLogger(__name__)


class ClassificationRouter:
"""Simple DevRel triage - determines if message needs DevRel assistance"""

Expand All @@ -17,20 +18,87 @@ def __init__(self, llm_client=None):
google_api_key=settings.gemini_api_key
)

async def should_process_message(self, message: str, context: Dict[str, Any] = None) -> Dict[str, Any]:
# πŸ”₯ NEW: Proactive lightweight pattern detection
def _simple_pattern_match(self, message: str):
"""
Lightweight proactive detection before calling LLM.
Returns classification dict if matched, else None.
"""

msg = message.lower().strip()

greetings = ["hi", "hello", "hey"]
thanks = ["thanks", "thank you"]
onboarding_keywords = ["new here", "how to start", "beginner", "first time"]
issue_keywords = ["good first issue", "beginner issue", "start contributing"]

if msg in greetings:
return {
"needs_devrel": True,
"priority": "low",
"reasoning": "Greeting detected - proactive onboarding opportunity",
"original_message": message,
"proactive_type": "greeting"
}

if any(k in msg for k in onboarding_keywords):
return {
"needs_devrel": True,
"priority": "high",
"reasoning": "Onboarding request detected",
"original_message": message,
"proactive_type": "onboarding"
}

if any(k in msg for k in issue_keywords):
return {
"needs_devrel": True,
"priority": "medium",
"reasoning": "Contributor looking for issues",
"original_message": message,
"proactive_type": "issue_suggestion"
}

if any(t in msg for t in thanks):
return {
"needs_devrel": False,
"priority": "low",
"reasoning": "Acknowledgment message - no processing needed",
"original_message": message,
"proactive_type": "acknowledgment"
}
Comment on lines +62 to +69

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Acknowledgment returns needs_devrel: True but reasoning says "no processing needed" β€” contradictory.

The reasoning field states "Acknowledgment message - no processing needed" yet needs_devrel is True, meaning this will be routed for DevRel processing. Either update the reasoning to reflect the intent, or set needs_devrel: False if you truly don't want processing (noting that the downstream handler must then be accessible).

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/classification/classification_router.py` around lines 62 - 69,
The acknowledgment branch currently returns needs_devrel: True while saying "no
processing needed"; change needs_devrel to False and update the reasoning string
to match (e.g., "Acknowledgment message - no devrel processing needed") in the
returned dict that contains keys needs_devrel, priority, reasoning,
original_message, proactive_type so the boolean and message are consistent.


return None

async def should_process_message(
self,
message: str,
context: Dict[str, Any] = None
) -> Dict[str, Any]:
"""Simple triage: Does this message need DevRel assistance?"""

try:
# πŸ”₯ Step 1: Lightweight proactive pattern check
pattern_result = self._simple_pattern_match(message)
if pattern_result:
logger.info("Pattern-based proactive classification triggered")
return pattern_result

# πŸ”₯ Step 2: Fallback to LLM
triage_prompt = DEVREL_TRIAGE_PROMPT.format(
message=message,
context=context or 'No additional context'
context=context or "No additional context"
)

response = await self.llm.ainvoke([HumanMessage(content=triage_prompt)])
response = await self.llm.ainvoke(
[HumanMessage(content=triage_prompt)]
)

response_text = response.content.strip()
if '{' in response_text:
json_start = response_text.find('{')
json_end = response_text.rfind('}') + 1

if "{" in response_text:
json_start = response_text.find("{")
json_end = response_text.rfind("}") + 1
json_str = response_text[json_start:json_end]

import json
Expand Down
110 changes: 90 additions & 20 deletions backend/integrations/discord/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

logger = logging.getLogger(__name__)


class DiscordBot(commands.Bot):
"""Discord bot with LangGraph agent integration"""

Expand All @@ -31,10 +32,12 @@ def __init__(self, queue_manager: AsyncQueueManager, **kwargs):

def _register_queue_handlers(self):
"""Register handlers for queue messages"""
self.queue_manager.register_handler("discord_response", self._handle_agent_response)
self.queue_manager.register_handler(
"discord_response",
self._handle_agent_response
)

async def on_ready(self):
"""Bot ready event"""
logger.info(f'Enhanced Discord bot logged in as {self.user}')
print(f'Bot is ready! Logged in as {self.user}')
try:
Expand All @@ -44,7 +47,6 @@ async def on_ready(self):
print(f"Failed to sync slash commands: {e}")

async def on_message(self, message):
"""Handles regular chat messages, but ignores slash commands."""
if message.author == self.user:
return

Expand All @@ -67,9 +69,50 @@ async def on_message(self, message):
except Exception as e:
logger.error(f"Error processing message: {str(e)}")

async def _handle_devrel_message(self, message, triage_result: Dict[str, Any]):
"""This now handles both new requests and follow-ups in threads."""
async def _handle_devrel_message(
self,
message,
triage_result: Dict[str, Any]
):
"""Handles both proactive responses and agent requests"""

try:
# πŸ”₯ PROACTIVE LAYER
if "proactive_type" in triage_result:
proactive_type = triage_result["proactive_type"]

if proactive_type == "greeting":
await message.channel.send(
f"Hi {message.author.mention}! πŸ‘‹\n"
"Welcome to the community!\n"
"If you're new, I can guide you on how to start contributing πŸš€"
)
return

if proactive_type == "onboarding":
await message.channel.send(
f"Awesome {message.author.mention}! πŸŽ‰\n"
"Here’s how you can start:\n"
"1️⃣ Look for `good first issue`\n"
"2️⃣ Set up the project locally\n"
"3️⃣ Read CONTRIBUTING.md\n\n"
"Would you like me to suggest beginner-friendly issues?"
)
return
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if proactive_type == "issue_suggestion":
await message.channel.send(
f"{message.author.mention} πŸ”\n"
"You can check open issues labeled `good first issue`.\n"
"Would you like me to fetch some right now?"
)
return

if proactive_type == "acknowledgment":
return
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

# πŸ”₯ NORMAL AGENT FLOW (Fallback)

user_id = str(message.author.id)
thread_id = await self._get_or_create_thread(message, user_id)

Expand All @@ -88,27 +131,39 @@ async def _handle_devrel_message(self, message, triage_result: Dict[str, Any]):
"author": {
"username": message.author.name,
"display_name": message.author.display_name,
"avatar_url": str(message.author.avatar.url) if message.author.avatar else None
"avatar_url": str(message.author.avatar.url)
if message.author.avatar else None
}
}
priority_map = {"high": QueuePriority.HIGH,
"medium": QueuePriority.MEDIUM,
"low": QueuePriority.LOW
}
priority = priority_map.get(triage_result.get("priority"), QueuePriority.MEDIUM)

priority_map = {
"high": QueuePriority.HIGH,
"medium": QueuePriority.MEDIUM,
"low": QueuePriority.LOW
}

priority = priority_map.get(
triage_result.get("priority"),
QueuePriority.MEDIUM
)

await self.queue_manager.enqueue(agent_message, priority)

# --- "PROCESSING" MESSAGE RESTORED ---
if thread_id:
thread = self.get_channel(int(thread_id))
if thread:
await thread.send("I'm processing your request, please hold on...")
# ------------------------------------
await thread.send(
"I'm processing your request, please hold on..."
)

except Exception as e:
logger.error(f"Error handling DevRel message: {str(e)}")

async def _get_or_create_thread(self, message, user_id: str) -> Optional[str]:
async def _get_or_create_thread(
self,
message,
user_id: str
) -> Optional[str]:
try:
if user_id in self.active_threads:
thread_id = self.active_threads[user_id]
Expand All @@ -118,28 +173,43 @@ async def _get_or_create_thread(self, message, user_id: str) -> Optional[str]:
else:
del self.active_threads[user_id]

# This part only runs if it's not a follow-up message in an active thread.
if isinstance(message.channel, discord.TextChannel):
thread_name = f"DevRel Chat - {message.author.display_name}"
thread = await message.create_thread(name=thread_name, auto_archive_duration=60)
thread = await message.create_thread(
name=thread_name,
auto_archive_duration=60
)
self.active_threads[user_id] = str(thread.id)
await thread.send(f"Hello {message.author.mention}! I've created this thread to help you. How can I assist?")
await thread.send(
f"Hello {message.author.mention}! "
"I've created this thread to help you."
)
return str(thread.id)

except Exception as e:
logger.error(f"Failed to create thread: {e}")

return str(message.channel.id)

async def _handle_agent_response(self, response_data: Dict[str, Any]):
async def _handle_agent_response(
self,
response_data: Dict[str, Any]
):
try:
thread_id = response_data.get("thread_id")
response_text = response_data.get("response", "")

if not thread_id or not response_text:
return

thread = self.get_channel(int(thread_id))
if thread:
for i in range(0, len(response_text), 2000):
await thread.send(response_text[i:i+2000])
else:
logger.error(f"Thread {thread_id} not found for agent response")
logger.error(
f"Thread {thread_id} not found for agent response"
)

except Exception as e:
logger.error(f"Error handling agent response: {str(e)}")