Skip to content

Commit 703ddd7

Browse files
committed
fix: 세션 관련 이슈 해결
1 parent a4596c4 commit 703ddd7

8 files changed

Lines changed: 85 additions & 46 deletions

File tree

app/dto/request.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
class Request(BaseModel):
44
message: str
5+
session_id: str = None

app/dto/response.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Response(BaseModel):
1010
success: Optional[bool] = None
1111
progress_details: Optional[List[ProgressDetail]] = None
1212
error_reason: Optional[str] = None
13+
session_id: Optional[str] = None
1314

1415
@staticmethod
1516
def cargo_progres_result_to_response(result: CargoProgressResult) -> "Response":

app/routes.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from flask import Blueprint, request, jsonify
1+
from flask import Blueprint, request, jsonify, make_response
22
from app.dto.request import Request
33
from flasgger import swag_from
44
from .service import run_model
5+
import uuid
56

67
api_blueprint = Blueprint("api", __name__)
78

@@ -19,6 +20,11 @@
1920
'question': {
2021
'type': 'string',
2122
'example': '이 물건의 세금이 얼마나 나올까?'
23+
},
24+
'session_id': {
25+
'type': 'string',
26+
'example': 'uuid-1234',
27+
'required': False
2228
}
2329
},
2430
'required': ['question']
@@ -34,6 +40,10 @@
3440
'answer': {
3541
'type': 'string',
3642
'example': '이 물건은 8%의 부가세가 부과됩니다.'
43+
},
44+
'session_id': {
45+
'type': 'string',
46+
'example': 'uuid-1234'
3747
}
3848
}
3949
}
@@ -42,5 +52,15 @@
4252
})
4353
def predict():
4454
request_data = Request(**request.get_json())
45-
answer = run_model(question=request_data.message)
46-
return jsonify(answer.model_dump())
55+
session_id = request_data.session_id or request.cookies.get("session_id")
56+
answer = run_model(question=request_data.message, session_id=session_id)
57+
# 응답 dict에서 session_id 제거
58+
answer_dict = answer.model_dump()
59+
answer_dict.pop("session_id", None)
60+
response = make_response(jsonify(answer_dict))
61+
# 쿠키는 그대로 관리
62+
if answer.session_id:
63+
response.set_cookie("session_id", answer.session_id)
64+
else:
65+
response.delete_cookie("session_id")
66+
return response

app/service.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
from app.dto.response import Response
22
from core.graphs.runner import run_customs_agent
33

4-
def run_model(question: str) -> "Response":
4+
def run_model(question: str, session_id: str = None) -> "Response":
55
"""
66
중앙 관리 모델을 통해 각 요청을 적절한 모델로 라우팅
77
아무 모델과도 관계없는 경우 중앙 모델에서 적절한 응답을 생성해야 합니다.
88
ex : "안녕, 너는 뭘 할 수 있어?"
99
예시 코드는 아래와 같습니다.
1010
"""
11-
state = run_customs_agent(question)
11+
state = run_customs_agent(question, session_id=session_id)
1212
return Response(
1313
reply=state.get("final_response"),
1414
progress_details=state.get("progress_details"),
1515
error_reason=state.get("error_reason"),
16-
success=True
16+
success=True,
17+
session_id=state.get("session_id")
1718
)

core/graphs/runner.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from core.shared.states.states import CustomsAgentState
55

66

7-
def run_customs_agent(query: str) -> CustomsAgentState:
7+
def run_customs_agent(query: str, session_id: str = None) -> CustomsAgentState:
88
"""관세청 에이전트를 실행합니다."""
99

1010
# 그래프 생성
@@ -18,7 +18,8 @@ def run_customs_agent(query: str) -> CustomsAgentState:
1818
final_response="",
1919
intermediate_results={},
2020
error_reason=None,
21-
progress_details=None
21+
progress_details=None,
22+
session_id=session_id
2223
)
2324

2425
# 그래프 실행

