Skip to content

Commit 8700e48

Browse files
Merge pull request #90 from smokeyScraper/agent_orchestration
[feature]: entirely ReAct based workflow; aligns agents; introduces github agent
2 parents 0e69cb9 + 76b1532 commit 8700e48

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+768
-330
lines changed

backend/app/agents/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
from .devrel.agent import DevRelAgent
22
from .base_agent import BaseAgent, AgentState
3-
from .classification_router import ClassificationRouter
43

54
__all__ = [
65
"DevRelAgent",
76
"BaseAgent",
87
"AgentState",
9-
"ClassificationRouter"
108
]

backend/app/agents/classification_router.py

Lines changed: 0 additions & 208 deletions
This file was deleted.

backend/app/agents/devrel/agent.py

Lines changed: 34 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@
55
from langchain_google_genai import ChatGoogleGenerativeAI
66
from langgraph.checkpoint.memory import InMemorySaver
77
from ..base_agent import BaseAgent, AgentState
8-
from ..classification_router import MessageCategory
98
from .tools.search_tool import TavilySearchTool
109
from .tools.faq_tool import FAQTool
10+
from .github.github_toolkit import GitHubToolkit
1111
from app.core.config import settings
1212
from .nodes.gather_context import gather_context_node
13-
from .nodes.handlers.faq import handle_faq_node
14-
from .nodes.handlers.web_search import handle_web_search_node
15-
from .nodes.handlers.technical_support import handle_technical_support_node
16-
from .nodes.handlers.onboarding import handle_onboarding_node
17-
from .generate_response_node import generate_response_node
1813
from .nodes.summarization import check_summarization_needed, summarize_conversation_node, store_summary_to_database
14+
from .nodes.react_supervisor import react_supervisor_node, supervisor_decision_router
15+
from .tool_wrappers import web_search_tool_node, faq_handler_tool_node, onboarding_tool_node, github_toolkit_tool_node
16+
from .nodes.generate_response import generate_response_node
1917

2018
logger = logging.getLogger(__name__)
2119

@@ -31,48 +29,55 @@ def __init__(self, config: Dict[str, Any] = None):
3129
)
3230
self.search_tool = TavilySearchTool()
3331
self.faq_tool = FAQTool()
32+
self.github_toolkit = GitHubToolkit()
3433
self.checkpointer = InMemorySaver()
3534
super().__init__("DevRelAgent", self.config)
3635

3736
def _build_graph(self):
3837
"""Build the DevRel agent workflow graph"""
3938
workflow = StateGraph(AgentState)
4039

41-
# Add nodes
40+
# Phase 1: Gather Context
4241
workflow.add_node("gather_context", gather_context_node)
43-
workflow.add_node("handle_faq", partial(handle_faq_node, faq_tool=self.faq_tool))
44-
workflow.add_node("handle_web_search", partial(
45-
handle_web_search_node, search_tool=self.search_tool, llm=self.llm))
46-
workflow.add_node("handle_technical_support", handle_technical_support_node)
47-
workflow.add_node("handle_onboarding", handle_onboarding_node)
42+
43+
# Phase 2: ReAct Supervisor - Decide what to do next
44+
workflow.add_node("react_supervisor", partial(react_supervisor_node, llm=self.llm))
45+
workflow.add_node("web_search_tool", partial(web_search_tool_node, search_tool=self.search_tool, llm=self.llm))
46+
workflow.add_node("faq_handler_tool", partial(faq_handler_tool_node, faq_tool=self.faq_tool))
47+
workflow.add_node("onboarding_tool", onboarding_tool_node)
48+
workflow.add_node("github_toolkit_tool", partial(github_toolkit_tool_node, github_toolkit=self.github_toolkit))
49+
50+
# Phase 3: Generate Response
4851
workflow.add_node("generate_response", partial(generate_response_node, llm=self.llm))
52+
53+
# Phase 4: Summarization
4954
workflow.add_node("check_summarization", check_summarization_needed)
5055
workflow.add_node("summarize_conversation", partial(summarize_conversation_node, llm=self.llm))
5156

