| from fastapi import APIRouter, HTTPException
|
| from pydantic import BaseModel
|
| from typing import List, Optional
|
| from app.services.llm import llm_service
|
| from app.services.vector import vector_service
|
| from app.services.search import search_service
|
| from app.services.intent import IntentService
|
| from app.services.files import file_service
|
| from fastapi import UploadFile, File
|
|
|
|
|
| intent_service = IntentService(llm_service)
|
|
|
| router = APIRouter()
|
|
|
|
|
| class QueryRequest(BaseModel):
|
| query: str
|
|
|
| class Source(BaseModel):
|
| title: str
|
| url: str
|
| snippet: str
|
|
|
| class ChallengeRequest(BaseModel):
|
| original_query: str
|
| original_answer: str
|
| sources_text: str
|
|
|
| class QueryResponse(BaseModel):
|
| answer: str
|
| sources: List[Source]
|
| confidence: str
|
| search_queries: List[str]
|
| intent: Optional[str] = None
|
| thought_process: Optional[str] = None
|
|
|
|
|
|
|
| @router.post("/query", response_model=QueryResponse)
|
| async def process_query(request: QueryRequest):
|
| """
|
| Main orchestration endpoint for the Trust-First Copilot.
|
| """
|
| user_query = request.query
|
| print(f"Refining query: {user_query}")
|
|
|
| try:
|
|
|
| print("🧠 Analyzing Intent...")
|
| try:
|
| intent = await intent_service.analyze(user_query)
|
| print(f" Category: {intent.category}")
|
| print(f" Reasoning: {intent.reasoning}")
|
| print(f" Risk: {intent.risk_level}")
|
| except Exception as e:
|
| print(f"Intent Error: {e}")
|
| from app.services.intent import IntentResponse
|
| intent = IntentResponse(category="SEARCH_REQUIRED", reasoning="Error", risk_level="LOW")
|
|
|
|
|
| if intent.risk_level == "HIGH":
|
| return QueryResponse(
|
| answer="I cannot fulfill this request as it has been flagged as high risk/safety violation.",
|
| sources=[],
|
| confidence="Blocked",
|
| search_queries=[],
|
| intent="High Risk",
|
| thought_process=f"Blocked by Risk Analyzer. Reasoning: {intent.reasoning}"
|
| )
|
|
|
|
|
| search_results = []
|
|
|
|
|
| if intent.category == "SEARCH_REQUIRED" or intent.category == "DATA_ANALYSIS":
|
| print("🔍 Initiating Web Search...")
|
| search_results = await search_service.search(user_query)
|
| if not search_results:
|
|
|
| pass
|
|
|
|
|
| elif intent.category == "CODING_TASK":
|
| print("💻 Coding Task - Focused Generation")
|
|
|
|
|
|
|
| else:
|
| print("💬 Chat Mode - Direct Generation")
|
|
|
|
|
| context_text = ""
|
| final_sources = []
|
|
|
| if search_results:
|
|
|
| print("Indexing search results in Vector DB...")
|
| vector_service.create_index_from_results(search_results)
|
|
|
| print("Searching Vector DB for relevant context...")
|
| relevant_chunks = vector_service.search_similar(user_query, k=5)
|
| final_sources = relevant_chunks if relevant_chunks else search_results
|
|
|
| context_text = "\n\n".join([
|
| f"Source {i+1}:\nTitle: {r.get('title')}\nURL: {r.get('url')}\nContent: {r.get('content')}"
|
| for i, r in enumerate(final_sources)
|
| ])
|
| else:
|
| context_text = "No external sources used. Answering from internal knowledge."
|
|
|
|
|
|
|
| answer = await llm_service.synthesize_answer(user_query, context_text)
|
|
|
|
|
| confidence_level = "Medium"
|
| if intent.category == "SEARCH_REQUIRED":
|
| confidence_assessment = await llm_service.verify_confidence(answer, context_text)
|
| if "High confidence" in confidence_assessment: confidence_level = "High"
|
| elif "Low confidence" in confidence_assessment: confidence_level = "Low"
|
| else:
|
| confidence_level = "N/A (Chat)"
|
|
|
|
|
| formatted_sources = [
|
| Source(title=r.get('title', 'Unknown'), url=r.get('url', '#'), snippet=r.get('content', '')[:200])
|
| for r in (search_results if search_results else [])
|
| ]
|
|
|
| return QueryResponse(
|
| answer=answer,
|
| sources=formatted_sources,
|
| confidence=confidence_level,
|
| search_queries=[user_query],
|
| intent=intent.category,
|
| thought_process=f"Intent: {intent.category}. Reasoning: {intent.reasoning}"
|
| )
|
|
|
| except Exception as e:
|
| print(f"Error processing query: {e}")
|
| raise HTTPException(status_code=500, detail=str(e))
|
|
|
| @router.post("/challenge", response_model=QueryResponse)
|
| async def challenge_answer(request: ChallengeRequest):
|
| """
|
| 'Disagree-with-Me' Mode: Critiques the previous answer.
|
| """
|
| try:
|
| from app.core import prompts
|
|
|
| messages = [
|
| {"role": "system", "content": prompts.MASTER_PROMPT_CHALLENGE},
|
| {"role": "user", "content": f"Query: {request.original_query}\n\nAnswer to critique: {request.original_answer}\n\nSources used:\n{request.sources_text}"}
|
| ]
|
|
|
| critique = await llm_service._generate(messages, temperature=0.7)
|
|
|
|
|
| return QueryResponse(
|
| answer=critique,
|
| sources=[],
|
| confidence="High (Critique)",
|
| search_queries=[],
|
| intent="CRITIQUE",
|
| thought_process="Devil's Advocate Mode Activated."
|
| )
|
| except Exception as e:
|
| raise HTTPException(status_code=500, detail=str(e))
|
|
|
| @router.post("/upload")
|
| async def upload_file(file: UploadFile = File(...)):
|
| """
|
| Parses an uploaded file and returns its text content for RAG.
|
| """
|
| try:
|
| filename = file.filename
|
| print(f"📂 Processing file: {filename}")
|
| content = await file_service.process_file(file)
|
| return {"filename": filename, "content": content}
|
| except Exception as e:
|
| raise HTTPException(status_code=500, detail=f"Upload failed: {str(e)}")
|
|
|