-
-
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 6 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,10 @@ | ||||||||||||||||||||||||||
| from .services.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 +17,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 +40,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) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # β 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 |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import httpx | ||
| from typing import List, Dict | ||
|
|
||
| GITHUB_API_BASE = "https://api.github.com" | ||
|
|
||
|
|
||
| class IssueSuggestionService: | ||
|
|
||
| def __init__(self, token: str): | ||
| self.token = token | ||
|
|
||
| async def fetch_global_beginner_issues( | ||
| self, | ||
| user_query: str, | ||
| limit: int = 5 | ||
| ) -> List[Dict]: | ||
|
|
||
| headers = { | ||
| "Authorization": f"Bearer {self.token}", | ||
| "Accept": "application/vnd.github+json" | ||
| } | ||
|
|
||
| # Base GitHub search query | ||
| search_query = 'label:"good first issue" is:issue state:open' | ||
|
|
||
| query_lower = user_query.lower() | ||
|
|
||
| # Language filter | ||
| if "python" in query_lower: | ||
| search_query += " language:python" | ||
|
|
||
| # Org filter | ||
| if "django" in query_lower: | ||
| search_query += " org:django" | ||
|
|
||
| url = f"{GITHUB_API_BASE}/search/issues?q={search_query}&per_page={limit}" | ||
|
|
||
| print("π GitHub Search Query:", search_query) | ||
| print("π GitHub URL:", url) | ||
|
|
||
| async with httpx.AsyncClient() as client: | ||
| response = await client.get(url, headers=headers) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| if response.status_code != 200: | ||
| print("β GitHub API Error:", response.status_code) | ||
| print("β Response Body:", response.text) | ||
| return [] | ||
|
|
||
| data = response.json() | ||
|
|
||
| results = [] | ||
|
|
||
| for item in data.get("items", []): | ||
| results.append({ | ||
| "repo": item["repository_url"].split("/")[-1], | ||
| "number": item["number"], | ||
| "title": item["title"], | ||
| "url": item["html_url"] | ||
| }) | ||
|
|
||
| print(f"β Found {len(results)} issues") | ||
|
|
||
| return results | ||
| 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,34 @@ | ||
| from fastapi import APIRouter, HTTPException | ||
| from services.github.issue_suggestion_service import IssueSuggestionService | ||
| from config import GITHUB_TOKEN, GITHUB_ORG | ||
|
|
||
| router = APIRouter() | ||
|
|
||
| issue_service = IssueSuggestionService(GITHUB_TOKEN) | ||
|
|
||
|
|
||
| @router.get("/beginner-issues") | ||
| async def get_beginner_issues(repo: str): | ||
| if not GITHUB_TOKEN: | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail="GitHub token not configured" | ||
| ) | ||
|
|
||
| try: | ||
| issues = await issue_service.fetch_beginner_issues( | ||
| owner=GITHUB_ORG, | ||
| repo=repo | ||
| ) | ||
|
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 { | ||
| "repo": repo, | ||
| "count": len(issues), | ||
| "issues": issues | ||
| } | ||
|
|
||
| except Exception as e: | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail=f"Failed to fetch issues: {str(e)}" | ||
| ) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
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.
π§© Analysis chain
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 494
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 1891
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 1446
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 473
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 1654
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 1017
Inconsistent token source β
settings.github_tokenvsconfig.GITHUB_TOKEN.routes.pyandapp/api/v1/github.pyinstantiateIssueSuggestionService(GITHUB_TOKEN)fromconfig(directos.getenv()with fallback toGH_TOKEN), whilegithub_toolkit.pyusessettings.github_tokenfromapp.core.config(Pydantic BaseSettings). Although both attributes exist, they read from environment variables through different mechanisms with different fallback behaviors:config.GITHUB_TOKEN:os.getenv("GITHUB_TOKEN") or os.getenv("GH_TOKEN")settings.github_token: Pydantic BaseSettings (defaults to empty string if not set)This inconsistency could cause
IssueSuggestionServiceto receive different token values or fail silently if one configuration path is properly set while the other isn't.π€ Prompt for AI Agents