diff --git a/backend/app/agents/__init__.py b/backend/app/agents/__init__.py index a48cce4..a093f86 100644 --- a/backend/app/agents/__init__.py +++ b/backend/app/agents/__init__.py @@ -1,10 +1,8 @@ from .devrel.agent import DevRelAgent from .base_agent import BaseAgent, AgentState -from .classification_router import ClassificationRouter __all__ = [ "DevRelAgent", "BaseAgent", "AgentState", - "ClassificationRouter" ] diff --git a/backend/app/agents/classification_router.py b/backend/app/agents/classification_router.py deleted file mode 100644 index a0fd81b..0000000 --- a/backend/app/agents/classification_router.py +++ /dev/null @@ -1,208 +0,0 @@ -import json -import re -import logging -from typing import Dict, Any, Optional -from enum import Enum -from langchain_google_genai import ChatGoogleGenerativeAI -from langchain_core.prompts import ChatPromptTemplate -from app.core.config import settings -from langsmith import traceable - -logger = logging.getLogger(__name__) - -class MessageCategory(str, Enum): - FAQ = "faq" - TECHNICAL_SUPPORT = "technical_support" - COMMUNITY_ENGAGEMENT = "community_engagement" - DOCUMENTATION = "documentation" - ONBOARDING = "onboarding" - BUG_REPORT = "bug_report" - FEATURE_REQUEST = "feature_request" - WEB_SEARCH = "web_search" - NOT_DEVREL = "not_devrel" - -class DevRelNeed(str, Enum): - HIGH = "high" - MEDIUM = "medium" - LOW = "low" - NONE = "none" - -class ClassificationRouter: - """Enhanced message classification with DevRel assessment""" - - def __init__(self, llm_client=None): - self.llm = llm_client or ChatGoogleGenerativeAI( - model=settings.classification_agent_model, - temperature=0.1, - google_api_key=settings.gemini_api_key - ) - self._setup_prompts() - - def _setup_prompts(self): - self.classification_prompt = ChatPromptTemplate.from_messages([ - ("system", """You are a DevRel assistant classifier. Analyze messages to determine: - 1. Category (FAQ, Technical Support, Community Engagement, Documentation, Onboarding, Bug Report, Feature Request, Web Search, Not DevRel) - 2. DevRel need level (High, Medium, Low, None) - 3. Suggested agent (devrel, github, none) - 4. Priority level (high, medium, low) - - DevRel Intervention Guidelines: - - Technical questions about the project: HIGH - - Onboarding and getting started help: HIGH - - Community discussions and engagement: MEDIUM - - Documentation requests: MEDIUM - - Bug reports and feature requests: MEDIUM - - Developer experience feedback: HIGH - - Web search requests (asking to search for something): HIGH -> WEB_SEARCH - - Off-topic conversations: NONE - - Web Search Indicators: - - "search for", "look up", "find information about" - - "what's the latest", "recent news about" - - "research", "investigate" - - Respond ONLY with valid JSON: - {{ - "category": "category_name", - "needs_devrel": true/false, - "devrel_need_level": "high|medium|low|none", - "suggested_agent": "devrel|github|none", - "priority": "high|medium|low", - "confidence": 0.9, - "reasoning": "brief explanation" - }}"""), - ("human", "Message: {message}\nContext: {context}") - ]) - - @traceable(name="user_intent_classification", run_type="llm") - async def classify_message(self, message: str, context: Dict[str, Any] = None) -> Dict[str, Any]: - """Classify a message and determine if DevRel intervention is needed""" - try: - # Quick pattern matching for obvious cases - quick_result = self._quick_classify(message) - if quick_result: - return quick_result - - # LLM-based classification for complex cases - context_str = str(context) if context else "No additional context" - - response = await self.llm.ainvoke( - self.classification_prompt.format_messages( - message=message, - context=context_str - ) - ) - - # Parse LLM response - return self._parse_llm_response(response.content, message) - - except Exception as e: - logger.error(f"Classification error: {str(e)}") - return self._fallback_classification(message) - - def _quick_classify(self, message: str) -> Optional[Dict[str, Any]]: - """Quick pattern-based classification for obvious cases""" - message_lower = message.lower() - - # Web search patterns - search_patterns = [ - r"search for", r"look up", r"find information about", - r"what's the latest", r"recent news about", r"research", - r"investigate", r"google", r"find out about" - ] - - if any(re.search(pattern, message_lower) for pattern in search_patterns): - return { - "category": MessageCategory.WEB_SEARCH, - "needs_devrel": True, - "devrel_need_level": DevRelNeed.HIGH, - "suggested_agent": "devrel", - "priority": "high", - "confidence": 0.9, - "reasoning": "Web search request pattern match" - } - - # FAQ patterns - faq_patterns = [ - r"what is devr\.?ai", - r"how do i contribute", - r"how to get started", - r"what platforms.*support" - ] - - for pattern in faq_patterns: - if re.search(pattern, message_lower): - return { - "category": MessageCategory.FAQ, - "needs_devrel": True, - "devrel_need_level": DevRelNeed.MEDIUM, - "suggested_agent": "devrel", - "priority": "medium", - "confidence": 0.9, - "reasoning": "FAQ pattern match" - } - - # Bug report patterns - bug_patterns = [ - r"bug", r"error", r"broken", r"not working", r"issue with" - ] - - if any(re.search(pattern, message_lower) for pattern in bug_patterns): - return { - "category": MessageCategory.BUG_REPORT, - "needs_devrel": True, - "devrel_need_level": DevRelNeed.HIGH, - "suggested_agent": "github", - "priority": "high", - "confidence": 0.8, - "reasoning": "Bug report pattern match" - } - - return None - - def _parse_llm_response(self, response: str, original_message: str) -> Dict[str, Any]: - """Parse LLM response""" - try: - # Extract JSON from response - json_start = response.find('{') - json_end = response.rfind('}') + 1 - - if json_start != -1 and json_end != -1: - json_str = response[json_start:json_end] - parsed = json.loads(json_str) - if "category" in parsed: - category_str = parsed["category"].lower() - try: - category_mapping = { - "faq": MessageCategory.FAQ, - "web_search": MessageCategory.WEB_SEARCH, - "onboarding": MessageCategory.ONBOARDING, - "technical_support": MessageCategory.TECHNICAL_SUPPORT, - "community_engagement": MessageCategory.COMMUNITY_ENGAGEMENT, - "documentation": MessageCategory.DOCUMENTATION, - "bug_report": MessageCategory.BUG_REPORT, - "feature_request": MessageCategory.FEATURE_REQUEST, - "not_devrel": MessageCategory.NOT_DEVREL - } - parsed["category"] = category_mapping.get(category_str, MessageCategory.TECHNICAL_SUPPORT) - except (KeyError, AttributeError): - parsed["category"] = MessageCategory.TECHNICAL_SUPPORT - return parsed - - raise ValueError("No JSON found in response") - - except Exception as e: - logger.error(f"Error parsing LLM response: {str(e)}") - return self._fallback_classification(original_message) - - def _fallback_classification(self, original_message: str) -> Dict[str, Any]: - """Fallback classification when other methods fail""" - return { - "category": MessageCategory.TECHNICAL_SUPPORT, - "needs_devrel": True, - "devrel_need_level": DevRelNeed.LOW, - "suggested_agent": "devrel", - "priority": "low", - "confidence": 0.5, - "reasoning": f"Fallback classification for message: {original_message[:50]}..." - } diff --git a/backend/app/agents/devrel/agent.py b/backend/app/agents/devrel/agent.py index d3733db..53a5a93 100644 --- a/backend/app/agents/devrel/agent.py +++ b/backend/app/agents/devrel/agent.py @@ -5,17 +5,15 @@ from langchain_google_genai import ChatGoogleGenerativeAI from langgraph.checkpoint.memory import InMemorySaver from ..base_agent import BaseAgent, AgentState -from ..classification_router import MessageCategory from .tools.search_tool import TavilySearchTool from .tools.faq_tool import FAQTool +from .github.github_toolkit import GitHubToolkit from app.core.config import settings from .nodes.gather_context import gather_context_node -from .nodes.handlers.faq import handle_faq_node -from .nodes.handlers.web_search import handle_web_search_node -from .nodes.handlers.technical_support import handle_technical_support_node -from .nodes.handlers.onboarding import handle_onboarding_node -from .generate_response_node import generate_response_node from .nodes.summarization import check_summarization_needed, summarize_conversation_node, store_summary_to_database +from .nodes.react_supervisor import react_supervisor_node, supervisor_decision_router +from .tool_wrappers import web_search_tool_node, faq_handler_tool_node, onboarding_tool_node, github_toolkit_tool_node +from .nodes.generate_response import generate_response_node logger = logging.getLogger(__name__) @@ -31,6 +29,7 @@ def __init__(self, config: Dict[str, Any] = None): ) self.search_tool = TavilySearchTool() self.faq_tool = FAQTool() + self.github_toolkit = GitHubToolkit() self.checkpointer = InMemorySaver() super().__init__("DevRelAgent", self.config) @@ -38,41 +37,47 @@ def _build_graph(self): """Build the DevRel agent workflow graph""" workflow = StateGraph(AgentState) - # Add nodes + # Phase 1: Gather Context workflow.add_node("gather_context", gather_context_node) - workflow.add_node("handle_faq", partial(handle_faq_node, faq_tool=self.faq_tool)) - workflow.add_node("handle_web_search", partial( - handle_web_search_node, search_tool=self.search_tool, llm=self.llm)) - workflow.add_node("handle_technical_support", handle_technical_support_node) - workflow.add_node("handle_onboarding", handle_onboarding_node) + + # Phase 2: ReAct Supervisor - Decide what to do next + workflow.add_node("react_supervisor", partial(react_supervisor_node, llm=self.llm)) + workflow.add_node("web_search_tool", partial(web_search_tool_node, search_tool=self.search_tool, llm=self.llm)) + workflow.add_node("faq_handler_tool", partial(faq_handler_tool_node, faq_tool=self.faq_tool)) + workflow.add_node("onboarding_tool", onboarding_tool_node) + workflow.add_node("github_toolkit_tool", partial(github_toolkit_tool_node, github_toolkit=self.github_toolkit)) + + # Phase 3: Generate Response workflow.add_node("generate_response", partial(generate_response_node, llm=self.llm)) + + # Phase 4: Summarization workflow.add_node("check_summarization", check_summarization_needed) workflow.add_node("summarize_conversation", partial(summarize_conversation_node, llm=self.llm)) - # Add edges + # Entry point + workflow.set_entry_point("gather_context") + workflow.add_edge("gather_context", "react_supervisor") + + # ReAct supervisor routing workflow.add_conditional_edges( - "gather_context", - self._route_to_handler, + "react_supervisor", + supervisor_decision_router, { - MessageCategory.FAQ: "handle_faq", - MessageCategory.WEB_SEARCH: "handle_web_search", - MessageCategory.ONBOARDING: "handle_onboarding", - MessageCategory.TECHNICAL_SUPPORT: "handle_technical_support", - MessageCategory.COMMUNITY_ENGAGEMENT: "handle_technical_support", - MessageCategory.DOCUMENTATION: "handle_technical_support", - MessageCategory.BUG_REPORT: "handle_technical_support", - MessageCategory.FEATURE_REQUEST: "handle_technical_support", - MessageCategory.NOT_DEVREL: "handle_technical_support" + "web_search": "web_search_tool", + "faq_handler": "faq_handler_tool", + "onboarding": "onboarding_tool", + "github_toolkit": "github_toolkit_tool", + "complete": "generate_response" } ) - # All handlers lead to response generation - for node in ["handle_faq", "handle_web_search", "handle_technical_support", "handle_onboarding"]: - workflow.add_edge(node, "generate_response") + # All tools return to supervisor + for tool in ["web_search_tool", "faq_handler_tool", "onboarding_tool", "github_toolkit_tool"]: + workflow.add_edge(tool, "react_supervisor") workflow.add_edge("generate_response", "check_summarization") - # Conditional edge for summarization + # Summarization routing workflow.add_conditional_edges( "check_summarization", self._should_summarize, @@ -82,42 +87,11 @@ def _build_graph(self): } ) - # End after summarization workflow.add_edge("summarize_conversation", END) - # Set entry point - workflow.set_entry_point("gather_context") - - # Compile with InMemorySaver checkpointer + # Compile with checkpointer self.graph = workflow.compile(checkpointer=self.checkpointer) - def _route_to_handler(self, state: AgentState) -> str: - """Route to the appropriate handler based on intent""" - classification = state.context.get("classification", {}) - intent = classification.get("category") - - if isinstance(intent, str): - try: - intent = MessageCategory(intent.lower()) - except ValueError: - logger.warning(f"Unknown intent string '{intent}', defaulting to TECHNICAL_SUPPORT") - intent = MessageCategory.TECHNICAL_SUPPORT - - logger.info(f"Routing based on intent: {intent} for session {state.session_id}") - - # Mapping from MessageCategory enum to string keys used in add_conditional_edges - if intent in [MessageCategory.FAQ, MessageCategory.WEB_SEARCH, - MessageCategory.ONBOARDING, MessageCategory.TECHNICAL_SUPPORT, - MessageCategory.COMMUNITY_ENGAGEMENT, MessageCategory.DOCUMENTATION, - MessageCategory.BUG_REPORT, MessageCategory.FEATURE_REQUEST, - MessageCategory.NOT_DEVREL]: - logger.info(f"Routing to handler for: {intent}") - return intent - - # Later to be changed to handle anomalies - logger.info(f"Unknown intent '{intent}', routing to technical support") - return MessageCategory.TECHNICAL_SUPPORT - def _should_summarize(self, state: AgentState) -> str: """Determine if conversation should be summarized""" if state.summarization_needed: diff --git a/backend/app/agents/devrel/generate_response_node.py b/backend/app/agents/devrel/generate_response.py similarity index 96% rename from backend/app/agents/devrel/generate_response_node.py rename to backend/app/agents/devrel/generate_response.py index a83bcb5..5d202c5 100644 --- a/backend/app/agents/devrel/generate_response_node.py +++ b/backend/app/agents/devrel/generate_response.py @@ -2,7 +2,7 @@ from typing import Dict, Any from app.agents.state import AgentState from langchain_core.messages import HumanMessage -from .prompts.base_prompt import GENERAL_LLM_RESPONSE_PROMPT +from .prompts.response_prompt import RESPONSE_PROMPT from .nodes.handlers.web_search import create_search_response logger = logging.getLogger(__name__) @@ -46,7 +46,7 @@ async def _create_llm_response(state: AgentState, task_result: Dict[str, Any], l current_context_str = "\n".join(context_parts) try: - prompt = GENERAL_LLM_RESPONSE_PROMPT.format( + prompt = RESPONSE_PROMPT.format( conversation_summary=conversation_summary, latest_message=latest_message, conversation_history=conversation_history_str, diff --git a/backend/app/agents/devrel/github/github_toolkit.py b/backend/app/agents/devrel/github/github_toolkit.py new file mode 100644 index 0000000..1a874e9 --- /dev/null +++ b/backend/app/agents/devrel/github/github_toolkit.py @@ -0,0 +1,134 @@ +import logging +from typing import Dict, Any +from langchain_google_genai import ChatGoogleGenerativeAI +from langchain_core.messages import HumanMessage +from app.core.config import settings +from .prompts.intent_analysis import GITHUB_INTENT_ANALYSIS_PROMPT +from .tools.search import handle_web_search +# TODO: Implement all tools +# from .tools.contributor_recommendation import handle_contributor_recommendation +# from .tools.repository_query import handle_repo_query +# from .tools.issue_creation import handle_issue_creation +# from .tools.documentation_generation import handle_documentation_generation +from .tools.general_github_help import handle_general_github_help +logger = logging.getLogger(__name__) + + +class GitHubToolkit: + """ + GitHub Toolkit - Main entry point for GitHub operations + + This class serves as both the intent classifier and execution coordinator. + It thinks (classifies intent) and acts (delegates to appropriate tools). + """ + + def __init__(self): + self.llm = ChatGoogleGenerativeAI( + model=settings.github_agent_model, + temperature=0.1, + google_api_key=settings.gemini_api_key + ) + self.tools = [ + "web_search", + "contributor_recommendation", + "repo_support", + "issue_creation", + "documentation_generation", + "find_good_first_issues", + "general_github_help" + ] + + async def classify_intent(self, user_query: str) -> Dict[str, Any]: + """ + Classify intent and return classification with reasoning. + + Args: + user_query: The user's request or question + + Returns: + Dictionary containing classification, reasoning, and confidence + """ + logger.info(f"Classifying intent for query: {user_query[:100]}") + + try: + prompt = GITHUB_INTENT_ANALYSIS_PROMPT.format(user_query=user_query) + response = await self.llm.ainvoke([HumanMessage(content=prompt)]) + + import json + result = json.loads(response.content.strip()) + + classification = result.get("classification") + if classification not in self.tools: + logger.warning(f"Returned invalid function: {classification}, defaulting to general_github_help") + classification = "general_github_help" + result["classification"] = classification + + result["query"] = user_query + + logger.info(f"Classified intent as for query: {user_query} is: {classification}") + logger.info(f"Reasoning: {result.get('reasoning', 'No reasoning provided')}") + logger.info(f"Confidence: {result.get('confidence', 'unknown')}") + + return result + + except json.JSONDecodeError as e: + logger.error(f"Error parsing JSON response from LLM: {str(e)}") + logger.error(f"Raw response: {response.content}") + return { + "classification": "general_github_help", + "reasoning": f"Failed to parse LLM response: {str(e)}", + "confidence": "low", + "query": user_query + } + except Exception as e: + logger.error(f"Error in intent classification: {str(e)}") + return { + "classification": "general_github_help", + "reasoning": f"Error occurred during classification: {str(e)}", + "confidence": "low", + "query": user_query + } + + async def execute(self, query: str) -> Dict[str, Any]: + """ + Main execution method - classifies intent and delegates to appropriate tools + """ + logger.info(f"Executing GitHub toolkit for query: {query[:100]}") + + try: + intent_result = await self.classify_intent(query) + classification = intent_result["classification"] + + logger.info(f"Executing {classification} for query") + + if classification == "contributor_recommendation": + result = "Not implemented" + # result = await handle_contributor_recommendation(query) + elif classification == "repo_support": + result = "Not implemented" + # result = await handle_repo_query(query) + elif classification == "issue_creation": + result = "Not implemented" + # result = await handle_issue_creation(query) + elif classification == "documentation_generation": + result = "Not implemented" + # result = await handle_documentation_generation(query) + elif classification == "web_search": + result = await handle_web_search(query) + else: + result = await handle_general_github_help(query, self.llm) + + result["intent_analysis"] = intent_result + result["type"] = "github_toolkit" + + return result + + except Exception as e: + logger.error(f"Error in GitHub toolkit execution: {str(e)}") + return { + "status": "error", + "type": "github_toolkit", + "query": query, + "error": str(e), + "message": "Failed to execute GitHub operation" + } diff --git a/backend/app/agents/devrel/github/prompts/general_github_help.py b/backend/app/agents/devrel/github/prompts/general_github_help.py new file mode 100644 index 0000000..76396cd --- /dev/null +++ b/backend/app/agents/devrel/github/prompts/general_github_help.py @@ -0,0 +1,24 @@ +GENERAL_GITHUB_HELP_PROMPT = """You are a GitHub DevRel expert assistant. Provide helpful guidance for this GitHub-related query. + +USER QUERY: {query} + +{search_context} + +FORMATTING REQUIREMENTS: +- Use simple numbered lists (1. 2. 3.) instead of markdown bullets +- Avoid complex markdown formatting like **bold** or *italic* +- Use plain text with clear line breaks +- Format links as plain URLs: https://example.com +- Use simple emojis +- Keep paragraphs short and scannable +- Avoid complex markdown formatting + +Provide a comprehensive, helpful response that: +1. Directly addresses their question using your GitHub expertise +2. Incorporates relevant information from the web search results above +3. Offers practical next steps and actionable advice +4. Suggests related GitHub features or best practices +5. Provides examples or code snippets when relevant +6. Format for readability - clean, simple text + +Be conversational and actionable. Focus on being genuinely helpful for their GitHub needs.""" diff --git a/backend/app/agents/devrel/github/prompts/intent_analysis.py b/backend/app/agents/devrel/github/prompts/intent_analysis.py new file mode 100644 index 0000000..eb3f9a6 --- /dev/null +++ b/backend/app/agents/devrel/github/prompts/intent_analysis.py @@ -0,0 +1,29 @@ +GITHUB_INTENT_ANALYSIS_PROMPT = """You are an expert GitHub DevRel AI assistant. Analyze the user query and classify the intent. + +AVAILABLE FUNCTIONS: +- web_search: Search the web for information +- contributor_recommendation: Finding the right people to review PRs, assign issues, or collaborate +- repo_support: Questions about codebase structure, dependencies, impact analysis, architecture +- issue_creation: Creating bug reports, feature requests, or tracking items +- documentation_generation: Generating docs, READMEs, API docs, guides, or explanations +- find_good_first_issues: Finding beginner-friendly issues to work on across repositories +- general_github_help: General GitHub-related assistance and guidance + +USER QUERY: {user_query} + +Classification guidelines: +- contributor_recommendation: Finding reviewers, assignees, collaborators +- repo_support: Code structure, dependencies, impact analysis, architecture +- issue_creation: Creating bugs, features, tracking items +- documentation_generation: Docs, READMEs, guides, explanations +- find_good_first_issues: Beginners, newcomers, "good first issue" +- web_search: General information needing external search +- general_github_help: General GitHub questions not covered above + +CRITICAL: Return ONLY raw JSON. No markdown, no code blocks, no explanation text. + +{{ + "classification": "function_name_from_list_above", + "reasoning": "Brief explanation of why you chose this function", + "confidence": "high|medium|low" +}}""" diff --git a/backend/app/agents/github/__init__.py b/backend/app/agents/devrel/github/tools/__init__.py similarity index 100% rename from backend/app/agents/github/__init__.py rename to backend/app/agents/devrel/github/tools/__init__.py diff --git a/backend/app/agents/devrel/github/tools/contributor_recommendation.py b/backend/app/agents/devrel/github/tools/contributor_recommendation.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/app/agents/devrel/github/tools/contributor_recommendation.py @@ -0,0 +1 @@ + diff --git a/backend/app/agents/devrel/github/tools/documentation_generation.py b/backend/app/agents/devrel/github/tools/documentation_generation.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/app/agents/devrel/github/tools/documentation_generation.py @@ -0,0 +1 @@ + diff --git a/backend/app/agents/devrel/github/tools/general_github_help.py b/backend/app/agents/devrel/github/tools/general_github_help.py new file mode 100644 index 0000000..a93e86b --- /dev/null +++ b/backend/app/agents/devrel/github/tools/general_github_help.py @@ -0,0 +1,50 @@ +from typing import Dict, Any +import logging +from langchain_core.messages import HumanMessage +from app.agents.devrel.nodes.handlers.web_search import _extract_search_query +from .search import handle_web_search +from app.agents.devrel.github.prompts.general_github_help import GENERAL_GITHUB_HELP_PROMPT + +logger = logging.getLogger(__name__) + + +async def handle_general_github_help(query: str, llm) -> Dict[str, Any]: + """Execute general GitHub help with web search and LLM knowledge""" + logger.info("Providing general GitHub help") + + try: + query = await _extract_search_query(query, llm) + search_result = await handle_web_search(query) + + if search_result.get("status") == "success": + search_context = "SEARCH RESULTS:\n" + for result in search_result.get("results", []): + search_context += f"- {result.get('title', 'No title')}: {result.get('content', 'No content')}\n" + else: + search_context = "No search results available." + + help_prompt = GENERAL_GITHUB_HELP_PROMPT.format( + query=query, + search_context=search_context + ) + + response = await llm.ainvoke([HumanMessage(content=help_prompt)]) + + return { + "status": "success", + "sub_function": "general_github_help", + "query": query, + "response": response.content.strip(), + "search_context": search_context, + "message": "Provided GitHub help using LLM expertise and web search" + } + + except Exception as e: + logger.error(f"Error in general GitHub help: {str(e)}") + return { + "status": "error", + "sub_function": "general_github_help", + "query": query, + "error": str(e), + "message": "Failed to provide general GitHub help" + } diff --git a/backend/app/agents/devrel/github/tools/issue_creation.py b/backend/app/agents/devrel/github/tools/issue_creation.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/app/agents/devrel/github/tools/issue_creation.py @@ -0,0 +1 @@ + diff --git a/backend/app/agents/devrel/nodes/handlers/user_support.py b/backend/app/agents/devrel/github/tools/repository_query.py similarity index 100% rename from backend/app/agents/devrel/nodes/handlers/user_support.py rename to backend/app/agents/devrel/github/tools/repository_query.py diff --git a/backend/app/agents/devrel/github/tools/search.py b/backend/app/agents/devrel/github/tools/search.py new file mode 100644 index 0000000..533a838 --- /dev/null +++ b/backend/app/agents/devrel/github/tools/search.py @@ -0,0 +1,41 @@ +import logging +from typing import Dict, Any +from app.agents.devrel.tools.search_tool import TavilySearchTool +logger = logging.getLogger(__name__) + + +async def handle_web_search(query: str) -> Dict[str, Any]: + """Handle web search using Tavily search tool""" + logger.info("Handling web search request") + + try: + search_tool = TavilySearchTool() + search_results = await search_tool.search(query, max_results=5) + + if not search_results: + return { + "status": "no_results", + "sub_function": "web_search", + "query": query, + "message": "No web search results found for the query", + "results": [] + } + + return { + "status": "success", + "sub_function": "web_search", + "query": query, + "results": search_results, + "total_results": len(search_results), + "message": f"Found {len(search_results)} web search results" + } + + except Exception as e: + logger.error(f"Error in web search: {str(e)}") + return { + "status": "error", + "sub_function": "web_search", + "query": query, + "error": str(e), + "message": "Failed to perform web search" + } diff --git a/backend/app/agents/devrel/nodes/gather_context.py b/backend/app/agents/devrel/nodes/gather_context.py index 0a33de6..dc2f9e7 100644 --- a/backend/app/agents/devrel/nodes/gather_context.py +++ b/backend/app/agents/devrel/nodes/gather_context.py @@ -1,10 +1,11 @@ import logging from datetime import datetime +from typing import Dict, Any from app.agents.state import AgentState logger = logging.getLogger(__name__) -async def gather_context_node(state: AgentState) -> AgentState: +async def gather_context_node(state: AgentState) -> Dict[str, Any]: """Gather additional context for the user and their request""" logger.info(f"Gathering context for session {state.session_id}") @@ -31,5 +32,6 @@ async def gather_context_node(state: AgentState) -> AgentState: return { "messages": [new_message], "context": updated_context, - "current_task": "context_gathered" + "current_task": "context_gathered", + "last_interaction_time": datetime.now() } diff --git a/backend/app/agents/devrel/nodes/generate_response.py b/backend/app/agents/devrel/nodes/generate_response.py new file mode 100644 index 0000000..5796877 --- /dev/null +++ b/backend/app/agents/devrel/nodes/generate_response.py @@ -0,0 +1,101 @@ +import logging +import json +from typing import Dict, Any +from app.agents.state import AgentState +from langchain_core.messages import HumanMessage +from ..prompts.response_prompt import RESPONSE_PROMPT + +logger = logging.getLogger(__name__) + +async def generate_response_node(state: AgentState, llm) -> Dict[str, Any]: + """ + Final Response Generation Node + """ + logger.info(f"Generating response for session {state.session_id}") + + try: + final_response = await _create_response(state, llm) + + return { + "final_response": final_response, + "current_task": "response_generated" + } + + except Exception as e: + logger.error(f"Error generating response: {str(e)}") + return { + "final_response": "I apologize, but I encountered an error while generating my response. Please try asking your question again.", + "errors": state.errors + [str(e)], + "current_task": "response_error" + } + +async def _create_response(state: AgentState, llm) -> str: + """ + Response Generation and LLM synthesis + """ + logger.info(f"Creating response for session {state.session_id}") + + latest_message = _get_latest_message(state) + + conversation_summary = state.conversation_summary or "This is the beginning of our conversation." + + recent_messages_count = min(10, len(state.messages)) + conversation_history = "" + if state.messages: + conversation_history = "\n".join([ + f"{msg.get('role', 'user')}: {msg.get('content', '')}" + for msg in state.messages[-recent_messages_count:] + ]) + + if len(state.messages) > recent_messages_count: + conversation_history = f"[Showing last {recent_messages_count} of {len(state.messages)} messages]\n" + \ + conversation_history + else: + conversation_history = "No previous conversation" + + context_parts = [ + f"Platform: {state.platform}", + f"Total interactions: {state.interaction_count}", + f"Session duration: {(state.last_interaction_time - state.session_start_time).total_seconds() / 60:.1f} minutes" + ] + + if state.key_topics: + context_parts.append(f"Key topics discussed: {', '.join(state.key_topics)}") + if state.user_profile: + context_parts.append(f"User profile: {state.user_profile}") + + current_context = "\n".join(context_parts) + + supervisor_thinking = state.context.get("supervisor_thinking", "No reasoning process available") + + tool_results = state.context.get("tool_results", []) + tool_results_str = json.dumps(tool_results, indent=2) if tool_results else "No tool results" + + task_result = state.task_result or {} + task_result_str = json.dumps(task_result, indent=2) if task_result else "No task result" + + try: + prompt = RESPONSE_PROMPT.format( + latest_message=latest_message, + conversation_summary=conversation_summary, + conversation_history=conversation_history, + current_context=current_context, + supervisor_thinking=supervisor_thinking, + tool_results=tool_results_str, + task_result=task_result_str + ) + + logger.info(f"Generated response prompt using existing RESPONSE_PROMPT") + + except KeyError as e: + logger.error(f"Missing key in RESPONSE_PROMPT: {e}") + return f"Error: Response template formatting error - {str(e)}" + + response = await llm.ainvoke([HumanMessage(content=prompt)]) + return response.content.strip() + +def _get_latest_message(state: AgentState) -> str: + """Extract the latest message from state""" + if state.messages: + return state.messages[-1].get("content", "") + return state.context.get("original_message", "") diff --git a/backend/app/agents/devrel/nodes/react_supervisor.py b/backend/app/agents/devrel/nodes/react_supervisor.py new file mode 100644 index 0000000..12bec4c --- /dev/null +++ b/backend/app/agents/devrel/nodes/react_supervisor.py @@ -0,0 +1,112 @@ +import logging +import json +from typing import Dict, Any, Literal +from app.agents.state import AgentState +from langchain_core.messages import HumanMessage +from ..prompts.react_prompt import REACT_SUPERVISOR_PROMPT + +logger = logging.getLogger(__name__) + +async def react_supervisor_node(state: AgentState, llm) -> Dict[str, Any]: + """ReAct Supervisor: Think -> Act -> Observe""" + logger.info(f"ReAct Supervisor thinking for session {state.session_id}") + + # Get current context + latest_message = _get_latest_message(state) + conversation_history = _get_conversation_history(state) + tool_results = state.context.get("tool_results", []) + iteration_count = state.context.get("iteration_count", 0) + + prompt = REACT_SUPERVISOR_PROMPT.format( + latest_message=latest_message, + platform=state.platform, + interaction_count=state.interaction_count, + iteration_count=iteration_count, + conversation_history=conversation_history, + tool_results=json.dumps(tool_results, indent=2) if tool_results else "No previous tool results" + ) + + response = await llm.ainvoke([HumanMessage(content=prompt)]) + decision = _parse_supervisor_decision(response.content) + + logger.info(f"ReAct Supervisor decision: {decision['action']}") + + # Update state with supervisor's thinking + return { + "context": { + **state.context, + "supervisor_thinking": response.content, + "supervisor_decision": decision, + "iteration_count": iteration_count + 1 + }, + "current_task": f"supervisor_decided_{decision['action']}" + } + +def _parse_supervisor_decision(response: str) -> Dict[str, Any]: + """Parse the supervisor's decision from LLM response""" + try: + lines = response.strip().split('\n') + decision = {"action": "complete", "reasoning": "", "thinking": ""} + + for line in lines: + if line.startswith("THINK:"): + decision["thinking"] = line.replace("THINK:", "").strip() + elif line.startswith("ACT:"): + action = line.replace("ACT:", "").strip().lower() + if action in ["web_search", "faq_handler", "onboarding", "github_toolkit", "complete"]: + decision["action"] = action + elif line.startswith("REASON:"): + decision["reasoning"] = line.replace("REASON:", "").strip() + + return decision + except Exception as e: + logger.error(f"Error parsing supervisor decision: {e}") + return {"action": "complete", "reasoning": "Error in decision parsing", "thinking": ""} + +def supervisor_decision_router(state: AgentState) -> Literal["web_search", "faq_handler", "onboarding", "github_toolkit", "complete"]: + """Route based on supervisor's decision""" + decision = state.context.get("supervisor_decision", {}) + action = decision.get("action", "complete") + + # Safety check for infinite loops + iteration_count = state.context.get("iteration_count", 0) + if iteration_count > 10: + logger.warning(f"Max iterations reached for session {state.session_id}") + return "complete" + + return action + +def add_tool_result(state: AgentState, tool_name: str, result: Dict[str, Any]) -> Dict[str, Any]: + """Add tool result to state context""" + tool_results = state.context.get("tool_results", []) + tool_results.append({ + "tool": tool_name, + "result": result, + "iteration": state.context.get("iteration_count", 0) + }) + + return { + "context": { + **state.context, + "tool_results": tool_results + }, + "tools_used": state.tools_used + [tool_name], + "current_task": f"completed_{tool_name}" + } + +def _get_latest_message(state: AgentState) -> str: + """Extract the latest message from state""" + if state.messages: + return state.messages[-1].get("content", "") + return state.context.get("original_message", "") + +def _get_conversation_history(state: AgentState, max_messages: int = 5) -> str: + """Get formatted conversation history""" + if not state.messages: + return "No previous conversation" + + recent_messages = state.messages[-max_messages:] + return "\n".join([ + f"{msg.get('role', 'user')}: {msg.get('content', '')}" + for msg in recent_messages + ]) diff --git a/backend/app/agents/devrel/nodes/summarization.py b/backend/app/agents/devrel/nodes/summarization.py index 10281a2..eac976a 100644 --- a/backend/app/agents/devrel/nodes/summarization.py +++ b/backend/app/agents/devrel/nodes/summarization.py @@ -8,7 +8,7 @@ logger = logging.getLogger(__name__) # Configuration constants -SUMMARIZATION_THRESHOLD = 5 +SUMMARIZATION_THRESHOLD = 15 THREAD_TIMEOUT_HOURS = 1 async def check_summarization_needed(state: AgentState) -> Dict[str, Any]: diff --git a/backend/app/agents/devrel/prompts/base_prompt.py b/backend/app/agents/devrel/prompts/base_prompt.py deleted file mode 100644 index bf3dded..0000000 --- a/backend/app/agents/devrel/prompts/base_prompt.py +++ /dev/null @@ -1,28 +0,0 @@ -GENERAL_LLM_RESPONSE_PROMPT = ( - "You are a helpful DevRel assistant. " - "Your goal is to assist users with their technical questions, onboarding, and community engagement.\n\n" - - "CONVERSATION SUMMARY:\n" - "{conversation_summary}\n\n" - - "RECENT CONVERSATION:\n" - "{conversation_history}\n\n" - - "USER'S CURRENT MESSAGE:\n" - "\"{latest_message}\"\n\n" - - "CURRENT CONTEXT:\n" - "{current_context}\n\n" - - "TASK HANDLED: {task_type}\n" - "TASK DETAILS:\n" - "{task_details}\n\n" - - "Instructions:\n" - "- Use the conversation summary for long-term context\n" - "- Use recent conversation for immediate context\n" - "- Provide helpful and personalized responses\n" - "- Reference previous discussions when relevant\n\n" - - "Response: " -) diff --git a/backend/app/agents/devrel/prompts/react_prompt.py b/backend/app/agents/devrel/prompts/react_prompt.py new file mode 100644 index 0000000..b92e5f6 --- /dev/null +++ b/backend/app/agents/devrel/prompts/react_prompt.py @@ -0,0 +1,35 @@ +REACT_SUPERVISOR_PROMPT = """You are a DevRel AI assistant. Use ReAct reasoning: Think -> Act -> Observe. + +CURRENT SITUATION: +- User Message: {latest_message} +- Platform: {platform} +- Interaction Count: {interaction_count} +- Current Iteration: {iteration_count} + +CONVERSATION HISTORY: +{conversation_history} + +TOOL RESULTS FROM PREVIOUS ACTIONS: +{tool_results} + +AVAILABLE ACTIONS: +1. web_search - Search the web for external information +2. faq_handler - Answer common questions from knowledge base +3. onboarding - Welcome new users and guide exploration +4. github_toolkit - Handle GitHub operations (issues, PRs, repos, docs) +5. complete - Task is finished, format final response + +THINK: Analyze the user's request and current context. What needs to be done? + +Then choose ONE action: +- If you need external information or recent updates → web_search +- If this is a common question with a known answer → faq_handler +- If this is a new user needing guidance → onboarding +- If this involves GitHub repositories, issues, PRs, or code → github_toolkit +- If you have enough information to fully answer → complete + +Respond in this exact format: +THINK: [Your reasoning about what the user needs] +ACT: [Choose one: web_search, faq_handler, onboarding, github_toolkit, complete] +REASON: [Why you chose this action] +""" diff --git a/backend/app/agents/devrel/prompts/response_prompt.py b/backend/app/agents/devrel/prompts/response_prompt.py new file mode 100644 index 0000000..99291f3 --- /dev/null +++ b/backend/app/agents/devrel/prompts/response_prompt.py @@ -0,0 +1,41 @@ +RESPONSE_PROMPT = """You are a helpful DevRel assistant. Create a comprehensive response based on all available information. + +USER'S REQUEST: +{latest_message} + +CONVERSATION SUMMARY: +{conversation_summary} + +RECENT CONVERSATION: +{conversation_history} + +CURRENT CONTEXT: +{current_context} + +YOUR REASONING PROCESS: +{supervisor_thinking} + +TOOL RESULTS: +{tool_results} + +TASK RESULT: +{task_result} + +DISCORD FORMATTING REQUIREMENTS: +- Use simple numbered lists (1. 2. 3.) instead of markdown bullets +- Avoid complex markdown formatting like **bold** or *italic* +- Use plain text with clear line breaks +- Format links as plain URLs: https://example.com +- Use simple emojis for visual appeal: 🔗 📚 ⚡ +- Keep paragraphs short and scannable +- Use "→" for arrows instead of markdown arrows + +Instructions: +1. Synthesize all information - Use reasoning process, tool results, and task results together +2. Address the user's needs - Focus on what they're trying to accomplish +3. Be actionable - Provide specific steps, resources, or guidance +4. Stay DevRel-focused - Be encouraging, helpful, and community-oriented +5. Reference sources - Mention what you researched or considered when relevant +6. Format for readability - Clean, simple text that displays well + +Create a helpful, comprehensive response:""" diff --git a/backend/app/agents/devrel/prompts/support_prompt.py b/backend/app/agents/devrel/prompts/support_prompt.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/devrel/state.py b/backend/app/agents/devrel/state.py deleted file mode 100644 index 8bf2290..0000000 --- a/backend/app/agents/devrel/state.py +++ /dev/null @@ -1 +0,0 @@ -# Placeholder to enhance ..shared/state.py if required diff --git a/backend/app/agents/devrel/tool_wrappers.py b/backend/app/agents/devrel/tool_wrappers.py new file mode 100644 index 0000000..7fa10bb --- /dev/null +++ b/backend/app/agents/devrel/tool_wrappers.py @@ -0,0 +1,57 @@ +import logging +from typing import Dict, Any +from app.agents.state import AgentState +from .nodes.react_supervisor import add_tool_result +from .nodes.handlers.faq import handle_faq_node +from .nodes.handlers.web_search import handle_web_search_node +from .nodes.handlers.onboarding import handle_onboarding_node + +logger = logging.getLogger(__name__) + +async def web_search_tool_node(state: AgentState, search_tool, llm) -> Dict[str, Any]: + """Execute web search tool and add result to ReAct context""" + logger.info(f"Executing web search tool for session {state.session_id}") + + handler_result = await handle_web_search_node(state, search_tool, llm) + tool_result = handler_result.get("task_result", {}) + return add_tool_result(state, "web_search", tool_result) + +async def faq_handler_tool_node(state: AgentState, faq_tool) -> Dict[str, Any]: + """Execute FAQ handler tool and add result to ReAct context""" + logger.info(f"Executing FAQ handler tool for session {state.session_id}") + + handler_result = await handle_faq_node(state, faq_tool) + tool_result = handler_result.get("task_result", {}) + return add_tool_result(state, "faq_handler", tool_result) + +async def onboarding_tool_node(state: AgentState) -> Dict[str, Any]: + """Execute onboarding tool and add result to ReAct context""" + logger.info(f"Executing onboarding tool for session {state.session_id}") + + handler_result = await handle_onboarding_node(state) + tool_result = handler_result.get("task_result", {}) + return add_tool_result(state, "onboarding", tool_result) + + +async def github_toolkit_tool_node(state: AgentState, github_toolkit) -> Dict[str, Any]: + """Execute GitHub toolkit tool and add result to ReAct context""" + logger.info(f"Executing GitHub toolkit tool for session {state.session_id}") + + latest_message = "" + if state.messages: + latest_message = state.messages[-1].get("content", "") + elif state.context.get("original_message"): + latest_message = state.context["original_message"] + + try: + github_result = await github_toolkit.execute(latest_message) + tool_result = github_result + except Exception as e: + logger.error(f"Error in GitHub toolkit: {str(e)}") + tool_result = { + "type": "github_toolkit", + "error": str(e), + "status": "error" + } + + return add_tool_result(state, "github_toolkit", tool_result) diff --git a/backend/app/agents/devrel/tools/github_query.py b/backend/app/agents/devrel/tools/github_query.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/devrel/tools/user_lookup.py b/backend/app/agents/devrel/tools/user_lookup.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/agent.py b/backend/app/agents/github/agent.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/nodes/issue_manager.py b/backend/app/agents/github/nodes/issue_manager.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/nodes/pr_manager.py b/backend/app/agents/github/nodes/pr_manager.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/nodes/webhook_processor.py b/backend/app/agents/github/nodes/webhook_processor.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/state.py b/backend/app/agents/github/state.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/tools/__init__.py b/backend/app/agents/github/tools/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/tools/code_chunker.py b/backend/app/agents/github/tools/code_chunker.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/tools/repo_analyzer.py b/backend/app/agents/github/tools/repo_analyzer.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/tools/user_profiler.py b/backend/app/agents/github/tools/user_profiler.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/agents/github/nodes/__init__.py b/backend/app/classification/__init__.py similarity index 100% rename from backend/app/agents/github/nodes/__init__.py rename to backend/app/classification/__init__.py diff --git a/backend/app/classification/classification_router.py b/backend/app/classification/classification_router.py new file mode 100644 index 0000000..1708dce --- /dev/null +++ b/backend/app/classification/classification_router.py @@ -0,0 +1,59 @@ +import logging +from typing import Dict, Any +from langchain_google_genai import ChatGoogleGenerativeAI +from langchain_core.messages import HumanMessage +from app.core.config import settings +from .prompt import DEVREL_TRIAGE_PROMPT + +logger = logging.getLogger(__name__) + +class ClassificationRouter: + """Simple DevRel triage - determines if message needs DevRel assistance""" + + def __init__(self, llm_client=None): + self.llm = llm_client or ChatGoogleGenerativeAI( + model=settings.classification_agent_model, + temperature=0.1, + google_api_key=settings.gemini_api_key + ) + + 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: + triage_prompt = DEVREL_TRIAGE_PROMPT.format( + message=message, + context=context or 'No additional context' + ) + + 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 + json_str = response_text[json_start:json_end] + + import json + result = json.loads(json_str) + + return { + "needs_devrel": result.get("needs_devrel", True), + "priority": result.get("priority", "medium"), + "reasoning": result.get("reasoning", "LLM classification"), + "original_message": message + } + + return self._fallback_triage(message) + + except Exception as e: + logger.error(f"Triage error: {str(e)}") + return self._fallback_triage(message) + + def _fallback_triage(self, message: str) -> Dict[str, Any]: + """Fallback: assume it needs DevRel help""" + return { + "needs_devrel": True, + "priority": "medium", + "reasoning": "Fallback - assuming DevRel assistance needed", + "original_message": message + } diff --git a/backend/app/classification/prompt.py b/backend/app/classification/prompt.py new file mode 100644 index 0000000..5b458d3 --- /dev/null +++ b/backend/app/classification/prompt.py @@ -0,0 +1,26 @@ +DEVREL_TRIAGE_PROMPT = """Analyze this message to determine if it needs DevRel assistance. + +Message: {message} + +Context: {context} + +DevRel handles: +- Technical questions about projects/APIs +- Developer onboarding and support +- Bug reports and feature requests +- Community discussions about development +- Documentation requests +- General developer experience questions + +Respond ONLY with JSON: +{{ + "needs_devrel": true/false, + "priority": "high|medium|low", + "reasoning": "brief explanation" +}} + +Examples: +- "How do I contribute?" → {{"needs_devrel": true, "priority": "high", "reasoning": "Onboarding question"}} +- "What's for lunch?" → {{"needs_devrel": false, "priority": "low", "reasoning": "Not development related"}} +- "API is throwing errors" → {{"needs_devrel": true, "priority": "high", "reasoning": "Technical support needed"}} +""" diff --git a/backend/app/core/config/settings.py b/backend/app/core/config/settings.py index 5ccb79e..f314a94 100644 --- a/backend/app/core/config/settings.py +++ b/backend/app/core/config/settings.py @@ -1,6 +1,7 @@ from pydantic_settings import BaseSettings from dotenv import load_dotenv from pydantic import field_validator +from typing import Optional load_dotenv() @@ -26,14 +27,14 @@ class Settings(BaseSettings): langsmith_project: str = "DevR_AI" # Agent Configuration - devrel_agent_model: str = "gemini-2.0-flash" - github_agent_model: str = "gemini-2.0-flash" - classification_agent_model: str = "gemini-1.5-flash" + devrel_agent_model: str = "gemini-2.5-flash" + github_agent_model: str = "gemini-2.5-flash" + classification_agent_model: str = "gemini-2.0-flash" agent_timeout: int = 30 max_retries: int = 3 - + # RabbitMQ configuration - rabbitmq_url: str = "" + rabbitmq_url: Optional[str] = None # Backend URL backend_url: str = "" diff --git a/backend/app/core/orchestration/agent_coordinator.py b/backend/app/core/orchestration/agent_coordinator.py index b80ab97..4221077 100644 --- a/backend/app/core/orchestration/agent_coordinator.py +++ b/backend/app/core/orchestration/agent_coordinator.py @@ -1,10 +1,7 @@ import logging import uuid from typing import Dict, Any -from datetime import datetime from app.agents.devrel.agent import DevRelAgent -# TODO: Implement GitHub agent -# from app.agents.github.agent import GitHubAgent from app.agents.state import AgentState from app.core.orchestration.queue_manager import AsyncQueueManager from app.agents.devrel.nodes.summarization import store_summary_to_database @@ -18,18 +15,14 @@ class AgentCoordinator: def __init__(self, queue_manager: AsyncQueueManager): self.queue_manager = queue_manager self.devrel_agent = DevRelAgent() - # self.github_agent = GitHubAgent() self.active_sessions: Dict[str, AgentState] = {} - # Register handlers self._register_handlers() def _register_handlers(self): """Register message handlers""" self.queue_manager.register_handler("devrel_request", self._handle_devrel_request) self.queue_manager.register_handler("clear_thread_memory", self._handle_clear_memory_request) - # TODO: Register GitHub agent handler after implementation - # self.queue_manager.register_handler("github_request", self._handle_github_request) @traceable(name="devrel_request_coordination", run_type="chain") async def _handle_devrel_request(self, message_data: Dict[str, Any]): @@ -129,6 +122,3 @@ async def _send_response_to_platform(self, original_message: Dict[str, Any], res async def _send_error_response(self, original_message: Dict[str, Any], error_message: str): """Send error response to platform""" await self._send_response_to_platform(original_message, error_message) - - # TODO: Implement GitHub agent - # async def _handle_github_request(self, message_data: Dict[str, Any]): diff --git a/backend/integrations/discord/bot.py b/backend/integrations/discord/bot.py index d17d534..a83651c 100644 --- a/backend/integrations/discord/bot.py +++ b/backend/integrations/discord/bot.py @@ -3,7 +3,7 @@ import logging from typing import Dict, Any, Optional from app.core.orchestration.queue_manager import AsyncQueueManager, QueuePriority -from app.agents.classification_router import ClassificationRouter +from app.classification.classification_router import ClassificationRouter logger = logging.getLogger(__name__) @@ -51,8 +51,7 @@ async def on_message(self, message): return try: - # Classify message locally first - classification = await self.classifier.classify_message( + triage_result = await self.classifier.should_process_message( message.content, { "channel_id": str(message.channel.id), @@ -61,16 +60,15 @@ async def on_message(self, message): } ) - logger.info(f"Message classified as: {classification}") + logger.info(f"Message triage result: {triage_result}") - # Only process if DevRel intervention is needed - if classification.get("needs_devrel", False): - await self._handle_devrel_message(message, classification) + if triage_result.get("needs_devrel", False): + await self._handle_devrel_message(message, triage_result) except Exception as e: logger.error(f"Error processing message: {str(e)}") - async def _handle_devrel_message(self, message, classification: Dict[str, Any]): + async def _handle_devrel_message(self, message, triage_result: Dict[str, Any]): """Handle messages that need DevRel intervention""" try: user_id = str(message.author.id) @@ -87,7 +85,7 @@ async def _handle_devrel_message(self, message, classification: Dict[str, Any]): "thread_id": thread_id, "memory_thread_id": user_id, "content": message.content, - "classification": classification, + "triage": triage_result, "platform": "discord", "timestamp": message.created_at.isoformat(), "author": { @@ -96,13 +94,13 @@ async def _handle_devrel_message(self, message, classification: Dict[str, Any]): } } - # Determine priority based on classification + # Determine priority based on triage priority_map = { "high": QueuePriority.HIGH, "medium": QueuePriority.MEDIUM, "low": QueuePriority.LOW } - priority = priority_map.get(classification.get("priority"), QueuePriority.MEDIUM) + priority = priority_map.get(triage_result.get("priority"), QueuePriority.MEDIUM) # Enqueue for agent processing await self.queue_manager.enqueue(agent_message, priority)