fastAPI 심화

- Chart.js
- pdf, csv 파일 업로드 후 데이터 정제하여 llm으로 처리 후 결과 도출
- sqlite로 데이터 저장
- ORM - SQLAlchemy
This commit is contained in:
2026-06-16 18:03:02 +09:00
parent ccfdac1286
commit 06eb3c57ab
43 changed files with 1912 additions and 39 deletions
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PortForwardingSettings">
<ports>
<entry key="8000">
<ForwardedPortInfo>
<option name="hostPort" value="8000" />
<option name="readOnly" value="false" />
</ForwardedPortInfo>
</entry>
</ports>
</component>
</project>
+1 -1
View File
@@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/rag_app.iml" filepath="$PROJECT_DIR$/.idea/rag_app.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/rag-app.iml" filepath="$PROJECT_DIR$/.idea/rag-app.iml" />
</modules>
</component>
</project>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PySourceRootDetectionService">
<option name="sourcePathsSet">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
+14 -6
View File
@@ -1,25 +1,33 @@
from fastapi import APIRouter, Request, UploadFile
from backend.services.llm_service import question_and_answer
from backend.schemas.basic_schema import QuestionRequest
from backend.services.rag_service import upload_document
from backend.services.rag_service import upload_document, rag_chat, rag_chat_stream
from fastapi.responses import StreamingResponse
router = APIRouter(prefix="/api")
# http://127.0.0.1:8000/api/question
@router.post("/question")
async def question(req:QuestionRequest):
async def question(req: QuestionRequest):
answer = question_and_answer(req.question)
return {"message" : answer}
# http://127.0.0.1:8000/api/rag/upload
@router.post("/rag/upload")
async def fileUpload(file:UploadFile):
async def file_Upload(file: UploadFile):
# 서비스 호출
return upload_document(file)
# http://127.0.0.1:8000/api/rag/question
@router.post("/rag/question")
async def question():
pass
async def question(req: QuestionRequest):
answer = rag_chat(req.question)
return {"message" : answer}
@router.post("/rag/question/stream")
async def question_stream(req: QuestionRequest):
answer = rag_chat_stream(req.question)
return StreamingResponse(rag_chat_stream(req.question), media_type="text/plain")
@@ -1,6 +1,90 @@
# pdf 업로드 => 분할 => 인덱스 생성
def upload_document(file):
pass
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from backend.ai.embedding import watson_embedding
from backend.ai.llm import watson_llm
UPLOAD_PATH = "uploads"
def upload_document(file):
# file 저장
file_path = os.path.join(UPLOAD_PATH, file.filename)
with open(file_path, "wb") as f:
f.write(file.file.read())
# pdf 업로드 => 분할 => 인덱스 생성
# pdf 로드
loader = PyPDFLoader(file_path)
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
chunks = splitter.split_documents(docs)
faiss_store = FAISS.from_documents(documents=chunks, embedding=watson_embedding)
faiss_store.save_local("./db/vectorstore")
return {"message": "업로드 성공"}
# 질문 => 유사도 검색 => 문서 => llm 답변 생성
def rag_chat(question: str):
faiss_store = FAISS.load_local("./db/vectorstore", watson_embedding, allow_dangerous_deserialization=True)
retriever = faiss_store.as_retriever(search_kwargs={"k": 3})
### LLM
# 1. prompt
message = """\
당신은 PDF 기반 RAG AI 입니다.
다음 문서를 참고해서 질문에 답변하세요.
문서:
{context}
질문:
{question}
"""
rag_prompt = ChatPromptTemplate.from_template(message)
# 2. chain
chain = {"context": retriever, "question": RunnablePassthrough()} | rag_prompt | watson_llm | StrOutputParser()
# 3. answer
answer = chain.invoke(question)
return answer
async def rag_chat_stream(question: str):
faiss_store = FAISS.load_local("./db/vectorstore", watson_embedding, allow_dangerous_deserialization=True)
retriever = faiss_store.as_retriever(search_kwargs={"k": 3})
### LLM
# 1. prompt
message = """\
당신은 PDF 기반 RAG AI 입니다.
다음 문서를 참고해서 질문에 답변하세요.
문서:
{context}
질문:
{question}
"""
rag_prompt = ChatPromptTemplate.from_template(message)
# 2. chain
chain = {"context": retriever, "question": RunnablePassthrough()} | rag_prompt | watson_llm | StrOutputParser()
# 3. answer
# chain.stream() : 동기방식(요청 => 응답을 할 떄까지 기다리는 방식)
# chain.astream() : 비동기방식(다른 일 처리 가능)
async for chunk in chain.astream(question):
yield chunk
+44 -14
View File
@@ -17,28 +17,58 @@ async function ask() {
}
// 파일 업로드
document.querySelector("#uploadBtn").addEventListener("click", uploadFile)
async function uploadFile()
{
const fileInput = document.querySelector("#file");
document.querySelector("#uploadBtn").addEventListener("click",uploadFile)
async function uploadFile() {
const fileInput = document.querySelector("#file")
// 첨부파일 정보 가져오기
const file = fileInput.files[0];
const file = fileInput.files[0]
if(!file)
{
alert("파일을 선택해주세요.");
if(!file){
alert('파일을 선택하세요');
return;
}
// form 만들어 전송
const formData = new FormData()
formData.append("file", file)
const formData = new FormData();
formData.append("file",file);
const response = await fetch("/api/reg/question", {
method: "POST",
body: formData
const response = await fetch("/api/rag/upload",{
method:"POST",
body:formData
})
// 전송 후 answer 도착 시 answer 화면에 보여주기
const answer = await response.json()
document.querySelector('#answer').textContent = answer.message
document.querySelector('#result').textContent = answer.message
}
document.querySelector("#askBtn").addEventListener("click", rag_ask)
async function rag_ask() {
// 사용자가 질문 입력 시 질문을 서버로 전송
const question = document.querySelector('#question').value
const response = await fetch("/api/rag/question", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({question : question})
})
// 전송 후 answer 도착 시 answer 화면에 보여주기
// const answer = await response.json()
// document.querySelector('#answer_result').textContent = answer.message
// stream 방식
const reader = response.body.getReader();
const decoder = new TextDecoder();
let answer = "";
while(true){
const { value, done } = await reader.read();
if (done) {
break;
}
answer += decoder.decode(value);
document.querySelector('#answer_result').textContent = answer;
}
}
@@ -12,6 +12,12 @@
</div>
<hr>
<div id="result"></div>
<hr>
<div>
<input type="text" name="question" id="question">
<button id="askBtn">질문</button>
</div>
<div id="answer_result"></div>
<script src="{{ url_for('static', path='js/index.js')}}"></script>
</body>
</html>
Binary file not shown.
Binary file not shown.
+3 -1
View File
@@ -2,4 +2,6 @@
name = "rag-app"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
dependencies = [
"recursivecharactertextsplitter>=0.1.0",
]
+992
View File
File diff suppressed because it is too large Load Diff