Ryanfafa commited on
Commit
86d84a0
·
verified ·
1 Parent(s): 188f4e4

Update api.py

Browse files
Files changed (1) hide show
  1. api.py +90 -35
api.py CHANGED
@@ -1,27 +1,25 @@
1
  """
2
- api.py — FastAPI REST endpoint for DocMind AI
3
- Runs alongside the Streamlit app, exposed on port 7861.
4
- Add this to your HuggingFace Space and update your Dockerfile to run both.
5
  """
6
 
7
  from fastapi import FastAPI, UploadFile, File, HTTPException
8
  from fastapi.middleware.cors import CORSMiddleware
9
  from pydantic import BaseModel
10
- import tempfile
11
  import os
12
 
13
- app = FastAPI(title="DocMind AI API")
14
 
15
- # Allow requests from your portfolio website
16
  app.add_middleware(
17
  CORSMiddleware,
18
- allow_origins=["*"], # Replace with "https://ryanfarahani.com" for production
19
  allow_credentials=True,
20
  allow_methods=["*"],
21
  allow_headers=["*"],
22
  )
23
 
24
- # Lazy-load the RAG engine (shared with Streamlit app)
25
  _rag_engine = None
26
 
27
  def get_rag():
@@ -32,60 +30,117 @@ def get_rag():
32
  return _rag_engine
33
 
34
 
35
- # ── Models ─────────────────────────────────────────────────────────────────
 
36
  class QueryRequest(BaseModel):
37
  question: str
38
 
39
  class QueryResponse(BaseModel):
40
- answer: str
41
- sources: list[str]
42
- success: bool
43
- error: str = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
 
45
 
46
- # ── Routes ─────────────────────────────────────────────────────────────────
47
  @app.get("/health")
48
  def health():
49
- return {"status": "ok", "service": "DocMind AI API"}
50
 
51
 
52
- @app.post("/upload")
53
  async def upload_document(file: UploadFile = File(...)):
54
- """Upload and ingest a PDF or TXT document."""
55
- try:
56
- suffix = os.path.splitext(file.filename)[-1].lower() or ".txt"
57
- if suffix not in [".pdf", ".txt"]:
58
- raise HTTPException(status_code=400, detail="Only PDF and TXT files are supported.")
 
 
 
 
 
 
 
 
 
59
 
60
- rag = get_rag()
 
61
  chunks = rag.ingest_file(file)
62
- return {
63
- "success": True,
64
- "filename": file.filename,
65
- "chunks": chunks,
66
- "message": f"Successfully indexed {chunks} chunks from {file.filename}"
 
 
 
67
  }
 
 
 
 
 
 
 
 
68
  except Exception as e:
69
  raise HTTPException(status_code=500, detail=str(e))
70
 
71
 
72
  @app.post("/query", response_model=QueryResponse)
73
  async def query_document(req: QueryRequest):
74
- """Ask a question about the currently loaded document."""
 
 
75
  try:
76
- if not req.question.strip():
77
- raise HTTPException(status_code=400, detail="Question cannot be empty.")
78
-
79
- rag = get_rag()
80
  answer, sources = rag.query(req.question)
81
- return QueryResponse(answer=answer, sources=sources, success=True)
 
 
 
 
 
82
  except Exception as e:
83
  return QueryResponse(answer="", sources=[], success=False, error=str(e))
84
 
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  @app.post("/reset")
87
  def reset():
88
- """Reset the loaded document."""
89
  global _rag_engine
90
  _rag_engine = None
91
- return {"success": True, "message": "Document cleared."}
 
1
  """
2
+ api.py — FastAPI REST endpoint for DocMind AI (Multimodal + Memory)
3
+ Runs on port 7861 alongside the Streamlit app (port 7860).
 
4
  """
5
 
6
  from fastapi import FastAPI, UploadFile, File, HTTPException
7
  from fastapi.middleware.cors import CORSMiddleware
8
  from pydantic import BaseModel
9
+ from typing import List
10
  import os
11
 
12
+ app = FastAPI(title="DocMind AI API", version="2.0")
13
 
 
14
  app.add_middleware(
15
  CORSMiddleware,
16
+ allow_origins=["*"], # Lock down to "https://rayanfarahani.com" in production
17
  allow_credentials=True,
18
  allow_methods=["*"],
19
  allow_headers=["*"],
20
  )
21
 
22
+ # Shared RAG engine instance
23
  _rag_engine = None
