Skip to content

Commit 4040389

Browse files
authored
Merge pull request #9 from Block-Guard/feat/#8/connect-gpt-api
[Feat] Connect GPT API
2 parents db39166 + e190004 commit 4040389

File tree

13 files changed

+206
-11
lines changed

13 files changed

+206
-11
lines changed

.coderabbit/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
features:
2+
docstrings: false

.github/workflows/deploy.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ env:
1414
AWS_HOST: ${{ secrets.AWS_HOST }}
1515
AWS_SSH_KEY: ${{ secrets.AWS_SSH_KEY }}
1616
AWS_USER: ${{ secrets.AWS_USER }}
17+
GPT_API_KEY: ${{ secrets.GPT_API_KEY }}
1718

1819

1920
jobs:
@@ -55,4 +56,9 @@ jobs:
5556
fi
5657
5758
docker pull "${{ env.DOCKER_REPO }}:latest"
58-
docker run -d --name "Block-Guard-AI" -p 8000:8000 ${{ env.DOCKER_REPO }}:latest
59+
60+
docker run -d \
61+
--name "Block-Guard-AI" \
62+
-p 8000:8000 \
63+
-e GPT_API_KEY="${{ env.GPT_API_KEY }}" \
64+
${{ env.DOCKER_REPO }}:latest

app/api/fraud_analysis.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from fastapi import APIRouter, HTTPException
2+
from app.services.gpt_service import call_gpt
3+
from app.models.fraud_request import FraudRequest
4+
from app.models.fraud_response import FraudResponse
5+
6+
router = APIRouter()
7+
8+
@router.post(
9+
path = "",
10+
response_model=FraudResponse,
11+
summary="사기 유형 분석"
12+
)
13+
async def fraud_analysis(request: FraudRequest):
14+
try:
15+
answer = await call_gpt(request)
16+
response = FraudResponse.model_validate_json(answer)
17+
return response
18+
except RuntimeError as e:
19+
raise HTTPException(status_code=500, detail=f"사기분석 실패: {e}") from e
20+
except Exception as e:
21+
raise HTTPException(status_code=500, detail=f"응답 파싱 실패: {e}") from e

app/config/setting.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from pydantic_settings import BaseSettings
2+
3+
class Settings(BaseSettings):
4+
gpt_api_key: str
5+
6+
class Config:
7+
env_file = ".env"
8+
9+
settings = Settings()

app/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from fastapi import FastAPI
2-
from app.routers import fraud_analysis
2+
from app.api import fraud_analysis
33

44
app = FastAPI()
55

66
# router 등록
7-
app.include_router(fraud_analysis.router, prefix="/api/fraud_analysis", tags=["fraud_analysis"])
7+
app.include_router(fraud_analysis.router, prefix="/api/fraud-analysis", tags=["fraud-analysis"])
88

99
@app.get("/api/hello")
1010
async def hello():

app/models/fraud_request.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from pydantic import BaseModel
2+
from typing import List
3+
from typing import Optional
4+
5+
class FraudRequest(BaseModel):
6+
messageContent: Optional[str] = None
7+
keywords: List[str]
8+
additionalDescription: Optional[str] = None
9+
imageContent: Optional[str] = None

app/models/fraud_response.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from pydantic import BaseModel, Field
2+
from typing import List
3+
4+
class FraudResponse(BaseModel):
5+
estimatedFraudType: str # 분류된 사기 유형
6+
keywords: List[str] = Field(..., min_items=1, max_items=3, description="주요 위험 키워드 (최대 3개)")
7+
explanation: str # 해당 유형으로 판단한 이유
8+
score: float = Field(..., ge=0, le=100, description="위험도(0~100)")

