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)}")