File size: 8,408 Bytes
53ae001
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
"""

RAG Agent API Service



FastAPI application providing a RAG (Retrieval-Augmented Generation) endpoint

for querying documentation and generating grounded answers.



Endpoints:

    POST /query - Submit a question and get an answer with sources

    GET /health - Health check endpoint



Features:

    - Query validation with Pydantic

    - Integration with Qdrant for document retrieval

    - OpenAI integration for answer generation

    - Source attribution and deduplication

    - Structured logging

    - CORS support for web clients



Usage:

    uvicorn rag_agent:app --reload --host 0.0.0.0 --port 8000

"""

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import logging
from datetime import datetime

from models.request_models import QueryRequest
from models.response_models import QueryResponse, ErrorResponse
from services.integration_service import IntegrationService
from exceptions import RAGException, ValidationError, RetrievalError, GenerationError, RateLimitError
from config.logging_config import setup_logging, get_logger

# Try to import translation router (optional - requires PyTorch)
translation_router = None
try:
    from routers.translation import router as translation_router
except Exception as e:
    print(f"[WARNING] Translation router not available: {e}")
    print("[INFO] RAG agent will run without translation support")

# Initialize FastAPI app
app = FastAPI(
    title="RAG Agent API",
    description="Retrieval-Augmented Generation API for documentation Q&A",
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc"
)

# Configure CORS for development (allow all origins)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # In production, restrict to specific origins
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Configure logging
setup_logging(level="INFO", json_format=False)
logger = get_logger('rag_agent')

# Initialize integration service
integration_service = IntegrationService()

# Register routers (only if available)
if translation_router is not None:
    app.include_router(translation_router)
    print("[INFO] Translation router registered")
else:
    print("[INFO] Running without translation support - use mock_translation_server.py for translations")


@app.get("/health")
async def health_check():
    """

    Health check endpoint.



    Returns:

        dict: Health status with version and timestamp

    """
    return {
        "status": "healthy",
        "version": "1.0.0",
        "timestamp": datetime.utcnow().isoformat() + "Z"
    }


@app.get("/")
async def root():
    """

    Root endpoint with API information.



    Returns:

        dict: API info and documentation links

    """
    return {
        "name": "RAG Agent API",
        "version": "1.0.0",
        "description": "Retrieval-Augmented Generation API for documentation Q&A",
        "docs": "/docs",
        "redoc": "/redoc",
        "health": "/health"
    }


@app.post("/query", response_model=QueryResponse, responses={

    422: {"model": ErrorResponse, "description": "Validation Error"},

    503: {"model": ErrorResponse, "description": "Retrieval Error"},

    502: {"model": ErrorResponse, "description": "Generation Error"},

    429: {"model": ErrorResponse, "description": "Rate Limit Error"}

})
async def query_documentation(request: QueryRequest):
    """

    Submit a question and get an answer with source attribution.



    This endpoint implements the full RAG pipeline:

    1. Validates the query

    2. Retrieves relevant documentation chunks from Qdrant

    3. Generates a grounded answer using OpenAI

    4. Returns the answer with deduplicated source references



    Args:

        request: QueryRequest with query, top_k, and model



    Returns:

        QueryResponse with answer, sources, and metadata



    Raises:

        HTTPException: For validation, retrieval, generation, or rate limit errors

    """
    start_time = datetime.utcnow()

    logger.info(
        "Received query request",
        extra={
            "query": request.query[:50] + "..." if len(request.query) > 50 else request.query,
            "top_k": request.top_k,
            "model": request.model
        }
    )

    try:
        # Process query through RAG pipeline
        response = await integration_service.process_query(
            query=request.query,
            top_k=request.top_k,
            model=request.model
        )

        # Log success
        latency_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)
        logger.info(
            "Query processed successfully",
            extra={
                "query": request.query[:50] + "..." if len(request.query) > 50 else request.query,
                "latency_ms": latency_ms,
                "tokens_used": response.tokens_used,
                "sources_count": len(response.sources)
            }
        )

        return response

    except ValidationError as e:
        # Log validation error
        logger.warning(
            f"Validation error: {e.message}",
            extra={
                "query": request.query,
                "error_type": e.error_type,
                "status_code": e.status_code
            }
        )
        return JSONResponse(
            status_code=e.status_code,
            content=ErrorResponse(
                error_type=e.error_type,
                error_message=e.message,
                query=request.query,
                status_code=e.status_code
            ).model_dump()
        )

    except RetrievalError as e:
        # Log retrieval error
        logger.error(
            f"Retrieval error: {e.message}",
            extra={
                "query": request.query,
                "error_type": e.error_type,
                "status_code": e.status_code
            }
        )
        return JSONResponse(
            status_code=e.status_code,
            content=ErrorResponse(
                error_type=e.error_type,
                error_message=e.message,
                query=request.query,
                status_code=e.status_code
            ).model_dump()
        )

    except GenerationError as e:
        # Log generation error
        logger.error(
            f"Generation error: {e.message}",
            extra={
                "query": request.query,
                "error_type": e.error_type,
                "status_code": e.status_code
            }
        )
        return JSONResponse(
            status_code=e.status_code,
            content=ErrorResponse(
                error_type=e.error_type,
                error_message=e.message,
                query=request.query,
                status_code=e.status_code
            ).model_dump()
        )

    except RateLimitError as e:
        # Log rate limit error
        logger.warning(
            f"Rate limit error: {e.message}",
            extra={
                "query": request.query,
                "error_type": e.error_type,
                "status_code": e.status_code
            }
        )
        return JSONResponse(
            status_code=e.status_code,
            content=ErrorResponse(
                error_type=e.error_type,
                error_message=e.message,
                query=request.query,
                status_code=e.status_code
            ).model_dump()
        )

    except Exception as e:
        # Log unexpected error
        logger.error(
            f"Unexpected error: {str(e)}",
            extra={
                "query": request.query,
                "error_type": "InternalServerError"
            },
            exc_info=True
        )
        return JSONResponse(
            status_code=500,
            content=ErrorResponse(
                error_type="InternalServerError",
                error_message=f"An unexpected error occurred: {str(e)}",
                query=request.query,
                status_code=500
            ).model_dump()
        )


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)