app/prompts/data/fraud_examples.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from typing import List
2+
from app.prompts.fraud_prompts import FraudExample
3+
4+
# 여기에 18개 유형 × 3~4개 예시만큼 리스트를 정의
5+
FRAUD_EXAMPLES: List[FraudExample] = [
6+
FraudExample(
7+
type_name="피싱",
8+
message_content="귀하의 계정이 잠길 수 있습니다. 비밀번호를 확인하려면 여기를 클릭하세요.",
9+
additional_description="늦은 밤, 낯선 번호로 온 SMS를 우연히 열어보았습니다.",
10+
keywords=["계정 잠금", "비밀번호 확인", "SMS 링크"],
11+
image_content="은행알림 Bot: 당신의 계좌가 비정상적으로 활동되어 잠길 예정입니다. https://bit.ly/secure-link"
12+
),
13+
FraudExample(
14+
type_name="투자 사기",
15+
message_content="2주 만에 30% 수익 보장! 지금 투자하세요.",
16+
additional_description="점심시간 카페에서 친구가 공유한 링크를 클릭했습니다.",
17+
keywords=["30% 수익", "지금 투자", "보장된 수익"],
18+
image_content="InvestPro: 한정 시간 30% 수익, 투자하기 ▶ http://invest.example.com"
19+
),
20+
FraudExample(
21+
type_name="복권 사기",
22+
message_content="축하합니다! 당신은 1,000,000달러에 당첨되었습니다. 수수료를 지불하면 수령 가능합니다.",
23+
additional_description="출근길 지하철에서 받은 이메일에 포함된 링크를 클릭했습니다.",
24+
keywords=["당첨", "수수료", "이메일 링크"],
25+
image_content="LotteryKingdom: Congratulations! You've won $1,000,000. Click here https://lotto.fake/claim"
26+
),
27+
# … (총 60~70개 항목)
28+
]

app/prompts/fraud_example.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from pydantic import BaseModel
2+
from typing import List
3+
4+
class FraudExample(BaseModel):
5+
type_name: str
6+
message_content: str
7+
keywords: List[str]
8+
additional_description: str
9+
image_content: str

app/prompts/fraud_prompts.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from typing import List, Dict
2+
from app.prompts.fraud_example import FraudExample
3+
from app.prompts.data.fraud_examples import FRAUD_EXAMPLES
4+
5+
def get_fraud_detection_prompt(
6+
message_content: str,
7+
additional_description: str,
8+
keywords: List[str],
9+
image_content: str,
10+
examples: List[FraudExample] = FRAUD_EXAMPLES
11+
) -> List[Dict[str, str]]:
12+
13+
system = {
14+
"role": "system",
15+
"content": (
16+
"당신은 사기 탐지 어시스턴트입니다. "
17+
"입력된 텍스트를 반드시 미리 정의된 사기 유형 중 하나로 분류하고, "
18+
"최소 1개에서 최대 3개의 주요 위험 키워드를 추출하며, 그 이유를 설명하고, "
19+
"위험 점수(0–100%)를 제공해야 합니다.\n"
20+
"출력은 반드시 valid JSON 객체로만 응답하세요. 아래는 응답 예시입니다:\n"
21+
"{\n"
22+
" \"estimatedFraudType\": \"복권 사기\",\n"
23+
" \"keywords\": [\"키워드1\", \"키워드2\"],\n"
24+
" \"explanation\": \"...\",\n"
25+
" \"score\": 92.4\n"
26+
"}\n"
27+
)
28+
}
29+
30+
example_lines = build_example_lines(examples)
31+
assistant_content = "".join(example_lines)
32+
assistant_content += (
33+
"이제 아래 입력을 같은 형식으로 분류하세요:\n"
34+
)
35+
36+
assistant = {
37+
"role": "assistant",
38+
"content": assistant_content
39+
}
40+
41+
user = {
42+
"role": "user",
43+
"content": (
44+
f"messageContent: '{message_content}'\n"
45+
f"additionalDescription: '{additional_description}'\n"
46+
f"keywords: {', '.join(keywords) if keywords else 'None'}\n"
47+
f"imageContent: '{image_content}'"
48+
)
49+
}
50+
51+
return [system, assistant, user]
52+
53+
def build_example_lines(examples):
54+
example_lines = ["사기 유형 및 예시:\n"]
55+
for idx, ex in enumerate(examples, start=1):
56+
example_lines.append(f"{idx}. {ex.type_name}:\n")
57+
example_lines.append(f" messageContent: '{ex.message_content}'\n")
58+
example_lines.append(f" additionalDescription: '{ex.additional_description}'\n")
59+
example_lines.append(f" keywords: '{ex.keywords}'\n")
60+
example_lines.append(f" imageContent: '{ex.image_content}'\n")
61+
example_lines.append(" 출력 JSON 예시:\n")
62+
example_lines.append(
63+
f" {{\"estimatedFraudType\": \"{ex.type_name}\", "
64+
f"\"keywords\": [...], \"explanation\": \"...\", \"score\": ...}}\n\n"
65+
)
66+
67+
return example_lines

0 commit comments

Comments
 (0)