core/shared/router/intent_router.py

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from langchain_core.messages import SystemMessage, AIMessage
22
import re
33
from typing import List, Optional
4+
import uuid
45

56
from core.shared.states.states import CustomsAgentState
67
from core.shared.utils.llm import get_llm
@@ -89,57 +90,76 @@ def _classify_with_llm(query: str) -> str:
8990

9091

9192
def _add_classification_message(state: CustomsAgentState, intent: str, reason: str) -> None:
92-
"""의도 분류 완료 메시지를 추가합니다."""
93-
state["messages"].append(
94-
AIMessage(content=f"의도 분류 완료: {intent} ({reason})")
95-
)
93+
# 의도 분류 메시지를 messages에 남기지 않음
94+
pass
9695

9796

9897
def intent_router(state: CustomsAgentState) -> CustomsAgentState:
99-
"""사용자 쿼리의 의도를 분류합니다."""
100-
98+
prev_intent = state.get("intent")
10199
current_query = state["query"].strip()
102100
if not current_query:
103101
state["intent"] = DEFAULT_INTENT
104102
_add_classification_message(state, DEFAULT_INTENT, "빈 쿼리")
105103
return state
106-
107104
# 세션 연속성 확인
108105
is_in_tariff_session = _is_in_tariff_session(state)
109-
110106
# 관세 예측 세션 중이면 무조건 tariff_prediction으로 분류
111107
if is_in_tariff_session:
112108
state["intent"] = "tariff_prediction"
113109
_add_classification_message(state, "tariff_prediction", "관세 예측 세션 연속성 유지")
110+
if not state.get("session_id") or state.get("session_id") in [None, '']:
111+
state["session_id"] = str(uuid.uuid4())
114112
return state
115-
116113
# 패턴 기반 분류
117114
is_number_selection = _is_number_selection(current_query)
118115
is_question = _is_question(current_query)
119-
120116
# 질문 형태이면서 관세 예측 세션이 아닌 경우 QnA로 분류
121117
if is_question:
122118
state["intent"] = "qna"
123119
_add_classification_message(state, "qna", "질문 형태 감지(우선)")
120+
if prev_intent == "tariff_prediction" and state.get("session_id"):
121+
try:
122+
from core.tariff_prediction.agent.tariff_prediction_agent import workflow_manager
123+
workflow_manager.cleanup_session(state["session_id"])
124+
except Exception:
125+
pass
126+
state["session_id"] = None
124127
return state
125-
126128
# 키워드 기반 분류
127129
keyword_intent = _classify_by_keywords(current_query)
128130
if keyword_intent:
129131
state["intent"] = keyword_intent
130132
_add_classification_message(state, keyword_intent, "키워드 기반 분류")
133+
if prev_intent == "tariff_prediction" and keyword_intent != "tariff_prediction" and state.get("session_id"):
134+
try:
135+
from core.tariff_prediction.agent.tariff_prediction_agent import workflow_manager
136+
workflow_manager.cleanup_session(state["session_id"])
137+
except Exception:
138+
pass
139+
if keyword_intent == "tariff_prediction":
140+
if not state.get("session_id") or state.get("session_id") in [None, '']:
141+
state["session_id"] = str(uuid.uuid4())
142+
else:
143+
state["session_id"] = None
131144
return state
132-
133-
# 숫자 선택이지만 관세 예측 세션이 아닌 경우에도 tariff_prediction으로 분류
134-
# (HS 코드 직접 입력 등의 경우)
135145
if is_number_selection:
136146
state["intent"] = "tariff_prediction"
137147
_add_classification_message(state, "tariff_prediction", "숫자 선택 감지")
148+
if not state.get("session_id") or state.get("session_id") in [None, '']:
149+
state["session_id"] = str(uuid.uuid4())
138150
return state
139-
140-
# LLM 기반 의도 분류를 수행
141151
intent = _classify_with_llm(current_query)
142152
state["intent"] = intent
143153
_add_classification_message(state, intent, "LLM 분류")
144-
154+
if prev_intent == "tariff_prediction" and intent != "tariff_prediction" and state.get("session_id"):
155+
try:
156+
from core.tariff_prediction.agent.tariff_prediction_agent import workflow_manager
157+
workflow_manager.cleanup_session(state["session_id"])
158+
except Exception:
159+
pass
160+
if intent == "tariff_prediction":
161+
if not state.get("session_id") or state.get("session_id") in [None, '']:
162+
state["session_id"] = str(uuid.uuid4())
163+
else:
164+
state["session_id"] = None
145165
return state