52-
# Add edges
57+
# Entry point
58+
workflow.set_entry_point("gather_context")
59+
workflow.add_edge("gather_context", "react_supervisor")
60+
61+
# ReAct supervisor routing
5362
workflow.add_conditional_edges(
54-
"gather_context",
55-
self._route_to_handler,
63+
"react_supervisor",
64+
supervisor_decision_router,
5665
{
57-
MessageCategory.FAQ: "handle_faq",
58-
MessageCategory.WEB_SEARCH: "handle_web_search",
59-
MessageCategory.ONBOARDING: "handle_onboarding",
60-
MessageCategory.TECHNICAL_SUPPORT: "handle_technical_support",
61-
MessageCategory.COMMUNITY_ENGAGEMENT: "handle_technical_support",
62-
MessageCategory.DOCUMENTATION: "handle_technical_support",
63-
MessageCategory.BUG_REPORT: "handle_technical_support",
64-
MessageCategory.FEATURE_REQUEST: "handle_technical_support",
65-
MessageCategory.NOT_DEVREL: "handle_technical_support"
66+
"web_search": "web_search_tool",
67+
"faq_handler": "faq_handler_tool",
68+
"onboarding": "onboarding_tool",
69+
"github_toolkit": "github_toolkit_tool",
70+
"complete": "generate_response"
6671
}
6772
)
6873

69-
# All handlers lead to response generation
70-
for node in ["handle_faq", "handle_web_search", "handle_technical_support", "handle_onboarding"]:
71-
workflow.add_edge(node, "generate_response")
74+
# All tools return to supervisor
75+
for tool in ["web_search_tool", "faq_handler_tool", "onboarding_tool", "github_toolkit_tool"]:
76+
workflow.add_edge(tool, "react_supervisor")
7277

7378
workflow.add_edge("generate_response", "check_summarization")
7479

75-
# Conditional edge for summarization
80+
# Summarization routing
7681
workflow.add_conditional_edges(
7782
"check_summarization",
7883
self._should_summarize,
@@ -82,42 +87,11 @@ def _build_graph(self):
8287
}
8388
)
8489

85-
# End after summarization
8690
workflow.add_edge("summarize_conversation", END)
8791

88-
# Set entry point
89-
workflow.set_entry_point("gather_context")
90-
91-
# Compile with InMemorySaver checkpointer
92+
# Compile with checkpointer
9293
self.graph = workflow.compile(checkpointer=self.checkpointer)
9394

94-
def _route_to_handler(self, state: AgentState) -> str:
95-
"""Route to the appropriate handler based on intent"""
96-
classification = state.context.get("classification", {})
97-
intent = classification.get("category")
98-
99-
if isinstance(intent, str):
100-
try:
101-
intent = MessageCategory(intent.lower())
102-
except ValueError:
103-
logger.warning(f"Unknown intent string '{intent}', defaulting to TECHNICAL_SUPPORT")
104-
intent = MessageCategory.TECHNICAL_SUPPORT
105-
106-
logger.info(f"Routing based on intent: {intent} for session {state.session_id}")
107-
108-
# Mapping from MessageCategory enum to string keys used in add_conditional_edges
109-
if intent in [MessageCategory.FAQ, MessageCategory.WEB_SEARCH,
110-
MessageCategory.ONBOARDING, MessageCategory.TECHNICAL_SUPPORT,
111-
MessageCategory.COMMUNITY_ENGAGEMENT, MessageCategory.DOCUMENTATION,
112-
MessageCategory.BUG_REPORT, MessageCategory.FEATURE_REQUEST,
113-
MessageCategory.NOT_DEVREL]:
114-
logger.info(f"Routing to handler for: {intent}")
115-
return intent
116-
117-
# Later to be changed to handle anomalies
118-
logger.info(f"Unknown intent '{intent}', routing to technical support")
119-
return MessageCategory.TECHNICAL_SUPPORT
120-
12195
def _should_summarize(self, state: AgentState) -> str:
12296
"""Determine if conversation should be summarized"""
12397
if state.summarization_needed:

backend/app/agents/devrel/generate_response_node.py renamed to backend/app/agents/devrel/generate_response.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Dict, Any
33
from app.agents.state import AgentState
44
from langchain_core.messages import HumanMessage
5-
from .prompts.base_prompt import GENERAL_LLM_RESPONSE_PROMPT
5+
from .prompts.response_prompt import RESPONSE_PROMPT
66
from .nodes.handlers.web_search import create_search_response
77

88
logger = logging.getLogger(__name__)
@@ -46,7 +46,7 @@ async def _create_llm_response(state: AgentState, task_result: Dict[str, Any], l
4646
current_context_str = "\n".join(context_parts)
4747

4848
try:
49-
prompt = GENERAL_LLM_RESPONSE_PROMPT.format(
49+
prompt = RESPONSE_PROMPT.format(
5050
conversation_summary=conversation_summary,
5151
latest_message=latest_message,
5252
conversation_history=conversation_history_str,

0 commit comments

Comments
 (0)