9.2 KiB
9.2 KiB
참조 구조 · 역할 · 페이로드
이 문서는 "정답 구조"가 아니라 출발점입니다. 팀은 이 구조를 바꿔도 되고, 바꾼 까닭을 설계서에 적으면 됩니다. 원본 요구의 작성 예시(Slack → 권한 · 전처리 → 분류 → RAG → 답변 · 검증 → 신뢰도 분기 → 답변 / Jira)를 우리 환경의 노드로 옮겼습니다.
1. 전체 흐름
flowchart TD
W(["Webhook<br/>POST /webhook/팀-support<br/>Header Auth"]) --> P["전처리 Code<br/>request_id · 공백 · 마크다운 제거 · 글자 정리"]
P --> V{"입력 검증<br/>빈 · 501자 · 직원"}
V -->|잘못| R400(["400 codes"])
V --> D["중복 확인<br/>cache: request_id"]
D -->|이미 처리| RP(["저장된 결과 그대로"])
D --> S{"규칙 · 판별<br/>주입 · 범위 밖"}
S -->|공격| RB(["차단"])
S --> C{{"분류 Agent<br/>유형 · 긴급도 · 필요한 정보"}}
C -->|정보 부족 · attempt < 3| RQ(["보완 질문"])
C -->|긴급 · 보안 사고| HN["이관 준비<br/>priority high"]
C --> K["지식 검색<br/>권한 필터 · top_k"]
C --> T["접수 조회<br/>requester = user"]
K & T --> M["모으기 Code"] --> A{{"답변 Agent<br/>근거로만"}} --> Q{{"검증 Agent + Code<br/>근거 · 정책 · 개인정보"}}
Q -->|신뢰도 충분| OF["출력 필터<br/>가리기"] --> OK(["자동 답변 + sources"])
Q -->|부족| HN
HN --> TK["티켓 생성<br/>key = request_id"] --> AL["알림"] --> RH(["담당자 이관 + ticket"])
TK -.->|실패| DLQ["DLQ + 알림"] -.-> RH
OK & RH & RB & RQ --> LOG["실행 기록<br/>개인정보 없이"]
| 단계 | 노드 (n8n) | 원본 요구 | 64~79일차 |
|---|---|---|---|
| 문의 수신 | Webhook (Header Auth) | Slack 메시지 수신 | 65 · 66 |
| 전처리 | Code | 문의 전처리, 변환(마크다운 제거 · 시간대 통일) | 66 JSON, 73 글자 정리 |
| 권한 · 입력 검증 | HTTP Request(/employees) + Code + If |
사용자 · 채널 권한 확인 | 73 입력 검증 |
| 중복 확인 | HTTP Request(/cache/<request_id>) + If |
중복 이벤트 재처리 차단 | 72 캐시, 76 중복 방지 |
| 보안 | Code(규칙) + Agent(판별) + If | (보안 · 권한 문의는 자동 답변하지 않음) | 73 |
| 분류 | Agent + Code(JSON 읽기) 또는 Switch | 문의 유형 · 긴급도 분류 | 68 라우터 |
| 검색 | Vector Store(검색) + Embeddings, HTTP Request(접수) | RAG 검색, 과거 유사 티켓 | 67 Tool, 71 RAG |
| 답변 | Agent (Tool 없음, 근거만 받음) | 답변 초안 생성 | 69 역할 분리 |
| 검증 | Agent(판단) + Code(점수 · 출처 · 개인정보 확인) | 근거 · 정책 검증 | 69 검토, 72 답 검사 |
| 분기 | If · Switch | 신뢰도 기준 분기 | 68 |
| 이관 | HTTP Request(POST /tickets, POST /alerts) |
Jira 티켓 생성 | 76 |
| 출력 | Code(출력 필터) + Respond to Webhook | Slack 답변 | 73 |
| 기록 | HTTP Request(POST /logs) |
처리 결과 · 피드백 저장 | 73 감사 로그, 77 |
| 오류 | Error Workflow, 오류 출력, DLQ | 오류 처리 | 72 · 76 |
2. 에이전트 역할 (최소 3개)
| 역할 | 입력 | 출력 (JSON) | Tool | 하지 않는 것 |
|---|---|---|---|---|
| 분류 (triage) | 질문 · 직원 부서 | {"type", "urgency", "missing": [...], "ticket_id"} |
없음 | 답하지 않음 |
| 답변 (writer) | 질문 · 검색 결과(근거) · 접수 상태 | 답 글 | 없음 | 근거 밖 내용 · 약속 |
| 검증 (verifier) | 질문 · 답 · 근거 | {"grounded": true, "policy_ok": true, "issues": [...]} |
없음 | 답을 새로 쓰지 않음 (고칠 점만) |
| (선택) 판별 (guard) | 질문 | {"attack", "reason"} |
없음 | 답하지 않음 |
- 69일차처럼 역할마다 Agent를 따로 두고, 역할 카드(목적 · 입력 · 출력 · 하지 않는 것)를 설계서에 둡니다.
- 판단이 분명한 것은 Agent가 아니라 Code로: 점수 기준, 권한, 중복, 길이, 출력 필터. Agent의 출력은 JSON으로 받고 Code가 읽으며, 읽지 못하면 안전한 쪽(이관)으로 갑니다 (69 · 73일차 fail-closed).
- 검색(RAG)은 Agent의 Tool로 줄 수도 있지만, 워크플로우가 먼저 검색해 근거를 넘기는 쪽이 근거를 기록 · 검증하기 쉽습니다 (71일차).
3. 공통 계약 · 페이로드
3.1 요청 (Webhook 본문)
| 칸 | 필수 | 형식 | 예 | 변환 |
|---|---|---|---|---|
request_id |
✔ | 글자, 64자 이하 | req-001 |
없으면 400 (중복 방지의 키) |
user |
✔ | 직원 이름 | 박서연 |
/employees로 확인, 없으면 400 unknown_user |
question |
✔ | 1~500자 | VPN이 계속 끊겨요 |
앞뒤 공백 · Slack 마크다운 제거, 글자 정리는 검사에만 |
channel |
글자 | it-help |
기록용 | |
attempt |
1 이상 정수, 기본 1 | 2 |
보완 질문 루프의 횟수 | |
context |
이전 보완 질문의 답 | "Windows, 오류 809" |
질문에 붙여 다시 분류 |
3.2 응답
| 칸 | 늘 있음 | 뜻 |
|---|---|---|
request_id |
✔ | 요청과 같음 |
route |
✔ | 자동 답변 · 보완 질문 · 담당자 이관 · 권한 없음 · 차단 |
answer |
✔ | 직원에게 보여 줄 글 (가린 뒤) |
sources |
✔ | [{"doc", "section", "score"}], 근거 없으면 [] |
ticket |
✔ | 이관이면 {"ticket_id", "priority", "duplicate"}, 아니면 null (생성 실패면 null + reason) |
agents |
✔ | 지나간 역할 이름 (["triage", "retriever", "writer", "verifier"]) |
security |
✔ | {"blocked_by", "reason", "masked"} (73일차) |
reason |
이관 · 실패의 까닭 (no_evidence · low_confidence · urgent · tickets_down · llm_down) |
|
timings |
단계별 ms (72일차, 결과서의 처리시간) |
3.3 연결선마다 페이로드 (설계서 6절에 이렇게)
원본 평가 기준은 "각 연결선(Edge)별로" 스키마 · 변환 · 필수/선택 · 예시를 요구합니다. 노드 사이 주요 연결선마다 아래 칸을 채웁니다.
| 연결선 | 필수 | 선택 | 변환 (Mapper · Formatter) | 예시 |
|---|---|---|---|---|
| Webhook → 전처리 | request_id, user, question |
channel, attempt, context |
없음 (원문) | 위 3.1 |
| 전처리 → 분류 | request_id, user, dept, clean_question |
attempt |
마크다운 제거, 공백 정리, 부서 붙이기 | {"request_id": "req-001", "user": "박서연", "dept": "재무팀", "clean_question": "VPN이 계속 끊겨요"} |
| 분류 → 검색 | query, type, access_levels |
ticket_id |
분류 JSON 읽기, 권한 목록 만들기 | {"query": "VPN 끊김", "type": "network", "access_levels": ["all"]} |
| 검색 → 답변 | evidence: [{"doc", "section", "text", "score"}] |
ticket |
점수 기준 미만 빼기, 중복 청크 빼기 | |
| 답변 → 검증 | answer, evidence |
|||
| 검증 → 분기 | grounded, confidence, issues |
검증 JSON 읽기, 못 읽으면 grounded: false |
||
| 분기 → 티켓 | key(=request_id), title, requester, category, priority |
evidence 요약 |
질문 요약, 개인정보 가림 |
n8n에서 각 노드의 입력 · 출력은 실행 화면(Executions)에서 바로 볼 수 있습니다. 설계서의 예시는 실제 실행에서 복사하고 개인정보를 가립니다.
4. 분기 · 병렬 · 루프 (원본 평가 기준)
| 구조 | 어디에 | n8n | 주의 |
|---|---|---|---|
| 분기 | 신뢰도 · 권한 · 긴급 · 유형 | If · Switch | 기준값은 재서 정함 (RAG 설계서), 설계서에 숫자로 |
| 분기 | 보안 · 권한 문의는 자동 답변하지 않음 | Switch | 오탐 시나리오도 함께 (03 문서 5절) |
| 병렬 | 지식 검색 + 접수 조회 (또는 과거 티켓 검색) | 한 노드에서 두 갈래 → Merge 또는 Code에서 모으기 | n8n v1 실행 순서는 갈래를 차례로 실행합니다 (66일차). 구조는 병렬이지만 시간은 합. 시간이 중요하면 결과서에 잰 값을 적고, "운영이라면 비동기 · 큐"를 설계서에 |
| 루프 | 보완 질문 (요청 사이) | attempt 칸, attempt >= 3이면 이관 |
루프의 끝(최대 횟수)을 꼭 정함 |
| 루프 | 검색 재시도 (요청 안) | 점수가 낮으면 질문을 다시 써서 한 번 더 검색 (Code 카운터 + If, 또는 Loop Over Items) | 최대 1~2회, 늘어나는 시간을 잼 |
| 루프 | 외부 호출 재시도 | 노드 설정 retryOnFail · maxTries |
401 · 403에는 재시도하지 않음 |
5. 하위 워크플로우로 나누기 (권장)
| 워크플로우 | 웹훅 | 맡는 사람 |
|---|---|---|
<팀>-support (본체) |
POST /webhook/<팀>-support |
오케스트레이션 |
<팀>-index (색인) |
POST /webhook/<팀>-index |
RAG |
<팀>-search (검색, 본체가 부름) |
POST /webhook/<팀>-search |
RAG |
<팀>-handoff (티켓 · 알림 · DLQ) |
POST /webhook/<팀>-handoff |
연동 · 안정성 |
<팀>-errors (Error Workflow) |
Error Trigger | 연동 · 안정성 |
본체가 다른 웹훅을 부를 때(79일차 "다른 내 웹훅을 부르기")는 HTTP Request에 timeout과 오류 출력을 둡니다. 하위가 멈추면 본체가 정해진 답을 해야 합니다.