From bb4eb0a55360411601d79188e34f01fa8e24dc86 Mon Sep 17 00:00:00 2001 From: kite_U Date: Sun, 7 Sep 2025 17:25:04 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20open=20AI=20API=20kye=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 5e336f3..00a7b51 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -5,14 +5,11 @@ load_dotenv() redis_url = os.getenv("REDIS_SERVER_URL") -bucket_name = os.getenv("BUCKET_NAME") -s3_region = os.getenv("S3_REGION") - +oepnai_key = os.getenv("OPENAI_API_KEY") class Settings(BaseSettings): REDIS_URL: str = redis_url - S3_REGION: str = bucket_name - BUCKET_NAME: str = s3_region + OPENAI_KEY: str = oepnai_key STREAM_JOB: str = "image.jobs" # SpringBoot에서 job 발행 (FastAPI에서 listen) STREAM_RESULT: str = "image.results" # FastAPI에서 결과 발행 (SpringBoot에서 listen) From 38eaf14edea8525167b23eb17aabf9202897cdec Mon Sep 17 00:00:00 2001 From: kite_U Date: Sun, 7 Sep 2025 17:25:17 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20openAI=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5cf02b2..d06a886 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,9 @@ uvicorn redis pydantic pydantic-settings -boto3 requests torch==2.8.0 torchvision==0.23.0 Pillow==11.3.0 -dotenv \ No newline at end of file +dotenv +openai \ No newline at end of file From db909042e755301c4f973a383adce92734b8f3cb Mon Sep 17 00:00:00 2001 From: kite_U Date: Sun, 7 Sep 2025 17:25:35 +0900 Subject: [PATCH 3/6] =?UTF-8?q?del:=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C?= =?UTF-8?q?=20boto=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/s3_service.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/app/services/s3_service.py b/app/services/s3_service.py index 8cf7719..1e8debb 100644 --- a/app/services/s3_service.py +++ b/app/services/s3_service.py @@ -1,27 +1,15 @@ -import boto3 -from botocore.config import Config as BotoConfig import requests from io import BytesIO -from app.core.config import settings - class S3Service: def __init__(self): - self.client = boto3.client( - "s3", - region_name=settings.S3_REGION, - config=BotoConfig( - retries={"max_attempts": 5, "mode": "standard"}, - read_timeout=30, - connect_timeout=5, - ), - ) + pass def download_file_from_presigned_url(self, presigned_url: str) -> BytesIO: response = requests.get(presigned_url) response.raise_for_status() - - return BytesIO(response.content) # response 안의 content Stream으로 처리 + # response의 content를 BytesIO로 감싸 반환 + return BytesIO(response.content) s3_service = S3Service() From e885d49792cc576efb9872f5ed3248e370218ce4 Mon Sep 17 00:00:00 2001 From: kite_U Date: Sun, 7 Sep 2025 17:25:54 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20OpenAI=20=EC=A7=88=EB=AC=B8=20?= =?UTF-8?q?=ED=9B=84=20=EC=84=A4=EB=AA=85=20=EB=B0=98=ED=99=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/openai_service.py | 66 ++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 app/services/openai_service.py diff --git a/app/services/openai_service.py b/app/services/openai_service.py new file mode 100644 index 0000000..7455f19 --- /dev/null +++ b/app/services/openai_service.py @@ -0,0 +1,66 @@ +from app.core.config import settings +from openai import OpenAI +import json + +client = OpenAI(api_key=settings.OPENAI_KEY) + +class PregnancySafetyChecker: + def __init__(self, client: OpenAI): + self.client = client + + """ + - isSafe: 안전하면 1, 안전하지 않으면 0 + - description: 복용 가능 여부 설명 + """ + def ask_chatgpt_about_pregnancy_safety(self, pill_name: str) -> tuple[str, int]: + prompt = f""" + 약 이름: {pill_name} + 질문: 이 약은 임산부가 복용해도 안전한가요? 복용 가능 여부와 주의사항을 알려주세요. + description 안에는 문장마다 \\n 을 적용하세요. + 결과를 JSON 형식으로 정확히 반환하세요. 설명이나 다른 텍스트를 절대 덧붙이지 마세요. + 스키마: + {{ + "description": "복용 가능 여부 및 주의사항에 대한 설명", + "isSafe": 1 또는 0 + }} + """ + + response = self.client.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "user", "content": prompt}], + temperature=0, + max_tokens=600, + response_format={"type": "json_object"} + ) + print("GPT Asking 성공...") + raw = response.choices[0].message.content.strip() + + try: + data = json.loads(raw) + except json.JSONDecodeError: + start = raw.find("{") + end = raw.rfind("}") + if start != -1 and end != -1 and start < end: + data = json.loads(raw[start:end+1]) + else: + # 디버깅 + preview = raw[:200].replace("\n", "\\n") + raise ValueError(f"응답이 유효한 JSON이 아닙니다. preview='{preview}'") + + description = data.get("description") + isSafe = data.get("isSafe") + + if isinstance(isSafe, bool): + isSafe = 1 if isSafe else 0 + elif isinstance(isSafe, str): + isSafe = 1 if isSafe.strip() in {"1", "true", "True"} else 0 + elif not isinstance(isSafe, int): + isSafe = 0 + + if not isinstance(description, str): + description = "" # 안전장치 + + return description, int(isSafe) + + +checker = PregnancySafetyChecker(client) \ No newline at end of file From 022e9f3099f86d3f31b17dd56f2698aaacbf7266 Mon Sep 17 00:00:00 2001 From: kite_U Date: Sun, 7 Sep 2025 17:26:02 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20OpenAI=20=EC=A7=88=EB=AC=B8=20?= =?UTF-8?q?=ED=9B=84=20=EC=84=A4=EB=AA=85=20=EB=B0=98=ED=99=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/worker/tasks.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/worker/tasks.py b/app/worker/tasks.py index 3a22e33..63dfdc5 100644 --- a/app/worker/tasks.py +++ b/app/worker/tasks.py @@ -5,6 +5,7 @@ from app.core.config import settings from app.schemas.job import ImageJob, JobResult +from app.services.openai_service import checker from app.services.predictor_service import predictor_service from app.services.s3_service import s3_service @@ -27,11 +28,8 @@ async def process_image_scan(job: ImageJob, redis_client: redis.Redis): predictor_service.predict, stream_file ) - - # TODO: ChatGPT에 요청 결과 출력 - - isSafe = 0 - description = "일단은 테스트입니다. 추후에 GPT 부분 추가할 예정" + print(f"[task] Start Asking GPT for job_id={correlationId}") + description, isSafe = checker.ask_chatgpt_about_pregnancy_safety(pillName) finishedAt = datetime.utcnow().isoformat() result = JobResult( From 0541d2784de708e619b1a59371de0df3a5add296 Mon Sep 17 00:00:00 2001 From: kite_U Date: Sun, 7 Sep 2025 17:44:25 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 00a7b51..1bf7e00 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -5,11 +5,11 @@ load_dotenv() redis_url = os.getenv("REDIS_SERVER_URL") -oepnai_key = os.getenv("OPENAI_API_KEY") +openai_key = os.getenv("OPENAI_API_KEY") class Settings(BaseSettings): REDIS_URL: str = redis_url - OPENAI_KEY: str = oepnai_key + OPENAI_KEY: str = openai_key STREAM_JOB: str = "image.jobs" # SpringBoot에서 job 발행 (FastAPI에서 listen) STREAM_RESULT: str = "image.results" # FastAPI에서 결과 발행 (SpringBoot에서 listen)