Trust-first-AI-Copilot / app /api /endpoints.py
Abhisingh-18's picture
Mirror of github.com/Abhisingh18/Trust-first-AI-Copilot
f35583f verified
Raw
History Blame Contribute Delete
6.93 kB
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
# Initialize Intent Service
intent_service = IntentService(llm_service)
router = APIRouter()
# --- Pydantic Models ---
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
# --- Endpoints ---
@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:
# --- PHASE 1: INTENT & RISK ANALYSIS ---
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")
# Risk Guard
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}"
)
# --- PHASE 2: EXECUTION ---
search_results = []
# Branch 1: Needs Search
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:
# Fallback if search finds nothing but intent was search
pass
# Branch 2: Coding (Skip Search usually, unless specific docs needed)
elif intent.category == "CODING_TASK":
print("💻 Coding Task - Focused Generation")
# Potential future improvement: Search for docs if needed
# Branch 3: Chat / General
else:
print("💬 Chat Mode - Direct Generation")
# --- PHASE 3: CONTEXT & RAG ---
context_text = ""
final_sources = []
if search_results:
# RAG Logic
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."
# --- PHASE 4: SYNTHESIS ---
# Modify prompt based on intent? For now, standard synthesis but context aware.
answer = await llm_service.synthesize_answer(user_query, context_text)
# --- PHASE 5: VERIFICATION ---
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)"
# Construct Response
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
# Construct the critique prompt
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 as a new message, but marked as a critique
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)}")