File size: 6,933 Bytes
f35583f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | 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)}")
|