55from fastapi import APIRouter , HTTPException , status
66from fastapi .responses import StreamingResponse
77
8- from apps .api .routes .v1 .schemas .qa import (
9- QARequest ,
10- ContextItem ,
11- IssueRequest ,
12- IssueContextItem ,
13- )
8+ from apps .api .routes .v1 .schemas .qa import QARequest , ContextItem
149from apps .api .utils import APIResponse
1510from packages .memory .qa_service import QAService
1611from packages .memory .services .graph_service import GraphService
17- from packages .memory .services .issue_analysis import IssueAnalyzer
1812from packages .database .graph .graph import neo4j_client
1913from packages .config import Settings
20- from packages . config . feature_flags import is_feature_enabled , FeatureFlag
14+
2115
2216router = APIRouter (prefix = "/ask" , tags = ["Question Answering" ])
2317settings = Settings ()
2620# Initialize services once per module import
2721graph_service = GraphService (neo4j_client )
2822qa_service = QAService (graph_service )
29- issue_analyzer = IssueAnalyzer (graph_service )
3023
3124
3225@router .post (
3326 "" ,
3427 summary = "Ask a question about the codebase" ,
3528 description = (
36- "Submit a natural language question about the codebase. The system "
37- "retrieves relevant code context (hybrid or vector search) and uses an LLM "
38- "to generate an answer."
29+ "Submit a question about the codebase with different agent types. "
30+ "Supports streaming responses and specialized agents: "
31+ "pathfinder (code structure), chronicle (history), diagnostician (debugging), "
32+ "blueprint (architecture), sentinel (code review)."
3933 ),
4034)
41- async def question_answer (request : QARequest ):
42- """Return an AI-generated answer with supporting context.
43-
44- Process:
45- 1. Retrieve relevant code elements using the selected search strategy.
46- 2. Provide context snippets to the configured LLM provider.
47- 3. Return an answer plus the context used.
48- """
35+ async def ask (request : QARequest ):
36+ """Ask a question using different agent types."""
4937 try :
50- logger .info (f"Processing question: { request .question [:50 ]} ..." )
38+ logger .info (
39+ f"Processing question with { request .agent_type .value } agent: { request .question [:50 ]} ..."
40+ )
5141
42+ # If streaming is requested, return streaming response
43+ if request .stream :
44+ return StreamingResponse (
45+ _stream_answer (request ),
46+ media_type = "text/event-stream"
47+ )
48+
49+ # Non-streaming response
5250 result : dict [str , Any ] = qa_service .ask (
5351 question = request .question ,
52+ agent_type = request .agent_type .value ,
5453 search_type = request .search_type ,
55- node_type = request .node_type ,
5654 context_limit = request .context_limit ,
5755 )
5856
5957 return APIResponse .success (
6058 data = {
6159 "answer" : result ["answer" ],
6260 "question" : result ["question" ],
61+ "agent_type" : result ["agent_type" ],
6362 "context" : [ContextItem (** item ) for item in result ["context" ]],
6463 "context_count" : result ["context_count" ],
6564 "search_type" : result ["search_type" ],
66- "node_type" : result .get ("node_type" ),
6765 "node_types" : result .get ("node_types" ),
6866 "model" : result ["model" ],
6967 "provider" : result ["provider" ],
@@ -84,72 +82,25 @@ async def question_answer(request: QARequest):
8482 detail = f"Service error: { str (e )} " ,
8583 )
8684 except Exception as e : # noqa: BLE001
87- logger .exception ("Unexpected error in question_answer endpoint" )
85+ logger .exception ("Unexpected error in ask endpoint" )
8886 raise HTTPException (
8987 status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
9088 detail = "An unexpected error occurred while processing your question" ,
9189 )
9290
9391
94- @router .post (
95- "/issue" ,
96- summary = "Analyze a GitHub issue" ,
97- description = (
98- "Analyze a GitHub issue to identify causes and solutions based on the Knowledge Graph. "
99- "Returns a detailed report and the context used."
100- ),
101- )
102- async def analyze_issue (request : IssueRequest ):
103- """Analyze an issue and return a report.
104-
105- Process:
106- 1. Search for relevant code and commit history using hybrid search.
107- 2. Use LLM to generate a root cause analysis and suggested fix.
108- 3. Return the report and context.
109- """
110- try :
111- logger .info (f"Analyzing issue: { request .title } " )
112-
113- if is_feature_enabled (FeatureFlag .ENABLE_TOKEN_STREAMING ):
114- return StreamingResponse (
115- _stream_issue_analysis (request .title , request .body ),
116- media_type = "text/event-stream"
117- )
118-
119- result = issue_analyzer .analyze_issue (request .title , request .body )
120-
121- if "error" in result :
122- raise HTTPException (
123- status_code = status .HTTP_404_NOT_FOUND ,
124- detail = result ["error" ],
125- )
126-
127- return APIResponse .success (
128- data = {
129- "report" : result ["report" ],
130- "context_used" : [IssueContextItem (** item ) for item in result ["context_used" ]],
131- },
132- message = "Successfully analyzed the issue."
133- )
134-
135- except HTTPException :
136- raise
137- except Exception as e :
138- logger .exception ("Unexpected error in analyze_issue endpoint" )
139- raise HTTPException (
140- status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
141- detail = "An unexpected error occurred. Please try again later." ,
142- )
143-
144-
145- def _stream_issue_analysis (title : str , body : str ):
146- """Generator for streaming issue analysis."""
92+ def _stream_answer (request : QARequest ):
93+ """Generator for streaming answers."""
14794 try :
148- for chunk in issue_analyzer .analyze_issue_stream (title , body ):
95+ for chunk in qa_service .ask_stream (
96+ question = request .question ,
97+ agent_type = request .agent_type .value ,
98+ search_type = request .search_type ,
99+ context_limit = request .context_limit ,
100+ ):
149101 yield f"data: { json .dumps (chunk )} \n \n "
150102 except Exception as e :
151- logger .exception ("Error during streaming issue analysis " )
103+ logger .exception ("Error during streaming" )
152104 yield f"data: { json .dumps ({'error' : str (e )})} \n \n "
153105 finally :
154106 yield "data: [DONE]\n \n "
155-
0 commit comments