# exAiChatBot 프로세스 흐름 > **현재 기준:** Agent 모드 운영, Qdrant 단일 벡터 스토어, `chatbotApp` 웹 UI, `chatbotAdmin` 학습·통계 운영 > **주요 엔드포인트:** RAG API `:28012`, Vector Admin `:28013`, Qdrant `:6333` > **최종 갱신:** 2026-07-14 이 문서는 `exAiChatBot`이 실제 운영 흐름에서 어떤 역할을 하는지 정리합니다. 상세 시스템 배치는 [`../SYSTEM_ARCHITECTURE.md`](../SYSTEM_ARCHITECTURE.md), Agent 구조는 [`../docs/AGENT_ARCHITECTURE.md`](../docs/AGENT_ARCHITECTURE.md)를 기준 문서로 봅니다. --- ## 1. 한눈에 보는 현재 흐름 ```mermaid flowchart LR User[사용자] Kakao[카카오톡 / 오픈빌더] Web[kakaoChatbotSkill\n:8083] App[chatbotApp\n/chatbot/ai/] RAG[exAiChatBot API\n:28012] WAS[chatbotApi\n:8086] QD[(Qdrant\n:6333)] LLM[LLM / Embedding / Reranker\nGateway] ADM[exAiChatBot Admin\n:28013] CA[chatbotAdmin\n:8087] Mongo[(MongoDB)] Oracle[(Oracle)] Data[data.ex.co.kr] User --> Kakao Kakao -->|fallback skill| Web Web -->|redirect-only button| App App -->|POST /web/ask| Web Web -->|POST /agent/chat| RAG RAG -->|tool definitions / execute| WAS WAS -->|WAS domain tool| Oracle WAS -->|WEB domain tool| Web Web --> Data RAG -->|rag_search| QD RAG -->|chat / embed / rerank| LLM RAG -->|history / pending| Mongo CA -->|학습 / FAQ CRUD| ADM ADM --> QD ``` ### 핵심 원칙 | 구분 | 현재 동작 | |------|-----------| | 카카오톡 | 답변 본문 처리보다 `chatbotApp`을 여는 버튼 제공 중심 | | 웹 채팅 | `/web/ask`가 Agent 응답을 직접 받아 `WebAskResponse`로 매핑 | | Agent | `/agent/chat`에서 LLM tool loop 실행 | | FAQ 검색 | `rag_search` tool이 Qdrant 벡터 검색 + rerank 수행 | | 도메인 조회 | RAG Agent가 WAS tool API를 호출, WAS/WEB fetchOwner에 따라 실행 | | 환각 방지 | tool 또는 Qdrant 근거가 없으면 자유 답변 폐기 후 guidance 안내 | | 학습 관리 | `chatbotAdmin` → `exAiChatBot Admin(:28013)` → Qdrant upsert/delete | --- ## 2. 실행 서비스와 책임 | 서비스 | 포트 | 주요 파일 | 책임 | |--------|------|-----------|------| | RAG API | `28012` | `scripts/run_service_qa.py` | `/agent/chat`, `/health` | | AgentService | 내부 | `scripts/agent/agent_service.py` | tool loop, pending, 강제 RAG, guidance, 최종 응답 | | ToolExecutor | 내부 | `scripts/agent/tool_executor.py` | `rag_search`, `ask_user`, WAS domain tool 실행 | | Vector Admin | `28013` | `scripts/admin_service.py` | FAQ 검색/목록/추가/수정/삭제, keyword-search | | QdrantStore | `6333` | `scripts/vector_store.py` | Qdrant collection 연결, upsert, query, scroll, delete | | Batch ingest | 일회성 | `scripts/ingest_qa.py` | `qa_raw.jsonl` → embedding → Qdrant upsert | > `build_index_qa.py`는 FAISS 시절 단계입니다. `VECTOR_STORE=qdrant`이면 즉시 종료하며, 현재 Qdrant 운영에서는 별도 인덱스 파일을 만들지 않고 upsert가 곧 반영입니다. --- ## 3. 런타임 초기화 ```mermaid flowchart TD Start[api 컨테이너 시작] Config[환경변수 로드\nConfig.from_env] Clients[LLM / Embedding / Reranker client] Qdrant[QdrantStore 연결\ncollection qa_vectors] Mongo[MongoDB 연결\n대화 이력 / pending] Handlers[SearchHandler / PromptBuilder\nLLMHandler / QueryRewriter 등] Tools[ChatbotToolClient\nWAS tool API] Agent[AgentService 생성] Ready[FastAPI 대기\n/agent/chat, /health] Start --> Config --> Clients --> Qdrant --> Mongo --> Handlers --> Tools --> Agent --> Ready ``` ### 주요 환경변수 | 환경변수 | 기본값 | 설명 | |----------|--------|------| | `VECTOR_STORE` | `qdrant` | 운영 벡터 스토어 | | `QDRANT_COLLECTION` | `qa_vectors` | FAQ collection | | `FAISS_TOP_K` | `30` | 이름은 레거시지만 Qdrant 검색 top-k에 사용 | | `FAISS_THRESHOLD` | `0.55` | 1차 벡터 검색 score threshold | | `FAISS_THRESHOLD_REWRITE` | `0.50` | no-match 재검색 완화 threshold | | `RERANK_CANDIDATES` | `20` | reranker 입력 후보 수 | | `TOP_N_FOR_LLM` | `5` | 최종 답변 프롬프트 참고 FAQ 수 | | `LOW_CONFIDENCE_THRESHOLD` | `0.65` | 낮은 신뢰도·제안 기준 | | `HIGH_CONFIDENCE_THRESHOLD` | `0.75` | high confidence 기준 | | `AGENT_MAX_ROUNDS` | `6` | Agent tool loop 최대 반복 | | `CHATBOT_API_BASE_URL` | `http://127.0.0.1:8086/api` | WAS tool API | | `INTERNAL_TOOL_API_KEY` | 빈 값 | RAG→WAS 내부 인증 키 | | `CHAT_HISTORY_LIMIT` | `10` | 최근 대화 이력 조회 수 | | `CHAT_HISTORY_HOURS` | `24` | 대화 이력 조회 시간 범위 | --- ## 4. Agent 응답 흐름 (`POST /agent/chat`) 운영 주 경로입니다. `kakaoChatbotSkill`의 `RagAgentClient`와 웹 채팅의 `WebChatAgentService`가 호출합니다. ### 요청 ```json { "query": "사용자 질문", "botId": "conversation-id", "pendingIntentType": "FARE_SEARCH", "pendingParams": { "toIc": "신갈" } } ``` `pendingIntentType`, `pendingParams`는 선택입니다. 저장된 pending이 있으면 `AgentPendingStore`에서 보완합니다. ### 처리 순서 ```mermaid flowchart TD Q[사용자 질문] Pending[저장 pending 조회] Special{인사/종료?} History[대화 이력 로드] Tools[tool 목록 구성\nrag_search + ask_user + WAS tools] Loop[LLM tool loop] Tool{tool 호출} RAG[rag_search\nQdrant + rerank] Domain[WAS domain tool\n/tools/execute] Ask[ask_user / clarify] Ground[근거 기반 최종 답변 생성\nPromptBuilder 공통] Safe{근거 있음?} Guidance[guidance 안내\n콜센터/정확 확인] Save[Mongo 이력 저장] Response[AgentResponse 반환] Q --> Pending --> Special Special -->|yes| Response Special -->|no| History --> Tools --> Loop --> Tool Tool -->|rag_search| RAG --> Loop Tool -->|domain tool| Domain --> Loop Tool -->|ask_user| Ask --> Response Loop --> Safe Safe -->|domainData 또는 references| Ground --> Save --> Response Safe -->|없음| Guidance --> Save --> Response ``` ### Agent 안전망 | 안전망 | 목적 | |--------|------| | 매 턴 tool 사용 강제 프롬프트 | 대화 이력만 보고 사실 답변을 재생성하지 않도록 제한 | | 강제 `rag_search` | LLM이 정보성 질문에서 tool을 스킵하면 Qdrant를 강제 조회 | | 검색어 맥락화 | 이력이 있는 후속 질문은 `QueryRewriter`로 완결형 검색어를 만든 뒤 검색 | | 도메인 의도 가드 | 통행요금·미납처럼 도메인 의도가 강하면 FAQ 검색보다 되물음 우선 | | pending 중 안전망 스킵 | 슬롯필링 중 강제 RAG가 pending을 지우는 회귀 방지 | | 단일 system 메시지 | LLM tool-call 요청에서 pending 안내를 첫 system prompt에 병합해 게이트웨이 400 오류 방지 | | 근거 없는 final 폐기 | tool/Qdrant 근거가 없으면 LLM 자유 답변을 버리고 guidance 반환 | | PromptBuilder 사용 | Agent 최종 답변에서 FAQ·도메인 근거를 일관된 형식으로 사용 | --- ## 5. `rag_search` 상세 흐름 `rag_search`는 Agent의 로컬 tool입니다. FAQ/일반 안내 질문에 사용합니다. ```mermaid flowchart TD Query[검색어] Embed[EmbeddingClient.embed\nis_query=true] Search1[Qdrant query_points\nthreshold 0.55] Retry{검색 결과 있음?} Search2[Qdrant 재검색\nthreshold 0.50] Candidates[meta 언랩\nq/a/url/category] Rerank[Reranker\n질문+답변 passage] HasRefs{references 있음?} KeywordGuard{keyword retry 허용?} Keyword[Qdrant 문자열 검색\n최대 3건] KeywordRerank[Reranker 검증\nLOW_CONFIDENCE_THRESHOLD 이상] Refs[references 최대 5개] Empty[references 0건\nguidance 후보] Query --> Embed --> Search1 --> Retry Retry -->|yes| Candidates Retry -->|no| Search2 --> Candidates Candidates --> Rerank --> HasRefs HasRefs -->|yes| Refs HasRefs -->|no| KeywordGuard KeywordGuard -->|yes| Keyword --> KeywordRerank KeywordGuard -->|no| Empty KeywordRerank -->|통과| Refs KeywordRerank -->|미달| Empty ``` ### 현재 검색 특성 | 항목 | 동작 | |------|------| | 저장 임베딩 | FAQ 질문 `q`만 document 모드로 임베딩 | | sparse 저장 | `qa_vectors_v2` 또는 `HYBRID_SEARCH_ENABLED=true`면 `q+a+category+source` 기반 모델 없는 sparse vector도 저장 | | 검색 임베딩 | 사용자 검색어를 query 모드로 임베딩 | | 1차 검색 | Qdrant cosine score threshold `0.55` | | hybrid 검색 | 활성화 시 dense 후보 + sparse 후보를 병합한 뒤 rerank | | 재검색 | 0건이면 threshold `0.50`으로 한 번 완화 | | rerank 문서 | `질문: {q}\n답변: {a}` 형태로 Q+A 전체 전달 | | keyword retry | 벡터/완화 재검색 후 references가 0건일 때만 문자열 검색 최대 3건 | | keyword 검증 | keyword 결과도 reranker 최고 점수가 `LOW_CONFIDENCE_THRESHOLD` 이상일 때만 채택 | `희망드림`처럼 FAQ 질문에 실제 포함된 짧은 고유명사가 벡터 score threshold 아래로 누락되는 경우를 보완하기 위해, 최종 guidance 직전 보조 검색을 둡니다. 단 `할인`, `감면`, `요금`, `?`처럼 너무 넓거나 비정보성인 단독 발화는 keyword retry를 실행하지 않습니다. keyword 결과가 있어도 reranker 점수가 낮으면 references를 비워 두고 기존 guidance로 전환합니다. ### 5.1 Agent 재검색: 강제 RAG와 검색어 맥락화 Agent 모드에서는 LLM이 정상적으로 `rag_search`를 호출하면 위 흐름 그대로 검색합니다. 다만 LLM이 대화 이력만 보고 tool 호출 없이 final text를 만들거나, 도메인 근거도 Qdrant 근거도 없는 경우에는 `AgentService`가 안전망으로 `rag_search`를 강제 실행합니다. ```mermaid flowchart TD Final[LLM tool loop 종료] Domain{사용 가능한 domainData 있음?} RagDone{rag_search 이미 실행?} Pending{pending 진행 중?} Guard{명백한 도메인 의도?} History{botId + 대화 이력 있음?} Rewrite[QueryRewriter.rewrite_query] NoRewrite[원문 검색어 사용] Search[rag_search 강제 실행] Guide[근거 없으면 guidance] Final --> Domain Domain -->|yes| SearchDone[도메인 근거로 답변 생성] Domain -->|no| RagDone RagDone -->|yes| SearchDone RagDone -->|no| Pending Pending -->|yes| Guide Pending -->|no| Guard Guard -->|yes| Clarify[FAQ 대신 되물음] Guard -->|no| History History -->|yes| Rewrite --> Search History -->|no| NoRewrite --> Search Search --> SearchDone ``` #### 강제 RAG가 실행되는 조건 | 조건 | 의미 | |------|------| | 도메인 tool 결과 없음 | `domainData`가 없거나 사용할 수 없는 상태 | | `rag_search` 미실행 | LLM이 이번 턴에서 Qdrant를 조회하지 않음 | | pending 진행 중 아님 | 슬롯필링 중 강제 검색이 pending을 지우는 회귀 방지 | | 도메인 의도 가드 통과 | 차량번호/통행요금 계산처럼 명백한 도메인 조회는 FAQ 대신 되물음 | #### 검색어 맥락화 순서 1. `botId`가 있고 `CHAT_HISTORY_ENABLED=true`이면 MongoDB에서 최근 대화 이력을 조회합니다. 2. 조회 범위는 `CHAT_HISTORY_HOURS` 시간 이내, 최대 `CHAT_HISTORY_LIMIT`개입니다. 3. 이력은 오래된 대화 → 최신 대화 순서로 정렬됩니다. 4. 답변 본문에서 `혹시 이런 것을 찾으셨나요?` 제안 블록은 제거합니다. 5. `QueryRewriter.rewrite_query()`가 현재 질문이 후속 질문인지 판단합니다. 6. 후속 질문이면 완결형 검색어를 반환하고, 새 주제면 `__NO_REWRITE__`를 반환합니다. 7. 재작성 결과가 없거나 원문과 같으면 원문 검색어로 `rag_search`를 실행합니다. 예시는 다음과 같습니다. | 이전 대화 | 현재 질문 | 검색어 | |-----------|-----------|--------| | "하이패스 단말기가 뭔가요?" | "그럼 어디서 사나요?" | "하이패스 단말기는 어디서 구매할 수 있나요?" | | "하이패스 단말기에 대해 알려줘" | "얼마야?" | "하이패스 단말기 가격은 얼마인가요?" | | "통행요금 조회해줘" | "안녕" | 재작성 안 함 | | "하이패스 설명" | "동김천 휴게소 메뉴 알려줘" | 재작성 안 함 | ### 5.2 QueryRewriter 판단 기준 `QueryRewriter`는 무조건 이전 대화에 붙이지 않습니다. 현재 질문이 아래 조건 중 하나에 해당할 때만 후속 질문 후보로 보고, 최종 판단은 LLM 재작성 프롬프트에서 한 번 더 합니다. | 판단 기준 | 예 | |-----------|----| | 지시어 포함 | `그럼`, `그거`, `거기`, `아까`, `이어서`, `해당` | | 짧은 후속 질문 | 20자 이하의 `얼마야?`, `어디서 사?`, `어떻게 해?`, `가능해?` | | 시간/방법/이유 질문 | `언제`, `기간`, `왜`, `방법`, `절차`, `신청` | 재작성 프롬프트의 핵심 규칙은 다음과 같습니다. | 규칙 | 설명 | |------|------| | 후속 질문만 재작성 | 새 주제면 `__NO_REWRITE__`만 출력 | | 지시어 구체화 | `그거`, `거기` 등을 이전 대화의 명사로 치환 | | 의도 유지 | 사용자가 물은 범위를 넓히거나 새 정보를 추가하지 않음 | | 한 문장 출력 | 재작성된 질문 한 문장 또는 `__NO_REWRITE__`만 허용 | | thinking 제거 | `...`가 있으면 후처리에서 제거 | ## 6. 도메인 tool 흐름 RAG Agent가 tool을 선택하면 WAS `chatbotApi`가 실제 정책·검증·조회 실행을 담당합니다. ```mermaid sequenceDiagram participant Agent as exAiChatBot Agent participant WAS as chatbotApi participant Skill as kakaoChatbotSkill participant DB as Oracle participant Data as data.ex.co.kr Agent->>WAS: GET /api/v1/tools/definitions WAS-->>Agent: intent-definitions.yml 기반 tools Agent->>WAS: POST /api/v1/tools/execute alt fetchOwner=WAS WAS->>DB: Oracle/domain handler 조회 WAS-->>Agent: domainData, uiType, intentType else fetchOwner=WEB WAS->>Skill: POST /internal/tools/data-portal Skill->>Data: 외부 API 조회 Skill-->>WAS: domainData WAS-->>Agent: domainData, uiType, intentType else needsClarification WAS-->>Agent: clarificationQuestion, missingParams end ``` ### 대표 tool | 구분 | Tool | |------|------| | WAS/Oracle | `FARE_SEARCH`, `FARE_UNPAID`, `FARE_REFUND`, `IC_TEL`, `REST_AREA_PRICE`, `TROAD_*` | | WEB/data.ex.co.kr | `GASSTATION`, `BRAND_SHOP`, `REST_AREA_FOOD_LIST` | | RAG 로컬 | `rag_search`, `ask_user` | WAS가 `needsClarification=true`를 반환하면 Agent는 pending을 저장하고 `clarify` 응답을 반환합니다. --- ## 7. Pending · 멀티턴 흐름 ```mermaid sequenceDiagram participant User as 사용자 participant Web as kakaoChatbotSkill participant RAG as AgentService participant WAS as chatbotApi participant Mongo as MongoDB User->>Web: 신갈까지 통행요금 Web->>RAG: POST /agent/chat RAG->>WAS: FARE_SEARCH {toIc: 신갈} WAS-->>RAG: needsClarification, missing fromIc RAG->>Mongo: pending 저장 RAG-->>Web: clarify + pendingIntentType Web-->>User: 출발 IC를 알려주세요 User->>Web: 판교 Web->>RAG: POST /agent/chat RAG->>Mongo: pending 조회 RAG->>WAS: FARE_SEARCH {fromIc: 판교, toIc: 신갈} WAS-->>RAG: domainData RAG->>Mongo: pending clear RAG-->>Web: domain answer ``` | 저장소 | 역할 | |--------|------| | `AgentPendingStore` | `agent_pending` Mongo collection, 실패 시 in-memory fallback | | 대화 이력 | `rag_conversations` collection, 재작성·통계·문맥 참고 | | Skill | pending을 직접 소유하지 않고 응답 필드를 UI에 반영 | | WAS | stateless. 매 요청의 params를 기준으로 검증/조회 | --- ## 8. 최종 답변 생성 Agent loop의 `final_content`는 그대로 사용자에게 내보내지 않습니다. 근거가 있으면 `PromptBuilder`로 다시 최종 답변을 생성하고, 근거가 없으면 guidance로 전환합니다. ```mermaid flowchart TD ToolResult[tool 결과] Domain{사용 가능한 domainData?} RAG{Qdrant references?} Prompt[PromptBuilder.build_answer_prompt_messages] LLM[LLMHandler.generate_answer_from_messages] Suggest[낮은 신뢰도면 제안 질문 추가] Guide[PromptBuilder.build_guidance_prompt] Save[대화 이력 저장] Out[응답 반환] ToolResult --> Domain Domain -->|yes| Prompt Domain -->|no| RAG RAG -->|yes| Prompt RAG -->|no| Guide Prompt --> LLM --> Suggest --> Save --> Out Guide --> Save --> Out ``` ### 응답 주요 필드 | 필드 | 의미 | |------|------| | `answer`, `llmAnswer` | 사용자에게 보여줄 텍스트 | | `routeType` | `agent`, `domain`, `web_domain`, `clarify`, `greeting` 등 | | `intentType` | 도메인 tool 상세 (`FARE_SEARCH`, `IC_TEL` 등) | | `domainData` | 도메인 조회 결과 | | `references` | Qdrant FAQ 근거 | | `faqUrls` | HTTP/HTTPS 원문 URL 최대 3개 | | `toolTrace` | 호출 tool, arguments, 성공/실패 | | `needsClarification` | 되물음 여부 | | `pendingIntentType`, `missingParams` | 멀티턴 슬롯필링 정보 | --- ## 9. Web 채팅 흐름 (`/web/ask`) 현재 웹 채팅은 카카오 `SkillResponse`를 우회합니다. `WebChatService`가 Agent 응답을 받은 뒤 `WebChatResponseMapper`가 웹 전용 DTO로 변환합니다. ```mermaid flowchart TD App[chatbotApp] Controller[WebChatController] Service[WebChatService] AgentSvc[WebChatAgentService] RAG[exAiChatBot /agent/chat] Mapper[WebChatResponseMapper] DTO[WebAskResponse] App -->|POST /web/ask| Controller --> Service --> AgentSvc AgentSvc -->|agent.mode=agent| RAG RAG --> Mapper Mapper --> DTO --> App ``` ### Web 전용 변환 | 원본 필드 | 웹 UI | |-----------|-------| | `answer` | MarkdownBody 렌더링 | | `references`, `faqUrls` | 원문 버튼 | | `domainData`, `uiType` | 요금/주유/브랜드/음식/전화 카드 | | `icCandidates`, `quickReplies` | 빠른 선택 버튼 | | 제안 질문 블록 | 본문 안에서 클릭 가능한 세로 버튼 | --- ## 10. FAQ 데이터 적재 흐름 ### 10.1 Batch ingest ```mermaid flowchart TD Raw[data/qa_raw.jsonl] Load[q/a + source_created_at 검사] Dedup[동일 질문 최신 source_created_at만 유지] Embed[EmbeddingClient.embed\nis_query=false] Upsert[Qdrant upsert_vectors\nskip_if_older=true] QD[(qa_vectors)] Raw --> Load --> Dedup --> Embed --> Upsert --> QD ``` | 단계 | 내용 | |------|------| | 입력 | `q`/`a` 또는 `question`/`answer` 지원 | | 필수 | 질문, 답변, `source_created_at` | | 중복 | 동일 질문은 최신 `source_created_at`만 유지 | | 임베딩 | 질문 `q`만 document 모드로 임베딩 | | 저장 | payload에 `q`, `a`, `category`, `url`, `source`, `source_id`, 날짜 저장 | | 갱신 | 기존 데이터가 더 최신이면 upsert skip | ### 10.2 Vector Admin API `scripts/admin_service.py`는 `chatbotAdmin`과 운영자가 사용하는 Qdrant 큐레이션 API입니다. | Method | URL | 용도 | |--------|-----|------| | `POST` | `/api/search` | 벡터 의미 검색 | | `POST` | `/api/keyword-search` | 문자열 포함 검색 (`q`, `a`, `both`) | | `GET` | `/api/points` | FAQ 목록/페이지 조회 | | `GET` | `/api/points/{id}` | 단건 조회 | | `POST` | `/api/points` | FAQ 추가 | | `PUT` | `/api/points/{id}` | FAQ 수정, 질문 변경 시 새 ID 생성 후 기존 ID 삭제 | | `DELETE` | `/api/points/{id}` | FAQ 삭제 | | `POST` | `/api/points/delete-batch` | 일괄 삭제 | | `GET` | `/api/categories` | 카테고리 목록/건수 | > `/api/keyword-search`는 Admin 검색용 API입니다. 챗봇 `rag_search`는 같은 Qdrant 문자열 검색 메서드를 직접 호출하되, 벡터 검색 실패 후 guidance 직전에 최대 3건만 보조 검색합니다. --- ## 11. chatbotAdmin 학습 관리 흐름 ```mermaid flowchart TD Excel[엑셀 업로드] Rows[TrainingQaRow 파싱] Similar[유사 FAQ 검색\nFaqSimilarityService] Decision{유사 후보 있음?} Add[Qdrant 즉시 적재] Conflict[training_conflicts 저장\n검토 대기] RowLog[training_job_rows 저장\n행별 결과] Review[운영자 검토] Action{검토 액션} Keep[기존 유지 / 신규 폐기] New[신규 추가] Update[기존 업데이트\n질문 변경 시 delete + create] Excel --> Rows --> Similar --> Decision Decision -->|없음| Add --> RowLog Decision -->|있음| Conflict --> RowLog --> Review --> Action Action --> Keep Action --> New Action --> Update ``` | 저장소 | 역할 | |--------|------| | `training_jobs` | 업로드 작업 단위 상태·집계 | | `training_job_rows` | 모든 행의 결과 (`ADDED`, `CONFLICT`, `SKIPPED_*`, `FAILED`) | | `training_conflicts` | 유사 FAQ 충돌 검토 대상만 저장 | | Qdrant | 최종 FAQ 원천 | ### 검토 액션 | 액션 | 결과 | |------|------| | `keep_existing` / `skip_new` | 기존 FAQ 유지, 신규 데이터 폐기 | | `add_new` / `create_new` | 신규 FAQ 추가 | | `update_existing` | 기존 FAQ 수정. 질문이 바뀌면 기존 FAQ 삭제 후 신규 생성 | | `replace_existing` | 기존 FAQ 삭제 후 신규 FAQ 생성 | 삭제나 교체 전에는 `FaqService`가 기존 FAQ를 버전 스냅샷으로 남깁니다. 다만 현재 검토 화면에는 즉시 되돌리기 버튼은 없으므로, 삭제성 액션에는 확인 UX를 추가하는 것이 안전합니다. --- ## 12. 로깅·통계 Agent 응답은 MongoDB 대화 이력에 저장됩니다. | 필드 | 용도 | |------|------| | `bot_id` | 대화 단위 | | `user_query` | 사용자 발화 | | `ai_response` | 최종 답변 | | `matched_questions` | 참고 FAQ 질문 | | `scores` | rerank 점수 | | `metadata.routeType` | 처리 경로 | | `metadata.intentType` | 도메인 tool 상세 | | `metadata.answer_confidence` | high/medium/low | | `metadata.num_references` | 참고 문서 수 | `chatbotAdmin`의 통계 분석은 이 실제 대화 이력을 기준으로 실패질문, 전체 로그, 많이 묻는 질문, 근거 구분(Qdrant/도메인 툴)을 보여줍니다. --- ## 13. 주요 파일 역할 | 파일 | 역할 | |------|------| | `scripts/run_service_qa.py` | FastAPI 엔트리포인트, `/agent/chat`, `/health` | | `scripts/agent/agent_service.py` | Agent tool loop, pending, 강제 RAG, 도메인 가드, guidance | | `scripts/agent/tool_executor.py` | `rag_search`, `ask_user`, WAS domain tool 실행 | | `scripts/agent/chatbot_tool_client.py` | WAS `/api/v1/tools/*` HTTP client | | `scripts/agent/pending_store.py` | Agent pending 저장소 | | `scripts/handlers/search_handler.py` | Qdrant 검색과 rerank | | `scripts/handlers/prompt_builder.py` | 최종 답변/guidance 프롬프트 | | `scripts/handlers/query_rewriter.py` | 대화 이력 기반 검색어 재작성 | | `scripts/handlers/llm_handler.py` | 최종 LLM 답변 생성 | | `scripts/handlers/response_handler.py` | 대화 이력 저장·응답 보조 유틸 | | `scripts/vector_store.py` | QdrantStore 구현 | | `scripts/admin_service.py` | Vector Admin API | | `scripts/ingest_qa.py` | batch FAQ 적재 | | `scripts/api_clients.py` | LLM/Embedding/Reranker client | --- ## 14. 운영 확인 포인트 | 확인 항목 | 기준 | |-----------|------| | RAG API | `GET :28012/health`가 `ok` 또는 원인 확인 가능한 `degraded` 반환 | | Qdrant 적재 | `/health.vector_count > 0`, Admin 목록에서 FAQ 조회 | | Agent tool | `/agent/chat` 응답에 `toolTrace` 확인 | | FAQ 근거 | FAQ 답변에 `references`, `faqUrls` 포함 | | 도메인 근거 | 도메인 질문에 `intentType`, `domainData`, `uiType` 포함 | | 멀티턴 | clarify 후 다음 발화에서 pending이 이어지는지 확인 | | Web UI | `/web/ask` 응답이 `WebAskResponse` 형태로 카드/버튼을 포함 | | 학습 관리 | 업로드 후 `training_job_rows`, `training_conflicts` 상태 확인 |