openapi exception, retry, timeout finished
parent
c7b77fb532
commit
1665d11d66
|
|
@ -24,7 +24,7 @@ from app.home.schemas.home_schema import (
|
|||
ProcessedInfo,
|
||||
)
|
||||
from app.utils.upload_blob_as_request import AzureBlobUploader
|
||||
from app.utils.chatgpt_prompt import ChatgptService
|
||||
from app.utils.chatgpt_prompt import ChatgptService, ChatGPTResponseError
|
||||
from app.utils.common import generate_task_id
|
||||
from app.utils.logger import get_logger
|
||||
from app.utils.nvMapScraper import NvMapScraper, GraphQLException
|
||||
|
|
@ -293,6 +293,15 @@ async def _crawling_logic(url:str):
|
|||
f"[crawling] Step 3 완료 - 마케팅 분석 성공 ({step3_elapsed:.1f}ms)"
|
||||
)
|
||||
|
||||
except ChatGPTResponseError as e:
|
||||
step3_elapsed = (time.perf_counter() - step3_start) * 1000
|
||||
logger.error(
|
||||
f"[crawling] Step 3 FAILED - ChatGPT Error: status={e.status}, "
|
||||
f"code={e.error_code}, message={e.error_message} ({step3_elapsed:.1f}ms)"
|
||||
)
|
||||
marketing_analysis = None
|
||||
gpt_status = "failed"
|
||||
|
||||
except Exception as e:
|
||||
step3_elapsed = (time.perf_counter() - step3_start) * 1000
|
||||
logger.error(
|
||||
|
|
@ -301,6 +310,7 @@ async def _crawling_logic(url:str):
|
|||
logger.exception("[crawling] Step 3 상세 오류:")
|
||||
# GPT 실패 시에도 크롤링 결과는 반환
|
||||
marketing_analysis = None
|
||||
gpt_status = "failed"
|
||||
else:
|
||||
step2_elapsed = (time.perf_counter() - step2_start) * 1000
|
||||
logger.warning(
|
||||
|
|
@ -320,6 +330,7 @@ async def _crawling_logic(url:str):
|
|||
logger.info(f"[crawling] - GPT API 호출: {step3_3_elapsed:.1f}ms")
|
||||
|
||||
return {
|
||||
"status": gpt_status if 'gpt_status' in locals() else "completed",
|
||||
"image_list": scraper.image_link_list,
|
||||
"image_count": len(scraper.image_link_list) if scraper.image_link_list else 0,
|
||||
"processed_info": processed_info,
|
||||
|
|
|
|||
|
|
@ -158,13 +158,37 @@ class MarketingAnalysis(BaseModel):
|
|||
class CrawlingResponse(BaseModel):
|
||||
"""크롤링 응답 스키마"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"status": "completed",
|
||||
"image_list": ["https://example.com/image1.jpg", "https://example.com/image2.jpg"],
|
||||
"image_count": 2,
|
||||
"processed_info": {
|
||||
"customer_name": "스테이 머뭄",
|
||||
"region": "군산",
|
||||
"detail_region_info": "전북특별자치도 군산시 절골길 18"
|
||||
},
|
||||
"marketing_analysis": {
|
||||
"report": "마케팅 분석 리포트...",
|
||||
"tags": ["힐링", "감성숙소"],
|
||||
"facilities": ["조식", "주차"]
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
status: str = Field(
|
||||
default="completed",
|
||||
description="처리 상태 (completed: 성공, failed: ChatGPT 분석 실패)"
|
||||
)
|
||||
image_list: Optional[list[str]] = Field(None, description="이미지 URL 목록")
|
||||
image_count: int = Field(..., description="이미지 개수")
|
||||
processed_info: Optional[ProcessedInfo] = Field(
|
||||
None, description="가공된 장소 정보 (customer_name, region, detail_region_info)"
|
||||
)
|
||||
marketing_analysis: Optional[MarketingAnalysis] = Field(
|
||||
None, description="마케팅 분석 결과 (report, tags, facilities)"
|
||||
None, description="마케팅 분석 결과 (report, tags, facilities). 실패 시 null"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -108,15 +108,33 @@ class LyricStatusResponse(BaseModel):
|
|||
Usage:
|
||||
GET /lyric/status/{task_id}
|
||||
Returns the current processing status of a lyric generation task.
|
||||
|
||||
Status Values:
|
||||
- processing: 가사 생성 진행 중
|
||||
- completed: 가사 생성 완료
|
||||
- failed: ChatGPT API 오류 또는 생성 실패
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"examples": [
|
||||
{
|
||||
"summary": "성공",
|
||||
"value": {
|
||||
"task_id": "0694b716-dbff-7219-8000-d08cb5fce431",
|
||||
"status": "completed",
|
||||
"message": "가사 생성이 완료되었습니다.",
|
||||
}
|
||||
},
|
||||
{
|
||||
"summary": "실패",
|
||||
"value": {
|
||||
"task_id": "0694b716-dbff-7219-8000-d08cb5fce431",
|
||||
"status": "failed",
|
||||
"message": "가사 생성에 실패했습니다.",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -131,11 +149,18 @@ class LyricDetailResponse(BaseModel):
|
|||
Usage:
|
||||
GET /lyric/{task_id}
|
||||
Returns the generated lyric content for a specific task.
|
||||
|
||||
Note:
|
||||
- status가 "failed"인 경우 lyric_result에 에러 메시지가 저장됩니다.
|
||||
- 에러 메시지 형식: "ChatGPT Error: {message}" 또는 "Error: {message}"
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"examples": [
|
||||
{
|
||||
"summary": "성공",
|
||||
"value": {
|
||||
"id": 1,
|
||||
"task_id": "0694b716-dbff-7219-8000-d08cb5fce431",
|
||||
"project_id": 1,
|
||||
|
|
@ -143,14 +168,27 @@ class LyricDetailResponse(BaseModel):
|
|||
"lyric_result": "인스타 감성의 스테이 머뭄\n군산 신흥동 말랭이 마을에서\n여유로운 하루를 보내며\n추억을 만들어가요",
|
||||
"created_at": "2024-01-15T12:00:00",
|
||||
}
|
||||
},
|
||||
{
|
||||
"summary": "실패",
|
||||
"value": {
|
||||
"id": 1,
|
||||
"task_id": "0694b716-dbff-7219-8000-d08cb5fce431",
|
||||
"project_id": 1,
|
||||
"status": "failed",
|
||||
"lyric_result": "ChatGPT Error: Response incomplete: max_output_tokens",
|
||||
"created_at": "2024-01-15T12:00:00",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
id: int = Field(..., description="가사 ID")
|
||||
task_id: str = Field(..., description="작업 고유 식별자")
|
||||
project_id: int = Field(..., description="프로젝트 ID")
|
||||
status: str = Field(..., description="처리 상태")
|
||||
lyric_result: Optional[str] = Field(None, description="생성된 가사")
|
||||
status: str = Field(..., description="처리 상태 (processing, completed, failed)")
|
||||
lyric_result: Optional[str] = Field(None, description="생성된 가사 또는 에러 메시지 (실패 시)")
|
||||
created_at: Optional[datetime] = Field(None, description="생성 일시")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||
|
||||
from app.database.session import BackgroundSessionLocal
|
||||
from app.lyric.models import Lyric
|
||||
from app.utils.chatgpt_prompt import ChatgptService
|
||||
from app.utils.chatgpt_prompt import ChatgptService, ChatGPTResponseError
|
||||
from app.utils.prompts.prompts import Prompt
|
||||
from app.utils.logger import get_logger
|
||||
|
||||
|
|
@ -130,6 +130,14 @@ async def generate_lyric_background(
|
|||
logger.debug(f"[generate_lyric_background] - Step 2 (GPT API 호출): {step2_elapsed:.1f}ms")
|
||||
logger.debug(f"[generate_lyric_background] - Step 3 (DB 업데이트): {step3_elapsed:.1f}ms")
|
||||
|
||||
except ChatGPTResponseError as e:
|
||||
elapsed = (time.perf_counter() - task_start) * 1000
|
||||
logger.error(
|
||||
f"[generate_lyric_background] ChatGPT ERROR - task_id: {task_id}, "
|
||||
f"status: {e.status}, code: {e.error_code}, message: {e.error_message} ({elapsed:.1f}ms)"
|
||||
)
|
||||
await _update_lyric_status(task_id, "failed", f"ChatGPT Error: {e.error_message}")
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
elapsed = (time.perf_counter() - task_start) * 1000
|
||||
logger.error(f"[generate_lyric_background] DB ERROR - task_id: {task_id}, error: {e} ({elapsed:.1f}ms)", exc_info=True)
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ class ChatGPTResponseError(Exception):
|
|||
|
||||
class ChatgptService:
|
||||
"""ChatGPT API 서비스 클래스
|
||||
|
||||
GPT 5.0 모델을 사용하여 마케팅 가사 및 분석을 생성합니다.
|
||||
"""
|
||||
|
||||
def __init__(self, timeout: float = None):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
# ChatGPT API 에러 처리 개선 계획서
|
||||
|
||||
## 1. 현황 분석
|
||||
|
||||
### 1.1 `generate_structured_output` 사용처
|
||||
|
||||
| 파일 | 용도 | DB 상태 업데이트 | 응답 상태 변수 |
|
||||
|------|------|-----------------|----------------|
|
||||
| `app/lyric/worker/lyric_task.py` | 가사 생성 | ✅ "failed" 저장 | - (백그라운드) |
|
||||
| `app/home/api/routers/v1/home.py` | 크롤링 마케팅 분석 | ❌ 없음 | ❌ 없음 |
|
||||
|
||||
### 1.2 응답 스키마 상태 변수 현황
|
||||
|
||||
| 스키마 | 위치 | 상태 변수 | 조치 |
|
||||
|--------|------|----------|------|
|
||||
| `CrawlingResponse` | home_schema.py:158 | ❌ 없음 | `status` 추가 |
|
||||
| `GenerateLyricResponse` | lyric.py:72 | ✅ `success: bool` | `False`로 설정 |
|
||||
| `LyricStatusResponse` | lyric.py:105 | ✅ `status: str` | `"failed"` 설정 |
|
||||
| `LyricDetailResponse` | lyric.py:128 | ✅ `status: str` | `"failed"` 설정 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 개선 목표
|
||||
|
||||
1. **DB 상태 업데이트**: 에러 발생 시 DB에 `status = "failed"` 저장
|
||||
2. **클라이언트 응답**: 기존 상태 변수가 있으면 `"failed"` 설정, 없으면 변수 추가 후 설정
|
||||
|
||||
---
|
||||
|
||||
## 3. 상세 작업 계획
|
||||
|
||||
### 3.1 lyric_task.py - `ChatGPTResponseError` 명시적 처리
|
||||
|
||||
**파일**: `app/lyric/worker/lyric_task.py`
|
||||
|
||||
**현재 코드 (Line 138-141)**:
|
||||
```python
|
||||
except Exception as e:
|
||||
elapsed = (time.perf_counter() - task_start) * 1000
|
||||
logger.error(f"[generate_lyric_background] EXCEPTION - task_id: {task_id}, error: {e} ({elapsed:.1f}ms)", exc_info=True)
|
||||
await _update_lyric_status(task_id, "failed", f"Error: {str(e)}")
|
||||
```
|
||||
|
||||
**변경 코드**:
|
||||
```python
|
||||
from app.utils.chatgpt_prompt import ChatgptService, ChatGPTResponseError
|
||||
|
||||
# ... 기존 코드 ...
|
||||
|
||||
except ChatGPTResponseError as e:
|
||||
elapsed = (time.perf_counter() - task_start) * 1000
|
||||
logger.error(
|
||||
f"[generate_lyric_background] ChatGPT ERROR - task_id: {task_id}, "
|
||||
f"status: {e.status}, code: {e.error_code} ({elapsed:.1f}ms)"
|
||||
)
|
||||
await _update_lyric_status(task_id, "failed", f"ChatGPT Error: {e.error_message}")
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
# ... 기존 코드 유지 ...
|
||||
|
||||
except Exception as e:
|
||||
# ... 기존 코드 유지 ...
|
||||
```
|
||||
|
||||
**결과**: DB `lyric.status` = `"failed"`, `lyric.lyric_result` = 에러 메시지
|
||||
|
||||
---
|
||||
|
||||
### 3.2 home.py - CrawlingResponse에 status 추가
|
||||
|
||||
**파일**: `app/home/schemas/home_schema.py`
|
||||
|
||||
**현재 코드 (Line 158-168)**:
|
||||
```python
|
||||
class CrawlingResponse(BaseModel):
|
||||
"""크롤링 응답 스키마"""
|
||||
image_list: Optional[list[str]] = Field(None, description="이미지 URL 목록")
|
||||
image_count: int = Field(..., description="이미지 개수")
|
||||
processed_info: Optional[ProcessedInfo] = Field(None, ...)
|
||||
marketing_analysis: Optional[MarketingAnalysis] = Field(None, ...)
|
||||
```
|
||||
|
||||
**변경 코드**:
|
||||
```python
|
||||
class CrawlingResponse(BaseModel):
|
||||
"""크롤링 응답 스키마"""
|
||||
status: str = Field(
|
||||
default="completed",
|
||||
description="처리 상태 (completed, failed)"
|
||||
)
|
||||
image_list: Optional[list[str]] = Field(None, description="이미지 URL 목록")
|
||||
image_count: int = Field(..., description="이미지 개수")
|
||||
processed_info: Optional[ProcessedInfo] = Field(None, ...)
|
||||
marketing_analysis: Optional[MarketingAnalysis] = Field(None, ...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 home.py - 크롤링 엔드포인트 에러 처리
|
||||
|
||||
**파일**: `app/home/api/routers/v1/home.py`
|
||||
|
||||
**현재 코드 (Line 296-303)**:
|
||||
```python
|
||||
except Exception as e:
|
||||
step3_elapsed = (time.perf_counter() - step3_start) * 1000
|
||||
logger.error(...)
|
||||
marketing_analysis = None
|
||||
```
|
||||
|
||||
**변경 코드**:
|
||||
```python
|
||||
from app.utils.chatgpt_prompt import ChatgptService, ChatGPTResponseError
|
||||
|
||||
# ... 기존 코드 ...
|
||||
|
||||
except ChatGPTResponseError as e:
|
||||
step3_elapsed = (time.perf_counter() - step3_start) * 1000
|
||||
logger.error(
|
||||
f"[crawling] Step 3 FAILED - ChatGPT Error: {e.status}, {e.error_code} ({step3_elapsed:.1f}ms)"
|
||||
)
|
||||
marketing_analysis = None
|
||||
gpt_status = "failed"
|
||||
|
||||
except Exception as e:
|
||||
step3_elapsed = (time.perf_counter() - step3_start) * 1000
|
||||
logger.error(...)
|
||||
marketing_analysis = None
|
||||
gpt_status = "failed"
|
||||
|
||||
# 응답 반환 부분 수정
|
||||
return {
|
||||
"status": gpt_status if 'gpt_status' in locals() else "completed",
|
||||
"image_list": scraper.image_link_list,
|
||||
"image_count": len(scraper.image_link_list) if scraper.image_link_list else 0,
|
||||
"processed_info": processed_info,
|
||||
"marketing_analysis": marketing_analysis,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 파일 변경 요약
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|------|----------|
|
||||
| `app/lyric/worker/lyric_task.py` | `ChatGPTResponseError` import 및 명시적 처리 추가 |
|
||||
| `app/home/schemas/home_schema.py` | `CrawlingResponse`에 `status` 필드 추가 |
|
||||
| `app/home/api/routers/v1/home.py` | `ChatGPTResponseError` 처리, 응답에 `status` 포함 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 변경 후 동작
|
||||
|
||||
### 5.1 lyric_task.py (가사 생성)
|
||||
|
||||
| 상황 | DB status | DB lyric_result |
|
||||
|------|-----------|-----------------|
|
||||
| 성공 | `"completed"` | 생성된 가사 |
|
||||
| ChatGPT 에러 | `"failed"` | `"ChatGPT Error: {message}"` |
|
||||
| DB 에러 | `"failed"` | `"Database Error: {message}"` |
|
||||
| 기타 에러 | `"failed"` | `"Error: {message}"` |
|
||||
|
||||
### 5.2 home.py (크롤링)
|
||||
|
||||
| 상황 | 응답 status | marketing_analysis |
|
||||
|------|------------|-------------------|
|
||||
| 성공 | `"completed"` | 분석 결과 |
|
||||
| ChatGPT 에러 | `"failed"` | `null` |
|
||||
| 기타 에러 | `"failed"` | `null` |
|
||||
|
||||
---
|
||||
|
||||
## 6. 구현 순서
|
||||
|
||||
1. `app/home/schemas/home_schema.py` - `CrawlingResponse`에 `status` 필드 추가
|
||||
2. `app/lyric/worker/lyric_task.py` - `ChatGPTResponseError` 명시적 처리
|
||||
3. `app/home/api/routers/v1/home.py` - 에러 처리 및 응답 `status` 설정
|
||||
Loading…
Reference in New Issue