File size: 5,263 Bytes
defd965
 
 
 
 
3f9fb93
defd965
 
 
 
 
 
 
 
6292cce
3f9fb93
defd965
 
3f9fb93
 
 
 
defd965
3f9fb93
defd965
 
 
 
 
 
3f9fb93
 
 
 
defd965
 
 
 
 
 
 
 
 
 
 
 
 
3f9fb93
 
 
defd965
 
3f9fb93
defd965
3f9fb93
 
 
 
defd965
3f9fb93
 
defd965
3f9fb93
 
 
defd965
 
 
 
 
 
3f9fb93
defd965
3f9fb93
 
 
defd965
 
 
 
 
 
 
3f9fb93
defd965
 
 
 
3f9fb93
 
 
 
defd965
3f9fb93
defd965
 
 
 
3f9fb93
 
 
 
defd965
 
 
 
 
 
 
3f9fb93
 
 
 
 
 
 
 
defd965
 
3f9fb93
defd965
 
3f9fb93
 
 
 
 
defd965
 
 
 
 
 
3f9fb93
defd965
 
 
 
 
3f9fb93
 
 
defd965
3f9fb93
defd965
 
 
3f9fb93
defd965
 
 
3f9fb93
defd965
3f9fb93
defd965
3f9fb93
 
 
defd965
 
3f9fb93
 
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
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import sys
import os
import logging

# Add the parent directory to sys.path so we can import the chat functionality
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

# Import and validate configuration on startup
from secure_config import validate_config
validate_config()

from chat_openai import query_qdrant, format_response
from ingest_local_docs import ingest_local_docs  # <- only import the main function, no circular import

# Create FastAPI app
app = FastAPI(
    title="AI Assistant Backend",
    description="Backend API for Docusaurus AI Assistant"
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",
        "http://localhost:5173",
        "http://127.0.0.1:5173",
        "http://localhost:3001",
        "http://127.0.0.1:8000",
        "http://localhost:8000",
        "https://*.vercel.app"
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Define request/response models
class ChatRequest(BaseModel):
    query: str

class ChatResponse(BaseModel):
    response: str

# ======================
# Startup Event
# ======================
@app.on_event("startup")
async def startup_event():
    """Ensure Qdrant collection exists and ingest docs if empty"""
    try:
        print("Starting ingestion process on startup...")
        # This will safely create collection if needed and ingest docs
        documents_indexed = ingest_local_docs()
        print(f"Ingestion completed. Total chunks stored: {documents_indexed}")
    except Exception as e:
        logging.error(f"Startup ingestion failed: {e}")
        # Continue running backend even if ingestion fails

# ======================
# Basic endpoints
# ======================
@app.get("/")
def read_root():
    return {"message": "AI Assistant Backend is running!"}

@app.get("/health")
def health_check():
    return {"status": "ok", "service": "backend", "errors": False}

# ======================
# Chat endpoint
# ======================
@app.post("/chat", response_model=ChatResponse)
async def chat_endpoint(request: ChatRequest):
    try:
        query = request.query
        if not query:
            raise HTTPException(status_code=400, detail="Query is required")

        # Query Qdrant database
        try:
            search_results = query_qdrant(query)
        except Exception as e:
            logging.error(f"Error querying Qdrant: {str(e)}")
            raise HTTPException(
                status_code=500,
                detail=f"Unable to connect to the knowledge base: {str(e)}"
            )

        # Format the response
        try:
            response = format_response(query, search_results)
        except Exception as e:
            logging.error(f"Error formatting response: {str(e)}")
            raise HTTPException(
                status_code=500,
                detail=f"Error generating response: {str(e)}"
            )

        return ChatResponse(response=response)

    except HTTPException:
        raise
    except Exception as e:
        logging.error(f"Unexpected error in chat endpoint: {str(e)}")
        raise HTTPException(
            status_code=500,
            detail=f"An unexpected error occurred: {str(e)}"
        )

# ======================
# Admin endpoints
# ======================
@app.post("/admin/ingest")
async def admin_ingest():
    """Manual ingestion endpoint"""
    try:
        documents_indexed = ingest_local_docs()
        return {
            "status": "success",
            "message": "Documents ingested successfully",
            "documents_indexed": documents_indexed
        }
    except Exception as e:
        logging.error(f"Error during manual ingestion: {str(e)}")
        raise HTTPException(status_code=500, detail=f"Error during ingestion: {str(e)}")

@app.get("/admin/status")
async def admin_status():
    """Check collection status"""
    try:
        from qdrant_client import QdrantClient
        from config import QDRANT_URL, QDRANT_API_KEY, COLLECTION_NAME

        qdrant_client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY, prefer_grpc=False)
        collection_info = qdrant_client.get_collection(collection_name=COLLECTION_NAME)
        point_count = getattr(collection_info, "points_count", 0)
        return {"collection_exists": True, "point_count": point_count, "status": "healthy"}
    except Exception as e:
        return {"collection_exists": False, "point_count": 0, "status": "missing", "error": str(e)}

@app.get("/debug/docs")
async def debug_docs():
    """Check if docs directory exists and its contents"""
    docs_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'docs')
    if os.path.exists(docs_path):
        files = os.listdir(docs_path)
        return {"docs_exists": True, "file_count": len(files), "files": files}
    else:
        return {"docs_exists": False, "file_count": 0, "files": []}

# ======================
# Run Uvicorn
# ======================
if __name__ == "__main__":
    import uvicorn
    port = int(os.environ.get("PORT", 8000))
    uvicorn.run(app, host="0.0.0.0", port=port)