core/shared/states/states.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ class CustomsAgentState(TypedDict):
1515
final_response: str
1616
intermediate_results: Dict[str, Any]
1717
progress_details: Optional[List[ProgressDetail]]
18-
error_reason: Optional[str]
18+
error_reason: Optional[str]
19+
session_id: Optional[str] # 세션 ID 추가

core/tariff_prediction/agent/tariff_prediction_agent.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,14 @@
2323
class WorkflowManager:
2424
def __init__(self):
2525
self.sessions = {}
26-
2726
def get_session(self, session_id: str) -> 'TariffPredictionWorkflow':
28-
"""세션을 가져오거나 새로 생성합니다."""
2927
if session_id not in self.sessions:
3028
self.sessions[session_id] = TariffPredictionWorkflow()
3129
return self.sessions[session_id]
32-
3330
def cleanup_session(self, session_id: str):
34-
"""세션을 정리합니다."""
3531
if session_id in self.sessions:
3632
del self.sessions[session_id]
3733

38-
# 전역 매니저 인스턴스
3934
workflow_manager = WorkflowManager()
4035

4136
class TariffPredictionWorkflow:
@@ -524,17 +519,16 @@ def parse_tariff_result(self, tariff_result: str) -> Dict[str, Any]:
524519

525520

526521
def tariff_prediction_agent(state: CustomsAgentState) -> CustomsAgentState:
527-
session_id = DEFAULT_SESSION_ID
528-
529-
workflow = workflow_manager.get_session(session_id)
530-
522+
session_id = state.get("session_id")
523+
if session_id:
524+
workflow = workflow_manager.get_session(session_id)
525+
else:
526+
workflow = None
531527
messages = state.get("messages", [])
532528
context = ""
533529
previous_llm_responses = []
534-
535530
if messages:
536531
recent_messages = messages[-10:]
537-
538532
user_messages = []
539533
for msg in recent_messages:
540534
if hasattr(msg, 'type'):
@@ -545,19 +539,19 @@ def tariff_prediction_agent(state: CustomsAgentState) -> CustomsAgentState:
545539
content = msg.content
546540
if any(keyword in content for keyword in ['HS6 코드 후보', 'HS10 코드 후보', '번호를 선택']):
547541
previous_llm_responses.append(content)
548-
549542
if user_messages:
550-
context = " ".join(user_messages[-5:])
551-
543+
context = " ".join(user_messages[-5:])
552544
enhanced_context = context or ""
553545
if previous_llm_responses:
554546
enhanced_context += f"\n\n{RESPONSE_MESSAGES['previous_llm_response']}\n" + "\n".join(previous_llm_responses[-2:])
555-
556-
if enhanced_context:
557-
enhanced_query = f"{enhanced_context}\n\n{state['query']}"
558-
response = workflow.process_user_input(enhanced_query)
547+
if workflow:
548+
if enhanced_context:
549+
enhanced_query = f"{enhanced_context}\n\n{state['query']}"
550+
response = workflow.process_user_input(enhanced_query)
551+
else:
552+
response = workflow.process_user_input(state["query"])
559553
else:
560-
response = workflow.process_user_input(state["query"])
561-
554+
response = ERROR_MESSAGES.get('session_error', '세션이 유효하지 않습니다.')
562555
state["final_response"] = response
556+
state["session_id"] = session_id
563557
return state

0 commit comments

Comments
 (0)