24
 
25
  def get_rag():
 
30
  return _rag_engine
31
 
32
 
33
+ # ── Models ───────────────────────────────────────────────────────────────────
34
+
35
  class QueryRequest(BaseModel):
36
  question: str
37
 
38
  class QueryResponse(BaseModel):
39
+ answer: str
40
+ sources: List[str]
41
+ success: bool
42
+ error: str = ""
43
+ memory_count: int = 0 # how many past exchanges the model remembers
44
+
45
+ class UploadResponse(BaseModel):
46
+ success: bool
47
+ filename: str
48
+ chunks: int
49
+ file_type: str
50
+ message: str
51
+
52
+ class MemoryResponse(BaseModel):
53
+ exchanges: int
54
+ messages: List[dict]
55
+
56
 
57
+ # ── Routes ───────────────────────────────────────────────────────────────────
58
 
 
59
  @app.get("/health")
60
  def health():
61
+ return {"status": "ok", "service": "DocMind AI API", "version": "2.0"}
62
 
63
 
64
+ @app.post("/upload", response_model=UploadResponse)
65
  async def upload_document(file: UploadFile = File(...)):
66
+ """
67
+ Upload and ingest a document.
68
+ Supported: PDF, TXT, DOCX, CSV, XLSX, JPG, PNG, WEBP
69
+ """
70
+ filename = file.filename
71
+ suffix = os.path.splitext(filename)[-1].lower()
72
+
73
+ SUPPORTED = {".pdf", ".txt", ".docx", ".doc", ".csv", ".xlsx", ".xls",
74
+ ".jpg", ".jpeg", ".png", ".webp"}
75
+ if suffix not in SUPPORTED:
76
+ raise HTTPException(
77
+ status_code=400,
78
+ detail=f"Unsupported file type: {suffix}. Supported: {', '.join(sorted(SUPPORTED))}"
79
+ )
80
 
81
+ try:
82
+ rag = get_rag()
83
  chunks = rag.ingest_file(file)
84
+
85
+ type_labels = {
86
+ ".pdf": "PDF Document",
87
+ ".txt": "Text File",
88
+ ".docx": "Word Document", ".doc": "Word Document",
89
+ ".csv": "CSV Spreadsheet",
90
+ ".xlsx": "Excel Spreadsheet", ".xls": "Excel Spreadsheet",
91
+ ".jpg": "Image", ".jpeg": "Image", ".png": "Image", ".webp": "Image",
92
  }
93
+
94
+ return UploadResponse(
95
+ success=True,
96
+ filename=filename,
97
+ chunks=chunks,
98
+ file_type=type_labels.get(suffix, suffix),
99
+ message=f"Successfully indexed {chunks} chunks from {filename}"
100
+ )
101
  except Exception as e:
102
  raise HTTPException(status_code=500, detail=str(e))
103
 
104
 
105
  @app.post("/query", response_model=QueryResponse)
106
  async def query_document(req: QueryRequest):
107
+ """Ask a question. The model uses conversation memory for follow-ups."""
108
+ if not req.question.strip():
109
+ raise HTTPException(status_code=400, detail="Question cannot be empty.")
110
  try:
111
+ rag = get_rag()
 
 
 
112
  answer, sources = rag.query(req.question)
113
+ return QueryResponse(
114
+ answer=answer,
115
+ sources=sources,
116
+ success=True,
117
+ memory_count=rag.get_memory_count()
118
+ )
119
  except Exception as e:
120
  return QueryResponse(answer="", sources=[], success=False, error=str(e))
121
 
122
 
123
+ @app.get("/memory", response_model=MemoryResponse)
124
+ def get_memory():
125
+ """Return current conversation history."""
126
+ rag = get_rag()
127
+ return MemoryResponse(
128
+ exchanges=rag.get_memory_count(),
129
+ messages=rag.get_memory_messages()
130
+ )
131
+
132
+
133
+ @app.post("/memory/clear")
134
+ def clear_memory():
135
+ """Clear conversation history without removing the document."""
136
+ rag = get_rag()
137
+ rag.clear_memory()
138
+ return {"success": True, "message": "Conversation memory cleared."}
139
+
140
+
141
  @app.post("/reset")
142
  def reset():
143
+ """Reset everything document and memory."""
144
  global _rag_engine
145
  _rag_engine = None
146
+ return {"success": True, "message": "Document and memory cleared."}