77
88def parse_user_input_rule (user_input : str ) -> Dict [str , Any ]:
99 parsed = {}
10+
11+ # 상품명을 먼저 추출 (가장 중요한 정보)
12+ product_name = extract_product_name (user_input )
13+ if product_name :
14+ parsed ['product_name' ] = product_name
15+
1016 # 가격 정보 추출 (만원, 천원, 원, 달러, 엔, 위안 등)
1117 price = None
1218 for pattern in PRICE_PATTERNS :
@@ -24,6 +30,7 @@ def parse_user_input_rule(user_input: str) -> Dict[str, Any]:
2430 break
2531 except Exception :
2632 continue
33+
2734 # 수량 정보 추출 (숫자+개, 한 개, 두 개 등)
2835 quantity = None
2936 for pattern in QUANTITY_PATTERNS + [r'([한두세네]) ?개' ]:
@@ -39,6 +46,7 @@ def parse_user_input_rule(user_input: str) -> Dict[str, Any]:
3946 break
4047 except Exception :
4148 continue
49+
4250 # 국가 정보 추출 (미국에서, 일본에서 등 조사 포함)
4351 country = None
4452 for c in SUPPORTED_COUNTRIES .keys ():
@@ -50,49 +58,93 @@ def parse_user_input_rule(user_input: str) -> Dict[str, Any]:
5058 country = c
5159 parsed ['country' ] = c
5260 break
53- # 상품명/묘사 추출
61+
62+ return parsed
63+
64+ def extract_product_name (user_input : str ) -> str :
65+ """상품명을 추출하는 전용 함수"""
66+ # 간단한 상품명 패턴 (단일 단어 또는 짧은 구문)
67+ simple_patterns = [
68+ r'^([가-힣a-zA-Z0-9]+)$' , # 단일 단어 (커피, 노트북 등)
69+ r'^([가-힣a-zA-Z0-9\s]+)$' , # 단일 단어 + 공백
70+ r'([가-힣a-zA-Z0-9]+)\s*(?:을|를|이|가|의)' , # 조사 앞의 단어
71+ r'(?:이|가|을|를)\s*([가-힣a-zA-Z0-9]+)' , # 조사 뒤의 단어
72+ ]
73+
74+ for pattern in simple_patterns :
75+ match = re .search (pattern , user_input .strip ())
76+ if match :
77+ product = match .group (1 ).strip ()
78+ if product and len (product ) >= 2 : # 최소 2글자 이상
79+ return product
80+
81+ # 기존 방식으로 정제
5482 cleaned = user_input
5583 for pattern in PRICE_PATTERNS + QUANTITY_PATTERNS + [r'([한두세네]) ?개' ]:
5684 cleaned = re .sub (pattern , '' , cleaned )
57- if country :
58- cleaned = cleaned .replace (country , '' )
59- cleaned = cleaned .replace (country + '에서' , '' )
60- for keyword in REMOVE_KEYWORDS + ['샀어요' , '구매' , '예측해줘' , '관세' , '예측' , '해줘' ]:
85+
86+ # 국가명 제거
87+ for c in SUPPORTED_COUNTRIES .keys ():
88+ cleaned = cleaned .replace (c , '' )
89+ cleaned = cleaned .replace (c + '에서' , '' )
90+
91+ # 불필요한 키워드 제거
92+ for keyword in REMOVE_KEYWORDS + ['샀어요' , '구매' , '예측해줘' , '관세' , '예측' , '해줘' , '어떻게' , '알려줘' , '계산' , '해주세요' ]:
6193 cleaned = cleaned .replace (keyword , '' )
94+
6295 cleaned = cleaned .strip ()
63- if cleaned and len (cleaned ) > 1 :
64- parsed ['product_name' ] = cleaned
65- return parsed
96+
97+ # 정제된 결과가 있으면 반환
98+ if cleaned and len (cleaned ) >= 2 :
99+ return cleaned
100+
101+ # 마지막 수단: 입력 전체를 상품명으로 사용 (단, 너무 길지 않은 경우)
102+ if len (user_input .strip ()) <= 20 and len (user_input .strip ()) >= 2 :
103+ return user_input .strip ()
104+
105+ return ""
66106
67107@tool
68108def parse_user_input (user_input : str ) -> Dict [str , Any ]:
69109 """자연어 입력을 LLM으로 파싱하여 상품 정보를 추출합니다. 실패 시 rule 기반 파싱을 fallback으로 사용합니다."""
110+
111+ # 간단한 입력의 경우 rule 기반 파싱을 우선 사용
112+ if len (user_input .strip ()) <= 10 :
113+ rule_result = parse_user_input_rule (user_input )
114+ if rule_result .get ('product_name' ):
115+ return rule_result
116+
70117 prompt = f"""
71118아래는 관세 예측을 위한 사용자 입력입니다. 입력에서 다음 정보를 추출해 JSON으로 반환하세요.
72- - product_name: 상품명 또는 상품 설명 (예: 노트북, 운동화, 블루투스 이어폰 )
119+ - product_name: 상품명 또는 상품 설명 (가장 중요한 정보, 반드시 추출해야 함 )
73120- country: 구매 국가 (예: 미국, 일본, 독일 등)
74121- price: 상품 가격(원화가 아닌 경우 원래 통화 단위 그대로 유지, 숫자만)
75122- price_unit: 가격 단위 (원, 달러, 엔, 위안, 유로 등)
76123- quantity: 수량(숫자, 없으면 1)
77124
78125입력: "{ user_input } "
79126
127+ 주의사항:
128+ 1. product_name은 반드시 추출해야 합니다. 입력이 "커피"라면 product_name은 "커피"여야 합니다.
129+ 2. 입력이 단순한 상품명만 있는 경우에도 product_name을 추출하세요.
130+ 3. 가격이나 국가 정보가 없어도 상품명은 반드시 추출하세요.
131+
80132반환 예시:
81133{{
82- "product_name": "노트북 ",
83- "country": "미국" ,
84- "price": 150 ,
85- "price_unit": "달러" ,
134+ "product_name": "커피 ",
135+ "country": null ,
136+ "price": null ,
137+ "price_unit": null ,
86138 "quantity": 1
87139}}
88140
89141또는
90142
91143{{
92- "product_name": "운동화 ",
93- "country": "독일 ",
94- "price": 80 ,
95- "price_unit": "유로 ",
144+ "product_name": "노트북 ",
145+ "country": "미국 ",
146+ "price": 150 ,
147+ "price_unit": "달러 ",
96148 "quantity": 1
97149}}
98150
@@ -107,10 +159,17 @@ def parse_user_input(user_input: str) -> Dict[str, Any]:
107159 json_start = json_str .find ('{' )
108160 json_end = json_str .rfind ('}' ) + 1
109161 parsed = json .loads (json_str [json_start :json_end ])
110- # 값이 하나라도 있으면 반환
111- if parsed and ( parsed .get ('product_name' ) or parsed . get ( 'country' ) or parsed . get ( 'price' ) ):
162+ # product_name이 있으면 반환 (가장 중요한 정보)
163+ if parsed and parsed .get ('product_name' ):
112164 return parsed
113165 except Exception :
114166 pass
167+
115168 # 실패 시 rule 기반 파싱
116- return parse_user_input_rule (user_input )
169+ rule_result = parse_user_input_rule (user_input )
170+
171+ # rule 기반 파싱에서도 product_name이 없으면 입력 전체를 상품명으로 사용
172+ if not rule_result .get ('product_name' ) and user_input .strip ():
173+ rule_result ['product_name' ] = user_input .strip ()
174+
175+ return rule_result
0 commit comments