diff --git a/project/CREDIT_APP/.idea/.gitignore b/project/CREDIT_APP/.idea/.gitignore
new file mode 100644
index 0000000..93bca08
--- /dev/null
+++ b/project/CREDIT_APP/.idea/.gitignore
@@ -0,0 +1,10 @@
+# 디폴트 무시된 파일
+/shelf/
+/workspace.xml
+# 에디터 기반 HTTP 클라이언트 요청
+/httpRequests/
+# 쿼리 파일을 포함한 무시된 디폴트 폴더
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/project/CREDIT_APP/.idea/CREDIT_APP.iml b/project/CREDIT_APP/.idea/CREDIT_APP.iml
new file mode 100644
index 0000000..c162d8d
--- /dev/null
+++ b/project/CREDIT_APP/.idea/CREDIT_APP.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/.idea/csv-editor.xml b/project/CREDIT_APP/.idea/csv-editor.xml
new file mode 100644
index 0000000..65803b1
--- /dev/null
+++ b/project/CREDIT_APP/.idea/csv-editor.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/.idea/dataSources.xml b/project/CREDIT_APP/.idea/dataSources.xml
new file mode 100644
index 0000000..b70d3a3
--- /dev/null
+++ b/project/CREDIT_APP/.idea/dataSources.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/db/history.db
+ $ProjectFileDir$
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/db/users.db
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/.idea/db-forest-config.xml b/project/CREDIT_APP/.idea/db-forest-config.xml
new file mode 100644
index 0000000..90ec0d8
--- /dev/null
+++ b/project/CREDIT_APP/.idea/db-forest-config.xml
@@ -0,0 +1,10 @@
+
+
+
+ .
+ ----------------------------------------
+ 1:0:d34d9830-06d9-4e92-bd10-84567e5a4809
+ 2:0:2d5d9d87-b30f-47d4-9d9d-61542cb64ea0
+ .
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/.idea/inspectionProfiles/profiles_settings.xml b/project/CREDIT_APP/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/project/CREDIT_APP/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/.idea/modules.xml b/project/CREDIT_APP/.idea/modules.xml
new file mode 100644
index 0000000..88a04b1
--- /dev/null
+++ b/project/CREDIT_APP/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/.idea/pyLspTools.xml b/project/CREDIT_APP/.idea/pyLspTools.xml
new file mode 100644
index 0000000..e202fc5
--- /dev/null
+++ b/project/CREDIT_APP/.idea/pyLspTools.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/.idea/vcs.xml b/project/CREDIT_APP/.idea/vcs.xml
new file mode 100644
index 0000000..b2bdec2
--- /dev/null
+++ b/project/CREDIT_APP/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/ai/embedding.py b/project/CREDIT_APP/backend/ai/embedding.py
new file mode 100644
index 0000000..4f1a98a
--- /dev/null
+++ b/project/CREDIT_APP/backend/ai/embedding.py
@@ -0,0 +1,9 @@
+from langchain_ibm import WatsonxEmbeddings
+from backend.config.settings import settings
+
+watson_embedding = WatsonxEmbeddings(
+ model_id="ibm/granite-embedding-278m-multilingual",
+ url=f"{settings.watsonx_url}",
+ api_key=f"{settings.watsonx_api_key}",
+ project_id=f"{settings.watsonx_project_id}",
+)
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/ai/llm.py b/project/CREDIT_APP/backend/ai/llm.py
new file mode 100644
index 0000000..1132aa9
--- /dev/null
+++ b/project/CREDIT_APP/backend/ai/llm.py
@@ -0,0 +1,13 @@
+from langchain_ibm import ChatWatsonx
+from backend.config.settings import settings
+
+watson_llm = ChatWatsonx(
+ model_id="ibm/granite-4-h-small",
+ url=f"{settings.watsonx_url}",
+ api_key=f"{settings.watsonx_api_key}",
+ project_id=f"{settings.watsonx_project_id}",
+ max_tokens=2000,
+ params={
+ "temperature": 0
+ }
+)
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/config/settings.py b/project/CREDIT_APP/backend/config/settings.py
new file mode 100644
index 0000000..2a64ea1
--- /dev/null
+++ b/project/CREDIT_APP/backend/config/settings.py
@@ -0,0 +1,11 @@
+from pydantic import Field
+from pydantic_settings import BaseSettings, SettingsConfigDict
+
+class Settings(BaseSettings):
+ model_config = SettingsConfigDict(env_file="backend/.env", extra="ignore")
+ # 사용할 모델
+ watsonx_api_key: str = Field(alias="WATSONX_API_KEY")
+ watsonx_project_id: str = Field(alias="WATSONX_PROJECT_ID")
+ watsonx_url: str = Field(alias="WATSONX_URL")
+
+settings = Settings()
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/routers/api_router.py b/project/CREDIT_APP/backend/routers/api_router.py
new file mode 100644
index 0000000..6073315
--- /dev/null
+++ b/project/CREDIT_APP/backend/routers/api_router.py
@@ -0,0 +1,25 @@
+from fastapi import APIRouter, Request, UploadFile
+from backend.schemas.card_schema import AnalysisRequest
+from backend.services.card_service import upload_csv, card_history, get_dashboard, card_analysis
+from fastapi.templating import Jinja2Templates
+
+router = APIRouter(prefix="/api/card")
+templates = Jinja2Templates(directory="backend/templates")
+
+@router.post("/upload")
+async def upload_file(file: UploadFile):
+ return await upload_csv(file)
+
+@router.get("/history")
+async def history(request : Request):
+ card_infos = card_history()
+
+ return templates.TemplateResponse(request, name = "history.html", context = {"history": card_infos})
+
+@router.get("/dashboard")
+async def dashboard():
+ return get_dashboard()
+
+@router.post("/analysis")
+async def sql_llm_analysis(request : AnalysisRequest):
+ return card_analysis(request.question)
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/routers/page_router.py b/project/CREDIT_APP/backend/routers/page_router.py
new file mode 100644
index 0000000..d020a12
--- /dev/null
+++ b/project/CREDIT_APP/backend/routers/page_router.py
@@ -0,0 +1,22 @@
+from fastapi import APIRouter, Request
+from fastapi.templating import Jinja2Templates
+
+router = APIRouter()
+templates = Jinja2Templates(directory="backend/templates")
+
+# http://127.0.0.1:8000
+@router.get("/")
+async def home(request : Request):
+ return templates.TemplateResponse(request = request, name="index.html")
+
+@router.get("/card/upload")
+async def rag(request : Request):
+ return templates.TemplateResponse(request = request, name="card.html")
+
+@router.get("/card/dashboard")
+async def dashboard(request : Request):
+ return templates.TemplateResponse(request = request, name="dashboard.html")
+
+@router.get("/card/analysis")
+async def analysis(request : Request):
+ return templates.TemplateResponse(request = request, name="analysis.html")
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/schemas/card_schema.py b/project/CREDIT_APP/backend/schemas/card_schema.py
new file mode 100644
index 0000000..4ab1e18
--- /dev/null
+++ b/project/CREDIT_APP/backend/schemas/card_schema.py
@@ -0,0 +1,8 @@
+from pydantic import BaseModel
+
+class AnalysisRequest(BaseModel):
+ question: str
+
+class AnalysisResponse(BaseModel):
+ message: str
+
diff --git a/project/CREDIT_APP/backend/services/card_service.py b/project/CREDIT_APP/backend/services/card_service.py
new file mode 100644
index 0000000..0a53f7f
--- /dev/null
+++ b/project/CREDIT_APP/backend/services/card_service.py
@@ -0,0 +1,140 @@
+from fastapi import UploadFile
+from langchain_core.runnables import RunnablePassthrough
+
+from backend.services.db_service import get_connection
+from backend.services.db_service import get_table_columns
+from langchain_core.prompts import ChatPromptTemplate
+from langchain_core.output_parsers import StrOutputParser
+from backend.ai.llm import watson_llm
+import csv
+
+async def upload_csv(file :UploadFile):
+ """
+ csv 파일을 테이블에 저장
+ """
+
+ conn =get_connection()
+ cursor = conn.cursor()
+
+ # file.read() : 비동기 함수임
+ contents = await file.read()
+ csv_text = contents.decode('utf-8')
+ reader = csv.DictReader(csv_text.splitlines())
+ count = 0
+
+ for row in reader:
+ cursor.execute("""
+ INSERT INTO transactions (date, category, merchant, amount)
+ VALUES (?, ?,?, ?)"""
+ ,
+ (row['date'], row['category'], row['merchant'], row['amount'])
+ )
+ count += 1
+
+ conn.commit()
+ conn.close()
+ return {"message": f"{count} 건 저장 완료"}
+
+def card_history():
+ """
+ db에서 카드 정보 조회
+ """
+ conn =get_connection()
+ cursor = conn.cursor()
+
+ try :
+ cursor.execute("""
+ SELECT * FROM transactions ORDER BY date DESC
+ """)
+ rows = cursor.fetchall()
+ query_result = [dict(row) for row in rows]
+
+ conn.close()
+ except Exception as e:
+ query_result = [f"SQL 실행 오류 : {e}"]
+
+ return query_result
+
+def get_dashboard():
+ """
+ db에서 대시보드 정보 조회
+ """
+ conn = get_connection()
+ cursor = conn.cursor()
+
+ # 카테고리 별 사용금액
+ cursor.execute(
+ """
+ SELECT category, SUM(amount)
+ FROM transactions
+ GROUP BY category
+ ORDER BY SUM(amount) DESC
+ """
+ )
+
+ category_rows = cursor.fetchall()
+
+ # 월별 사용금액
+ cursor.execute(
+ """
+ SELECT strftime('%Y-%m', date), SUM(amount)
+ FROM transactions
+ GROUP BY strftime('%Y-%m', date)
+ ORDER BY strftime('%Y-%m', date)
+ """
+ )
+ month_rows = cursor.fetchall()
+ conn.close()
+
+ return {
+ "category": [{"category":row[0], "amount":row[1]} for row in category_rows],
+ "monthly": [{"month":row[0], "amount":row[1]} for row in month_rows],
+ }
+
+def sql_generate_llm(question):
+ """자연어 -> SQL"""
+ sql_prompt = ChatPromptTemplate.from_template("""
+ 당신은 SQLite 전문가입니다.
+
+ 테이블명 : transactions
+
+ 컬럼 : {columns}
+
+ 질문 : {question}
+
+ SQL 만 출력하세요
+ """)
+
+ columns = get_table_columns("transactions")
+ sql_chain = sql_prompt | watson_llm | StrOutputParser()
+
+ sql = sql_chain.invoke({"columns": columns, "question": question})
+ print(f"sql: {sql}")
+
+ sql = sql.replace("```sql","").replace("```", "").strip()
+
+ # sql 문 실제 실행
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute(sql)
+ rows = cursor.fetchall()
+ query_result = [dict(row) for row in rows]
+ conn.close()
+
+ return query_result
+
+def card_analysis(question):
+ query_result = sql_generate_llm(question)
+
+ analysis_prompt = ChatPromptTemplate.from_template("""
+ 사용자 질문 : {question}
+
+ SQL 결과
+ {result}
+
+ 결과를 설명하고 소비 습관을 분석하고 절약 팁을 제시해 주세요.
+ """)
+ analysis_chain = analysis_prompt | watson_llm | StrOutputParser()
+ answer = analysis_chain.invoke({"question": question, "result": query_result})
+
+ return {"message": answer}
diff --git a/project/CREDIT_APP/backend/services/db_service.py b/project/CREDIT_APP/backend/services/db_service.py
new file mode 100644
index 0000000..fd1ecfa
--- /dev/null
+++ b/project/CREDIT_APP/backend/services/db_service.py
@@ -0,0 +1,44 @@
+import sqlite3
+import os
+
+DB_PATH = "db/history.db"
+
+"""
+cursor.execute('select id, name for users')
+cursor.fetchone() => (1, 'alice', '010-1234-5678')
+
+row['id']
+"""
+
+def get_connection():
+ coon = sqlite3.connect(DB_PATH, isolation_level=None)
+ # 조회 결과를 어떻게 반활할지 설정
+ coon.row_factory = sqlite3.Row
+ return coon
+
+def init_db():
+ # 디렉토리 생성
+ os.makedirs("db", exist_ok=True)
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute("""create table if not exists transactions (
+ id integer primary key AUTOINCREMENT,
+ date text,
+ category text,
+ merchant text,
+ amount integer
+ )
+""")
+ conn.commit()
+ conn.close()
+
+ print("DB 초기화")
+
+def get_table_columns(table_name):
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute("PRAGMA table_info({})".format(table_name))
+ columns = cursor.fetchall()
+ conn.close()
+ return [column[1] for column in columns]
+
diff --git a/project/CREDIT_APP/backend/static/js/analysis.js b/project/CREDIT_APP/backend/static/js/analysis.js
new file mode 100644
index 0000000..e1d5207
--- /dev/null
+++ b/project/CREDIT_APP/backend/static/js/analysis.js
@@ -0,0 +1,17 @@
+document.querySelector("button").addEventListener("click", ask)
+
+async function ask() {
+ // 사용자가 질문 입력 시 질문을 서버로 전송
+ const question = document.querySelector('#question').value
+
+ const response = await fetch("/api/card/analysis", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({question : question})
+ })
+ // 전송 후 answer 도착 시 answer 화면에 보여주기
+ const answer = await response.json()
+ document.querySelector('#answer').textContent = answer.message
+}
diff --git a/project/CREDIT_APP/backend/static/js/card.js b/project/CREDIT_APP/backend/static/js/card.js
new file mode 100644
index 0000000..dad6539
--- /dev/null
+++ b/project/CREDIT_APP/backend/static/js/card.js
@@ -0,0 +1,24 @@
+// 파일 업로드
+document.querySelector("#uploadBtn").addEventListener("click",uploadFile)
+async function uploadFile() {
+ const fileInput = document.querySelector("#file")
+ // 첨부파일 정보 가져오기
+ const file = fileInput.files[0]
+
+ if(!file){
+ alert('파일을 선택하세요');
+ return;
+ }
+
+ // form 만들어 전송
+ const formData = new FormData();
+ formData.append("file",file);
+
+ const response = await fetch("/api/card/upload",{
+ method:"POST",
+ body:formData
+ })
+ // 전송 후 answer 도착 시 answer 화면에 보여주기
+ const answer = await response.json()
+ document.querySelector('#result').textContent = answer.message
+}
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/static/js/dashboard.js b/project/CREDIT_APP/backend/static/js/dashboard.js
new file mode 100644
index 0000000..10c2e18
--- /dev/null
+++ b/project/CREDIT_APP/backend/static/js/dashboard.js
@@ -0,0 +1,65 @@
+
+const drawCategoryChart = (data) => {
+ new Chart(document.querySelector("#categoryChart"), {
+ type: 'pie',
+ data: {
+ labels : data.map((x) => x.category),
+ datasets:[
+ {
+ data:data.map((x)=>x.amount)
+ }
+ ]
+ },
+ options: {
+ responsive: true,
+ plugins: {
+ legend: {
+ position: 'top',
+ },
+ title: {
+ display: true,
+ text: '카테고리별 소비내역'
+ }
+ }
+ },
+ })
+}
+
+const drawMonthlyChart = (data) => {
+ new Chart(document.querySelector("#monthlyChart"), {
+ type: 'line',
+ data: {
+ labels : data.map((x) => x.month),
+ datasets:[
+ {
+ label:"월별 소비",
+ data:data.map((x)=>x.amount)
+ }
+ ]
+ },
+ options: {
+ responsive: true,
+ plugins: {
+ legend: {
+ position: 'top',
+ },
+ title: {
+ display: true,
+ text: '월별 소비내역'
+ }
+ }
+ },
+ })
+}
+
+async function loadDashboard() {
+ const response = await fetch("/api/card/dashboard");
+
+ const data = await response.json();
+ console.log(data);
+
+ drawCategoryChart(data.category);
+ drawMonthlyChart(data.monthly)
+}
+
+window.onload = loadDashboard;
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/templates/analysis.html b/project/CREDIT_APP/backend/templates/analysis.html
new file mode 100644
index 0000000..79a71a0
--- /dev/null
+++ b/project/CREDIT_APP/backend/templates/analysis.html
@@ -0,0 +1,17 @@
+
+
+
+
+ Title
+
+
+ 신용카드 사용내역 분석
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/templates/card.html b/project/CREDIT_APP/backend/templates/card.html
new file mode 100644
index 0000000..921f7bf
--- /dev/null
+++ b/project/CREDIT_APP/backend/templates/card.html
@@ -0,0 +1,17 @@
+
+
+
+
+ Title
+
+
+ 카드 정보 추가
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/templates/dashboard.html b/project/CREDIT_APP/backend/templates/dashboard.html
new file mode 100644
index 0000000..52244ef
--- /dev/null
+++ b/project/CREDIT_APP/backend/templates/dashboard.html
@@ -0,0 +1,18 @@
+
+
+
+
+ Title
+
+
+ 소비내역 대시보드
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/templates/history.html b/project/CREDIT_APP/backend/templates/history.html
new file mode 100644
index 0000000..a2c983e
--- /dev/null
+++ b/project/CREDIT_APP/backend/templates/history.html
@@ -0,0 +1,28 @@
+
+
+
+
+ Title
+
+
+ 카드 소비 내역
+
+
+ | 날짜 |
+ 카테고리 |
+ 가맹점 |
+ 금액 |
+
+
+ {% for row in history %}
+
+ | {{ row.date }} |
+ {{ row.category }} |
+ {{ row.merchant }} |
+ {{ row.amount }} |
+
+ {% endfor %}
+
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/backend/templates/index.html b/project/CREDIT_APP/backend/templates/index.html
new file mode 100644
index 0000000..6d19ffe
--- /dev/null
+++ b/project/CREDIT_APP/backend/templates/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+ Title
+
+
+ 신용카드 분석
+
+
+
\ No newline at end of file
diff --git a/project/CREDIT_APP/db/history.db b/project/CREDIT_APP/db/history.db
new file mode 100644
index 0000000..70e551e
Binary files /dev/null and b/project/CREDIT_APP/db/history.db differ
diff --git a/project/CREDIT_APP/db/users.db b/project/CREDIT_APP/db/users.db
new file mode 100644
index 0000000..18cac5c
Binary files /dev/null and b/project/CREDIT_APP/db/users.db differ
diff --git a/project/CREDIT_APP/main.py b/project/CREDIT_APP/main.py
new file mode 100644
index 0000000..7052ab0
--- /dev/null
+++ b/project/CREDIT_APP/main.py
@@ -0,0 +1,23 @@
+from fastapi import FastAPI
+from starlette.staticfiles import StaticFiles
+from contextlib import asynccontextmanager
+from backend.services.db_service import init_db
+from backend.routers.api_router import router as api_router
+from backend.routers.page_router import router as page_router
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ print("서버 시작")
+ init_db()
+ yield
+ print("DB 초기화")
+
+app = FastAPI(lifespan=lifespan, title="Insights Advisor", version="1.0")
+# app = FastAPI()
+
+# static 폴더 지정
+app.mount("/static", StaticFiles(directory="backend/static"), name="static")
+
+# 라우터 등록
+app.include_router(page_router)
+app.include_router(api_router)
\ No newline at end of file
diff --git a/project/CREDIT_APP/sql.ipynb b/project/CREDIT_APP/sql.ipynb
new file mode 100644
index 0000000..52625e7
--- /dev/null
+++ b/project/CREDIT_APP/sql.ipynb
@@ -0,0 +1,105 @@
+{
+ "cells": [
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### ORM(Object Relational Mapping)\n",
+ "- 데이터베이스 테이블을 파이썬의 클래스로 매핑\n",
+ "- 컬럼 == 속성\n",
+ "- SQLALchemy 라이브러리가 ORM 지원"
+ ],
+ "id": "11f3431eb6bf7a12"
+ },
+ {
+ "cell_type": "code",
+ "id": "initial_id",
+ "metadata": {
+ "collapsed": true,
+ "ExecuteTime": {
+ "end_time": "2026-06-16T08:48:53.375278031Z",
+ "start_time": "2026-06-16T08:48:51.509377417Z"
+ }
+ },
+ "source": "!pip install sqlalchemy",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Requirement already satisfied: sqlalchemy in ./.venv/lib/python3.12/site-packages (2.0.51)\n",
+ "Requirement already satisfied: greenlet>=1 in ./.venv/lib/python3.12/site-packages (from sqlalchemy) (3.5.1)\n",
+ "Requirement already satisfied: typing-extensions>=4.6.0 in ./.venv/lib/python3.12/site-packages (from sqlalchemy) (4.15.0)\n"
+ ]
+ }
+ ],
+ "execution_count": 1
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-16T08:57:27.860045705Z",
+ "start_time": "2026-06-16T08:57:27.561065535Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Users 테이블 (id, name, email)\n",
+ "# create table users()\n",
+ "from sqlalchemy import Column, Integer, String, create_engine\n",
+ "from sqlalchemy.orm import declarative_base, sessionmaker\n",
+ "\n",
+ "# 데이터베이스 연결\n",
+ "engine = create_engine('sqlite:///db/users.db')\n",
+ "# engine = create_engine('sqlite:///users.db')\n",
+ "\n",
+ "# 모든 모델 크래스의 부모 클래스가 될 Base 객체 생성\n",
+ "Base = declarative_base()\n",
+ "class User(Base):\n",
+ " __tablename__ = 'users'\n",
+ "\n",
+ " id = Column(Integer, primary_key=True)\n",
+ " name = Column(String)\n",
+ " email = Column(String, unique=True)\n",
+ "\n",
+ " def __str__(self):\n",
+ " return f\"\"\n",
+ "\n",
+ "# 테이블 생성\n",
+ "Base.metadata.create_all(engine)"
+ ],
+ "id": "7a41f863de1efd48",
+ "outputs": [],
+ "execution_count": 2
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": "",
+ "id": "de1ebcd8f162fd42"
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2",
+ "version": "2.7.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/project/CREDIT_APP/transactions.csv b/project/CREDIT_APP/transactions.csv
new file mode 100644
index 0000000..a3618d1
--- /dev/null
+++ b/project/CREDIT_APP/transactions.csv
@@ -0,0 +1,24 @@
+date,category,merchant,amount
+2023-12-05,Online Shopping,E-Store,120
+2023-12-02,Utilities,Internet Bill,80
+2023-11-29,Dining,Restaurant A,85
+2023-11-22,Groceries,Supermarket,60
+2023-11-15,Travel,Airline Ticket,350
+2023-11-12,Utilities,Electricity Bill,75
+2023-11-08,Online Shopping,Online Marketplace,45
+2023-11-05,Entertainment,Cinema,30
+2023-10-20,Dining,Cafe,20
+2023-10-15,Travel,Hotel Booking,200
+2023-10-10,Groceries,Local Market,50
+2023-10-06,Utilities,Water Bill,40
+2023-09-25,Online Shopping,Tech Store,150
+2023-09-15,Travel,Taxi Service,25
+2023-09-22,Dining,Restaurant B,90
+2023-09-10,Utilities,Electricity Bill,100
+2023-09-05,Groceries,Grocery Store,80
+2023-08-18,Utilities,Gas Bill,60
+2023-08-12,Entertainment,Streaming Service,15
+2023-08-06,Online Shopping,E-Store,300
+2023-07-30,Travel,Train Ticket,40
+2023-07-10,Dining,Fast Food,25
+2023-07-03,Utilities,Water Bill,50
diff --git a/project/rag_app/.idea/forwardedPorts.xml b/project/rag_app/.idea/forwardedPorts.xml
deleted file mode 100644
index 034e067..0000000
--- a/project/rag_app/.idea/forwardedPorts.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/project/rag_app/.idea/modules.xml b/project/rag_app/.idea/modules.xml
index 647ccca..465401a 100644
--- a/project/rag_app/.idea/modules.xml
+++ b/project/rag_app/.idea/modules.xml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/project/rag_app/.idea/pySourceRootDetection.xml b/project/rag_app/.idea/pySourceRootDetection.xml
new file mode 100644
index 0000000..089a2ac
--- /dev/null
+++ b/project/rag_app/.idea/pySourceRootDetection.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/rag_app/.idea/rag_app.iml b/project/rag_app/.idea/rag-app.iml
similarity index 84%
rename from project/rag_app/.idea/rag_app.iml
rename to project/rag_app/.idea/rag-app.iml
index 4aa8216..8800a26 100644
--- a/project/rag_app/.idea/rag_app.iml
+++ b/project/rag_app/.idea/rag-app.iml
@@ -1,5 +1,5 @@
-
+
diff --git a/project/rag_app/backend/routers/api_router.py b/project/rag_app/backend/routers/api_router.py
index 7351745..bf96c2c 100644
--- a/project/rag_app/backend/routers/api_router.py
+++ b/project/rag_app/backend/routers/api_router.py
@@ -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")
\ No newline at end of file
diff --git a/project/rag_app/backend/services/rag_service.py b/project/rag_app/backend/services/rag_service.py
index 2426f8d..9d3840b 100644
--- a/project/rag_app/backend/services/rag_service.py
+++ b/project/rag_app/backend/services/rag_service.py
@@ -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
\ No newline at end of file
diff --git a/project/rag_app/backend/static/js/index.js b/project/rag_app/backend/static/js/index.js
index 094ceef..684ce0f 100644
--- a/project/rag_app/backend/static/js/index.js
+++ b/project/rag_app/backend/static/js/index.js
@@ -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;
+ }
}
\ No newline at end of file
diff --git a/project/rag_app/backend/templates/rag.html b/project/rag_app/backend/templates/rag.html
index 5e00f7c..a7f95c6 100644
--- a/project/rag_app/backend/templates/rag.html
+++ b/project/rag_app/backend/templates/rag.html
@@ -12,6 +12,12 @@
+
+
+
+
+
+