11from typing import Dict , Any
22import re
33from langchain_core .tools import tool
4-
4+ from core .shared .utils .llm import get_llm
5+ import json
56from core .tariff_prediction .constants import SUPPORTED_COUNTRIES , REMOVE_KEYWORDS , PRICE_PATTERNS , QUANTITY_PATTERNS
67
7- @tool
8- def parse_user_input (user_input : str ) -> Dict [str , Any ]:
9- """자연어 입력을 파싱하여 상품 정보를 추출합니다."""
8+ def parse_user_input_rule (user_input : str ) -> Dict [str , Any ]:
109 parsed = {}
11-
12- # 가격 정보 추출 (숫자 + 원/달러/엔/위안 등)
10+ # 가격 정보 추출 (만원, 천원, 원, 달러, 엔, 위안 등)
11+ price = None
1312 for pattern in PRICE_PATTERNS :
14- matches = re .findall (pattern , user_input )
15- if matches :
16- price_str = matches [0 ].replace (',' , '' )
17- if '만원' in user_input :
18- parsed ['price' ] = float (price_str ) * 10000
19- elif '천원' in user_input :
20- parsed ['price' ] = float (price_str ) * 1000
21- else :
22- parsed ['price' ] = float (price_str )
13+ match = re .search (pattern , user_input )
14+ if match :
15+ price_str = match .group (1 ).replace (',' , '' )
16+ unit = match .group (2 ) if len (match .groups ()) > 1 else ''
17+ try :
18+ price = float (price_str )
19+ if '만' in unit :
20+ price *= 10000
21+ elif '천' in unit :
22+ price *= 1000
23+ parsed ['price' ] = price
24+ break
25+ except Exception :
26+ continue
27+ # 수량 정보 추출 (숫자+개, 한 개, 두 개 등)
28+ quantity = None
29+ for pattern in QUANTITY_PATTERNS + [r'([한두세네]) ?개' ]:
30+ match = re .search (pattern , user_input )
31+ if match :
32+ try :
33+ if match .group (1 ).isdigit ():
34+ quantity = int (match .group (1 ))
35+ else :
36+ h2n = {'한' :1 , '두' :2 , '세' :3 , '네' :4 }
37+ quantity = h2n .get (match .group (1 ), 1 )
38+ parsed ['quantity' ] = quantity
39+ break
40+ except Exception :
41+ continue
42+ # 국가 정보 추출 (미국에서, 일본에서 등 조사 포함)
43+ country = None
44+ for c in SUPPORTED_COUNTRIES .keys ():
45+ if c in user_input :
46+ country = c
47+ parsed ['country' ] = c
2348 break
24-
25- # 수량 정보 추출
26- for pattern in QUANTITY_PATTERNS :
27- matches = re .findall (pattern , user_input )
28- if matches :
29- parsed ['quantity' ] = int (matches [0 ])
49+ elif c + '에서' in user_input :
50+ country = c
51+ parsed ['country' ] = c
3052 break
31-
32- # 국가 정보 추출
33- countries = list (SUPPORTED_COUNTRIES .keys ())
34- for country in countries :
35- if country in user_input :
36- parsed ['country' ] = country
37- break
38-
39- # 상품 묘사 추출 (가격, 수량, 국가 정보를 제외한 나머지 부분)
40- # 먼저 가격, 수량, 국가 관련 키워드를 제거
41- cleaned_input = user_input
42- for pattern in PRICE_PATTERNS + QUANTITY_PATTERNS :
43- cleaned_input = re .sub (pattern , '' , cleaned_input )
44-
45- for country in countries :
46- cleaned_input = cleaned_input .replace (country , '' )
47-
48- # 일반적인 키워드 제거
49- for keyword in REMOVE_KEYWORDS :
50- cleaned_input = cleaned_input .replace (keyword , '' )
51-
52- # 상품 묘사로 사용할 부분 추출
53- cleaned_input = cleaned_input .strip ()
54- if cleaned_input and len (cleaned_input ) > 2 : # 의미있는 길이인 경우만
55- parsed ['product_name' ] = cleaned_input
56-
57- return parsed
53+ # 상품명/묘사 추출
54+ cleaned = user_input
55+ for pattern in PRICE_PATTERNS + QUANTITY_PATTERNS + [r'([한두세네]) ?개' ]:
56+ cleaned = re .sub (pattern , '' , cleaned )
57+ if country :
58+ cleaned = cleaned .replace (country , '' )
59+ cleaned = cleaned .replace (country + '에서' , '' )
60+ for keyword in REMOVE_KEYWORDS + ['샀어요' , '구매' , '예측해줘' , '관세' , '예측' , '해줘' ]:
61+ cleaned = cleaned .replace (keyword , '' )
62+ cleaned = cleaned .strip ()
63+ if cleaned and len (cleaned ) > 1 :
64+ parsed ['product_name' ] = cleaned
65+ return parsed
66+
67+ @tool
68+ def parse_user_input (user_input : str ) -> Dict [str , Any ]:
69+ """자연어 입력을 LLM으로 파싱하여 상품 정보를 추출합니다. 실패 시 rule 기반 파싱을 fallback으로 사용합니다."""
70+ prompt = f"""
71+ 아래는 관세 예측을 위한 사용자 입력입니다. 입력에서 다음 정보를 추출해 JSON으로 반환하세요.
72+ - product_name: 상품명 또는 상품 설명 (예: 노트북, 운동화, 블루투스 이어폰)
73+ - country: 구매 국가 (예: 미국, 일본, 독일 등)
74+ - price: 상품 가격(숫자만, 단위는 원)
75+ - quantity: 수량(숫자, 없으면 1)
76+
77+ 입력: "{ user_input } "
78+
79+ 반환 예시:
80+ {{
81+ "product_name": "노트북",
82+ "country": "미국",
83+ "price": 1500000,
84+ "quantity": 1
85+ }}
86+
87+ 반드시 위와 같은 JSON만 반환하세요.
88+ """
89+ try :
90+ llm = get_llm ()
91+ response = llm .invoke ([{"role" : "user" , "content" : prompt }])
92+ json_str = response .content if hasattr (response , 'content' ) else str (response )
93+ if not isinstance (json_str , str ):
94+ raise ValueError ('LLM 응답이 문자열이 아님' )
95+ json_start = json_str .find ('{' )
96+ json_end = json_str .rfind ('}' ) + 1
97+ parsed = json .loads (json_str [json_start :json_end ])
98+ # 값이 하나라도 있으면 반환
99+ if parsed and (parsed .get ('product_name' ) or parsed .get ('country' ) or parsed .get ('price' )):
100+ return parsed
101+ except Exception :
102+ pass
103+ # 실패 시 rule 기반 파싱
104+ return parse_user_input_rule (user_input )
0 commit comments