-
-
Notifications
You must be signed in to change notification settings - Fork 138
fix: proper 401 handling with session reset and redirect #286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 9 commits
1e57ac9
3709a9d
00d2679
c1fcbda
54f69c7
9af9525
372a64e
3a180ae
af6394b
d5ca8a5
7249439
0273bb0
6b5fbae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,9 @@ | ||||||||||||||||||||||||||
| from services.github.issue_suggestion_service import IssueSuggestionService | ||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||
| import config | ||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||
| from .tools.github_support import handle_github_supp | ||||||||||||||||||||||||||
| from .tools.contributor_recommendation import handle_contributor_recommendation | ||||||||||||||||||||||||||
|
|
@@ -19,26 +16,18 @@ | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def normalize_org(org_from_user: str = None) -> str: | ||||||||||||||||||||||||||
| """Fallback to env org if user does not specify one.""" | ||||||||||||||||||||||||||
| if org_from_user and org_from_user.strip(): | ||||||||||||||||||||||||||
| return org_from_user.strip() | ||||||||||||||||||||||||||
| return DEFAULT_ORG | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 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). | ||||||||||||||||||||||||||
| GitHub Toolkit - Rule-based intent classifier + executor | ||||||||||||||||||||||||||
| (Gemini removed to avoid quota issues) | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 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", | ||||||||||||||||||||||||||
|
|
@@ -50,92 +39,129 @@ def __init__(self): | |||||||||||||||||||||||||
| "general_github_help" | ||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # -------------------------------------------------- | ||||||||||||||||||||||||||
| # RULE-BASED CLASSIFIER | ||||||||||||||||||||||||||
| # -------------------------------------------------- | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| async def classify_intent(self, user_query: str) -> Dict[str, Any]: | ||||||||||||||||||||||||||
| """Classify intent and return classification with reasoning.""" | ||||||||||||||||||||||||||
| 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)]) | ||||||||||||||||||||||||||
| query_lower = user_query.lower() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| content = response.content.strip() | ||||||||||||||||||||||||||
| if "beginner" in query_lower or "good first issue" in query_lower: | ||||||||||||||||||||||||||
| classification = "find_good_first_issues" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||
| result = json.loads(content) | ||||||||||||||||||||||||||
| except json.JSONDecodeError: | ||||||||||||||||||||||||||
| match = re.search(r"\{.*\}", content, re.DOTALL) | ||||||||||||||||||||||||||
| if match: | ||||||||||||||||||||||||||
| result = json.loads(match.group()) | ||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| logger.error(f"Invalid JSON in LLM response: {content}") | ||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||
| "classification": "general_github_help", | ||||||||||||||||||||||||||
| "reasoning": "Failed to parse LLM response as JSON", | ||||||||||||||||||||||||||
| "confidence": "low", | ||||||||||||||||||||||||||
| "query": user_query | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| elif "contributor" in query_lower: | ||||||||||||||||||||||||||
| classification = "contributor_recommendation" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||
| elif "repo" in query_lower: | ||||||||||||||||||||||||||
| classification = "repo_support" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| result["query"] = user_query | ||||||||||||||||||||||||||
| elif "github support" in query_lower: | ||||||||||||||||||||||||||
| classification = "github_support" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| logger.info(f"Classified intent for query: {user_query} -> {classification}") | ||||||||||||||||||||||||||
| logger.info(f"Reasoning: {result.get('reasoning', 'No reasoning provided')}") | ||||||||||||||||||||||||||
| logger.info(f"Confidence: {result.get('confidence', 'unknown')}") | ||||||||||||||||||||||||||
| elif "search" in query_lower: | ||||||||||||||||||||||||||
| classification = "web_search" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return result | ||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| classification = "general_github_help" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| logger.info(f"Rule-based classification: {user_query} -> {classification}") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||
| "classification": classification, | ||||||||||||||||||||||||||
| "reasoning": "Rule-based classification", | ||||||||||||||||||||||||||
| "confidence": "high", | ||||||||||||||||||||||||||
| "query": user_query | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # -------------------------------------------------- | ||||||||||||||||||||||||||
| # EXECUTION | ||||||||||||||||||||||||||
| # -------------------------------------------------- | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 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") | ||||||||||||||||||||||||||
| logger.info(f"Executing action: {classification}") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # ----------------------------------------- | ||||||||||||||||||||||||||
| # EXISTING HANDLERS | ||||||||||||||||||||||||||
| # ----------------------------------------- | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if classification == "contributor_recommendation": | ||||||||||||||||||||||||||
| result = await handle_contributor_recommendation(query) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| elif classification == "github_support": | ||||||||||||||||||||||||||
| org = normalize_org() | ||||||||||||||||||||||||||
| result = await handle_github_supp(query, org=org) | ||||||||||||||||||||||||||
| result["org_used"] = org | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| elif classification == "repo_support": | ||||||||||||||||||||||||||
| result = await handle_repo_support(query) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| elif classification == "issue_creation": | ||||||||||||||||||||||||||
| result = "Not implemented" | ||||||||||||||||||||||||||
| result = { | ||||||||||||||||||||||||||
| "message": "Issue creation not implemented yet" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| elif classification == "documentation_generation": | ||||||||||||||||||||||||||
| result = "Not implemented" | ||||||||||||||||||||||||||
| result = { | ||||||||||||||||||||||||||
| "message": "Documentation generation not implemented yet" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # ----------------------------------------- | ||||||||||||||||||||||||||
| # BEGINNER ISSUE SEARCH (FIXED) | ||||||||||||||||||||||||||
| # ----------------------------------------- | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| elif classification == "find_good_first_issues": | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| service = IssueSuggestionService(settings.github_token) | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: #!/bin/bash
# Check for github_token in app/core/config.py and GITHUB_TOKEN in config files
echo "=== Searching for github_token in app/core/config.py ==="
rg -n "github_token" backend/app/core/config.py
echo -e "\n=== Searching for GITHUB_TOKEN in config files ==="
rg -n "GITHUB_TOKEN" backend/config.py 2>/dev/null || echo "backend/config.py not found"
echo -e "\n=== Searching for all config references ==="
fd "config.py" backend/ --type f
echo -e "\n=== Check full app/core/config.py structure ==="
cat backend/app/core/config.py | head -50Repository: AOSSIE-Org/Devr.AI Length of output: 494 π Script executed: #!/bin/bash
# Search for IssueSuggestionService instantiation in routes.py and app/api/v1/github.py
echo "=== Searching for IssueSuggestionService in routes.py ==="
rg -n "IssueSuggestionService" backend/app/routes.py -A 1 -B 1
echo -e "\n=== Searching for IssueSuggestionService in app/api/v1/github.py ==="
rg -n "IssueSuggestionService" backend/app/api/v1/github.py -A 1 -B 1
echo -e "\n=== Searching for all IssueSuggestionService usages ==="
rg -n "IssueSuggestionService" backend/ -A 1 -B 1Repository: AOSSIE-Org/Devr.AI Length of output: 1891 π Script executed: #!/bin/bash
# Check full github_toolkit.py for settings import
echo "=== First 20 lines of github_toolkit.py ==="
head -20 backend/app/agents/devrel/github/github_toolkit.py
echo -e "\n=== Search for settings import in github_toolkit.py ==="
rg -n "^from|^import" backend/app/agents/devrel/github/github_toolkit.py | head -20
echo -e "\n=== Look for all settings references ==="
rg -n "settings\." backend/app/agents/devrel/github/github_toolkit.pyRepository: AOSSIE-Org/Devr.AI Length of output: 1446 π Script executed: #!/bin/bash
# Find where settings object is defined
echo "=== Search for Settings class definition ==="
rg -n "class Settings" backend/ -A 3
echo -e "\n=== Search for settings attribute assignments ==="
rg -n "github_token\s*=" backend/ -B 2 -A 1
echo -e "\n=== Check if settings module exists ==="
fd "settings" backend/ --type fRepository: AOSSIE-Org/Devr.AI Length of output: 473 π Script executed: #!/bin/bash
# Check the full Settings class definition
echo "=== Full Settings class in app/core/config/settings.py ==="
cat backend/app/core/config/settings.py
echo -e "\n=== Check app/core/config/__init__.py ==="
cat backend/app/core/config/__init__.py 2>/dev/null || echo "File not found"Repository: AOSSIE-Org/Devr.AI Length of output: 1654 π Script executed: #!/bin/bash
# Check config.py for full token configuration
echo "=== Full backend/config.py ==="
cat backend/config.py
echo -e "\n=== Check if github_token is in any config ==="
rg -n "github_token" backend/Repository: AOSSIE-Org/Devr.AI Length of output: 1017 Inconsistent token source β
This inconsistency could cause π€ Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # β FIXED β passing query argument | ||||||||||||||||||||||||||
| issues = await service.fetch_global_beginner_issues(query) | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
π Proposed fix- # β
FIXED β passing query argument
- issues = await service.fetch_global_beginner_issues(query)
+ # Extract language from query if possible, or default to "python"
+ issues = await service.fetch_beginner_issues(language="python", limit=5)π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if not issues: | ||||||||||||||||||||||||||
| result = { | ||||||||||||||||||||||||||
| "status": "success", | ||||||||||||||||||||||||||
| "message": "No beginner issues found globally right now.", | ||||||||||||||||||||||||||
| "issues": [] | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| formatted = "\n\n".join( | ||||||||||||||||||||||||||
| f"πΉ [{i['repo']}] #{i['number']} - {i['title']}\n{i['url']}" | ||||||||||||||||||||||||||
| for i in issues | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| result = { | ||||||||||||||||||||||||||
| "status": "success", | ||||||||||||||||||||||||||
| "message": f"Here are beginner-friendly issues across GitHub:\n\n{formatted}", | ||||||||||||||||||||||||||
| "issues": issues | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| elif classification == "web_search": | ||||||||||||||||||||||||||
| result = await handle_web_search(query) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # ----------------------------------------- | ||||||||||||||||||||||||||
| # DEFAULT FALLBACK | ||||||||||||||||||||||||||
| # ----------------------------------------- | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| result = await handle_general_github_help(query, self.llm) | ||||||||||||||||||||||||||
| result = await handle_general_github_help(query, None) | ||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 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)}") | ||||||||||||||||||||||||||
| logger.error(f"GitHub toolkit execution error: {str(e)}") | ||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||
| "status": "error", | ||||||||||||||||||||||||||
| "type": "github_toolkit", | ||||||||||||||||||||||||||
| "query": query, | ||||||||||||||||||||||||||
| "error": str(e), | ||||||||||||||||||||||||||
| "message": "Failed to execute GitHub operation" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,18 @@ | ||
| from .v1.github import router as github_router | ||
| from fastapi import APIRouter | ||
| from .v1.auth import router as auth_router | ||
| from .v1.health import router as health_router | ||
| from .v1.integrations import router as integrations_router | ||
|
|
||
| api_router = APIRouter() | ||
|
|
||
| api_router.include_router( | ||
| github_router, | ||
| prefix="/v1/github", | ||
| tags=["GitHub"] | ||
| ) | ||
|
Comment on lines
+9
to
+13
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: #!/bin/bash
# Find all routes that serve beginner-issues
echo "=== Searching for beginner-issues registrations ==="
rg -n 'beginner.issues' --type=py -B2 -A2
echo -e "\n=== Checking backend/routes.py existence ==="
find . -name "routes.py" -type f | head -20
echo -e "\n=== Checking for github router in app/api ==="
find . -path "*/app/api*" -name "*.py" -type f | head -20Repository: AOSSIE-Org/Devr.AI Length of output: 3781 π Script executed: #!/bin/bash
echo "=== backend/routes.py: beginner-issues endpoint ==="
sed -n '102,127p' backend/routes.py
echo -e "\n=== backend/app/api/v1/github.py: beginner-issues endpoint ==="
sed -n '10,35p' backend/app/api/v1/github.pyRepository: AOSSIE-Org/Devr.AI Length of output: 1476 Remove or consolidate the duplicate beginner-issues endpoint. Two endpoints serve identical functionality: π€ Prompt for AI Agents |
||
|
|
||
|
|
||
| api_router.include_router( | ||
| auth_router, | ||
| prefix="/v1/auth", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| from fastapi import APIRouter, HTTPException | ||
| from services.github.issue_suggestion_service import IssueSuggestionService | ||
| from config import GITHUB_TOKEN | ||
|
|
||
| router = APIRouter() | ||
|
|
||
| issue_service = IssueSuggestionService(GITHUB_TOKEN) | ||
|
|
||
|
|
||
| @router.get("/github/beginner-issues") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: fd "router.py" backend/app/api/ --type f -exec cat {} \;Repository: AOSSIE-Org/Devr.AI Length of output: 208 π Script executed: find backend/app/api -name "router.py" -type fRepository: AOSSIE-Org/Devr.AI Length of output: 87 π Script executed: cat backend/app/api/router.pyRepository: AOSSIE-Org/Devr.AI Length of output: 731 π Script executed: cat backend/app/api/v1/github.pyRepository: AOSSIE-Org/Devr.AI Length of output: 1039 π Script executed: grep -n "@router.get" backend/app/api/v1/github.pyRepository: AOSSIE-Org/Devr.AI Length of output: 103 Route path This router is included under the prefix π€ Prompt for AI Agents |
||
| async def get_beginner_issues( | ||
| language: str = "python", | ||
| limit: int = 5 | ||
| ): | ||
| """ | ||
| Fetch global beginner-friendly GitHub issues. | ||
| """ | ||
|
|
||
| if not GITHUB_TOKEN: | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail="GitHub token not configured" | ||
| ) | ||
|
|
||
| try: | ||
| issues = await issue_service.fetch_beginner_issues( | ||
| language=language, | ||
| limit=limit | ||
| ) | ||
|
Comment on lines
+27
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The imported Proposed fix (adjust to the actual method signature) issues = await issue_service.fetch_beginner_issues(
- owner=GITHUB_ORG,
- repo=repo
+ issues = await issue_service.fetch_global_beginner_issues(
+ language="python", # or derive from query param
+ limit=10
)You will also need to add π€ Prompt for AI Agents |
||
|
|
||
| return { | ||
| "language": language, | ||
| "count": len(issues), | ||
| "issues": issues | ||
| } | ||
|
|
||
| except Exception as e: | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail="Failed to fetch beginner issues" | ||
| ) from e | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
ModuleNotFoundErrorimport risk as inroutes.py.backend/services/andbackend/services/github/lack__init__.pyfiles, sofrom services.github.issue_suggestion_service import IssueSuggestionServicewill fail at startup. The fix is the same as previously flagged forroutes.py: either add the missing__init__.pyfiles or moveissue_suggestion_service.pyinto the properly-packagedbackend/app/services/github/tree.π€ Prompt for AI Agents