Skip to content

Commit 334ad38

Browse files
committed
[feature] Add GitHub Agent (SuperTool) integration module
1 parent dfd75c1 commit 334ad38

File tree

10 files changed

+281
-0
lines changed

10 files changed

+281
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import logging
2+
from typing import Dict, Any
3+
from langchain_google_genai import ChatGoogleGenerativeAI
4+
from langchain_core.messages import HumanMessage
5+
from app.core.config import settings
6+
from .prompts.intent_analysis import GITHUB_INTENT_ANALYSIS_PROMPT
7+
from .tools.search import handle_web_search
8+
# TODO: Implement all tools
9+
# from .tools.contributor_recommendation import handle_contributor_recommendation
10+
# from .tools.repository_query import handle_repo_query
11+
# from .tools.issue_creation import handle_issue_creation
12+
# from .tools.documentation_generation import handle_documentation_generation
13+
from .tools.general_github_help import handle_general_github_help
14+
logger = logging.getLogger(__name__)
15+
16+
17+
class GitHubToolkit:
18+
"""
19+
GitHub Toolkit - Main entry point for GitHub operations
20+
21+
This class serves as both the intent classifier and execution coordinator.
22+
It thinks (classifies intent) and acts (delegates to appropriate tools).
23+
"""
24+
25+
def __init__(self):
26+
self.llm = ChatGoogleGenerativeAI(
27+
model=settings.github_agent_model,
28+
temperature=0.1,
29+
google_api_key=settings.gemini_api_key
30+
)
31+
self.tools = [
32+
"web_search",
33+
"contributor_recommendation",
34+
"repo_support",
35+
"issue_creation",
36+
"documentation_generation",
37+
"find_good_first_issues",
38+
"general_github_help"
39+
]
40+
41+
async def classify_intent(self, user_query: str) -> Dict[str, Any]:
42+
"""
43+
Classify intent and return classification with reasoning.
44+
45+
Args:
46+
user_query: The user's request or question
47+
48+
Returns:
49+
Dictionary containing classification, reasoning, and confidence
50+
"""
51+
logger.info(f"Classifying intent for query: {user_query[:100]}")
52+
53+
try:
54+
prompt = GITHUB_INTENT_ANALYSIS_PROMPT.format(user_query=user_query)
55+
response = await self.llm.ainvoke([HumanMessage(content=prompt)])
56+
57+
import json
58+
result = json.loads(response.content.strip())
59+
60+
classification = result.get("classification")
61+
if classification not in self.tools:
62+
logger.warning(f"Returned invalid function: {classification}, defaulting to general_github_help")
63+
classification = "general_github_help"
64+
result["classification"] = classification
65+
66+
result["query"] = user_query
67+
68+
logger.info(f"Classified intent as for query: {user_query} is: {classification}")
69+
logger.info(f"Reasoning: {result.get('reasoning', 'No reasoning provided')}")
70+
logger.info(f"Confidence: {result.get('confidence', 'unknown')}")
71+
72+
return result
73+
74+
except json.JSONDecodeError as e:
75+
logger.error(f"Error parsing JSON response from LLM: {str(e)}")
76+
logger.error(f"Raw response: {response.content}")
77+
return {
78+
"classification": "general_github_help",
79+
"reasoning": f"Failed to parse LLM response: {str(e)}",
80+
"confidence": "low",
81+
"query": user_query
82+
}
83+
except Exception as e:
84+
logger.error(f"Error in intent classification: {str(e)}")
85+
return {
86+
"classification": "general_github_help",
87+
"reasoning": f"Error occurred during classification: {str(e)}",
88+
"confidence": "low",
89+
"query": user_query
90+
}
91+
92+
async def execute(self, query: str) -> Dict[str, Any]:
93+
"""
94+
Main execution method - classifies intent and delegates to appropriate tools
95+
"""
96+
logger.info(f"Executing GitHub toolkit for query: {query[:100]}")
97+
98+
try:
99+
intent_result = await self.classify_intent(query)
100+
classification = intent_result["classification"]
101+
102+
logger.info(f"Executing {classification} for query")
103+
104+
if classification == "contributor_recommendation":
105+
result = "Not implemented"
106+
# result = await handle_contributor_recommendation(query)
107+
elif classification == "repo_support":
108+
result = "Not implemented"
109+
# result = await handle_repo_query(query)
110+
elif classification == "issue_creation":
111+
result = "Not implemented"
112+
# result = await handle_issue_creation(query)
113+
elif classification == "documentation_generation":
114+
result = "Not implemented"
115+
# result = await handle_documentation_generation(query)
116+
elif classification == "web_search":
117+
result = await handle_web_search(query)
118+
else:
119+
result = await handle_general_github_help(query, self.llm)
120+
121+
result["intent_analysis"] = intent_result
122+
result["type"] = "github_toolkit"
123+
124+
return result
125+
126+
except Exception as e:
127+
logger.error(f"Error in GitHub toolkit execution: {str(e)}")
128+
return {
129+
"status": "error",
130+
"type": "github_toolkit",
131+
"query": query,
132+
"error": str(e),
133+
"message": "Failed to execute GitHub operation"
134+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
GENERAL_GITHUB_HELP_PROMPT = """You are a GitHub DevRel expert assistant. Provide helpful guidance for this GitHub-related query.
2+
3+
USER QUERY: {query}
4+
5+
{search_context}
6+
7+
FORMATTING REQUIREMENTS:
8+
- Use simple numbered lists (1. 2. 3.) instead of markdown bullets
9+
- Avoid complex markdown formatting like **bold** or *italic*
10+
- Use plain text with clear line breaks
11+
- Format links as plain URLs: https://example.com
12+
- Use simple emojis
13+
- Keep paragraphs short and scannable
14+
- Avoid complex markdown formatting
15+
16+
Provide a comprehensive, helpful response that:
17+
1. Directly addresses their question using your GitHub expertise
18+
2. Incorporates relevant information from the web search results above
19+
3. Offers practical next steps and actionable advice
20+
4. Suggests related GitHub features or best practices
21+
5. Provides examples or code snippets when relevant
22+
6. Format for readability - clean, simple text
23+
24+
Be conversational and actionable. Focus on being genuinely helpful for their GitHub needs."""
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
GITHUB_INTENT_ANALYSIS_PROMPT = """You are an expert GitHub DevRel AI assistant. Analyze the user query and classify the intent.
2+
3+
AVAILABLE FUNCTIONS:
4+
- web_search: Search the web for information
5+
- contributor_recommendation: Finding the right people to review PRs, assign issues, or collaborate
6+
- repo_support: Questions about codebase structure, dependencies, impact analysis, architecture
7+
- issue_creation: Creating bug reports, feature requests, or tracking items
8+
- documentation_generation: Generating docs, READMEs, API docs, guides, or explanations
9+
- find_good_first_issues: Finding beginner-friendly issues to work on across repositories
10+
- general_github_help: General GitHub-related assistance and guidance
11+
12+
USER QUERY: {user_query}
13+
14+
Classification guidelines:
15+
- contributor_recommendation: Finding reviewers, assignees, collaborators
16+
- repo_support: Code structure, dependencies, impact analysis, architecture
17+
- issue_creation: Creating bugs, features, tracking items
18+
- documentation_generation: Docs, READMEs, guides, explanations
19+
- find_good_first_issues: Beginners, newcomers, "good first issue"
20+
- web_search: General information needing external search
21+
- general_github_help: General GitHub questions not covered above
22+
23+
CRITICAL: Return ONLY raw JSON. No markdown, no code blocks, no explanation text.
24+
25+
{{
26+
"classification": "function_name_from_list_above",
27+
"reasoning": "Brief explanation of why you chose this function",
28+
"confidence": "high|medium|low"
29+
}}"""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from typing import Dict, Any
2+
import logging
3+
from langchain_core.messages import HumanMessage
4+
from app.agents.devrel.nodes.handlers.web_search import _extract_search_query
5+
from .search import handle_web_search
6+
from ..prompts.general_github_help import GENERAL_GITHUB_HELP_PROMPT
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
async def handle_general_github_help(query: str, llm) -> Dict[str, Any]:
12+
"""Execute general GitHub help with web search and LLM knowledge"""
13+
logger.info("Providing general GitHub help")
14+
15+
try:
16+
query = await _extract_search_query(query, llm)
17+
search_result = await handle_web_search(query)
18+
19+
if search_result.get("status") == "success":
20+
search_context = "SEARCH RESULTS:\n"
21+
for result in search_result.get("results", []):
22+
search_context += f"- {result.get('title', 'No title')}: {result.get('content', 'No content')}\n"
23+
else:
24+
search_context = "No search results available."
25+
26+
help_prompt = GENERAL_GITHUB_HELP_PROMPT.format(
27+
query=query,
28+
search_context=search_context
29+
)
30+
31+
response = await llm.ainvoke([HumanMessage(content=help_prompt)])
32+
33+
return {
34+
"status": "success",
35+
"sub_function": "general_github_help",
36+
"query": query,
37+
"response": response.content.strip(),
38+
"search_context": search_context,
39+
"message": "Provided GitHub help using LLM expertise and web search"
40+
}
41+
42+
except Exception as e:
43+
logger.error(f"Error in general GitHub help: {str(e)}")
44+
return {
45+
"status": "error",
46+
"sub_function": "general_github_help",
47+
"query": query,
48+
"error": str(e),
49+
"message": "Failed to provide general GitHub help"
50+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

backend/app/agents/devrel/github/tools/repository_query.py

Whitespace-only changes.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import logging
2+
from typing import Dict, Any
3+
from app.agents.devrel.tools.search_tool import TavilySearchTool
4+
logger = logging.getLogger(__name__)
5+
6+
7+
async def handle_web_search(query: str) -> Dict[str, Any]:
8+
"""Handle web search using Tavily search tool"""
9+
logger.info("Handling web search request")
10+
11+
try:
12+
search_tool = TavilySearchTool()
13+
search_results = await search_tool.search(query, max_results=5)
14+
15+
if not search_results:
16+
return {
17+
"status": "no_results",
18+
"sub_function": "web_search",
19+
"query": query,
20+
"message": "No web search results found for the query",
21+
"results": []
22+
}
23+
24+
return {
25+
"status": "success",
26+
"sub_function": "web_search",
27+
"query": query,
28+
"results": search_results,
29+
"total_results": len(search_results),
30+
"message": f"Found {len(search_results)} web search results"
31+
}
32+
33+
except Exception as e:
34+
logger.error(f"Error in web search: {str(e)}")
35+
return {
36+
"status": "error",
37+
"sub_function": "web_search",
38+
"query": query,
39+
"error": str(e),
40+
"message": "Failed to perform web search"
41+
}

0 commit comments

Comments
 (0)