Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coderabbit/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
features:
docstrings: false
8 changes: 7 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ env:
AWS_HOST: ${{ secrets.AWS_HOST }}
AWS_SSH_KEY: ${{ secrets.AWS_SSH_KEY }}
AWS_USER: ${{ secrets.AWS_USER }}
GPT_API_KEY: ${{ secrets.GPT_API_KEY }}


jobs:
Expand Down Expand Up @@ -55,4 +56,9 @@ jobs:
fi

docker pull "${{ env.DOCKER_REPO }}:latest"
docker run -d --name "Block-Guard-AI" -p 8000:8000 ${{ env.DOCKER_REPO }}:latest

docker run -d \
--name "Block-Guard-AI" \
-p 8000:8000 \
-e GPT_API_KEY="${{ env.GPT_API_KEY }}" \
${{ env.DOCKER_REPO }}:latest
20 changes: 20 additions & 0 deletions app/api/fraud_analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from fastapi import APIRouter, HTTPException
from app.services.gpt_service import call_gpt
from app.models.fraud_request import FraudRequest
from app.models.fraud_response import FraudResponse

router = APIRouter()

@router.post(
path = "",
response_model=FraudResponse,
summary="사기 μœ ν˜• 뢄석"
)
async def fraud_analysis(request: FraudRequest):
try:
answer = await call_gpt(request)
response = FraudResponse.model_validate_json(answer)
return response

