fix: 이미지 태그 EASONING_EFFORT = "low" 지정

This commit is contained in:
김성경 2026-09-07 11:31:32 +09:00
parent ac7e4bde9f
commit 9aa4e0a343
2 changed files with 33 additions and 9 deletions

View File

@ -6,6 +6,10 @@ from app.utils.prompts.schemas import SpaceType, Subject, Camera, MotionRecommen
import asyncio
# medium 추론은 출력 비용의 80%를 차지하고, minimal은 A/B 비교(4회)에서 narrative 점수가
# welcome 단계로 편향되고 태그를 과다 선택하는 패턴이 반복돼 low로 고정한다.
IMAGE_TAG_REASONING_EFFORT = "low"
async def autotag_image(image_url : str, industry: str = "") -> list[str]: #tag_list
chatgpt = ChatgptService(model_type="gpt")
image_input_data = {
@ -17,7 +21,7 @@ async def autotag_image(image_url : str, industry: str = "") -> list[str]: #tag_
"motion_recommended" : list(MotionRecommended)
}
image_result = await chatgpt.generate_structured_output(image_autotag_prompt, image_input_data, image_url, False)
image_result = await chatgpt.generate_structured_output(image_autotag_prompt, image_input_data, image_url, True, reasoning_effort=IMAGE_TAG_REASONING_EFFORT)
return image_result
async def autotag_images(image_url_list : list[str], industry: str = "") -> list[dict]: #tag_list
@ -31,7 +35,7 @@ async def autotag_images(image_url_list : list[str], industry: str = "") -> list
"motion_recommended" : list(MotionRecommended)
}for image_url in image_url_list]
image_result_tasks = [chatgpt.generate_structured_output(image_autotag_prompt, image_input_data, image_input_data['img_url'], False, silent = True) for image_input_data in image_input_data_list]
image_result_tasks = [chatgpt.generate_structured_output(image_autotag_prompt, image_input_data, image_input_data['img_url'], True, silent = True, reasoning_effort=IMAGE_TAG_REASONING_EFFORT) for image_input_data in image_input_data_list]
image_result_list: list[BaseModel | BaseException] = await asyncio.gather(*image_result_tasks, return_exceptions=True)
MAX_RETRY = 2
for _ in range(MAX_RETRY):
@ -40,7 +44,7 @@ async def autotag_images(image_url_list : list[str], industry: str = "") -> list
if not failed_idx:
break
retried = await asyncio.gather(
*[chatgpt.generate_structured_output(image_autotag_prompt, image_input_data_list[i], image_input_data_list[i]['img_url'], False, silent=True) for i in failed_idx],
*[chatgpt.generate_structured_output(image_autotag_prompt, image_input_data_list[i], image_input_data_list[i]['img_url'], True, silent=True, reasoning_effort=IMAGE_TAG_REASONING_EFFORT) for i in failed_idx],
return_exceptions=True
)
for i, result in zip(failed_idx, retried):

View File

@ -46,7 +46,20 @@ class ChatgptService:
)
case _:
raise NotImplementedError(f"Unknown Provider : {model_type}")
def _log_usage(self, response, model: str, output_format: type[BaseModel]) -> None:
usage = getattr(response, "usage", None)
if usage is None:
return
# 토큰 소모량 로깅 (필요 시 주석 해제)
# cached = getattr(getattr(usage, "prompt_tokens_details", None), "cached_tokens", None) or 0
# reasoning = getattr(getattr(usage, "completion_tokens_details", None), "reasoning_tokens", None) or 0
# logger.info(
# f"[ChatgptService({self.model_type})] usage model={model} output={output_format.__name__} "
# f"prompt={usage.prompt_tokens} cached={cached} "
# f"completion={usage.completion_tokens} reasoning={reasoning} total={usage.total_tokens}"
# )
async def _call_pydantic_output(
self,
prompt : str,
@ -113,9 +126,10 @@ class ChatgptService:
self,
prompt : str,
output_format : BaseModel, #입력 output_format의 경우 Pydantic BaseModel Class를 상속한 Class 자체임에 유의할 것
model : str,
model : str,
img_url : str,
image_detail_high : bool) -> BaseModel:
image_detail_high : bool,
reasoning_effort : Optional[str] = None) -> BaseModel:
content = []
if img_url:
content.append({
@ -129,13 +143,16 @@ class ChatgptService:
"type": "text",
"text": prompt
})
# gpt-5.4 계열/Gemini 호환 엔드포인트는 허용 값이 다르거나 파라미터를 거부하므로 지정된 경우에만 전달
extra_kwargs = {"reasoning_effort": reasoning_effort} if reasoning_effort else {}
last_error = None
for attempt in range(self.max_retries + 1):
try:
response = await self.client.beta.chat.completions.parse(
model=model,
messages=[{"role": "user", "content": content}],
response_format=output_format
response_format=output_format,
**extra_kwargs,
)
except (ValidationError, json.JSONDecodeError) as e:
# 모델이 스키마에 맞지 않는 JSON을 반환한 경우 (예: trailing characters).
@ -148,6 +165,7 @@ class ChatgptService:
if attempt < self.max_retries:
logger.info(f"[ChatgptService({self.model_type})] Retrying request...")
continue
self._log_usage(response, model, output_format)
# Response 디버그 로깅
# logger.debug(f"[ChatgptService({self.model_type})] attempt: {attempt}")
# logger.debug(f"[ChatgptService({self.model_type})] Response ID: {response.id}")
@ -225,6 +243,7 @@ class ChatgptService:
continue
raise last_error
self._log_usage(response, model, output_format)
choice = response.choices[0]
if choice.finish_reason == "stop":
return choice.message.parsed
@ -242,7 +261,8 @@ class ChatgptService:
input_data : dict,
img_url : Optional[str] = None,
img_detail_high : bool = False,
silent : bool = True
silent : bool = True,
reasoning_effort : Optional[str] = None,
) -> BaseModel:
prompt_text = prompt.build_prompt(input_data, silent)
@ -253,5 +273,5 @@ class ChatgptService:
# GPT API 호출
#parsed = await self._call_structured_output_with_response_gpt_api(prompt_text, prompt.prompt_output, prompt.prompt_model)
# parsed = await self._call_pydantic_output(prompt_text, prompt.prompt_output_class, prompt.prompt_model, img_url, img_detail_high)
parsed = await self._call_pydantic_output_chat_completion(prompt_text, prompt.prompt_output_class, prompt.prompt_model, img_url, img_detail_high)
parsed = await self._call_pydantic_output_chat_completion(prompt_text, prompt.prompt_output_class, prompt.prompt_model, img_url, img_detail_high, reasoning_effort)
return parsed