umar-100 commited on
Commit
1d9f240
·
1 Parent(s): 4abf205

initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ venv
2
+ InternTaskGenAI.pdf
3
+ .env
README.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # smart-research-assistant
2
+
3
+ ## TODO:
4
+ - [] pinecone utilis
5
+ - [] RAG component using langchain/langgraph
6
+ - [] database setup
7
+ - [] backend using FastAPI
8
+ - [] frontend using streamlit
9
+ ## Deliverabilities
10
+ - Answer questions that require comprehension and inference
11
+ - Pose logic-based questions to users and evaluate their responses
12
+ - Justify every answer with a reference from the document
13
+ ### Functional Requirements
14
+ - Input file (pdf/txt)
15
+ - 2 modes (a) Ask anything (b) challenge me
16
+ - Auto summary after document upload
17
+ - Streamlit + FastAPI (current stack)
18
+ - Bonus features i.e. state management and context highlighting
__pycache__/chroma_utils.cpython-311.pyc ADDED
Binary file (3.46 kB). View file
 
__pycache__/db_utils.cpython-311.pyc ADDED
Binary file (4.78 kB). View file
 
__pycache__/langchain_utils.cpython-311.pyc ADDED
Binary file (2.51 kB). View file
 
__pycache__/main.cpython-311.pyc ADDED
Binary file (5.47 kB). View file
 
__pycache__/pinecone_utilis.cpython-311.pyc ADDED
Binary file (4.81 kB). View file
 
__pycache__/pydantic_models.cpython-311.pyc ADDED
Binary file (1.99 kB). View file
 