except Exception as e:
raise HTTPException(status_code=500, detail=f"사기뢄석 μ‹€νŒ¨: {e}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

μ˜ˆμ™Έ 처리 및 였λ₯˜ λ‘œκΉ… κ°œμ„  ν•„μš”

ν˜„μž¬ μ˜ˆμ™Έ μ²˜λ¦¬μ—μ„œ 원본 μ˜ˆμ™Έ 정보가 μ†μ‹€λ˜κ³ , λ‘œκΉ…μ΄ μ—†μ–΄ 디버깅이 μ–΄λ ΅μŠ΅λ‹ˆλ‹€. λ˜ν•œ JSON νŒŒμ‹± μ‹€νŒ¨μ™€ GPT API 호좜 μ‹€νŒ¨λ₯Ό κ΅¬λΆ„ν•˜μ—¬ μ²˜λ¦¬ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 κ°œμ„ ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

+import logging
+
+logger = logging.getLogger(__name__)
+
 async def fraud_analysis(request: FraudRequest):
     try:
         answer = await call_gpt(request)
+        logger.info(f"GPT 응닡 μˆ˜μ‹ : {len(answer)} κΈ€μž")
+    except Exception as e:
+        logger.error(f"GPT API 호좜 μ‹€νŒ¨: {e}", exc_info=True)
+        raise HTTPException(status_code=500, detail=f"GPT API 호좜 μ‹€νŒ¨: {e}") from e
+    
+    try:
         response = FraudResponse.model_validate_json(answer)
         return response
-        
     except Exception as e:
-        raise HTTPException(status_code=500, detail=f"사기뢄석 μ‹€νŒ¨: {e}")
+        logger.error(f"응닡 νŒŒμ‹± μ‹€νŒ¨: {e}, 원본 응닡: {answer}", exc_info=True)
+        raise HTTPException(status_code=500, detail=f"응닡 νŒŒμ‹± μ‹€νŒ¨: {e}") from e
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
answer = await call_gpt(request)
response = FraudResponse.model_validate_json(answer)
return response
except Exception as e:
raise HTTPException(status_code=500, detail=f"사기뢄석 μ‹€νŒ¨: {e}")
import logging
logger = logging.getLogger(__name__)
async def fraud_analysis(request: FraudRequest):
try:
answer = await call_gpt(request)
logger.info(f"GPT 응닡 μˆ˜μ‹ : {len(answer)} κΈ€μž")
except Exception as e:
logger.error(f"GPT API 호좜 μ‹€νŒ¨: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"GPT API 호좜 μ‹€νŒ¨: {e}") from e
try:
response = FraudResponse.model_validate_json(answer)
return response
except Exception as e:
logger.error(f"응닡 νŒŒμ‹± μ‹€νŒ¨: {e}, 원본 응닡: {answer}", exc_info=True)
raise HTTPException(status_code=500, detail=f"응닡 νŒŒμ‹± μ‹€νŒ¨: {e}") from e
🧰 Tools
πŸͺ› Ruff (0.12.2)

20-20: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

πŸ€– Prompt for AI Agents
In app/api/fraud_analysis.py around lines 14 to 20, the current exception
handling loses the original error details and lacks logging, making debugging
difficult. Improve this by adding proper logging of the caught exceptions and
differentiate between JSON parsing errors and GPT API call failures using
separate except blocks. This will help provide clearer error context and
maintain the original exception information in the HTTPException detail.

9 changes: 9 additions & 0 deletions app/config/setting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
gpt_api_key: str
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

API ν‚€ ν•„λ“œμ— λŒ€ν•œ 검증 κ°•ν™” ν•„μš”

API ν‚€λŠ” ν•„μˆ˜ ν•„λ“œμ΄λ―€λ‘œ λͺ…μ‹œμ μœΌλ‘œ μ„€μ •ν•˜κ³  기본값을 μ œκ±°ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. λ˜ν•œ 빈 λ¬Έμžμ—΄ 검증도 μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 κ°œμ„ ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

+from pydantic import Field, validator
+
 class Settings(BaseSettings):
-    gpt_api_key: str
+    gpt_api_key: str = Field(..., min_length=1, description="OpenAI GPT API ν‚€")
+    
+    @validator('gpt_api_key')
+    def validate_api_key(cls, v):
+        if not v or not v.strip():
+            raise ValueError('API ν‚€λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€')
+        return v.strip()
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
gpt_api_key: str
from pydantic import Field, validator
class Settings(BaseSettings):
gpt_api_key: str = Field(..., min_length=1, description="OpenAI GPT API ν‚€")
@validator('gpt_api_key')
def validate_api_key(cls, v):
if not v or not v.strip():
raise ValueError('API ν‚€λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€')
return v.strip()
πŸ€– Prompt for AI Agents
In app/config/setting.py at line 4, the gpt_api_key field currently lacks
validation and a default value. To fix this, explicitly mark gpt_api_key as a
required field without a default and add validation to ensure it is not an empty
string. This will enforce that the API key must be provided and cannot be empty.


class Config:
env_file = ".env"

settings = Settings()
4 changes: 2 additions & 2 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from fastapi import FastAPI
from app.routers import fraud_analysis
from app.api import fraud_analysis

app = FastAPI()

# router 등둝
app.include_router(fraud_analysis.router, prefix="/api/fraud_analysis", tags=["fraud_analysis"])
app.include_router(fraud_analysis.router, prefix="/api/fraud-analysis", tags=["fraud-analysis"])

@app.get("/api/hello")
async def hello():
Expand Down
8 changes: 8 additions & 0 deletions app/models/fraud_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel
from typing import List

class FraudRequest(BaseModel):
messageContent: str
keywords: List[str]
additionalDescription: str
imageContent: str
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

ν•„λ“œ 검증 및 선택적 ν•„λ“œ κ³ λ €κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

ν˜„μž¬ λͺ¨λ“  ν•„λ“œκ°€ ν•„μˆ˜λ‘œ μ„€μ •λ˜μ–΄ μžˆμ–΄ API μ‚¬μš©μ„±μ΄ μ œν•œλ  수 μžˆμŠ΅λ‹ˆλ‹€. 특히 additionalDescriptionκ³Ό imageContentλŠ” μ„ νƒμ μœΌλ‘œ λ§Œλ“œλŠ” 것을 κ³ λ €ν•΄λ³΄μ„Έμš”.

from pydantic import BaseModel
from typing import List
+from typing import Optional

class FraudRequest(BaseModel):
    messageContent: str
    keywords: List[str]
-    additionalDescription: str
-    imageContent: str
+    additionalDescription: Optional[str] = None
+    imageContent: Optional[str] = None
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class FraudRequest(BaseModel):
messageContent: str
keywords: List[str]
additionalDescription: str
imageContent: str
from pydantic import BaseModel
from typing import List
from typing import Optional
class FraudRequest(BaseModel):
messageContent: str
keywords: List[str]
additionalDescription: Optional[str] = None
imageContent: Optional[str] = None
πŸ€– Prompt for AI Agents
In app/models/fraud_request.py around lines 4 to 8, all fields in the
FraudRequest model are currently required, which limits API usability. Modify
the model to make additionalDescription and imageContent optional by using
typing.Optional and providing default values such as None. Also, consider adding
validation to ensure required fields are properly checked while allowing these
two fields to be omitted.

8 changes: 8 additions & 0 deletions app/models/fraud_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel, conint
from typing import List

class FraudResponse(BaseModel):
estimatedFraudType: str # λΆ„λ₯˜λœ 사기 μœ ν˜•
keywords: List[str] # μ£Όμš” μœ„ν—˜ ν‚€μ›Œλ“œ (μ΅œλŒ€ 3개)
explanation: str # ν•΄λ‹Ή μœ ν˜•μœΌλ‘œ νŒλ‹¨ν•œ 이유
score: float # μœ„ν—˜λ„(0~100)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

ν•„λ“œ 검증 둜직 κ°•ν™” ν•„μš”

μ£Όμš” ν•„λ“œλ“€μ— λŒ€ν•œ 검증이 λΆ€μ‘±ν•©λ‹ˆλ‹€. 특히 scoreλŠ” 0-100 λ²”μœ„μ—¬μ•Ό ν•˜κ³ , keywordsλŠ” μ΅œλŒ€ 3개둜 μ œν•œλ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 검증을 μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

 class FraudResponse(BaseModel):
     estimatedFraudType: str                  # λΆ„λ₯˜λœ 사기 μœ ν˜•
-    keywords: List[str]              # μ£Όμš” μœ„ν—˜ ν‚€μ›Œλ“œ (μ΅œλŒ€ 3개)
+    keywords: List[str] = Field(..., min_items=1, max_items=3, description="μ£Όμš” μœ„ν—˜ ν‚€μ›Œλ“œ (μ΅œλŒ€ 3개)")
     explanation: str                      # ν•΄λ‹Ή μœ ν˜•μœΌλ‘œ νŒλ‹¨ν•œ 이유
-    score: float                  # μœ„ν—˜λ„(0~100)
+    score: float = Field(..., ge=0, le=100, description="μœ„ν—˜λ„(0~100)")
+    
+    @validator('estimatedFraudType')
+    def validate_fraud_type(cls, v):
+        if not v.strip():
+            raise ValueError('사기 μœ ν˜•μ€ ν•„μˆ˜μž…λ‹ˆλ‹€')
+        return v.strip()
+    
+    @validator('explanation')
+    def validate_explanation(cls, v):
+        if not v.strip():
+            raise ValueError('μ„€λͺ…은 ν•„μˆ˜μž…λ‹ˆλ‹€')
+        return v.strip()
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class FraudResponse(BaseModel):
estimatedFraudType: str # λΆ„λ₯˜λœ 사기 μœ ν˜•
keywords: List[str] # μ£Όμš” μœ„ν—˜ ν‚€μ›Œλ“œ (μ΅œλŒ€ 3개)
explanation: str # ν•΄λ‹Ή μœ ν˜•μœΌλ‘œ νŒλ‹¨ν•œ 이유
score: float # μœ„ν—˜λ„(0~100)
class FraudResponse(BaseModel):
estimatedFraudType: str # λΆ„λ₯˜λœ 사기 μœ ν˜•
keywords: List[str] = Field(
...,
min_items=1,
max_items=3,
description="μ£Όμš” μœ„ν—˜ ν‚€μ›Œλ“œ (μ΅œλŒ€ 3개)"
)
explanation: str # ν•΄λ‹Ή μœ ν˜•μœΌλ‘œ νŒλ‹¨ν•œ 이유
score: float = Field(
...,
ge=0,
le=100,
description="μœ„ν—˜λ„(0~100)"
)
@validator('estimatedFraudType')
def validate_fraud_type(cls, v):
if not v.strip():
raise ValueError('사기 μœ ν˜•μ€ ν•„μˆ˜μž…λ‹ˆλ‹€')
return v.strip()
@validator('explanation')
def validate_explanation(cls, v):
if not v.strip():
raise ValueError('μ„€λͺ…은 ν•„μˆ˜μž…λ‹ˆλ‹€')
return v.strip()
πŸ€– Prompt for AI Agents
In app/models/fraud_response.py around lines 4 to 8, the FraudResponse model
lacks validation for key fields. Add validation to ensure the score field is
constrained between 0 and 100, and limit the keywords list to a maximum of 3
items. Use Pydantic validators or field constraints to enforce these rules,
raising appropriate errors if the conditions are not met.

28 changes: 28 additions & 0 deletions app/prompts/data/fraud_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from typing import List
from app.prompts.fraud_prompts import FraudExample

# 여기에 18개 μœ ν˜• Γ— 3~4개 μ˜ˆμ‹œλ§ŒνΌ 리슀트λ₯Ό μ •μ˜
FRAUD_EXAMPLES: List[FraudExample] = [
FraudExample(
type_name="ν”Όμ‹±",
message_content="κ·€ν•˜μ˜ 계정이 잠길 수 μžˆμŠ΅λ‹ˆλ‹€. λΉ„λ°€λ²ˆν˜Έλ₯Ό ν™•μΈν•˜λ €λ©΄ μ—¬κΈ°λ₯Ό ν΄λ¦­ν•˜μ„Έμš”.",
additional_description="λŠ¦μ€ λ°€, λ‚―μ„  번호둜 온 SMSλ₯Ό μš°μ—°νžˆ μ—΄μ–΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.",
keywords=["계정 잠금", "λΉ„λ°€λ²ˆν˜Έ 확인", "SMS 링크"],
image_content="μ€ν–‰μ•Œλ¦Ό Bot: λ‹Ήμ‹ μ˜ κ³„μ’Œκ°€ λΉ„μ •μƒμ μœΌλ‘œ ν™œλ™λ˜μ–΄ 잠길 μ˜ˆμ •μž…λ‹ˆλ‹€. https://bit.ly/secure-link"
),
FraudExample(
type_name="투자 사기",
message_content="2μ£Ό λ§Œμ— 30% 수읡 보μž₯! μ§€κΈˆ νˆ¬μžν•˜μ„Έμš”.",
additional_description="μ μ‹¬μ‹œκ°„ μΉ΄νŽ˜μ—μ„œ μΉœκ΅¬κ°€ κ³΅μœ ν•œ 링크λ₯Ό ν΄λ¦­ν–ˆμŠ΅λ‹ˆλ‹€.",
keywords=["30% 수읡", "μ§€κΈˆ 투자", "보μž₯된 수읡"],
image_content="InvestPro: ν•œμ • μ‹œκ°„ 30% 수읡, νˆ¬μžν•˜κΈ° β–Ά http://invest.example.com"
),
FraudExample(
type_name="볡ꢌ 사기",
message_content="μΆ•ν•˜ν•©λ‹ˆλ‹€! 당신은 1,000,000λ‹¬λŸ¬μ— λ‹Ήμ²¨λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 수수료λ₯Ό μ§€λΆˆν•˜λ©΄ 수령 κ°€λŠ₯ν•©λ‹ˆλ‹€.",
additional_description="좜근길 μ§€ν•˜μ² μ—μ„œ 받은 이메일에 ν¬ν•¨λœ 링크λ₯Ό ν΄λ¦­ν–ˆμŠ΅λ‹ˆλ‹€.",
keywords=["당첨", "수수료", "이메일 링크"],
image_content="LotteryKingdom: Congratulations! You've won $1,000,000. Click here https://lotto.fake/claim"
),
# … (총 60~70개 ν•­λͺ©)
]
9 changes: 9 additions & 0 deletions app/prompts/fraud_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pydantic import BaseModel
from typing import List

class FraudExample(BaseModel):
type_name: str
message_content: str
keywords: List[str]
additional_description: str
image_content: str
68 changes: 68 additions & 0 deletions app/prompts/fraud_prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from typing import List, Dict
from app.prompts.fraud_example import FraudExample
from app.prompts.data.fraud_examples import FRAUD_EXAMPLES

def get_fraud_detection_prompt(
message_content: str,
additional_description: str,
keywords: List[str],
image_content: str,
examples: List[FraudExample] = FRAUD_EXAMPLES
) -> List[Dict[str, str]]:

system = {
"role": "system",
"content": (
"당신은 사기 탐지 μ–΄μ‹œμŠ€ν„΄νŠΈμž…λ‹ˆλ‹€. "
"μž…λ ₯된 ν…μŠ€νŠΈλ₯Ό λ°˜λ“œμ‹œ 미리 μ •μ˜λœ 사기 μœ ν˜• 쀑 ν•˜λ‚˜λ‘œ λΆ„λ₯˜ν•˜κ³ , "
"μ΅œμ†Œ 1κ°œμ—μ„œ μ΅œλŒ€ 3개의 μ£Όμš” μœ„ν—˜ ν‚€μ›Œλ“œλ₯Ό μΆ”μΆœν•˜λ©°, κ·Έ 이유λ₯Ό μ„€λͺ…ν•˜κ³ , "
"μœ„ν—˜ 점수(0–100%)λ₯Ό μ œκ³΅ν•΄μ•Ό ν•©λ‹ˆλ‹€.\n"
"좜λ ₯은 λ°˜λ“œμ‹œ valid JSON 객체둜만 μ‘λ‹΅ν•˜μ„Έμš”. μ•„λž˜λŠ” 응닡 μ˜ˆμ‹œμž…λ‹ˆλ‹€:\n"
"{\n"
" \"estimatedFraudType\": \"볡ꢌ 사기\",\n"
" \"keywords\": [\"ν‚€μ›Œλ“œ1\", \"ν‚€μ›Œλ“œ2\"],\n"
" \"explanation\": \"...\",\n"
" \"score\": 92.4\n"
"}\n"
)
}

example_lines = build_example_lines(examples)
assistant_content = "".join(example_lines)
assistant_content += (
"이제 μ•„λž˜ μž…λ ₯을 같은 ν˜•μ‹μœΌλ‘œ λΆ„λ₯˜ν•˜μ„Έμš”:\n"
)
print(assistant_content)

assistant = {
"role": "assistant",
"content": assistant_content
}

user = {
"role": "user",
"content": (
f"messageContent: '{message_content}'\n"
f"additionalDescription: '{additional_description}'\n"
f"keywords: '{keywords}'\n"
f"imageContent: '{image_content}'"
)
}

return [system, assistant, user]

def build_example_lines(examples):
example_lines = ["사기 μœ ν˜• 및 μ˜ˆμ‹œ:\n"]
for idx, ex in enumerate(examples, start=1):
example_lines.append(f"{idx}. {ex.type_name}:\n")
example_lines.append(f" messageContent: '{ex.message_content}'\n")
example_lines.append(f" additionalDescription: '{ex.additional_description}'\n")
example_lines.append(f" keywords: '{ex.keywords}'\n")
example_lines.append(f" imageContent: '{ex.image_content}'\n")
example_lines.append(" 좜λ ₯ JSON μ˜ˆμ‹œ:\n")
example_lines.append(
f" {{\"estimatedFraudType\": \"{ex.type_name}\", "
f"\"keywords\": [...], \"explanation\": \"...\", \"score\": ...}}\n\n"
)

return example_lines
7 changes: 0 additions & 7 deletions app/routers/fraud_analysis.py

This file was deleted.

32 changes: 32 additions & 0 deletions app/services/gpt_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from openai import OpenAI, OpenAIError
from app.config.setting import settings
from app.models.fraud_request import FraudRequest
from app.prompts.fraud_prompts import get_fraud_detection_prompt

client = OpenAI(
api_key = settings.gpt_api_key
)

async def call_gpt(request: FraudRequest):
messages = get_fraud_detection_prompt(
message_content = request.messageContent,
additional_description = request.additionalDescription,
keywords = request.keywords,
image_content = request.imageContent
)

try:
response = client.responses.create(
model="gpt-4o-mini",
input = messages,
temperature = 0.5, # μƒμ„±λœ ν…μŠ€νŠΈμ˜ λ¬΄μž‘μœ„μ„±μ„ κ²°μ •
max_output_tokens = 200
)
print(response)

except OpenAIError as e:
raise RuntimeError(f"GPT API 호좜 μ‹€νŒ¨: {e}")
except Exception as e:
return f"μ„œλ²„ 였λ₯˜ λ°œμƒ: {e}"

return response.output_text.strip()
Comment on lines 18 to 37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

OpenAI API 호좜 방식 및 μ˜ˆμ™Έ 처리 μˆ˜μ • ν•„μš”

ν˜„μž¬ μ½”λ“œμ—μ„œ μ—¬λŸ¬ κ°€μ§€ μ€‘μš”ν•œ λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€:

  1. client.responses.createλŠ” μ˜¬λ°”λ₯Έ OpenAI API λ©”μ„œλ“œκ°€ μ•„λ‹™λ‹ˆλ‹€
  2. 응닡 객체의 output_text 속성도 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€
  3. μ˜ˆμ™Έ μ²˜λ¦¬μ—μ„œ 원본 μ˜ˆμ™Έ 정보가 μ†μ‹€λ©λ‹ˆλ‹€
  4. ν”„λ‘œλ•μ…˜ μ½”λ“œμ— λ””λ²„κΉ…μš© print문이 λ‚¨μ•„μžˆμŠ΅λ‹ˆλ‹€

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€:

+import logging
+
+logger = logging.getLogger(__name__)
+
 async def call_gpt(request: FraudRequest):
     messages = get_fraud_detection_prompt(
         message_content = request.messageContent, 
         additional_description = request.additionalDescription,
         keywords = request.keywords,
         image_content = request.imageContent
     )
     
     try:
-        response = client.responses.create(
+        response = await client.chat.completions.create(
             model="gpt-4o-mini",
-            input = messages,
+            messages=messages,
             temperature = 0.5, # μƒμ„±λœ ν…μŠ€νŠΈμ˜ λ¬΄μž‘μœ„μ„±μ„ κ²°μ •
-            max_output_tokens = 200
+            max_tokens=200
         )
-        print(response)
+        logger.info(f"GPT API 호좜 성곡, μ‚¬μš© 토큰: {response.usage}")
         
     except OpenAIError as e:
-        raise RuntimeError(f"GPT API 호좜 μ‹€νŒ¨: {e}")
+        logger.error(f"GPT API 호좜 μ‹€νŒ¨: {e}", exc_info=True)
+        raise RuntimeError(f"GPT API 호좜 μ‹€νŒ¨: {e}") from e
     except Exception as e:
-        return f"μ„œλ²„ 였λ₯˜ λ°œμƒ: {e}"
+        logger.error(f"μ˜ˆμƒμΉ˜ λͺ»ν•œ μ„œλ²„ 였λ₯˜: {e}", exc_info=True)
+        raise RuntimeError(f"μ„œλ²„ 였λ₯˜ λ°œμƒ: {e}") from e

-    return response.output_text.strip()
+    return response.choices[0].message.content.strip()
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
response = client.responses.create(
model="gpt-4o-mini",
input = messages,
temperature = 0.5, # μƒμ„±λœ ν…μŠ€νŠΈμ˜ λ¬΄μž‘μœ„μ„±μ„ κ²°μ •
max_output_tokens = 200
)
print(response)
except OpenAIError as e:
raise RuntimeError(f"GPT API 호좜 μ‹€νŒ¨: {e}")
except Exception as e:
return f"μ„œλ²„ 였λ₯˜ λ°œμƒ: {e}"
return response.output_text.strip()
import logging
logger = logging.getLogger(__name__)
async def call_gpt(request: FraudRequest):
messages = get_fraud_detection_prompt(
message_content=request.messageContent,
additional_description=request.additionalDescription,
keywords=request.keywords,
image_content=request.imageContent
)
try:
response = await client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
temperature=0.5, # μƒμ„±λœ ν…μŠ€νŠΈμ˜ λ¬΄μž‘μœ„μ„±μ„ κ²°μ •
max_tokens=200
)
logger.info(f"GPT API 호좜 성곡, μ‚¬μš© 토큰: {response.usage}")
except OpenAIError as e:
logger.error(f"GPT API 호좜 μ‹€νŒ¨: {e}", exc_info=True)
raise RuntimeError(f"GPT API 호좜 μ‹€νŒ¨: {e}") from e
except Exception as e:
logger.error(f"μ˜ˆμƒμΉ˜ λͺ»ν•œ μ„œλ²„ 였λ₯˜: {e}", exc_info=True)
raise RuntimeError(f"μ„œλ²„ 였λ₯˜ λ°œμƒ: {e}") from e
return response.choices[0].message.content.strip()
🧰 Tools
πŸͺ› Ruff (0.12.2)

28-28: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

πŸ€– Prompt for AI Agents
In app/services/gpt_service.py around lines 18 to 32, the code incorrectly uses
client.responses.create which is not a valid OpenAI API method, and attempts to
access a non-existent output_text attribute on the response. Additionally, the
exception handling loses original error details and there is a leftover print
statement. To fix this, replace client.responses.create with the correct method
for chat completions (e.g., client.chat.completions.create), access the
generated text from the proper response field (like
response.choices[0].message.content), remove the print statement, and update
exception handling to preserve and propagate original error information
properly.

11 changes: 10 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
annotated-types==0.7.0
anyio==4.9.0
click==8.1.3
certifi==2025.7.14
click==8.2.1
distro==1.9.0
dotenv==0.9.9
fastapi==0.116.0
h11==0.16.0
httpcore==1.0.9
httptools==0.6.4
httpx==0.28.1
idna==3.10
jiter==0.10.0
openai==1.97.1
pydantic==2.11.7
pydantic-settings==2.10.1
pydantic_core==2.33.2
python-dotenv==1.1.1
PyYAML==6.0.2
sniffio==1.3.1
starlette==0.46.2
tqdm==4.67.1
typing-inspection==0.4.1
typing_extensions==4.14.1
uvicorn==0.35.0
Expand Down
Loading