AutoPercenty3/titleManager/gpt_client.py

362 lines
19 KiB
Python

import logging
from openai import OpenAI
import json
import re
import logging
import requests
# class GPTClient:
# def __init__(self, logger, api_key: str = "", model="gpt-4o-mini", temperature=0.0):
# self.logger = logger
# self.client = OpenAI(api_key=api_key)
# self.model = model
# self.temperature = temperature
# def ask(self, prompt: str) -> dict:
# """프롬프트를 이용하여 GPT 모델로부터 응답을 받습니다. 항상 JSON 형식으로 반환."""
# try:
# url = "http://146.56.101.199:8900/chatgpt"
# headers = {"Content-Type": "application/json"}
# payload = {"query": prompt}
# response = requests.post(url, json=payload, headers=headers)
# response.raise_for_status() # HTTP 에러 발생 시 예외 발생
# # 응답 헤더의 Content-Type에 'application/json'이 포함되어 있으면 json() 메서드 사용
# if 'application/json' in response.headers.get('Content-Type', ''):
# return response.json()
# else:
# # 그렇지 않으면 response.text를 이용해 직접 파싱 시도
# return json.loads(response.text)
# except Exception as e:
# self.logger.log(f'GPT 통신 오류: {e}', level=logging.ERROR, exc_info=True)
# return {}
class GPTClient:
def __init__(self, logger, api_key: str = "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", model="gpt-4o-mini", temperature=0.0):
self.logger = logger
self.client = OpenAI(api_key=api_key)
self.model = model
self.temperature = temperature
def ask(self, prompt: str) -> dict:
"""프롬프트를 이용하여 GPT 모델로부터 응답을 받습니다. 항상 JSON 형식으로 반환."""
try:
response = self.client.chat.completions.create(
model=self.model,
temperature=self.temperature,
messages=[{"role": "user", "content": prompt}]
)
# GPT 응답 내용 가져오기
content = response.choices[0].message.content.strip()
self.logger.log(f'GPT 응답: {content}', level=logging.DEBUG)
# 불필요한 포맷팅 제거 (```json```)
cleaned_content = re.sub(r"^```json|```$", "", content).strip()
# JSON 변환 시도
return json.loads(cleaned_content)
except json.JSONDecodeError as e:
self.logger.log(f'JSON 디코딩 실패: {e}. 원본 응답: {content}', level=logging.ERROR, exc_info=True)
return {}
except Exception as e:
self.logger.log(f'GPT 통신 오류: {e}', level=logging.ERROR, exc_info=True)
return {}
def is_related_product(self, original_name: str, keyword_name: str) -> bool:
"""상품 연관 여부 판단"""
prompt = (
f"Are the products '{original_name}' and '{keyword_name}' from the same category? "
"Respond strictly in JSON format: {\"related\": true} or {\"related\": false}."
)
result = self.ask(prompt)
return result.get("related", False)
def generate_product_name_next(self, words: list, original_name: str, top_titles: list, category, title_generator_prompt, max_length=40) -> str:
"""
주어진 단어와 원본 상품명을 참고하여 업계 용어와 고유 단어를 포함해 최종 상품명 생성.
top_titles의 형식을 참고해 작성.
"""
# 특수문자 제거 및 키워드 정리
clean_words = [re.sub(r'[^\w\s]', '', word) for word in words]
# 1. 상품명 생성 프롬프트 생성
# product_prompt = (
# "너는 상품명 편집 전문가야. 주어진 중국 원본 상품명을 한국에서 잘 팔릴 수 있는 상품명으로 수정해야 해.\n\n"
# "### 작업 규칙:\n"
# "1. 키워드 배치는 자연스럽고, 고객이 검색할 때 쉽게 매칭되도록 작성.\n"
# "2. 아래의 이미 팔린상품명은 고객에게 선택된 신뢰도 높은 상품명들이야.\n"
# "3. 단어 리스트는 이미 팔린 상품명에서 금지단어와, 중복단어를 제거한 리스트야.\n"
# "4. 상품의 카테고리는 해당상품이 속해있는 카테고리로, 상품명 작성시 해당 카테고리에서 사용하는 전문용어들도 포함될수 있어.\n"
# "5. 원본 상품명과 팔린상품명을 적절히 조합하여 원본상품의 특징은 약간 살리면서 팔린상품명을 활용해 새로운 상품명을 만들어줘.\n"
# "6. 상품명의 길이제한은 한글기준 공백포함 40자 ~ 48자야.\n"
# )
option_prompt = (
"### 입력 데이터:\n"
f"- 원본 상품명: {original_name}\n"
f"- 팔린 상품명 리스트: {top_titles}\n"
f"- 팔린 상품명에서 추출한 단어 리스트: {clean_words}\n"
f"- 상품의 카테고리: {category}\n"
# f"- 상품명 길이제한: 공백 포함 {max_length}자 ~ {max_length+(max_length*0.3)}자 이내\n\n"
"### 출력 형식:\n"
"JSON 형식으로 결과를 반환해줘:\n"
"{ \"product_name\": \"수정된 상품명\" }\n"
)
product_prompt = title_generator_prompt + option_prompt
# GPT에게 상품명 생성 요청
product_response = self.ask(product_prompt)
try:
if not isinstance(product_response, dict):
self.logger.log("[GPTClient] product_response가 dict가 아닙니다. 빈 결과 반환.", level=logging.ERROR)
return ""
return product_response.get("product_name", "").strip()
except Exception as e:
self.logger.log(f'Error parsing product name from GPT response: {e}', level=logging.ERROR, exc_info=True)
return ""
def generate_product_name_next_ori(self, words: list, original_name: str, top_titles: list, category, max_length=40) -> str:
"""
주어진 단어와 원본 상품명을 참고하여 업계 용어와 고유 단어를 포함해 최종 상품명 생성.
top_titles의 형식을 참고해 작성.
"""
# 특수문자 제거 및 키워드 정리
clean_words = [re.sub(r'[^\w\s]', '', word) for word in words]
# # 1. 키워드 분류를 위한 프롬프트 생성
# classify_prompt = (
# "너는 키워드 분류 전문가야. 주어진 키워드와 참고 상품명을 분석하여 키워드를 대형, 중형, 소형으로 분류해줘.\n\n"
# "### 분류 기준:\n"
# "1. **대형 키워드**: 상품의 범주나 카테고리를 나타내며, 범용적이고 일반적인 키워드.\n"
# " - 예: '가방', '냉풍기', '여자구두', '책상'\n"
# "2. **중형 키워드**: 대형 키워드에 추가적인 속성이 붙은 키워드로, 범위가 좁아짐.\n"
# " - 예: '스웨이드 가방', '산업용냉풍기', '여자앵클부츠', '접이식책상'\n"
# "3. **소형 키워드**: 상품의 구체적이고 특징적인 키워드로, 가장 세부적인 정보.\n"
# " - 예: '스웨이드 크로스 가방', '산업용코끼리냉풍기', '키작은여자앵클부츠', '접이식 강화유리 책상'\n\n"
# "### 입력 데이터:\n"
# f"- 키워드 리스트: {clean_words}\n"
# f"- 팔린 상품명 리스트: {top_titles}\n\n"
# "### 출력 형식:\n"
# "JSON 형식으로 반환해줘:\n"
# "{\n"
# " \"large_keywords\": [\"대형 키워드 리스트\"],\n"
# " \"medium_keywords\": [\"중형 키워드 리스트\"],\n"
# " \"small_keywords\": [\"소형 키워드 리스트\"]\n"
# "}\n"
# )
# # GPT에게 키워드 분류 요청
# classify_response = self.ask(classify_prompt)
# try:
# large_keywords = classify_response.get("large_keywords", [])
# medium_keywords = classify_response.get("medium_keywords", [])
# small_keywords = classify_response.get("small_keywords", [])
# except Exception as e:
# self.logger.log(f'Error parsing keyword classification response: {e}', level=logging.ERROR, exc_info=True)
# return ""
# 2. 상품명 생성 프롬프트 생성
product_prompt = (
"너는 상품명 편집 전문가야. 주어진 중국 원본 상품명을 한국에서 잘 팔릴 수 있는 상품명으로 수정해야 해.\n\n"
"### 작업 규칙:\n"
# "1. 상품명은 소형, 중형, 대형 키워드를 조합하여 작성해야 해:\n"
# " - 소형 키워드: 전체 키워드의 50%를 차지하며, 가장 구체적이고 특징적인 키워드.\n"
# " - 중형 키워드: 전체 키워드의 30%를 차지하며, 소형 키워드보다 범위가 넓지만 대형 키워드보다는 구체적임.\n"
# " - 대형 키워드: 전체 키워드의 20%를 차지하며, 상품의 용도(사용 목적)와 재질(소재)를 포함.\n"
# "2. 상품명에는 반드시 고유 상품 코드가 포함되어야 해.\n"
"1. 키워드 배치는 자연스럽고, 고객이 검색할 때 쉽게 매칭되도록 작성.\n"
"2. 아래의 이미 팔린상품명은 고객에게 선택된 신뢰도 높은 상품명들이야.\n"
"3. 단어 리스트는 이미 팔린 상품명에서 금지단어와, 중복단어를 제거한 리스트야.\n"
"4. 상품의 카테고리는 해당상품이 속해있는 카테고리로, 상품명 작성시 해당 카테고리에서 사용하는 전문용어들도 포함될수 있어.\n"
"5. 원본 상품명과 팔린상품명을 적절히 조합하여 원본상품의 특징은 약간 살리면서 팔린상품명을 활용해 새로운 상품명을 만들어줘.\n"
"### 입력 데이터:\n"
f"- 원본 상품명: {original_name}\n"
f"- 팔린 상품명 리스트: {top_titles}\n"
f"- 팔린 상품명에서 추출한 단어 리스트: {clean_words}\n"
f"- 상품의 카테고리: {category}\n"
# f"- 대형 키워드: {large_keywords}\n"
# f"- 중형 키워드: {medium_keywords}\n"
# f"- 소형 키워드: {small_keywords}\n\n"
f"- 상품명 길이제한: 공백 포함 {max_length}자 ~ {max_length+(max_length*0.3)}자 이내\n\n"
"### 출력 형식:\n"
"JSON 형식으로 결과를 반환해줘:\n"
"{ \"product_name\": \"수정된 상품명\" }\n"
)
# GPT에게 상품명 생성 요청
product_response = self.ask(product_prompt)
try:
if not isinstance(product_response, dict):
self.logger.log("[GPTClient] product_response가 dict가 아닙니다. 빈 결과 반환.", level=logging.ERROR)
return ""
return product_response.get("product_name", "").strip()
except Exception as e:
self.logger.log(f'Error parsing product name from GPT response: {e}', level=logging.ERROR, exc_info=True)
return ""
def extract_proper_nouns(self, words: list, category: str) -> dict:
"""
주어진 단어 리스트를 특정 카테고리에서 상표권 등록 가능성과 불가능성으로 필터링합니다.
Args:
words (list): 검증할 단어 리스트.
category (str): 상품 카테고리 (예: "가전제품", "의류", "소프트웨어").
Returns:
dict: "eligible""ineligible"로 분류된 결과.
"""
prompt = (
f"Analyze the following list of words in the context of the '{category}' category: {words}. "
"Determine if each word could potentially be eligible for trademark registration in this category based on these criteria:\n\n"
"1. Words suitable for trademark registration:\n"
" - Unique identifiers or names that are not commonly used.\n"
" - Creative or distinctive words that are not descriptive of product features or functionality.\n\n"
"2. Words not suitable for trademark registration:\n"
" - Generic or common terms that describe product features (e.g., 'Portable').\n"
" - Industry-standard terms (e.g., 'AirCooler').\n"
" - Common adjectives or technical terms (e.g., 'EcoFriendly').\n"
" - Words that are overly broad or generic.\n\n"
"Respond strictly in JSON format with two categories: "
"{'eligible': ['word1', 'word2'], 'ineligible': ['word3', 'word4']}."
)
response = self.ask(prompt)
if isinstance(response, dict):
return response
else:
self.logger.log(f'응답이 JSON 형식이 아닙니다: {response}', level=logging.ERROR)
return {"eligible": [], "ineligible": []}
def generate_promotion_text(self, recognized_texts: list, product_name: str, max_length=100) -> str:
"""
Generate a promotional message based on recognized texts from the product image.
:param recognized_texts: List of recognized texts from the product image.
:param product_name: Name of the product.
:param max_length: Maximum length of the promotional message.
:return: Generated promotional message as a string.
"""
# Create the prompt for GPT
prompt = (
f"The following texts were recognized from the product image: {recognized_texts}.\n\n"
f"The product name is '{product_name}'.\n\n"
"### Task:\n"
"1. Create a compelling and concise promotional message in Korean based on the recognized texts and the product name.\n"
f"2. The message must not exceed {max_length} characters, including spaces.\n"
"3. Highlight the product's unique selling points to attract customers.\n\n"
"### Output:\n"
"Provide the promotional message as plain text without any additional formatting."
)
# Send the prompt to GPT
self.logger.log(f"Sending prompt to GPT for promotional message generation: {prompt}", level=logging.DEBUG)
response = self.ask(prompt)
# Process the response
if isinstance(response, str):
result = response.strip()
self.logger.log(f"Generated promotional message: {result}", level=logging.DEBUG)
return result
else:
self.logger.log("GPT response is not a valid string.", level=logging.ERROR)
raise ValueError("GPT response is not a valid string.")
def translate_options(self, original_data, product_name):
"""
주어진 옵션 데이터를 GPT 모델을 통해 번역하는 메서드.
:param original_data: 원본 옵션 데이터 (dict 형태).
:param product_name: 상품명 (str 형태).
:return: 번역된 옵션명 (파이썬의 dict 형태).
"""
self.logger.log(f"옵션 데이터를 번역 중: {original_data}", level=logging.DEBUG)
# 데이터 정리
cleaned_data = {key: self.clean_special_chars(value) for key, value in original_data.items()}
self.logger.log(f"정리된 옵션 데이터: {cleaned_data}", level=logging.DEBUG)
# GPT 프롬프트 생성
prompt = (
f"다음은 옵션의 특징만 간단하게 남긴 후 번역해야 할 원래 옵션 이름들입니다: {json.dumps(cleaned_data, ensure_ascii=False)}.\n\n"
f"원래 제품 이름은 '{product_name}'이며, 번역 시 이를 참조하여 제품의 특징을 선별해야 합니다.\n\n"
"### 번역 규칙:\n"
"1. 각 옵션은 고유한 특징을 유지하면서 공백포함 25자 이내로 간단하게 만드세요.\n"
"2. 각 옵션의 고유한 특징은 크기, 무게, 재질, 사이즈, 용량, 전압, 전류 또는 제품 코드를 말한다.\n"
"3. 번역된 모든 옵션 이름은 한글로 작성해야 하며, 중국어가 포함되지 않아야 합니다.\n"
"4. 번역 후 모든옵션에 공통된 단어들은 제거해야한다.\n"
"5. 번역 후 옵션 이름이 중복될 경우, 원래 옵션 이름에서 추가적인 고유 특징을 추출하여 구별되도록 하세요.\n"
"6. 고객 서비스 문의, 가격 문의, 견적 또는 예약을 요청하는 옵션 이름은 제거하세요.\n"
"7. 의미가 일치하는 경우 긴 단어를 짧은 단어로 대체하세요 (예: 'Display Panel''Screen'으로 대체).\n"
"8. 번역된 옵션 이름은 다음과 같은 JSON 형식으로 반환하세요:\n\n"
"{\n"
" \"trans_option_1\": \"번역된 옵션 이름 1\",\n"
" \"trans_option_2\": \"번역된 옵션 이름 2\",\n"
" \"trans_option_3\": \"번역된 옵션 이름 3\",\n"
" \"trans_option_4\": \"번역된 옵션 이름 4\"\n"
"}"
)
# GPT 모델 호출
self.logger.log("GPT 모델에 프롬프트를 전달하여 응답을 기다리는 중...", level=logging.DEBUG)
gpt_response = self.ask(prompt)
self.logger.log(f"GPT 응답: {gpt_response}", level=logging.DEBUG)
# 응답 데이터에서 JSON 형식 추출
if isinstance(gpt_response, dict):
self.logger.log(f"번역된 데이터: {gpt_response}", level=logging.DEBUG)
return gpt_response
else:
self.logger.log("GPT 응답이 JSON 형식이 아닙니다.", level=logging.ERROR)
raise ValueError("GPT 응답이 JSON 형식이 아닙니다.")
def clean_special_chars(self, text):
"""
텍스트에서 허용되지 않는 특수 문자를 제거하고,
필요한 특수 문자를 대체하는 메서드.
:param text: 입력 텍스트.
:return: 정리된 텍스트.
"""
self.allowed_chars = "!$~()._-=+/"
self.replacements = {
"*" : "X",
"" : "(",
"" : ")",
"[" : "(",
"]" : ")",
"," : "."
}
self.logger.log(f"텍스트에서 특수 문자를 정리 중: {text}", level=logging.DEBUG)
cleaned_text = []
for char in text:
if char in self.replacements:
cleaned_text.append(self.replacements[char]) # 대체 문자 추가
self.logger.log(f"문자 '{char}'를 대체 문자로 변경: {self.replacements[char]}", level=logging.DEBUG)
elif char not in self.allowed_chars and not char.isalnum() and not char.isspace():
# self.logger.log(f"허용되지 않은 문자 제거: {char}", level=logging.DEBUG)
continue # 특수 문자 제거
else:
cleaned_text.append(char) # 허용된 문자 추가
# self.logger.log(f"허용된 문자 추가: {char}", level=logging.DEBUG)
cleaned_text_str = ''.join(cleaned_text)
self.logger.log(f"정리된 텍스트: {cleaned_text_str}", level=logging.DEBUG)
return cleaned_text_str