app.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from pinecone_utilis import create_pinecone_vectorstore,load_and_split_document, index_document_to_pinecone
2
+
3
+ file_path="InternTaskGenAI.pdf"
4
+
5
+ print(index_document_to_pinecone(file_path=file_path, file_id=1))
backend/main.py ADDED
File without changes
backend/utilis.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.document_loaders import PyPDFLoader
2
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
3
+ from pinecone import Pinecone, ServerlessSpec
4
+ from langchain_core.prompts import PromptTemplate
5
+ from langchain_core.output_parsers import StrOutputParser
6
+ from operator import itemgetter
7
+
8
+ class RAG:
9
+ def load_split_file(self, file_path):
10
+ loader = PyPDFLoader(file_path)
11
+ pages = loader.load_and_split()
12
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
13
+ docs = text_splitter.split_documents(pages)
14
+ return docs
15
+
16
+ def create_index(self, index_name, PINECONE_API_KEY):
17
+ pc = Pinecone(api_key=PINECONE_API_KEY)
18
+ if index_name in pc.list_indexes().names():
19
+ pc.delete_index(index_name) # To avoid any conflicts in retrieval
20
+ pc.create_index(
21
+ name=index_name,
22
+ dimension=384,
23
+ metric='cosine',
24
+ spec=ServerlessSpec(
25
+ cloud="aws",
26
+ region="us-east-1"
27
+ )
28
+ )
29
+ return index_name
30
+
31
+ def final_response(self, index, question, model):
32
+ retriever = index.as_retriever()
33
+ parser = StrOutputParser()
34
+ template = """
35
+ You must provide an answer based strictly on the context below.
36
+ The answer is highly likely to be found within the given context, so analyze it thoroughly before responding.
37
+ Only if there is absolutely no relevant information, respond with "I don't know".
38
+ Do not make things up.
39
+
40
+ Context: {context}
41
+
42
+ Question: {question}
43
+ """
44
+ prompt = PromptTemplate.from_template(template)
45
+ prompt.format(context="Here is some context", question="Here is a question")
46
+
47
+ chain = (
48
+ {
49
+ "context": itemgetter("question") | retriever,
50
+ "question": itemgetter("question"),
51
+ }
52
+ | prompt
53
+ | model
54
+ | parser
55
+ )
56
+ matching_results = index.similarity_search(question, k=2)
57
+ return f"Answer: {chain.invoke({'question': question})}", matching_results
db_utils.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ from datetime import datetime
3
+
4
+ DB_NAME = "rag_app.db"
5
+
6
+ def get_db_connection():
7
+ conn = sqlite3.connect(DB_NAME)
8
+ conn.row_factory = sqlite3.Row
9
+ return conn
10
+
11
+ def create_application_logs():
12
+ conn = get_db_connection()
13
+ conn.execute('''CREATE TABLE IF NOT EXISTS application_logs
14
+ (id INTEGER PRIMARY KEY AUTOINCREMENT,
15
+ session_id TEXT,
16
+ user_query TEXT,
17
+ gpt_response TEXT,
18
+ model TEXT,
19
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
20
+ conn.close()
21
+
22
+ def create_document_store():
23
+ conn = get_db_connection()
24
+ conn.execute('''CREATE TABLE IF NOT EXISTS document_store
25
+ (id INTEGER PRIMARY KEY AUTOINCREMENT,
26
+ filename TEXT,
27
+ upload_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
28
+ conn.close()
29
+
30
+ def insert_application_logs(session_id, user_query, gpt_response, model):
31
+ conn = get_db_connection()
32
+ conn.execute('INSERT INTO application_logs (session_id, user_query, gpt_response, model) VALUES (?, ?, ?, ?)',
33
+ (session_id, user_query, gpt_response, model))
34
+ conn.commit()
35
+ conn.close()
36
+
37
+ def get_chat_history(session_id):
38
+ conn = get_db_connection()
39
+ cursor = conn.cursor()
40
+ cursor.execute('SELECT user_query, gpt_response FROM application_logs WHERE session_id = ? ORDER BY created_at', (session_id,))
41
+ messages = []
42
+ for row in cursor.fetchall():
43
+ messages.extend([
44
+ {"role": "human", "content": row['user_query']},
45
+ {"role": "ai", "content": row['gpt_response']}
46
+ ])
47
+ conn.close()
48
+ return messages
49
+ def insert_document_record(filename):
50
+ conn = get_db_connection()
51
+ cursor = conn.cursor()
52
+ cursor.execute('INSERT INTO document_store (filename) VALUES (?)', (filename,))
53
+ file_id = cursor.lastrowid
54
+ conn.commit()
55
+ conn.close()
56
+ return file_id
57
+
58
+ def delete_document_record(file_id):
59
+ conn = get_db_connection()
60
+ conn.execute('DELETE FROM document_store WHERE id = ?', (file_id,))
61
+ conn.commit()
62
+ conn.close()
63
+ return True
64
+
65
+ def get_all_documents():
66
+ conn = get_db_connection()
67
+ cursor = conn.cursor()
68
+ cursor.execute('SELECT id, filename, upload_timestamp FROM document_store ORDER BY upload_timestamp DESC')
69
+ documents = cursor.fetchall()
70
+ conn.close()
71
+ return [dict(doc) for doc in documents]
72
+
73
+ # Initialize the database tables
74
+ create_application_logs()
75
+ create_document_store()
langchain_utils.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_openai import ChatOpenAI
2
+ from langchain_core.output_parsers import StrOutputParser
3
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
4
+ from langchain.chains import create_history_aware_retriever, create_retrieval_chain
5
+ from langchain.chains.combine_documents import create_stuff_documents_chain
6
+ from typing import List
7
+ from typing_extensions import List, TypedDict
8
+ from langchain_core.documents import Document
9
+ import os
10
+ from pinecone_utilis import vectorstore
11
+ from dotenv import load_dotenv
12
+ load_dotenv()
13
+ OPENAI_API_KEY=os.getenv("OPENAI_API_KEY")
14
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
15
+
16
+ output_parser = StrOutputParser()
17
+
18
+ contextualize_q_system_prompt = (
19
+ "Given a chat history and the latest user question "
20
+ "which might reference context in the chat history, "
21
+ "formulate a standalone question which can be understood "
22
+ "without the chat history. Do NOT answer the question, "
23
+ "just reformulate it if needed and otherwise return it as is."
24
+ )
25
+
26
+ contextualize_q_prompt = ChatPromptTemplate.from_messages([
27
+ ("system", contextualize_q_system_prompt),
28
+ MessagesPlaceholder("chat_history"),
29
+ ("human", "{input}"),
30
+ ])
31
+
32
+ qa_prompt = ChatPromptTemplate.from_messages([
33
+ ("system", "You are a helpful AI assistant. Use the following context to answer the user's question."),
34
+ ("system", "Context: {context}"),
35
+ MessagesPlaceholder(variable_name="chat_history"),
36
+ ("human", "{input}")
37
+ ])
38
+
39
+ class State(TypedDict):
40
+ messages: List[BaseMessage]
41
+
42
+
43
+
main.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException
2
+ from pydantic_models import QueryInput, QueryResponse, DocumentInfo, DeleteFileRequest
3
+ from langchain_utils import get_rag_chain
4
+ from db_utils import insert_application_logs, get_chat_history, get_all_documents, insert_document_record, delete_document_record
5
+ from chroma_utils import index_document_to_chroma, delete_doc_from_chroma
6
+ import os
7
+ import uuid
8
+ import logging
9
+ import shutil
10
+
11
+ # Set up logging
12
+ logging.basicConfig(filename='app.log', level=logging.INFO)
13
+
14
+ # Initialize FastAPI app
15
+ app = FastAPI()
16
+
17
+ @app.post("/chat", response_model=QueryResponse)
18
+ def chat(query_input: QueryInput):
19
+ session_id = query_input.session_id or str(uuid.uuid4())
20
+ logging.info(f"Session ID: {session_id}, User Query: {query_input.question}, Model: {query_input.model.value}")
21
+
22
+ chat_history = get_chat_history(session_id)
23
+ rag_chain = get_rag_chain(query_input.model.value)
24
+ answer = rag_chain.invoke({
25
+ "input": query_input.question,
26
+ "chat_history": chat_history
27
+ })['answer']
28
+
29
+ insert_application_logs(session_id, query_input.question, answer, query_input.model.value)
30
+ logging.info(f"Session ID: {session_id}, AI Response: {answer}")
31
+ return QueryResponse(answer=answer, session_id=session_id, model=query_input.model)
32
+
33
+
34
+ @app.post("/upload-doc")
35
+ def upload_and_index_document(file: UploadFile = File(...)):
36
+ allowed_extensions = ['.pdf', '.docx', '.html']
37
+ file_extension = os.path.splitext(file.filename)[1].lower()
38
+
39
+ if file_extension not in allowed_extensions:
40
+ raise HTTPException(status_code=400, detail=f"Unsupported file type. Allowed types are: {', '.join(allowed_extensions)}")
41
+
42
+ temp_file_path = f"temp_{file.filename}"
43
+
44
+ try:
45
+ # Save the uploaded file to a temporary file
46
+ with open(temp_file_path, "wb") as buffer:
47
+ shutil.copyfileobj(file.file, buffer)
48
+
49
+ file_id = insert_document_record(file.filename)
50
+ success = index_document_to_chroma(temp_file_path, file_id)
51
+
52
+ if success:
53
+ return {"message": f"File {file.filename} has been successfully uploaded and indexed.", "file_id": file_id}
54
+ else:
55
+ delete_document_record(file_id)
56
+ raise HTTPException(status_code=500, detail=f"Failed to index {file.filename}.")
57
+ finally:
58
+ if os.path.exists(temp_file_path):
59
+ os.remove(temp_file_path)
60
+
61
+ @app.get("/list-docs", response_model=list[DocumentInfo])
62
+ def list_documents():
63
+ return get_all_documents()
64
+
65
+ @app.post("/delete-doc")
66
+ def delete_document(request: DeleteFileRequest):
67
+ chroma_delete_success = delete_doc_from_chroma(request.file_id)
68
+
69
+ if chroma_delete_success:
70
+ db_delete_success = delete_document_record(request.file_id)
71
+ if db_delete_success:
72
+ return {"message": f"Successfully deleted document with file_id {request.file_id} from the system."}
73
+ else:
74
+ return {"error": f"Deleted from Chroma but failed to delete document with file_id {request.file_id} from the database."}
75
+ else:
76
+ return {"error": f"Failed to delete document with file_id {request.file_id} from Chroma."}
pinecone_utilis.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_community.document_loaders import PyPDFLoader, TextLoader
2
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
3
+ from langchain_openai import OpenAIEmbeddings
4
+ from pinecone import Pinecone, ServerlessSpec
5
+ from langchain_pinecone import PineconeVectorStore
6
+ from typing import List
7
+ from langchain_core.documents import Document
8
+ import os
9
+ from dotenv import load_dotenv
10
+ load_dotenv()
11
+
12
+ # API keys
13
+ PINECONE_API_KEY=os.getenv("PINECONE_API_KEY")
14
+ print(f"Pinecone API Key: {PINECONE_API_KEY}")
15
+ OPENAI_API_KEY=os.getenv("OPENAI_API_KEY")
16
+
17
+
18
+
19
+ # text splitter and embedding function
20
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200, length_function=len)
21
+ embeddings = OpenAIEmbeddings(model="text-embedding-3-large", dimensions=1024, api_key=OPENAI_API_KEY)
22
+
23
+ # Pinecone vector store
24
+ pc = Pinecone(api_key="pcsk_55CAuv_4KzXk8TYtxsauVoxwBsqqTgxWYmNJEEYrCpVxPqHcYbeS8njTWjZ14xCMEbksPS")
25
+
26
+ def load_and_split_document(file_path: str) -> List[Document]:
27
+ if file_path.endswith('.pdf'):
28
+ loader = PyPDFLoader(file_path)
29
+ elif file_path.endswith('.txt'):
30
+ loader = TextLoader(file_path)
31
+ else:
32
+ raise ValueError(f"Unsupported file type: {file_path}")
33
+
34
+ documents = loader.load()
35
+ return text_splitter.split_documents(documents)
36
+
37
+ INDEX_NAME = "smart-research-assistant"
38
+
39
+ def create_pinecone_vectorstore()-> PineconeVectorStore:
40
+ try:
41
+ if not pc.has_index(INDEX_NAME):
42
+ pc.create_index(
43
+ name=INDEX_NAME,
44
+ dimension=1024,
45
+ metric="cosine",
46
+ spec=ServerlessSpec(cloud="aws", region="us-east-1")
47
+ )
48
+
49
+ index = pc.Index(INDEX_NAME)
50
+ return PineconeVectorStore(index=index, embedding=embeddings)
51
+
52
+ except Exception as e:
53
+ print(f"Index initialization failed: {e}")
54
+ raise
55
+
56
+
57
+ vectorstore=create_pinecone_vectorstore()
58
+
59
+ def index_document_to_pinecone(file_path: str, file_id: int) -> bool:
60
+ try:
61
+ splits = load_and_split_document(file_path)
62
+
63
+ # Add metadata to each split
64
+ for split in splits:
65
+ split.metadata['file_id'] = file_id
66
+
67
+ vectorstore.add_documents(splits)
68
+ return True
69
+ except Exception as e:
70
+ print(f"Error indexing document: {e}")
71
+ return False
72
+
73
+ def delete_doc_from_pinecone(file_id: int):
74
+ try:
75
+ docs = vectorstore.get(where={"file_id": file_id})
76
+ print(f"Found {len(docs['ids'])} document chunks for file_id {file_id}")
77
+
78
+ vectorstore._collection.delete(where={"file_id": file_id})
79
+ print(f"Deleted all documents with file_id {file_id}")
80
+
81
+ return True
82
+ except Exception as e:
83
+ print(f"Error deleting document with file_id {file_id} from Chroma: {str(e)}")
84
+ return False
prompt_templates.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. Auto-summarization prompt template
2
+ AUTO_SUMMARY_TEMPLATE = """
3
+ Summarize the following document in no more than 150 words. Focus on the main points and key findings. Do not include information not present in the document.
4
+
5
+ DOCUMENT:
6
+ {document}
7
+
8
+ SUMMARY:
9
+ """
10
+
11
+ # 2. Question answering prompt template
12
+ QA_PROMPT_TEMPLATE = """
13
+ Answer the following question based only on the provided document. Your answer must be grounded in the document and include a specific reference to the text that supports your answer.
14
+
15
+ Document:
16
+ {document}
17
+
18
+ Question:
19
+ {question}
20
+
21
+ Answer:
22
+ """
23
+
24
+ # 3. Logic-based question generation prompt template
25
+ LOGIC_QUESTION_GENERATION_TEMPLATE = """
26
+ Generate three logic-based or comprehension-focused questions about the following document. Each question should require understanding or reasoning about the document content, not just simple recall. Provide each question on a new line.
27
+
28
+ Document:
29
+ {document}
30
+
31
+ Questions:
32
+ """
33
+
34
+ # 4. Answer evaluation prompt template
35
+ ANSWER_EVALUATION_TEMPLATE = """
36
+ Evaluate the following user answer to the question, using only the provided document as the source of truth. State whether the answer is correct or not, and provide a brief justification referencing the document.
37
+
38
+ Document:
39
+ {document}
40
+
41
+ Question:
42
+ {question}
43
+
44
+ User Answer:
45
+ {user_answer}
46
+
47
+ Evaluation:
48
+ """
49
+
50
+ # 5. For memory/follow-up: Chat prompt template
51
+ CHAT_PROMPT_TEMPLATE = """
52
+ The following is a conversation between a user and an AI assistant about a document. The assistant answers questions and provides justifications based on the document. Use the conversation history and the document to answer the new question.
53
+
54
+ Document:
55
+ {document}
56
+
57
+ Conversation History:
58
+ {history}
59
+
60
+ Question:
61
+ {question}
62
+
63
+ Answer:
64
+ """
pydantic_models.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from enum import Enum
3
+ from datetime import datetime
4
+
5
+ class ModelName(str, Enum):
6
+ GPT4_O = "gpt-4o"
7
+ GPT4_O_MINI = "gpt-4o-mini"
8
+
9
+ class QueryInput(BaseModel):
10
+ question: str
11
+ session_id: str = Field(default=None)
12
+ model: ModelName = Field(default=ModelName.GPT4_O_MINI)
13
+
14
+ class QueryResponse(BaseModel):
15
+ answer: str
16
+ session_id: str
17
+ model: ModelName
18
+
19
+ class DocumentInfo(BaseModel):
20
+ id: int
21
+ filename: str
22
+ upload_timestamp: datetime
23
+
24
+ class DeleteFileRequest(BaseModel):
25
+ file_id: int
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ langchain
2
+ langchain-openai
3
+ langchain-core
4
+ langchain_community
5
+ docx2txt
6
+ pypdf
7
+ langchain_chroma
8
+ python-multipart
9
+ fastapi
10
+ uvicorn
ui.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+
4
+ # Set the FastAPI backend URL
5
+ FASTAPI_URL = "http://localhost:8000"
6
+
7
+ st.title("Smart Research Assistant")
8
+
9
+ # Document Upload Section
10
+ uploaded_file = st.file_uploader("Upload a PDF or TXT document", type=["pdf", "txt"])
11
+ if uploaded_file:
12
+ files = {"file": uploaded_file}
13
+ response = requests.post(f"{FASTAPI_URL}/upload-doc", files=files)
14
+ if response.status_code == 200:
15
+ result = response.json()
16
+ st.success(result["message"])
17
+ file_id = result["file_id"]
18
+ # Here you would also call a summary endpoint if implemented
19
+ # For demo, assume summary is returned in the upload response
20
+ # st.write("Summary: ", result.get("summary", "Summary not available"))
21
+ else:
22
+ st.error(f"Error: {response.json().get('detail', 'Unknown error')}")
23
+
24
+ # List Documents (optional)
25
+ if st.button("List Documents"):
26
+ response = requests.get(f"{FASTAPI_URL}/list-docs")
27
+ if response.status_code == 200:
28
+ documents = response.json()
29
+ st.write("Available Documents:")
30
+ for doc in documents:
31
+ st.write(f"- {doc['filename']} (ID: {doc['file_id']})")
32
+ else:
33
+ st.error("Failed to list documents")
34
+
35
+ # Interaction Modes
36
+ mode = st.radio("Choose Mode", ["Ask Anything", "Challenge Me"])
37
+
38
+ if mode == "Ask Anything":
39
+ question = st.text_input("Ask a question about the document")
40
+ if question and st.button("Submit"):
41
+ payload = {
42
+ "question": question,
43
+ "session_id": "user123", # Replace with actual session management
44
+ "model": "default" # Replace with your model selection
45
+ }
46
+ response = requests.post(f"{FASTAPI_URL}/chat", json=payload)
47
+ if response.status_code == 200:
48
+ result = response.json()
49
+ st.write("Answer:", result["answer"])
50
+ # If your backend returns a source snippet, display it:
51
+ # st.write("Source:", result.get("source", ""))
52
+ else:
53
+ st.error("Failed to get answer")
54
+
55
+ # elif mode == "Challenge Me":
56
+ # if st.button("Generate Challenge Questions"):
57
+
58
+ # # Assume your backend has a `/generate-questions` endpoint
59
+ # # response = requests.post(f"{FASTAPI_URL}/generate-questions", json={"file_id": file_id})
60
+ # # if response.status_code == 200:
61
+ # # questions