@@ -51,7 +51,9 @@ def __init__(self):
5151 'hs10_code' : None ,
5252 'current_step' : 'scenario_selection' ,
5353 'session_active' : False ,
54- 'responses' : []
54+ 'responses' : [],
55+ 'predicted_scenario' : None , # 예측된 시나리오 저장
56+ 'last_user_input' : None # 마지막 사용자 입력 저장
5557 }
5658
5759 # 환율 지원 국가 목록
@@ -72,7 +74,9 @@ def reset_session(self):
7274 'hs10_code' : None ,
7375 'current_step' : 'scenario_selection' ,
7476 'session_active' : False ,
75- 'responses' : []
77+ 'responses' : [],
78+ 'predicted_scenario' : None ,
79+ 'last_user_input' : None
7680 }
7781
7882 def is_supported_country (self , country : str ) -> bool :
@@ -177,6 +181,7 @@ def handle_scenario_selection(self, user_input: str) -> str:
177181 response = (
178182 "구매하신 상품 정보를 입력해 주세요!\n \n "
179183 "💡 **상품 묘사의 정확도가 높을수록 정확한 관세 예측이 가능합니다!**\n \n "
184+ "💡 **가격은 배송비를 제외하고 입력해 주세요!**\n \n "
180185 "예시:\n "
181186 "• \" 아랫창은 고무로 되어있고 하얀색 운동화를 80000원에 독일에서 샀어요\" \n "
182187 "• \" 인텔 i7 노트북을 150만원에 미국에서 구매했어요\" \n "
@@ -194,6 +199,7 @@ def handle_scenario_selection(self, user_input: str) -> str:
194199 response = (
195200 "구매하신 상품 정보를 입력해 주세요!\n \n "
196201 "💡 **상품 묘사의 정확도가 높을수록 정확한 관세 예측이 가능합니다!**\n \n "
202+ "💡 **가격은 배송비를 제외하고 입력해 주세요!**\n \n "
197203 "예시:\n "
198204 "• \" 아랫창은 고무로 되어있고 하얀색 운동화를 80000원에 독일에서 샀어요\" \n "
199205 "• \" 인텔 i7 노트북을 150만원에 미국에서 구매했어요\" \n "
@@ -213,33 +219,35 @@ def handle_scenario_selection(self, user_input: str) -> str:
213219 self .state ['responses' ].append (response )
214220 return response
215221
222+ def josa_으로 (self , word : str ) -> str :
223+ if not word :
224+ return ""
225+ last_char = word [- 1 ]
226+ if (ord (last_char ) - 44032 ) % 28 == 0 :
227+ return word + "로"
228+ else :
229+ return word + "으로"
230+
216231 def handle_input_collection (self , user_input : str ) -> str :
217- # 컨텍스트에서 추가 정보 추출 시도
232+ # 최초 진입: 시나리오 예측
233+ if not self .state .get ('scenario' ):
234+ predicted = self .detect_scenario_from_input (user_input )
235+ if predicted :
236+ self .state ['scenario' ] = predicted
218237 enhanced_input = user_input
219-
220- # 이전 대화에서 상품 정보가 누락된 경우 컨텍스트에서 찾기
221238 if "이전 대화:" in user_input :
222239 context_part = user_input .split ("현재 질문:" )[0 ].replace ("이전 대화:" , "" ).strip ()
223240 current_part = user_input .split ("현재 질문:" )[1 ].strip () if "현재 질문:" in user_input else user_input
224-
225- # 컨텍스트에서 상품 정보 추출 시도
226241 context_info = extract_info_from_context (context_part )
227242 if context_info :
228- # 현재 입력에 누락된 정보를 컨텍스트에서 보완
229243 enhanced_input = merge_context_with_current (context_info , current_part )
230-
231244 parsed = self .parse_user_input (enhanced_input )
232-
233- # 상품명이 없으면 입력 전체를 상품명으로 사용
234245 if 'product_name' not in parsed or not parsed ['product_name' ]:
235- # 입력에서 불필요한 키워드 제거 후 상품명으로 사용
236246 cleaned_input = user_input .strip ()
237247 for keyword in ['관세' , '예측' , '계산' , '해줘' , '알려줘' , '어떻게' , '해주세요' ]:
238248 cleaned_input = cleaned_input .replace (keyword , '' ).strip ()
239249 if cleaned_input :
240250 parsed ['product_name' ] = cleaned_input
241-
242- # 필수 정보 확인
243251 missing_info = []
244252 if 'product_name' not in parsed or not parsed ['product_name' ]:
245253 missing_info .append ("상품명" )
@@ -248,7 +256,6 @@ def handle_input_collection(self, user_input: str) -> str:
248256 if 'price' not in parsed or not parsed ['price' ]:
249257 missing_info .append ("상품 가격" )
250258 if missing_info :
251- # 이미 입력된 정보는 보여주고, 누락된 정보만 안내
252259 info_lines = []
253260 if 'product_name' in parsed and parsed ['product_name' ]:
254261 info_lines .append (f"상품명: { parsed ['product_name' ]} " )
@@ -272,11 +279,8 @@ def handle_input_collection(self, user_input: str) -> str:
272279 )
273280 self .state ['responses' ].append (response )
274281 return response
275- # 환율 변환 처리
276282 price = parsed ['price' ]
277283 price_unit = parsed .get ('price_unit' , '원' )
278-
279- # 원화가 아닌 경우 환율 변환
280284 if price_unit != '원' :
281285 try :
282286 from core .tariff_prediction .tools .get_exchange_rate_info import get_exchange_rate_api
@@ -285,23 +289,16 @@ def handle_input_collection(self, user_input: str) -> str:
285289 price = price * exchange_rate
286290 price_unit = '원'
287291 else :
288- # 환율 조회 실패 시 기본 환율 사용
289292 if price_unit in DEFAULT_EXCHANGE_RATES :
290293 price = price * DEFAULT_EXCHANGE_RATES [price_unit ]
291294 price_unit = '원'
292- except Exception as e :
293- print (f"[DEBUG] 환율 변환 오류: { e } " )
294- # 오류 시 기본 환율 사용
295+ except Exception :
295296 if price_unit in DEFAULT_EXCHANGE_RATES :
296297 price = price * DEFAULT_EXCHANGE_RATES [price_unit ]
297298 price_unit = '원'
298-
299- # 상태 업데이트
300299 self .state .update (parsed )
301300 self .state ['price' ] = price
302301 self .state ['price_unit' ] = price_unit
303-
304- # step_api.py 활용
305302 req = TariffPredictionRequest (
306303 step = "input" ,
307304 product_description = parsed ['product_name' ],
@@ -318,15 +315,12 @@ def handle_input_collection(self, user_input: str) -> str:
318315 self .state ['hs6_candidates' ] = resp .hs6_candidates
319316 self .state ['current_step' ] = 'hs6_selection'
320317 scenario_str = self .state .get ('scenario' , '' )
321- scenario_guide = f"{ scenario_str } 로 예상하고 안내를 도와드릴게요.\n \n " if scenario_str else ""
322-
323- # 가격 표시 (원화 변환된 경우)
318+ scenario_guide = f"{ self .josa_으로 (scenario_str )} 예상하고 안내를 도와드릴게요.\n \n " if scenario_str else ""
324319 price_display = f"{ price :,.0f} 원"
325320 if price_unit != '원' and parsed .get ('price_unit' ) != '원' :
326321 original_price = parsed .get ('price' , price )
327322 original_unit = parsed .get ('price_unit' , price_unit )
328323 price_display = f"{ original_price } { original_unit } (약 { price :,.0f} 원)"
329-
330324 response = scenario_guide + f"상품묘사: { parsed ['product_name' ]} \n 국가: { parsed ['country' ]} \n 가격: { price_display } \n 수량: { parsed .get ('quantity' , 1 )} 개\n \n HS 코드 예측 모델로부터 HS6 코드 후보를 찾았습니다. 번호를 선택해 주세요:\n " + '\n ' .join ([
331325 f"{ i + 1 } . { c ['description' ]} (신뢰도: { c ['confidence' ]:.1%} )" for i , c in enumerate (resp .hs6_candidates or [])
332326 ]) + f"\n \n 💡 **위 후보 중 하나를 선택해 주세요.**\n 예시: \" 1번\" , \" 2번\" , \" 3번\" 등"
@@ -415,8 +409,7 @@ def handle_hs6_selection(self, user_input: str) -> str:
415409 self .state ['responses' ].append (response )
416410 return response
417411
418- except Exception as e :
419- print (f"[DEBUG] handle_hs6_selection intent detection error: { e } " )
412+ except Exception :
420413 # 예외 발생 시 안내 메시지로 graceful 처리
421414 response = f"입력 처리 중 오류가 발생했습니다. 숫자를 입력하거나, 재예측을 원하시면 '다시', '재예측' 등으로 입력해 주세요."
422415 self .state ['responses' ].append (response )
@@ -487,68 +480,41 @@ def handle_hs10_selection(self, user_input: str) -> str:
487480
488481
489482 def _perform_hs6_reprediction (self , user_input : str ) -> str :
490- """HS6 코드 재예측을 수행합니다."""
491483 from core .tariff_prediction .tools .parse_hs_results import parse_hs6_result
492484 from core .shared .utils .llm import get_llm
493-
494485 product_name = self .state .get ('product_name' )
495486 if not product_name or not isinstance (product_name , str ) or not product_name .strip ():
496487 response = "상품명을 알 수 없어 HS 코드 예측을 다시 시도할 수 없습니다. 처음부터 다시 입력해 주세요."
497488 self .state ['responses' ].append (response )
498489 return response
499-
500490 try :
501- # 재예측을 위한 명확한 프롬프트
502- reprediction_prompt = f"""아래 상품명과 사용자의 추가 의견을 참고하여 HS 코드 후보를 예측해주세요.
503-
504- 상품명: { product_name }
505- 사용자 추가 의견: { user_input }
506-
507- 다음 형식으로 HS 코드 후보 3개 이내를 반환하세요:
508- 1. [6자리 HS코드] (확률: [확률]%)
509- 2. [6자리 HS코드] (확률: [확률]%)
510- 3. [6자리 HS코드] (확률: [확률]%)
511-
512- 예시:
513- 1. 851770 (확률: 85.5%)
514- 2. 851712 (확률: 12.3%)
515- 3. 851713 (확률: 2.2%)"""
516-
491+ reprediction_prompt = f"""아래 상품명과 사용자의 추가 의견을 참고하여 HS 코드 후보를 예측해주세요.\n \n 상품명: { product_name } \n 사용자 추가 의견: { user_input } \n \n 다음 형식으로 HS 코드 후보 3개 이내를 반환하세요:\n 1. [6자리 HS코드] (확률: [확률]%)\n 2. [6자리 HS코드] (확률: [확률]%)\n 3. [6자리 HS코드] (확률: [확률]%)\n \n 예시:\n 1. 851770 (확률: 85.5%)\n 2. 851712 (확률: 12.3%)\n 3. 851713 (확률: 2.2%)"""
517492 llm = get_llm ()
518493 hs6_response = llm .invoke ([{"role" : "user" , "content" : reprediction_prompt }])
519494 hs6_result = extract_llm_response (hs6_response )
520-
521- # LLM 응답이 비어있거나 잘못된 경우 처리
522495 if not hs6_result or len (hs6_result .strip ()) < 10 :
523496 response = "HS 코드 예측에 실패했습니다. 상품명을 더 구체적으로 입력해 주세요."
524497 self .state ['responses' ].append (response )
525498 return response
526-
527- # parse_hs6_result 함수 호출 시 예외 처리
528499 try :
529500 hs6_candidates = parse_hs6_result (hs6_result )
530501 except Exception as parse_error :
531502 print (f"[DEBUG] parse_hs6_result error: { parse_error } " )
532503 response = "HS 코드 예측 결과를 처리하는 중 오류가 발생했습니다. 다시 시도해 주세요."
533504 self .state ['responses' ].append (response )
534505 return response
535-
536506 if not hs6_candidates :
537507 response = "HS 코드 예측에 다시 실패했습니다. 상품명을 더 구체적으로 입력해 주세요."
538508 self .state ['responses' ].append (response )
539509 return response
540-
541510 self .state ['hs6_candidates' ] = hs6_candidates
542511 scenario_str = self .state .get ('scenario' , '' )
543- scenario_guide = f"{ scenario_str } 로 예상하고 안내를 도와드릴게요.\n \n " if scenario_str else ""
544-
512+ scenario_guide = f"{ self .josa_으로 (scenario_str )} 예상하고 안내를 도와드릴게요.\n \n " if scenario_str else ""
545513 response = scenario_guide + f"상품묘사: { product_name } \n 국가: { self .state .get ('country' ,'' )} \n 가격: { self .state .get ('price' ,0 ):,} 원\n 수량: { self .state .get ('quantity' ,1 )} 개\n \n HS 코드 재예측 결과입니다. 번호를 선택해 주세요:\n " + '\n ' .join ([
546514 f"{ i + 1 } . { c ['description' ]} (신뢰도: { c ['confidence' ]:.1%} )" for i , c in enumerate (hs6_candidates )
547515 ]) + f"\n \n 💡 **위 후보 중 하나를 선택해 주세요.**\n 예시: \" 1번\" , \" 2번\" , \" 3번\" 등"
548-
549516 self .state ['responses' ].append (response )
550517 return response
551-
552518 except Exception as e :
553519 print (f"[DEBUG] _perform_hs6_reprediction error: { e } " )
554520 response = "HS 코드 재예측 중 오류가 발생했습니다. 다시 시도해 주세요."
0 commit comments