-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] Connect GPT API #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
428c43d
f575a6f
bf880be
0974eb2
50f0f41
d50d67f
eba9e38
d7fd1c6
218e02f
38b06f6
e190004
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| features: | ||
| docstrings: false |
| 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}") | ||
| 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 | ||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class Config: | ||||||||||||||||||||||||
| env_file = ".env" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| settings = Settings() | ||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| 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.
| 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) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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.
| 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 | ||
yeonju73 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # μ¬κΈ°μ 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κ° νλͺ©) | ||
| ] | ||
| 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 |
| 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) | ||
yeonju73 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| assistant = { | ||
| "role": "assistant", | ||
| "content": assistant_content | ||
| } | ||
|
|
||
| user = { | ||
| "role": "user", | ||
| "content": ( | ||
| f"messageContent: '{message_content}'\n" | ||
| f"additionalDescription: '{additional_description}'\n" | ||
| f"keywords: '{keywords}'\n" | ||
yeonju73 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 | ||
This file was deleted.
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OpenAI API νΈμΆ λ°©μ λ° μμΈ μ²λ¦¬ μμ νμ νμ¬ μ½λμμ μ¬λ¬ κ°μ§ μ€μν λ¬Έμ κ° μμ΅λλ€:
λ€μκ³Ό κ°μ΄ μμ ν΄μΌ ν©λλ€: +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
Suggested change
π§° Toolsπͺ Ruff (0.12.2)28-28: Within an (B904) π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
μμΈ μ²λ¦¬ λ° μ€λ₯ λ‘κΉ κ°μ νμ
νμ¬ μμΈ μ²λ¦¬μμ μλ³Έ μμΈ μ λ³΄κ° μμ€λκ³ , λ‘κΉ μ΄ μμ΄ λλ²κΉ μ΄ μ΄λ ΅μ΅λλ€. λν JSON νμ± μ€ν¨μ GPT API νΈμΆ μ€ν¨λ₯Ό ꡬλΆνμ¬ μ²λ¦¬νλ κ²μ΄ μ’μ΅λλ€.
λ€μκ³Ό κ°μ΄ κ°μ ν μ μμ΅λλ€:
π Committable suggestion
π§° Tools
πͺ Ruff (0.12.2)
20-20: Within an
exceptclause, raise exceptions withraise ... from errorraise ... from Noneto distinguish them from errors in exception handling(B904)
π€ Prompt for AI Agents