B["요청 컨텍스트 초기화
request_id, session_id"]
B --> C{"금지질문 필터
(ForbiddenQueryFilter)"}
C -->|"FORBIDDEN_BOTH"| C1["차단 응답 반환"]
C -->|"FORBIDDEN_RAG"| C2["RAG 스킵 플래그"]
C -->|"FORBIDDEN_BACKEND"| C3["Backend 스킵 플래그"]
C -->|"ALLOWED"| D["PII 마스킹
(INPUT 단계)"]
C2 --> D
C3 --> D
end
subgraph PRIVACY["2. 프라이버시 게이트"]
direction LR
D --> E{"Privacy Query Gate
명단 요청 감지"}
E -->|"타인+명단+민감속성"| E1["차단 응답 반환"]
E -->|"1인칭 or 허용"| F["라우팅 오케스트레이션"]
end
subgraph ROUTING["3. 의도 분류 & 라우팅"]
direction LR
F --> G["Rule Router
(키워드 기반 1차)"]
G --> G1["Smalltalk Gate"]
G --> G2["절차 AND 조건"]
G --> G3["애매한 경계 감지"]
G --> G4["치명 액션 확인"]
G --> G5["키워드 매칭"]
G1 --> H{"confidence >= 0.85?"}
G2 --> H
G">
B["요청 컨텍스트 초기화
request_id, session_id"]
B --> C{"금지질문 필터
(ForbiddenQueryFilter)"}
C -->|"FORBIDDEN_BOTH"| C1["차단 응답 반환"]
C -->|"FORBIDDEN_RAG"| C2["RAG 스킵 플래그"]
C -->|"FORBIDDEN_BACKEND"| C3["Backend 스킵 플래그"]
C -->|"ALLOWED"| D["PII 마스킹
(INPUT 단계)"]
C2 --> D
C3 --> D
end
subgraph PRIVACY["2. 프라이버시 게이트"]
direction LR
D --> E{"Privacy Query Gate
명단 요청 감지"}
E -->|"타인+명단+민감속성"| E1["차단 응답 반환"]
E -->|"1인칭 or 허용"| F["라우팅 오케스트레이션"]
end
subgraph ROUTING["3. 의도 분류 & 라우팅"]
direction LR
F --> G["Rule Router
(키워드 기반 1차)"]
G --> G1["Smalltalk Gate"]
G --> G2["절차 AND 조건"]
G --> G3["애매한 경계 감지"]
G --> G4["치명 액션 확인"]
G --> G5["키워드 매칭"]
G1 --> H{"confidence >= 0.85?"}
G2 --> H
G">
B["요청 컨텍스트 초기화
request_id, session_id"]
B --> C{"금지질문 필터
(ForbiddenQueryFilter)"}
C -->|"FORBIDDEN_BOTH"| C1["차단 응답 반환"]
C -->|"FORBIDDEN_RAG"| C2["RAG 스킵 플래그"]
C -->|"FORBIDDEN_BACKEND"| C3["Backend 스킵 플래그"]
C -->|"ALLOWED"| D["PII 마스킹
(INPUT 단계)"]
C2 --> D
C3 --> D
end
subgraph PRIVACY["2. 프라이버시 게이트"]
direction LR
D --> E{"Privacy Query Gate
명단 요청 감지"}
E -->|"타인+명단+민감속성"| E1["차단 응답 반환"]
E -->|"1인칭 or 허용"| F["라우팅 오케스트레이션"]
end
subgraph ROUTING["3. 의도 분류 & 라우팅"]
direction LR
F --> G["Rule Router
(키워드 기반 1차)"]
G --> G1["Smalltalk Gate"]
G --> G2["절차 AND 조건"]
G --> G3["애매한 경계 감지"]
G --> G4["치명 액션 확인"]
G --> G5["키워드 매칭"]
G1 --> H{"confidence >= 0.85?"}
G2 --> H
G">
flowchart LR
%% 1) 메인 플로우 (End-to-End) - 가로형
subgraph INPUT["1. 입력 처리"]
direction LR
A["사용자 질문"] --> B["요청 컨텍스트 초기화<br/>request_id, session_id"]
B --> C{"금지질문 필터<br/>(ForbiddenQueryFilter)"}
C -->|"FORBIDDEN_BOTH"| C1["차단 응답 반환"]
C -->|"FORBIDDEN_RAG"| C2["RAG 스킵 플래그"]
C -->|"FORBIDDEN_BACKEND"| C3["Backend 스킵 플래그"]
C -->|"ALLOWED"| D["PII 마스킹<br/>(INPUT 단계)"]
C2 --> D
C3 --> D
end
subgraph PRIVACY["2. 프라이버시 게이트"]
direction LR
D --> E{"Privacy Query Gate<br/>명단 요청 감지"}
E -->|"타인+명단+민감속성"| E1["차단 응답 반환"]
E -->|"1인칭 or 허용"| F["라우팅 오케스트레이션"]
end
subgraph ROUTING["3. 의도 분류 & 라우팅"]
direction LR
F --> G["Rule Router<br/>(키워드 기반 1차)"]
G --> G1["Smalltalk Gate"]
G --> G2["절차 AND 조건"]
G --> G3["애매한 경계 감지"]
G --> G4["치명 액션 확인"]
G --> G5["키워드 매칭"]
G1 --> H{"confidence >= 0.85?"}
G2 --> H
G3 --> H
G4 --> H
G5 --> H
H -->|"YES"| I["Rule 결과 확정"]
H -->|"NO"| J["LLM Router<br/>(LLM 기반 2차)"]
J --> I
I --> K{"needs_clarify?"}
K -->|"YES"| K1["되묻기 응답<br/>PendingAction 저장"]
K -->|"NO"| L{"requires_confirmation?"}
L -->|"YES"| L1["확인 요청<br/>PendingAction 저장"]
L -->|"NO"| M["라우트 실행"]
end
subgraph EXECUTION["4. 라우트 실행"]
direction LR
M --> N{"RouteType?"}
N -->|"RAG_INTERNAL"| O["RAG 검색<br/>(Policy/Education QA)"]
N -->|"BACKEND_API"| P["Backend 조회<br/>(개인화 데이터)"]
N -->|"MIXED_BACKEND_RAG"| Q["RAG + Backend<br/>(병렬 조회)"]
N -->|"LLM_ONLY"| R["LLM 직접 호출<br/>(일반 대화)"]
N -->|"ROUTE_SYSTEM_HELP"| S["시스템 도움말<br/>(템플릿)"]
end
subgraph RAG["5. RAG 파이프라인"]
direction LR
O --> O1["Query 정규화"]
O1 --> O2["Query Expansion<br/>(YAML 규칙 기반)"]
O2 --> O3["Milvus 검색<br/>(원문 쿼리)"]
O3 --> O4{"결과 0건?"}
O4 -->|"YES"| O5["2nd-chance<br/>top_k=15 재검색"]
O4 -->|"NO"| O6["RRF Fusion"]
O5 --> O6
O6 --> O7["Low-Relevance Gate<br/>(anchor 키워드)"]
O7 --> O8["Quality Gate<br/>(L2 거리 평가)"]
O8 --> O9{"Quality Action?"}
O9 -->|"REJECT"| O10["INSUFFICIENT<br/>명확화 응답"]
O9 -->|"PROCEED"| T["LLM 프롬프트 구성"]
O9 -->|"PROCEED_WITH_WARNING"| T
end
P --> T
Q --> T
R --> T
S --> T
subgraph LLM["6. LLM 처리"]
direction LR
T --> T1["System Prompt<br/>(route type별)"]
T1 --> T2["RAG/Backend<br/>컨텍스트 추가"]
T2 --> T3["대화 이력<br/>(멀티턴)"]
T3 --> T4["사용자 질문"]
T4 --> U["LLM 호출<br/>(Timeout 정책)"]
U --> U1{"성공?"}
U1 -->|"NO"| U2["에러 응답"]
U1 -->|"YES"| V["Answer Guard"]
end
subgraph GUARD["7. 응답 검증"]
direction LR
V --> V1["Answerability Gate<br/>(RAG 근거 확인)"]
V1 --> V2["Citation Guard<br/>(가짜 조항 검증)"]
V2 --> V3["Statistical Guard<br/>(통계 주장 검증)"]
V3 --> V4["Korean Output<br/>(한국어 검증)"]
end
subgraph OUTPUT["8. 출력 처리"]
direction LR
V4 --> W["PII 마스킹<br/>(OUTPUT 단계)"]
W --> X["응답 조립<br/>answer + sources + meta"]
X --> Y["AI 로그 생성<br/>(백그라운드)"]
Y --> Z["ChatResponse 반환"]
end
C1 --> Z
E1 --> Z
K1 --> Z
L1 --> Z
O10 --> Z
U2 --> Z
style A fill:#e3f2fd
style Z fill:#c8e6c9
style C1 fill:#ffcdd2
style E1 fill:#ffcdd2
style O10 fill:#fff3e0
style U2 fill:#ffcdd2
flowchart LR
%% 2) 라우팅 오케스트레이션 상세 - 가로형
subgraph RULE["Rule Router (1차 분류)"]
direction LR
A["마스킹된 쿼리"] --> B{"Smalltalk Gate<br/>길이 2~10자?"}
B -->|"YES"| B1{"업무 도메인<br/>힌트 없음?"}
B1 -->|"YES"| B2["GENERAL_CHAT<br/>confidence=0.95"]
B1 -->|"NO"| C
B -->|"NO"| C
C{"절차 AND 조건<br/>(최우선)"}
C -->|"절차+보안/사고"| C1["POLICY_QA<br/>confidence=0.92"]
C -->|"절차+교육"| C2["EDUCATION_QA<br/>confidence=0.92"]
C -->|"NO"| D
D{"애매한 경계?"}
D -->|"교육+애매동사"| D1["needs_clarify=true<br/>clarify_group=EDU_CONTENT_VS_STATUS"]
D -->|"연차/휴가+애매동사"| D2["needs_clarify=true<br/>clarify_group=POLICY_VS_PERSONAL"]
D -->|"NO"| E
E{"치명 액션?"}
E -->|"QUIZ_START"| E1["requires_confirmation=true<br/>sub_intent_id=QUIZ_START"]
E -->|"QUIZ_SUBMIT"| E2["requires_confirmation=true<br/>sub_intent_id=QUIZ_SUBMIT"]
E -->|"QUIZ_GENERATION"| E3["requires_confirmation=true<br/>sub_intent_id=QUIZ_GENERATION"]
E -->|"NO"| F
F["키워드 매칭"]
F --> F1{"HR 개인화?<br/>연차/근태/복지"}
F1 -->|"YES"| F2["BACKEND_STATUS"]
F1 -->|"NO"| F3{"정책/규정?"}
F3 -->|"YES"| F4["POLICY_QA"]
F3 -->|"NO"| F5{"교육 내용?"}
F5 -->|"YES"| F6["EDUCATION_QA"]
F5 -->|"NO"| F7{"시스템 도움말?"}
F7 -->|"YES"| F8["SYSTEM_HELP"]
F7 -->|"NO"| F9["POLICY_QA<br/>(기본값)"]
end
B2 --> G{"confidence >= 0.85?"}
C1 --> G
C2 --> G
D1 --> G
D2 --> G
E1 --> G
E2 --> G
E3 --> G
F2 --> G
F4 --> G
F6 --> G
F8 --> G
F9 --> G
subgraph LLM["LLM Router (2차 분류)"]
direction LR
G -->|"NO"| H["LLM Router 호출"]
H --> H1["System Prompt<br/>+ Few-shot Examples"]
H1 --> H2["JSON 응답 파싱"]
H2 --> H3{"유효한 JSON?"}
H3 -->|"NO"| H4["Rule 결과 유지"]
H3 -->|"YES"| H5["LLM 결과 적용"]
H5 --> I{"BACKEND_STATUS +<br/>빈 sub_intent_id?"}
I -->|"YES"| I1["Fallback Gate<br/>(절차 키워드 확인)"]
I1 -->|"절차+보안"| I2["POLICY_QA로 변경"]
I1 -->|"절차+교육"| I3["EDUCATION_QA로 변경"]
I1 -->|"그 외"| I4["clarify 처리"]
I -->|"NO"| J
end
G -->|"YES"| J["최종 결과"]
H4 --> J
I2 --> J
I3 --> J
I4 --> J
H5 --> J
subgraph POST["Post-Process"]
direction LR
J --> K{"needs_clarify?"}
K -->|"YES"| K1["PendingAction 저장<br/>TTL=5분"]
K1 --> K2["clarify_question 반환"]
K -->|"NO"| L{"requires_confirmation?"}
L -->|"YES"| L1["PendingAction 저장"]
L1 --> L2["confirmation_prompt 반환"]
L -->|"NO"| M["can_execute=true<br/>즉시 실행"]
end
style B2 fill:#e8f5e9
style C1 fill:#e3f2fd
style C2 fill:#e3f2fd
style F2 fill:#fff3e0
style D1 fill:#fce4ec
style D2 fill:#fce4ec
style E1 fill:#f3e5f5
style E2 fill:#f3e5f5
style E3 fill:#f3e5f5
flowchart LR
%% 3) RAG 파이프라인 상세 - 가로형
subgraph PREP["쿼리 전처리"]
direction LR
A["마스킹된 쿼리"] --> B["정규화<br/>마스킹 토큰 제거"]
B --> C{"길이 < 40자?"}
C -->|"YES"| D["Query Expansion"]
C -->|"NO"| E["원문 그대로"]
D --> D1["YAML 규칙 로드<br/>20개 핵심 키워드"]
D1 --> D2{"키워드 매칭?"}
D2 -->|"YES"| D3["동의어 + 관련어<br/>최대 8개 확장"]
D2 -->|"NO"| E
D3 --> F["확장된 쿼리"]
end
subgraph SEARCH["Milvus 검색"]
direction LR
E --> G["원문 쿼리 검색<br/>top_k=5"]
F --> G
G --> H{"결과 0건?"}
H -->|"YES"| I["2nd-chance<br/>top_k=15 재검색"]
H -->|"NO"| J["원문 결과"]
I --> I1{"여전히 0건?"}
I1 -->|"YES"| K["빈 결과"]
I1 -->|"NO"| J
F --> L["확장 쿼리 검색<br/>top_k=5"]
L --> M["확장 결과"]
end
subgraph RRF["RRF Fusion"]
direction LR
J --> N{"확장 결과<br/>존재?"}
M --> N
N -->|"NO"| O["원문 결과만 사용"]
N -->|"YES"| P["RRF 병합<br/>score = Σ 1/(k+rank)"]
P --> P1["양쪽 등장 문서<br/>= 높은 점수"]
P1 --> Q["재정렬된 결과"]
O --> Q
end
subgraph QUALITY["품질 평가"]
direction LR
K --> R["sources = []"]
Q --> S["Low-Relevance Gate"]
S --> S1["anchor 키워드 추출<br/>(명사, 순환형)"]
S1 --> S2["source별 anchor 매칭"]
S2 --> S3{"매칭 실패?"}
S3 -->|"YES"| S4["순위 강등<br/>(최소 1개 유지)"]
S3 -->|"NO"| T
S4 --> T
T["Quality Gate<br/>L2 거리 평가"]
R --> T
T --> U["거리 통계 계산<br/>min, avg, max"]
U --> V{"min_L2?"}
V -->|"<= 1.4"| W1["OK<br/>action=PROCEED"]
V -->|"1.4 ~ 1.6"| W2["LOW<br/>action=PROCEED_WITH_WARNING"]
V -->|"> 1.6"| W3["INSUFFICIENT<br/>action=REJECT"]
V -->|"sources=0"| W3
end
subgraph RESULT["결과 반환"]
direction LR
W1 --> X["RagRetrievalResult"]
W2 --> X
W3 --> Y["명확화 응답 생성"]
X --> X1["sources: ChatSource[]"]
X --> X2["retriever_used: MILVUS"]
X --> X3["quality_action: PROCEED"]
Y --> Y1["insufficient_evidence: true"]
Y --> Y2["clarify_message: 안내 메시지"]
end
style D3 fill:#e8f5e9
style P fill:#e3f2fd
style W1 fill:#c8e6c9
style W2 fill:#fff3e0
style W3 fill:#ffcdd2
flowchart LR
%% 4) LLM 프롬프트 구성 상세 - 가로형
subgraph SYSTEM["System Prompt 선택"]
direction LR
A["RouteType"] --> B{"타입?"}
B -->|"RAG_INTERNAL"| C1["SYSTEM_PROMPT_WITH_RAG<br/>근거 제시 강조"]
B -->|"BACKEND_API"| C2["SYSTEM_PROMPT_BACKEND_API<br/>데이터 기반 응답"]
B -->|"MIXED_BACKEND_RAG"| C3["SYSTEM_PROMPT_MIXED<br/>RAG+Backend 통합"]
B -->|"LLM_ONLY"| C4["SYSTEM_PROMPT_NO_RAG<br/>일반 지식"]
end
subgraph RAG_CTX["RAG 컨텍스트"]
direction LR
C1 --> D{"sources 존재?"}
D -->|"YES"| E["source 포맷팅"]
E --> E1["출처 1: [제목]<br/>내용..."]
E1 --> E2["출처 2: [제목]<br/>내용..."]
E2 --> F{"컨텍스트<br/>길이 초과?"}
F -->|"YES"| G["하위 source 제거"]
F -->|"NO"| H["RAG 컨텍스트 완성"]
G --> H
D -->|"NO"| I["Soft Guardrail<br/>확정 표현 금지 지시"]
end
subgraph BACKEND_CTX["Backend 컨텍스트"]
direction LR
C2 --> J["BackendContextFormatter"]
C3 --> J
J --> J1["개인화 데이터<br/>테이블 포맷"]
J --> J2["통계 데이터<br/>(관리자용)"]
J1 --> K["Backend 컨텍스트 완성"]
J2 --> K
end
subgraph HISTORY["대화 이력"]
direction LR
H --> L["ChatContextHandler"]
I --> L
K --> L
L --> L1["이전 대화 로드<br/>최대 10턴"]
L1 --> L2["토큰 버짓 체크"]
L2 --> L3{"초과?"}
L3 -->|"YES"| L4["오래된 턴 제거"]
L3 -->|"NO"| M["대화 이력 완성"]
L4 --> M
end
subgraph FINAL["최종 메시지 조립"]
direction LR
M --> N["사용자 질문<br/>(마스킹된)"]
N --> O["messages 배열"]
O --> O1["role: system<br/>content: system_prompt"]
O --> O2["role: user<br/>content: 이전 질문"]
O --> O3["role: assistant<br/>content: 이전 응답"]
O --> O4["role: user<br/>content: 현재 질문"]
end
subgraph LLM_CALL["LLM 호출"]
direction LR
O4 --> P["Timeout 계산<br/>route_type별"]
P --> Q["LLM API 호출"]
Q --> R{"스트리밍?"}
R -->|"YES"| S1["chunk별 yield"]
R -->|"NO"| S2["전체 응답 반환"]
end
style C1 fill:#e3f2fd
style C2 fill:#fff3e0
style C3 fill:#f3e5f5
style C4 fill:#e8f5e9
style I fill:#ffcdd2
flowchart LR
%% 5) Answer Guard 상세 - 가로형
subgraph INPUT["LLM 응답 입력"]
direction LR
A["raw_answer<br/>(LLM 생성 응답)"] --> B["sources<br/>(RAG 검색 결과)"]
end
subgraph ANSWERABILITY["1. Answerability Gate"]
direction LR
B --> C{"sources 존재?"}
C -->|"NO"| D{"intent가<br/>POLICY_QA or<br/>EDUCATION_QA?"}
D -->|"YES"| E["고정 템플릿 반환<br/>LLM 응답 무시"]
D -->|"NO"| F["LLM 응답 유지"]
C -->|"YES"| F
end
subgraph CITATION["2. Citation Hallucination Guard"]
direction LR
F --> G["조항 패턴 추출<br/>제N조, 제N항, 별표..."]
G --> H{"조항 인용<br/>존재?"}
H -->|"NO"| I["통과"]
H -->|"YES"| J["source 텍스트에서<br/>조항 존재 확인"]
J --> K{"모든 조항<br/>검증 통과?"}
K -->|"NO"| L["차단 응답 반환<br/>가짜 인용 감지"]
K -->|"YES"| I
end
subgraph STATS["3. Statistical Claim Guard"]
direction LR
I --> M["통계 패턴 감지"]
M --> M1["TOP N"]
M --> M2["N위"]
M --> M3["N%"]
M --> M4["가장 많이"]
M --> M5["대부분"]
M1 --> N{"패턴 매칭?"}
M2 --> N
M3 --> N
M4 --> N
M5 --> N
N -->|"NO"| O["통과"]
N -->|"YES"| P{"sources에<br/>해당 통계?"}
P -->|"NO"| Q["차단 응답 반환<br/>근거 없는 통계"]
P -->|"YES"| O
end
subgraph KOREAN["4. Korean Output Enforcement"]
direction LR
O --> R["영어 시작 패턴 체크"]
R --> R1["I'd be happy to..."]
R --> R2["I can help..."]
R --> R3["According to..."]
R --> R4["Based on..."]
R1 --> S{"영어로 시작?"}
R2 --> S
R3 --> S
R4 --> S
S -->|"YES"| T["한국어 재생성 요청<br/>또는 강제 변환"]
S -->|"NO"| U["최종 응답 확정"]
T --> U
end
subgraph OUTPUT["검증 완료"]
direction LR
E --> V["guard_result"]
L --> V
Q --> V
U --> V
V --> W["blocked: bool"]
V --> X["reason: str"]
V --> Y["final_answer: str"]
end
style E fill:#fff3e0
style L fill:#ffcdd2
style Q fill:#ffcdd2
style U fill:#c8e6c9
flowchart LR
%% 6) PII 마스킹 플로우 - 가로형
subgraph INPUT_STAGE["INPUT 단계"]
direction LR
A["사용자 질문<br/>(원문)"] --> B["PII 서비스 호출<br/>POST /mask"]
B --> C{"서비스 정상?"}
C -->|"YES"| D["PII 검출 & 마스킹"]
C -->|"NO"| E["Fail-Closed 발동"]
D --> D1["이름 → [NAME]"]
D --> D2["전화번호 → [PHONE]"]
D --> D3["이메일 → [EMAIL]"]
D --> D4["주민번호 → [RRN]"]
D1 --> F["마스킹된 쿼리"]
D2 --> F
D3 --> F
D4 --> F
E --> G["안전 메시지 반환<br/>원문 전송 차단"]
end
subgraph OUTPUT_STAGE["OUTPUT 단계"]
direction LR
H["LLM 응답<br/>(원문)"] --> I["PII 서비스 호출<br/>POST /mask"]
I --> J{"서비스 정상?"}
J -->|"YES"| K["PII 검출 & 마스킹"]
J -->|"NO"| L["Fail-Closed 발동"]
K --> K1["응답 내 PII 마스킹"]
K1 --> M["마스킹된 응답"]
L --> N["안전 메시지 반환<br/>원문 전송 차단"]
end
subgraph LOG_STAGE["LOG 단계"]
direction LR
O["질문 + 응답"] --> P["PII 서비스 호출<br/>stage=LOG"]
P --> Q["마스킹된 Q&A"]
Q --> R["Elasticsearch<br/>마스킹된 데이터만 저장"]
end
subgraph PRIVACY_GATE["Privacy Query Gate"]
direction LR
S["마스킹된 쿼리"] --> T["조합 규칙 체크"]
T --> T1["대상: 직원, 팀원, 누가<br/>(+2점)"]
T --> T2["행위: 명단, 리스트, 뽑아<br/>(+3점)"]
T --> T3["속성: 교육, 점수, 평가<br/>(+3점)"]
T1 --> U{"총점 >= 6?"}
T2 --> U
T3 --> U
U -->|"YES"| V["BLOCK_PII_LIST<br/>차단 응답"]
U -->|"NO"| W{"1인칭?<br/>내/저"}
W -->|"YES"| X["ALLOW<br/>개인화 조회 허용"]
W -->|"NO"| X
end
style E fill:#ffcdd2
style G fill:#ffcdd2
style L fill:#ffcdd2
style N fill:#ffcdd2
style V fill:#ffcdd2
style F fill:#c8e6c9
style M fill:#c8e6c9
style X fill:#c8e6c9
flowchart LR
%% 7) 전체 보안 게이트 요약 - 가로형
subgraph LAYER1["Layer 1: 입력 필터"]
direction LR
A["금지질문 필터"] --> B["PII 마스킹<br/>(INPUT)"]
B --> C["Privacy Query Gate"]
end
subgraph LAYER2["Layer 2: 의도 분류"]
direction LR
D["Smalltalk Gate"] --> E["절차 AND 조건"]
E --> F["애매한 경계 감지"]
F --> G["치명 액션 확인"]
end
subgraph LAYER3["Layer 3: 검색 품질"]
direction LR
H["Low-Relevance Gate<br/>(anchor 매칭)"] --> I["Quality Gate<br/>(L2 거리)"]
end
subgraph LAYER4["Layer 4: 응답 검증"]
direction LR
J["Answerability Gate"] --> K["Citation Guard"]
K --> L["Statistical Guard"]
L --> M["Korean Output"]
end
subgraph LAYER5["Layer 5: 출력 보호"]
direction LR
N["PII 마스킹<br/>(OUTPUT)"] --> O["Fail-Closed"]
end
C --> D
G --> H
I --> J
M --> N
style A fill:#ffcdd2
style C fill:#ffcdd2
style I fill:#fff3e0
style K fill:#f3e5f5
style L fill:#f3e5f5
style O fill:#ffcdd2
flowchart LR
%% 9) 멀티턴 대화 플로우 - 가로형
subgraph TURN1["1턴: 사용자 질문"]
direction LR
A["사용자: 연차 알려줘"] --> B["Rule Router"]
B --> C{"애매한 경계?"}
C -->|"YES"| D["needs_clarify=true"]
D --> E["되묻기 응답<br/>규정 설명? 잔여일수 조회?"]
E --> F["PendingAction 저장<br/>TTL=5분"]
end
subgraph TURN2["2턴: 사용자 응답"]
direction LR
G["사용자: 잔여일수"] --> H["PendingAction 확인"]
H --> I{"PendingAction<br/>존재?"}
I -->|"YES"| J["ClarifyAnswerHandler"]
I -->|"NO"| K["새 질문으로 처리"]
J --> L{"응답 길이?"}
L -->|"<= 20자"| M["키워드 매핑<br/>sub_intent_id 결정"]
L -->|"> 20자"| K
M --> N["잔여일수 → BACKEND_STATUS"]
N --> O["원본 쿼리 + sub_intent<br/>재라우팅"]
end
subgraph EXECUTION["실행"]
direction LR
O --> P["BACKEND_API 경로"]
P --> Q["개인화 데이터 조회"]
Q --> R["LLM 응답 생성"]
R --> S["응답 반환"]
end
subgraph CONTEXT["컨텍스트 관리"]
direction LR
T["ChatContextHandler"] --> T1["대화 이력 저장"]
T1 --> T2["다음 턴에서<br/>이력 참조"]
end
F --> G
S --> T
style D fill:#fce4ec
style E fill:#fce4ec
style N fill:#c8e6c9