Skip to content

Commit 99e0452

Browse files
authored
feat : AI 기능 추가 (#31)
* fix : deploy.yml 수정 * feat : AI 기능 추가
1 parent eae960f commit 99e0452

10 files changed

Lines changed: 701 additions & 223 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ jobs:
4646
key: ${{ secrets.EC2_KEY }}
4747
script: |
4848
cd ~/my-app
49+
echo "${{ secrets.ENV_FILE }}" > .env
4950
docker compose pull ai-server
5051
docker compose up -d ai-server

.gitignore

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
app/.env
2+
3+
# --- allow GitHub Actions workflows ---
4+
!.github/
5+
!.github/workflows/
6+
!.github/workflows/*.yml
7+
!.github/workflows/*.yaml
8+
9+
10+
# ---- Python ----
11+
__pycache__/
12+
*.py[cod]
13+
*.pyo
14+
*.pyd
15+
*.so
16+
*.egg
17+
*.egg-info/
18+
.eggs/
19+
*.manifest
20+
*.spec
21+
22+
# ---- Virtual Environment ----
23+
app/.env
24+
.venv/
25+
venv/
26+
ENV/
27+
env/
28+
Pipfile.lock
29+
poetry.lock
30+
31+
# ---- FastAPI / Uvicorn ----
32+
*.log
33+
*.db
34+
*.sqlite3
35+
server.log
36+
37+
# ---- Config / Secrets ----
38+
*.ini
39+
*.toml
40+
.env.local
41+
.env.*.local
42+
.envrc
43+
/secrets/
44+
45+
# ---- VSCode / PyCharm ----
46+
.vscode/
47+
.idea/
48+
*.iml
49+
50+
# ---- OS Files ----
51+
.DS_Store
52+
Thumbs.db
53+
54+
# ---- Tests / Coverage ----
55+
htmlcov/
56+
.tox/
57+
.nox/
58+
.coverage
59+
.coverage.*
60+
.cache
61+
pytest_cache/
62+
.pytest_cache/
63+
.pytest_results/
64+
coverage.xml
65+
*.cover
66+
*.py,cover
67+
.hypothesis/
68+
69+
# ---- Test Files (프로젝트 특정) ----
70+
test_*.py
71+
*_test.py
72+
73+
# ---- Build / Dist ----
74+
build/
75+
dist/
76+
wheels/
77+
*.egg-info/
78+
*.egg
79+
.installed.cfg
80+
*.egg-info/
81+
82+
# ---- Notebooks ----
83+
.ipynb_checkpoints
84+
*.ipynb
85+
86+
# ---- Misc ----
87+
.mypy_cache/
88+
.pyre/
89+
.dmypy.json
90+
dmypy.json
91+
92+
# Logs
93+
logs
94+
npm-debug.log*
95+
yarn-debug.log*
96+
yarn-error.log*
97+
dev-debug.log
98+
99+
# Dependency directories
100+
node_modules/
101+
102+
# Environment variables
103+
# Editor directories and files
104+
.idea
105+
.vscode
106+
*.suo
107+
*.ntvs*
108+
*.njsproj
109+
*.sln
110+
*.sw?
111+
112+
# OS specific
113+
114+
# Task files
115+
# tasks.json
116+
# tasks/

.idea/Jugger-AI.iml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/main.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
from fastapi import FastAPI
22
from app.routes import skt_classify
3+
from app.routes import gemini
4+
from pathlib import Path
5+
from dotenv import load_dotenv
36

4-
app = FastAPI(title="SKT KoBERT Text Classification API with URL Extraction")
7+
BASE_DIR = Path(__file__).resolve().parent
8+
load_dotenv(BASE_DIR / ".env")
9+
10+
app = FastAPI(title="문장 분석 기능입니다.")
511

612
# API 라우터 등록
7-
app.include_router(skt_classify.router, prefix="/api", tags=["SKT KoBERT Classification"])
13+
app.include_router(skt_classify.router, prefix="/ai", tags=["SKT KoBERT Classification"])
14+
app.include_router(gemini.router, prefix="/ai", tags=["GEMINI Classification"])
815

916
@app.get("/")
1017
def root():

app/routes/gemini.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from typing import List, Optional
2+
from fastapi import APIRouter, HTTPException
3+
from pydantic import BaseModel, Field, field_validator
4+
5+
from app.services.gemini_category import classify_paragraph_gemini
6+
7+
router = APIRouter()
8+
9+
class GeminiRequest(BaseModel):
10+
paragraph: str = Field(..., description="분류 대상 문단")
11+
userCategories: Optional[List[str]] = Field(default=None, description="카테고리 후보(선택)")
12+
threshold: float = Field(default=0.5, ge=0.0, le=1.0, description="카테고리 매칭 임계값(0~1)")
13+
k: int = Field(default=5, ge=1, le=10, description="추천 개수")
14+
15+
@field_validator("userCategories")
16+
@classmethod
17+
def _strip_empty(cls, v):
18+
if v:
19+
v = [s.strip() for s in v if isinstance(s, str) and s.strip()]
20+
if not v:
21+
return None
22+
return v
23+
24+
class SentenceOut(BaseModel):
25+
text: str
26+
urls: Optional[List[str]] = None
27+
invalid_urls: Optional[List[str]] = None
28+
schedules: Optional[List[dict]] = None
29+
30+
class GeminiResponse(BaseModel):
31+
category: str
32+
recommend_category: List[str]
33+
sentences: List[SentenceOut]
34+
35+
@router.post("/gemini", response_model=GeminiResponse)
36+
async def gemini_endpoint(req: GeminiRequest):
37+
try:
38+
return await classify_paragraph_gemini(
39+
paragraph=req.paragraph,
40+
user_categories=req.userCategories,
41+
threshold=req.threshold,
42+
k=req.k
43+
)
44+
except Exception as e:
45+
raise HTTPException(status_code=502, detail=f"Gemini 호출 실패: {e}")

app/routes/skt_classify.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
1+
from typing import List, Optional
12
from fastapi import APIRouter
2-
from pydantic import BaseModel
3-
from app.services.skt_text_processing import classify_paragraph_with_user
3+
from pydantic import BaseModel, Field, validator
4+
5+
from app.services.skt_text_processing import classify_paragraph
46

57
router = APIRouter()
68

79
class ParagraphRequest(BaseModel):
8-
user_uuid: str
9-
paragraph: str
10+
paragraph: str = Field(..., description="분류할 전체 문단 텍스트")
11+
userCategories: Optional[List[str]] = Field(
12+
default=None, description="스프링에서 내려주는 카테고리명 리스트"
13+
)
14+
threshold: Optional[float] = Field(
15+
default=0.5, ge=0.0, le=1.0, description="카테고리 매칭 임계값(0~1)"
16+
)
17+
18+
@validator("userCategories")
19+
def _strip_empty(cls, v):
20+
if v:
21+
v = [s.strip() for s in v if isinstance(s, str) and s.strip()]
22+
if len(v) == 0:
23+
return None
24+
return v
1025

11-
@router.post("/ai/classify")
26+
@router.post("/classify")
1227
async def classify_paragraph_api(request: ParagraphRequest):
13-
result = await classify_paragraph_with_user(request.user_uuid, request.paragraph)
28+
result = await classify_paragraph(
29+
paragraph=request.paragraph,
30+
user_categories=request.userCategories,
31+
threshold=request.threshold or 0.5
32+
)
1433
return result

0 commit comments

Comments
 (0)