문서

Malgn Helper 개발자 가이드 (전체 통합본)

이 문서 하나로 Malgn Helper 프로젝트 전체를 이해하는 것이 목표입니다. 신입·초급 개발자가 사전 지식 없이 읽어도 따라올 수 있도록, 용어부터 차근차근 설명하고 실제 코드·설정·절차를 최대한 구체적으로 적었습니다.

  • 작성 기준일: 2026-06-09
  • 정본 위치: malgn-helper-mng/docs/DEVELOPER-GUIDE.md (이 관리 허브 앱의 /docs에서도 볼 수 있음)
  • "설계(계획)"와 "현재 구현됨"을 구분해서 표기합니다. 문서에 적혀 있어도 아직 코드로 없는 것은 🔜 계획으로 명시합니다.

목차

  1. 한눈에 보는 큰 그림
  2. 먼저 알아야 할 용어 사전
  3. 전체 아키텍처와 데이터 흐름
  4. 인프라 구성요소 (Cloudflare/AWS) 쉽게 이해하기
  5. 레포지토리별 상세
  6. 데이터 모델 (DB 테이블)
  7. LLM·프롬프트 설계
  8. 검색 파이프라인
  9. 인증·보안
  10. 로컬 개발 환경 셋업 (절차)
  11. 배포 절차
  12. 운영 컨벤션
  13. 현재 구현 현황 vs 남은 작업
  14. 자주 만나는 함정 (트러블슈팅)

1. 한눈에 보는 큰 그림

Malgn Helper는 "NotebookLM 수준의 사내 솔루션 전문 고객상담 AI 챗봇"을 만드는 프로젝트입니다. 자사 솔루션(LMS 등) 사용법·안내를 자동화해서 상담 비용을 줄이고 24시간 응답 채널을 확보하는 것이 목적입니다.

핵심 원칙 5가지 (이게 모든 코드의 판단 기준):

  1. 근거 기반 답변 + 출처 인용 — 업로드된 자료에 근거해서만 답하고, 어디서 나온 답인지 출처를 단다.
  2. 정확성·일관성 우선 — 같은 질문엔 늘 같은 답. 잘못된 안내를 내느니 안 내는 게 낫다.
  3. 표준 답변 우선 — 회사가 검증한 "표준 답변"이 있으면 그걸 먼저 쓴다.
  4. "모르면 모른다" — 추측 금지. 애매하면 사람(상담사)에게 넘긴다(에스컬레이션).
  5. 한국어 중심 — 콘텐츠 대부분이 한국어다.

이 목적을 이루기 위해 5개의 독립 레포로 나눠서 개발하고 있습니다.

레포역할한 줄 설명종류
malgn-helper-api백엔드(두뇌)검색·LLM·DB 접근을 전부 담당하는 API 서버Hono / Cloudflare Workers
malgn-helper-pmsPMS 애드온기존 사내 PMS(맑은프로젝트게시판) 안에 끼워 상담사를 돕는 화면Nuxt 3 / Cloudflare Pages
malgn-helper-admin관리자단자료 업로드·표준답변 관리·로그/비용 검토 화면Nuxt 3 / Cloudflare Pages
malgn-helper사용자 챗봇고객이 직접 대화하는 챗봇 화면 (Phase 2 본격화)Nuxt 3 / Cloudflare Pages
malgn-helper-mng관리 허브이 프로젝트 자체의 진척·일정·문서를 조망 (제품 아님)Nuxt 3 / Cloudflare Pages + D1

중요한 구조 원칙: 프론트엔드 3종(pms/admin/helper)은 DB나 LLM에 직접 접근하지 않습니다. 모든 데이터·AI 호출은 반드시 malgn-helper-api를 거칩니다. 이렇게 해야 인증·권한·로깅·캐싱을 한 곳에서 통제할 수 있습니다.

프로젝트는 2단계로 나뉜다

  • Phase 1 — CS 관리자 + AI 추천 답변 (현재 진행 중): 상담사가 고객 문의를 받으면 AI가 답변을 추천하고, 상담사가 채택/수정/거절한다. 그 피드백이 자산으로 쌓인다.
  • Phase 2 — 고객용 상담 챗봇 (예정): 고객이 직접 챗봇과 대화한다. "모르면 모른다" 원칙으로 안전하게 상담사에게 넘긴다.

2. 먼저 알아야 할 용어 사전

초급 개발자가 이 프로젝트에서 자주 마주치는 단어들입니다. 모르는 단어가 나오면 여기로 돌아오세요.

AI / 검색 관련

  • LLM (Large Language Model, 대규모 언어 모델): ChatGPT 같은 "글을 이해하고 생성하는 AI". 여기서는 OpenAI의 gpt-4.1-mini 모델을 주로 쓴다.
  • RAG (Retrieval-Augmented Generation, 검색 증강 생성): LLM이 그냥 머릿속(학습한 지식)으로 답하면 틀린 말(환각)을 한다. 그래서 **먼저 회사 자료에서 관련 문서를 "검색(Retrieval)"해 찾아오고, 그 문서를 근거로 LLM이 답을 "생성(Generation)"**하게 한다. 이 프로젝트의 핵심 방식.
  • 임베딩 (Embedding): 문장을 의미가 담긴 **숫자 배열(벡터)**로 바꾼 것. 예: "환불 어떻게 해요?"를 1536개의 숫자로 변환. 의미가 비슷한 문장은 벡터도 가깝다 → "비슷한 뜻 찾기"가 가능해진다.
  • 벡터 검색 / k-NN: 임베딩(벡터)끼리의 거리를 재서 의미가 가까운 문서를 찾는 검색. k-NN = "가장 가까운 k개 이웃". 단어가 안 겹쳐도 뜻이 비슷하면 찾는다.
  • BM25: 전통적인 키워드(단어) 일치 기반 검색 점수 방식. 단어가 정확히 겹칠수록 점수가 높다.
  • 하이브리드 검색: 벡터 검색(의미) + BM25(키워드)를 함께 써서 둘의 장점을 합친 검색. (※ 이 프로젝트에서는 🔜 계획 단계. 현재는 단순 키워드 검색만 구현됨 — 8장 참고.)
  • 표준 답변 (Standard Answer): 회사가 미리 만들어 검증한 모범 답안. 같은 질문이 오면 AI가 새로 짜내기 전에 이걸 먼저 쓴다.
  • 에스컬레이션 (Escalation): AI가 자신 없을 때 "사람(상담사)에게 넘기기".
  • 환각 (Hallucination): LLM이 그럴듯하지만 사실이 아닌 답을 지어내는 것. 이 프로젝트가 가장 막고 싶어하는 현상.
  • Vision: LLM이 이미지를 보고 이해하는 기능. 게시글에 붙은 캡처 이미지를 분석해 "이건 무슨 화면이다"라고 설명을 단다.

인프라 관련

  • Cloudflare: 전 세계에 서버(엣지)를 둔 클라우드 회사. 이 프로젝트의 프론트·백엔드가 전부 여기서 돈다.
  • Cloudflare Pages: 정적/SSR 웹사이트를 배포하는 서비스. 프론트엔드 4종이 여기에 올라간다.
  • Cloudflare Workers: 서버 코드를 전 세계 엣지에서 실행하는 서비스. 백엔드 API가 여기서 돈다.
  • D1: Cloudflare가 제공하는 SQLite 기반 DB. (관리 허브 mng만 사용)
  • R2: Cloudflare의 파일 저장소(AWS S3 같은 것). 업로드한 원본 파일·이미지를 여기에 둔다.
  • Hyperdrive: Cloudflare Workers에서 외부 MySQL DB에 빠르게 연결하게 해주는 가속·풀링 장치. (커넥션을 매번 새로 여는 비용을 줄여줌)
  • AI Gateway: Cloudflare가 제공하는 LLM 호출 중계소. 우리가 OpenAI를 직접 부르지 않고 이 게이트웨이를 거치면 캐싱·로깅·rate limit(호출 제한) 을 한 곳에서 받을 수 있다.
  • BYOK (Bring Your Own Key): "내 OpenAI 키를 직접 쓴다"는 뜻. AI Gateway를 거치되 비용은 우리 OpenAI 계정으로 청구된다.
  • Aurora MySQL: AWS의 관리형 MySQL. 기존 PMS 운영 데이터가 들어있다.
  • OpenSearch: AWS의 검색 엔진(Elasticsearch 계열). 벡터+키워드 하이브리드 검색용. (🔜 계획)
  • SSR / 프리렌더: SSR = 요청이 올 때마다 서버가 HTML을 만들어 줌. 프리렌더 = 빌드할 때 미리 HTML을 구워둠.

도메인/CS 관련

  • PMS: 사내 "맑은프로젝트게시판". 프로젝트별로 고객 문의(게시글)와 답변(댓글)이 쌓이는 기존 시스템.
  • FRT (First Response Time): 고객 문의가 올라온 뒤 첫 응답까지 걸린 시간. CS 품질 핵심 지표.
  • FCR (First Contact Resolution): 한 번의 응답으로 문제가 해결됐는지.
  • 브리핑 카드: 한 프로젝트(고객사)의 최근 상황을 한 장으로 요약한 카드(담당자·문의 추이·미응답·핫토픽 등).
  • Q&A 평가 카드: 하나의 문의-답변 쌍을 5개 기준(축)으로 채점한 카드.
  • soft delete: 데이터를 진짜로 지우지 않고 status = -1로 표시만 해서 "지운 것처럼" 다루는 방식. 복구·감사에 유리.

3. 전체 아키텍처와 데이터 흐름

   고객 브라우저        상담사 (PMS 화면)        관리자 브라우저
        │                    │                       │
        ▼                    ▼                       ▼
  malgn-helper        malgn-helper-pms        malgn-helper-admin
  (사용자 챗봇)        (PMS 임베드 애드온)        (관리자단)
   Nuxt/Pages           Nuxt/Pages               Nuxt/Pages
        │                    │                       │
        └────────────────────┼───────────────────────┘
                             │  (모든 호출은 HTTPS API 한 곳으로)
                             ▼
                     malgn-helper-api
                   (Hono on Cloudflare Workers)
                             │
        ┌────────────┬───────┴────────┬───────────────┐
        ▼            ▼                ▼               ▼
   Hyperdrive       R2          AI Gateway      OpenSearch
        │       (원본 파일)    (malgn-helper2)   (k-NN+BM25)
        ▼                          │              🔜 계획
   Aurora MySQL                    ▼
   (PMS 운영 DB +              OpenAI API
    hp_* 헬퍼 테이블)        (gpt-4.1-mini)

  ※ 별도로, 프로젝트 진척 관리용 malgn-helper-mng 는
    Cloudflare D1(SQLite)만 사용하고 위 흐름과 독립적이다.

읽는 법:

  • 사용자/상담사/관리자는 각자의 프론트엔드 화면을 본다.
  • 프론트엔드는 데이터가 필요하면 무조건 malgn-helper-api(하나의 URL: https://malgn-helper-api.malgnsoft.workers.dev)에 HTTP 요청을 보낸다.
  • API 서버가 상황에 따라 MySQL을 읽거나, R2에서 파일을 가져오거나, AI Gateway를 통해 OpenAI를 부른다.
  • 프론트엔드는 절대 MySQL·OpenAI를 직접 만지지 않는다.

4. 인프라 구성요소 쉽게 이해하기

이 프로젝트가 실제로 쓰는 인프라를 하나씩 "왜 쓰는지" 중심으로 설명합니다.

구성요소종류이 프로젝트에서의 역할식별자/설정
Cloudflare Pages정적/SSR 호스팅프론트엔드 4종 배포 (helper/admin/pms/mng)빌드 출력 dist/
Cloudflare Workers서버리스 실행백엔드 API(malgn-helper-api) 실행account_id d2b8c5524b7259214fa302f1fecb4ad6
Cloudflare D1SQLite DB관리 허브(mng)의 진척 데이터만DB명 malgn-helper-project
Cloudflare R2객체 저장소업로드 원본 파일·이미지버킷 malgn-helper-files
HyperdriveDB 연결 가속Workers ↔ Aurora MySQL 연결id aea36082dfd84125a0da2063d5b8e5e0
AI GatewayLLM 중계OpenAI 호출 캐싱/로깅/제한게이트웨이명 malgn-helper2
Aurora MySQL관계형 DBPMS 운영 데이터 + hp_* 헬퍼 테이블Hyperdrive 경유
OpenAI APILLM 제공자실제 AI 답변 생성gpt-4.1-mini (BYOK)
AWS OpenSearch검색 엔진하이브리드 검색 (🔜 계획)미구축

Smart Placement: API Workers는 placement.mode = "smart"로 설정돼 있다. 이건 Cloudflare가 "백엔드(OpenAI·MySQL)와 가까운 위치"에서 Worker를 실행하도록 자동 배치하는 기능이다. 한국 엣지에서 OpenAI를 직접 부를 때 생긴 지역 차단·지연 문제를 우회하는 데도 도움이 됐다.

인덱싱(자료를 검색 가능하게 만드는 작업) 전략:

  • MVP(현재 계획): 자료 업로드 시 동기 처리 (텍스트 추출 → 잘게 나누기(청킹) → 임베딩 → 검색엔진에 저장).
  • 2단계: 동영상·대용량이 늘면 Cloudflare Queues + Indexer Worker로 비동기 파이프라인 전환.

5. 레포지토리별 상세

각 레포의 로컬 경로는 ~/Projects/<레포명> 입니다.

5.1 malgn-helper-api — 핵심 백엔드

이게 시스템의 두뇌입니다. 검색·LLM·DB·인증을 전부 여기서 처리합니다. 가장 중요하므로 가장 자세히 설명합니다.

기술 스택

  • 프레임워크: Hono (hono@^4.6.0) — Cloudflare Workers에서 도는 초경량 웹 프레임워크
  • DB 드라이버: mysql2@3.22.4
  • 런타임: Cloudflare Workers (nodejs_compat 플래그)

프로젝트 구조 (src/)

파일역할
src/index.ts메인 파일(약 2,800줄). 모든 라우트·DB 연결·LLM 호출·인증이 여기 모여 있다.
src/llm.tsOpenAI/AI Gateway 호출 추상화 (callOpenAiJson, callWorkersAi). Vision·JSON 강제·타임아웃 처리.
src/classify.ts사용자 분류 규칙(직원/협력사/고객).
src/business-hours.ts영업시간 기준 FRT 계산(월금 09:0017:00 KST, 공휴일 제외).
src/openapi.tsOpenAPI 3.1 스펙 + Scalar UI 문서 페이지 정의.

설정 파일 (wrangler.jsonc)

{
  "account_id": "d2b8c5524b7259214fa302f1fecb4ad6",
  "compatibility_date": "2026-05-27",
  "compatibility_flags": ["nodejs_compat"],
  "observability": { "enabled": true },
  "placement": { "mode": "smart" },
  "r2_buckets":  [{ "binding": "R2",         "bucket_name": "malgn-helper-files" }],
  "hyperdrive":  [{ "binding": "HYPERDRIVE", "id": "aea36082dfd84125a0da2063d5b8e5e0" }],
  "ai":          { "binding": "AI" },         // Workers AI (fallback 용)
  "vars": {
    "AI_GATEWAY_URL":    "https://gateway.ai.cloudflare.com/v1/d2b8c5524b7259214fa302f1fecb4ad6/malgn-helper2/compat",
    "LLM_MODEL_DEFAULT": "openai/gpt-4.1-mini",
    "LLM_MODEL_PREMIUM": "openai/gpt-4.1-mini"
  }
}

시크릿(코드·설정파일에 절대 넣지 않고 wrangler secret put으로 등록):

  • OPENAI_API_KEY — OpenAI 키 (AI Gateway 경유 호출용)
  • JWT_SECRET — 관리자 로그인 JWT 서명 키

HTTP 엔드포인트 (현재 구현된 것, 약 45개)

전체 대화형 문서는 배포된 서버의 /doc(Scalar UI)에서 볼 수 있고, 스펙 JSON은 /doc/openapi.json이다.

헬스체크·문서

  • GET / · GET /healthz — 살아있는지 확인
  • GET /doc · GET /doc/openapi.json — API 문서

WBS (R2에 JSON 저장)

  • GET /wbs · PUT /wbs — 간단한 WBS JSON 읽기/쓰기

PMS 연동 (/pms/*, Hyperdrive→MySQL 조회)

  • GET /pms/projects — 프로젝트 목록(검색·필터·페이지네이션)
  • GET /pms/projects/:id — 프로젝트 한 건 메타
  • GET /pms/groups — 프로젝트 그룹 목록
  • GET /pms/projects/:id/posts — 프로젝트의 게시글 목록(필터: unanswered/customer)
  • GET /pms/posts/:id — 게시글 1건 + 댓글 흐름 (비공개 댓글 본문은 마스킹)

브리핑 카드 (hp_briefing)

  • GET /pms/projects/:id/briefing — 즉석 DB 집계(저장 안 함)
  • POST /pms/projects/:id/briefing/generate — LLM로 브리핑 생성 (24h 캐시, ?force=1 강제 재생성, ?nollm=1 LLM 스킵)
  • GET /pms/projects/:id/briefings — 저장된 브리핑 히스토리
  • GET /pms/briefings/:id — 브리핑 1건
  • DELETE /pms/briefings/:id — soft delete

Q&A 평가 (hp_qa_eval)

  • POST /pms/posts/:id/eval/generate — 문의-답변 5축 평가 또는 (답변 없으면) 추천답변 생성. Vision·캐시 지원
  • POST /pms/posts/:id/announce-eval/generate — 직원 "안내글" 3축 평가 + 3개 변형
  • GET /pms/posts/:id/evals · GET /pms/evals/:id · DELETE /pms/evals/:id
  • POST /pms/projects/:id/standard-answer-suggestions — 평가 결과에서 표준답변 자동 생성

표준 답변 (hp_standard_answer)

  • POST /standard-answers — 등록(label·question·answer 필수)
  • GET /standard-answers · GET /standard-answers/:id — 목록/단건 (projectId 필터: 해당 프로젝트 + 전사 공통(NULL) 포함)
  • PATCH /standard-answers/:id · DELETE /standard-answers/:id
  • POST /standard-answers/:id/use — 사용 카운트 증가

이미지 자산 (hp_image_asset)

  • GET /image-assets · GET /image-assets/:id — Vision 분석된 이미지 메타 조회

관리자 대시보드 (/admin/*)

  • GET /admin/kpi — 홈 KPI 집계
  • GET /admin/cost — LLM 비용·지연·실패 집계(모델별/엔티티별/일별)
  • GET /admin/evals — Q&A 평가 목록(정렬·점수 필터)

인증 (/auth/*)

  • POST /auth/login · POST /auth/logout · GET /auth/me — JWT 쿠키 기반 (자세한 건 9장)

임시 DB 탐색 (/db/*, 안정화 후 제거 예정)

  • GET /db/ping · /db/whoami · /db/tables · /db/columns/:table · /db/sample/:table

DB 접근 방식

  • withConn(c, fn) 헬퍼가 Hyperdrive 바인딩에서 접속 정보를 꺼내 mysql2로 커넥션을 연다. 타임존은 +09:00(KST)로 고정 — PMS의 reg_date가 KST 기준 문자열(YYYYMMDDHHMMSS, varchar(14))이기 때문.
  • reg_date(문자열) ↔ ISO8601 변환은 toIso() 사용.

LLM 호출 방식 (src/llm.ts)

  • callOpenAiJson(env, opts) — AI Gateway URL로 OpenAI를 부르고 JSON만 받도록 강제(response_format: json_object). 이미지가 있으면 Vision 처리. 기본 타임아웃 25초.
  • 모델: 기본/프리미엄 모두 현재 openai/gpt-4.1-mini. Vision이 필요한 호출은 이미지를 첨부해 보낸다.
  • 모든 호출 결과(토큰·지연·비용·캐시 적중·에러)는 hp_llm_log에 기록된다.

package.json 스크립트

pnpm dev        # wrangler dev  (로컬, 보통 :8787)
pnpm run deploy # wrangler deploy  (Workers 배포)
pnpm typecheck  # tsc --noEmit

5.2 malgn-helper-pms — PMS 애드온

기존 사내 PMS 화면 안에 iframe/팝업으로 끼워 넣어 상담사를 돕는 Nuxt 앱. 상담사가 게시글을 보다가 "AI 분석" 버튼을 누르면 이 앱의 카드가 모달로 뜬다.

기술 스택: Nuxt 3 + @nuxt/ui v3 + Tailwind CSS v4. 다크모드는 비활성(라이트 고정). 한글 폰트 Pretendard.

API 기본 URL (코드 상수): https://malgn-helper-api.malgnsoft.workers.dev

주요 디렉터리

pages/
  projects/[id]/index.vue   # 브리핑 카드 워크플로 (임베드 진입점)
  projects/[id]/posts.vue   # 프로젝트 게시글 목록
  posts/[id]/index.vue      # 게시글 상세 + "AI 문의 답변 분석" 모달
  posts/[id]/eval.vue       # Q&A 평가 카드 전용 페이지 (외부 임베드용)
  admin/evals.vue           # 평가 목록(정렬·필터)
  index.vue                 # 데모 + 임베드 스니펫 예시
components/
  BriefingCard.vue          # 고객사 브리핑 카드(모달)
  QaEvalCard.vue            # Q&A 평가 카드(모달) — 로딩/에러/결과 상태 전환
  qa/QaAxisCard.vue         # 5축 중 한 축 카드
  qa/QaScoreSummary.vue     # 5축 종합 점수표
composables/
  useBriefingHistory.ts     # 브리핑 CRUD(서버 저장)
  useBriefingClipboard.ts   # 브리핑 평문 복사
  useQaEvalClipboard.ts     # 평가 평문 복사
utils/pms-html.ts           # fixPmsHtml(): 본문 이미지 상대경로 → 절대경로 보정
types/                      # briefing.ts, qa-eval.ts (데이터 타입 정의)

4개 핵심 카드 컴포넌트

  • BriefingCard: 프로젝트 1개의 요약. 담당자(고객/협력사/맑소), 최근 30일 응대 통계(건수·평균 FRT·미응답·긴급), 핫 카테고리, 알림, FAQ, 이미 안내한 정책 등을 접이식 섹션으로 보여준다. 배지(★우수/⚠주의/✕위험).
  • QaEvalCard: 문의-답변 1쌍의 평가. 메타(문의/응답 시각·FRT·공개여부), 본문, 추천 문의 답변(D축 템플릿), 5축 평가(QaAxisCard ×5), 종합 점수(QaScoreSummary).
  • QaAxisCard: AE 한 축을 ★15 또는 ⚠(주의)로 표시 + 측정 항목 표 + 코멘트.
  • QaScoreSummary: 5축 막대그래프 + 평균 ★.

PMS 임베드 메커니즘 (이 앱의 핵심 트릭)

  • 진입 URL:
    • 브리핑: /projects/{projectId}?modal=open (예: .../projects/1162?modal=open)
    • 평가: /posts/{postId}/eval
  • 임베드 감지: ?embed=1 또는 ?modal=open이면 isEmbedded = true → 내부 네비게이션/breadcrumb을 숨겨 깔끔한 모달처럼 보이게 한다.
  • 닫기 신호 (postMessage): 모달이 닫히면 부모 창(PMS)에 메시지를 쏜다.
    • 브리핑: { type: "malgn-helper:briefing:close" }
    • 평가: { type: "malgn-helper:qa-eval:close" }
    • window.open() 팝업으로 띄운 경우엔 window.close()도 시도.
  • PMS 쪽은 window.addEventListener('message', ...)로 이 신호를 받아 오버레이를 닫는다. (실제 삽입용 iframe/팝업 스니펫이 pages/index.vue에 복붙용으로 들어있다.)

상담사 작업 흐름 (예: Q&A 평가)

  1. 상담사가 PMS 게시글에서 "AI 문의 답변 분석" 클릭 → posts/[id] 모달 오픈
  2. 즉시 POST /pms/posts/:id/eval/generate 호출 → 로딩 표시
  3. 결과가 오면 QaEvalCard가 5축 평가 + 추천 답변을 렌더
  4. 마음에 드는 추천 답변에서 "표준답변으로 저장" 클릭 → POST /standard-answers (label·question·answer·projectId·sourcePostId·sourceAxis='D')
  5. 답변 없는 문의(inquiry-only)면 5축 평가는 생략하고 추천 답변 6종만 제시

배포: wrangler.toml(name malgn-helper-pms, pages_build_output_dir = "dist"), "deploy": "nuxt build && wrangler pages deploy". URL https://malgn-helper-pms.pages.dev.


5.3 malgn-helper-admin — 관리자단

자료 업로드·표준답변 관리·로그/비용 검토를 위한 관리자 화면. 기획(IA)은 17개 메뉴로 완성돼 있고, 우선순위 4개 화면 + 로그인만 실제 동작, 나머지는 stub(빈 골격)이다.

기술 스택: Nuxt 3.13 + @nuxt/ui v3 + Tailwind v4 + lucide-vue-next 아이콘. 폰트 Pretendard(한글)+DM Sans(숫자).

메뉴 구조 (5그룹 × 17메뉴, composables/use-admin-menu.tsADMIN_MENU)

  1. 운영 보드: 홈 / · 미커버 질문 /uncovered · 에스컬레이션 /escalations · 챗봇 로그 /chat-logs · Q&A 평가 /qa-evals
  2. 지식 자산: 표준답변 /standard-answers · 자료 /materials · 이미지 카탈로그 /images · 토픽·서비스 /catalog
  3. 분석·비용: LLM 비용 /cost · 응답 품질 /analytics/quality · 사용량 /analytics/usage
  4. 설정: AI /settings/ai · 안전가드 /settings/safety · 캐싱 /settings/cache · 외부연동 /settings/integrations
  5. 시스템: 계정 /accounts · 감사 로그 /audit-logs · API 문서(외부 링크)

구현 현황

  • 🟢 실제 동작 (5): login.vue, index.vue(홈 KPI → GET /admin/kpi), cost.vue(GET /admin/cost?days=), qa-evals.vue(GET /admin/evals + 행 클릭 시 PMS /posts/:id/eval를 iframe 모달로 임베드), images.vue(GET /image-assets)
  • 🔴 stub (나머지 ~12): AdminPagePlaceholder 컴포넌트로 "준비 중" 표시만.

인증 흐름 (composables/use-auth.ts, middleware/auth.global.ts)

  1. /login에서 loginId+password 입력 → POST /auth/login (credentials: "include")
  2. 서버가 helper_session 쿠키(8시간) 발급
  3. 모든 라우트 진입 시 전역 미들웨어가 GET /auth/me로 검증 → 실패하면 /login?redirect=...
  4. 역할 매핑 roleOf(level): level >= 9 → admin, >= 5 → developer, 그 외 → agent. 메뉴는 역할로 필터링.
  • Cloudflare Access는 아직 미사용(🔜 계획).

권한 매트릭스 (역할별 가능 작업, 설계)

역할은 로그인 시 tb_user.level로 정해진다(level>=9 admin / >=5 developer / 그 외 agent). 메뉴 노출과 동작 권한이 역할로 갈린다.

작업관리자(admin)개발자(developer)상담사(agent)
자료 업로드
표준답변 등록✅(제안만)
표준답변 승인
이미지 편집❌(조회만)
챗봇 로그 조회
설정 변경
계정 관리

백엔드에서도 같은 원칙을 미들웨어로 강제하는 설계다(예: requireRole(...)@malgnsoft.com + 역할 검사 후 403). 🔜 계획.

구현 우선순위 (주차별 계획, 설계)

  • Week 1 — 골격+인증: 레이아웃·로그인·JWT 검증·신규 테이블 DDL ← 대체로 완료
  • Week 2 — 표준답변+이미지: /standard-answers(목록·상세·승인), /images(그리드·검색·태그·숨김) ← images만 완료
  • Week 3 — 자료 업로드+인덱싱: /materials(파일/URL 업로드·재인덱싱·삭제) + R2 + OpenSearch 색인(동기 MVP)
  • Week 4 — 설정+모니터링: /settings/* + /qa-evals·/costqa-evals·cost 완료, settings stub
  • Week 5 — 계정·감사 + Phase 2 준비: /accounts CRUD·초대, /audit-logs

배포: wrangler.toml(name malgn-helper-admin), URL https://malgn-helper-admin.pages.dev.

⚠️ 주의: admin은 API 기본 URL을 각 페이지/컴포저블에 하드코딩(const API_BASE = "https://...")한다. 환경변수가 아니라서, URL이 바뀌면 여러 파일을 같이 고쳐야 한다.


5.4 malgn-helper — 사용자 챗봇

고객이 직접 대화하는 챗봇 프론트엔드. 현재는 스켈레톤(준비 중) 상태. Phase 2에서 본격 개발된다.

  • 현재 app.vue에 "사용자 챗봇 프론트엔드 (준비 중)" 텍스트만 있고, pages/·components/·composables/·UI 라이브러리·인증·API 연동이 모두 아직 없다.
  • 배포 골격(wrangler.toml name malgn-helper, nuxt.config.ts)은 준비됨 → 화면만 채우면 바로 배포 가능.
  • 향후: 채팅 UI, 세션 관리, "모르면 모른다" 기반 에스컬레이션, 비공개 자료 노출 금지 등이 추가될 예정.

5.5 malgn-helper-mng — 프로젝트 관리 허브

제품이 아니라, 이 프로젝트 자체의 진척·일정·문서를 한곳에서 보는 관리 허브. (지금 이 가이드 문서가 들어있는 레포)

  • 화면 5개: 대시보드 /, 현황판 /board, WBS 간트 /wbs, 문서 /docs, 작업 이력 /history
  • 데이터 정본 2종: 진척·작업·간트는 Cloudflare D1(malgn-helper-project), 문서·이력은 docs/ 마크다운(@nuxt/content, 빌드 타임 프리렌더)
  • 렌더링: /·/board·/wbs는 SSR(D1 런타임 조회), /docs·/history는 프리렌더
  • D1이 없으면(로컬 개발) 시드(server/utils/boardSeed.ts)로 자동 폴백
  • 더 자세한 설계는 같은 레포의 PROJECT_MANAGEMENT_BLUEPRINT.md 참고
  • 배포 URL https://malgn-helper-mng.pages.dev

6. 데이터 모델 (DB 테이블)

DB는 두 종류입니다.

  1. Aurora MySQL (pms 데이터베이스): 기존 PMS 운영 테이블(tb_*) + 헬퍼 전용 테이블(hp_*). 백엔드가 Hyperdrive로 접근.
  2. Cloudflare D1: 관리 허브(mng)의 진척 데이터만. (위와 무관, 별도)

6.1 헬퍼 전용 테이블 (hp_*, MySQL)

모두 hp_ 접두사 + soft delete(status TINYINT: 1=활성, -1=삭제) 규칙을 따른다.

테이블용도핵심 컬럼
hp_briefing프로젝트 브리핑 카드 캐시project_id, generated_at, generator(db_only/llm/hybrid), llm_model, llm_input_hash(CHAR 64), prompt_tokens, completion_tokens, latency_ms, briefing_json(LONGTEXT), status
hp_qa_eval게시글 Q&A/안내글 평가post_id, project_id, eval_json(LONGTEXT), overall_score(DECIMAL 3,2), overall_verdict(VARCHAR 100), llm_input_hash, 토큰·지연
hp_standard_answer표준 답변 카탈로그label, question, answer, project_id(NULL=전사공통), source_post_id, source_axis(A~E), usage_count, last_used_at, status
hp_llm_log모든 LLM 호출 감사route, entity_type(briefing/qa_eval/…), entity_id, model, prompt_tokens, completion_tokens, latency_ms, cost_usd(DECIMAL 10,6), cache_hit, error, request_at
hp_image_assetVision 분석 이미지 메타src_path(VARCHAR 500, UNIQUE), title, description, first_seen_post_id, first_seen_project_id, source(inquiry/reply), llm_model, usage_count

인덱스/스키마 핵심 결정(겪은 함정 포함)

  • hp_briefing/hp_qa_eval: (project_id, status, generated_at DESC), (llm_input_hash) 인덱스 → 최신 N건 조회 + 캐시 lookup용
  • hp_qa_eval.overall_verdict는 처음 VARCHAR(20)이었다가 VARCHAR(100)으로 확장(평가 문구가 길어서)
  • hp_standard_answer: InnoDB FULLTEXT(question, answer) + (status, usage_count DESC)(인기순)
  • hp_image_asset.src_path는 utf8mb4에서 UNIQUE prefix를 191자로 잡아야 한다(인덱스 바이트 한계). 한 이미지는 한 번만 분석하고 재사용하는 캐시 키.

🔜 계획인 신규 테이블 (관리자단 자료 업로드·표준답변 고도화용): hp_material/hp_material_chunk(자료·청크), hp_topic(토픽), hp_service(솔루션 카탈로그), hp_standard_answer_image(표준답변↔이미지), hp_account/hp_audit_log(계정·감사), hp_setting(설정).

6.2 레거시 PMS 운영 테이블 (tb_*, 읽기 전용으로 활용)

백엔드는 PMS 운영 테이블을 읽기만 한다. 주로:

  • tb_project(프로젝트), tb_project_group(그룹), tb_post(게시글/문의), tb_post_comment(댓글/응답), tb_user(사용자)
  • reg_datevarchar(14) YYYYMMDDHHMMSS 형식 → 문자열 비교로 기간 필터.
  • 레거시 인벤토리: 약 1,358건의 Q&A 후보, 200+ 프로젝트, 27개 tb_* 테이블. 비공개 댓글(private_yn='Y')이 약 9.3%, 첨부파일 다수(HWP 5천여 건 포함).
  • 부하 대책으로 추가한 인덱스: 예) tb_post(project_id, status, reg_date), tb_post_comment(post_id, status, reg_date) 등 → 캐시 미스 시 쿼리 시간 대폭 단축.

6.3 관리 허브 D1 (mng)

mng 레포는 위와 완전히 별개로 Cloudflare D1을 쓴다: board_meta, stage, task(현황판), wbs_item(간트). 자세한 건 mng 레포의 블루프린트 참고.


7. LLM·프롬프트 설계

7.1 모델과 호출 경로

  • 모델: 현재 기본·프리미엄 모두 openai/gpt-4.1-mini. 이미지 분석(Vision)이 필요한 호출은 이미지를 첨부해서 보낸다.
  • 경로: 코드 → Cloudflare AI Gateway(malgn-helper2) → OpenAI. 게이트웨이를 거치는 이유는 캐싱·로깅·rate limit을 한 곳에서 받기 위해서. (BYOK: 비용은 우리 OpenAI 키로 청구)
  • 모든 LLM 호출은 반드시 게이트웨이를 통과시킨다 — 직접 OpenAI를 부르는 코드는 금지.

7.2 캐싱 (비용·속도 핵심)

  • 입력을 SHA-256 해시(llm_input_hash) 로 만들어 저장한다.
  • 같은 입력이 24시간 이내에 또 오면 저장된 결과를 재사용(새 LLM 호출 안 함).
  • ?force=1로 캐시 무시, ?nollm=1로 LLM 자체를 건너뛸 수 있다.
  • 시스템 프롬프트·표준답변 카탈로그처럼 고정된 컨텍스트는 프롬프트 캐싱에 잘 태운다.

7.3 Q&A 5축 평가 (A~E)

하나의 문의-답변을 5개 축으로 ★1~5 채점한다.

  • A. 답변 정확성·완결성 — 직접 답했는가, 근거·대안·구체 안내·출처 인용이 있는가
  • B. 응대 시간·턴 효율 — FRT(첫 직원 응답 시간), FCR(1회 해결), 재문의 여부, 긴급 SLA
  • C. 톤·친절도 — 인삿말/사과·공감/능동 안내/격식 일관성
  • D. 표준답변화 가능성 — 반복 가능·정책 의존·시간 무관·표준답변 템플릿 초안 생성(이 축의 템플릿이 "표준답변으로 저장" 대상)
  • E. 챗봇 자동화 적합성 + 가시성 — 자동응답 가능성, 공개/비공개, 거절성 답변, PII·보안 위험

7.4 브리핑 카드

  • 한 프로젝트의 최근 30일(일부 지표는 180일/전체)을 요약. LLM은 핫토픽 군집화extras(긴급 건수·FAQ·정책·페르소나) 를 만든다.
  • 상태 라벨(휴면/원활/주의/경고/긴급)은 LLM이 멋대로 정하지 않고, 미응답·부하 임계값 같은 규칙으로 결정한다(일관성 확보).

7.5 안전 원칙(코드/프롬프트에 반영)

  • 표준답변 우선: 답을 새로 생성하기 전에 표준답변을 먼저 찾고, 있으면 그것을 컨텍스트로 우선 사용.
  • "모르면 모른다": 근거 자료가 없으면 추측하지 말고 모른다고 하거나 에스컬레이션(신뢰도 낮으면 사람 연결). (에스컬레이션 자동 분기 로직 자체는 🔜 계획/부분 구현)
  • 출처 인용: 답변에 근거 출처를 달도록 유도. 표준답변 사용 시 usage_count 증가·출처 추적.
  • 비공개 댓글 보호: Phase 1 상담사 보조에는 활용하되, Phase 2 챗봇에는 비공개 본문·출처를 절대 노출하지 않는다.

7.6 Vision(이미지 캡션)

  • 게시글/댓글 본문의 <img src="/data/..."> 같은 상대경로를 절대 URL로 변환(예: https://ppm.malgn.co.kr/data/...)해서 LLM에 넘긴다.
  • 분석 결과(title·description)는 hp_image_asset에 저장하고, src_path UNIQUE로 한 번만 분석 후 재사용(비용 절감).

8. 검색 파이프라인

이 장은 "설계(목표)"와 "현재 구현"의 차이가 가장 크다. RAG의 심장부라서 가장 자세히 설명하지만, 대부분은 아직 🔜 계획이라는 점을 먼저 기억하자. 지금 코드에 있는 건 8.1뿐이다.

8.1 현재 구현됨 — LIKE 키워드 검색

지금 백엔드에 실제로 있는 검색은 MySQL LIKE(부분일치) 한 가지다. 벡터도, OpenSearch도 아직 없다.

  • 표준답변 검색: question/answer/label 컬럼 LIKE '%검색어%'
  • 이미지 검색: title/description LIKE
  • 게시글 검색: subject/작성자 LIKE

한계: 한국어는 단어가 정확히 안 겹치면 못 찾는다("환불"↔"반환"). 그래서 의미 기반 검색(RAG)이 필요하고, 아래 8.2~가 그 설계다.

8.2 RAG 전체 흐름 (설계) 🔜 계획

RAG는 두 갈래의 작업으로 이뤄진다. ① 미리 자료를 검색 가능하게 만들어 두는 인덱싱(쓰기 시점), ② 질문이 올 때 찾아서 답하는 질의(읽기 시점).

[인덱싱 — 자료가 추가될 때 1회]
 자료(파일/URL/기존 Q&A)
   → 텍스트 추출(extract)
   → 청킹(chunking, 긴 글을 의미 단위로 분할)
   → 임베딩 생성(각 청크 → 1536차원 벡터)
   → OpenSearch 색인 저장 (원문 + 벡터)

[질의 — 사용자/상담사가 물어볼 때마다]
 질문
   → (A) 표준답변 우선 매칭   ── 있으면 그걸 최우선 컨텍스트로
   → (B) 질문을 임베딩 → OpenSearch 하이브리드 검색(k-NN + BM25)
   → 상위 N개 청크를 근거로 모음(+표준답변)
   → LLM 프롬프트에 "이 근거들로만, 출처를 달아 답하라" 로 주입
   → 답변 생성 + 출처 인용
   → 근거가 부족/신뢰도 낮으면 "모른다" 또는 에스컬레이션

핵심은 LLM이 자기 지식으로 답하지 않고, 우리가 찾아준 근거 안에서만 답하게 만드는 것이다. 이래야 환각을 막고 출처를 달 수 있다.

8.3 청킹(chunking) 전략 🔜 계획

긴 문서를 통째로 임베딩하면 검색 정확도가 떨어진다(한 벡터에 너무 많은 의미가 섞임). 그래서 의미 단위로 잘게 나눈다.

  • 한 청크는 대략 한 문단~몇 문단 수준(겹침(overlap)을 약간 둬 문맥 끊김 방지).
  • 스레드형 게시글(문의+여러 댓글)은 하나의 문서로 압축하되, 시간순 + 화자 라벨(문의자/직원)을 유지.
  • 청크는 hp_material_chunk(설계) 또는 OpenSearch chunks 인덱스에 저장하고, 원본 문서 메타(documents)와 연결한다.

8.4 OpenSearch 인덱스 설계 🔜 계획

검색 엔진(AWS OpenSearch)에는 보통 3개 인덱스를 둔다.

인덱스담는 것검색 방식
documents원본 문서 메타(제목·출처·프로젝트·가시성 등)필터/집계
chunks청크 본문 + 임베딩 벡터(1536d)k-NN(벡터) + BM25(본문)
standard_answers표준답변 질문/답변우선 매칭
  • k-NN 필드: 1536차원(임베딩 모델 출력 차원) 벡터. 코사인 유사도로 가까운 청크를 찾는다.
  • 가시성 필드가 중요: 청크마다 body_public(공개 댓글만) / body_internal(비공개 포함)을 구분 색인하고, has_internal_only_answer(공개엔 답이 없고 비공개에만 있음 → 에스컬레이션 후보) 플래그를 둔다.
    • Phase 1 상담사용: body_internal까지 검색 가능.
    • Phase 2 챗봇용: body_public만 검색 — 비공개 본문·출처를 절대 노출하지 않기 위해.

8.5 하이브리드 점수 결합 🔜 계획

벡터 검색(의미)과 BM25(키워드)는 각각 약점이 있다.

  • 벡터만: 정확한 고유명사·코드·버전번호를 놓칠 수 있음.
  • BM25만: 표현이 다르면(동의어) 못 찾음.

그래서 두 점수를 합쳐(하이브리드) 랭킹한다. 보통 각 검색의 상위 결과를 가져와 정규화 후 가중합하거나, RRF(Reciprocal Rank Fusion) 같은 순위 결합을 쓴다. 구체 가중치·결합식은 인덱싱 착수 시 튜닝 대상.

8.6 표준답변 우선 매칭 플로우 🔜 계획

검색보다 표준답변이 항상 우선이다(원칙 3). 흐름:

  1. 질문에서 LLM이 scope(common=전사 공통 / service=특정 솔루션)와 topic을 추론.
  2. scope=service면 사용자의 service_tag로 표준답변을 필터, common이면 서비스 무관.
  3. 후보가 여럿이면 tags 매치 수 + usage_count(많이 쓰인 것) 우선.
  4. 선택된 표준답변 N개를 LLM 컨텍스트 맨 앞에 붙여 "검증된 답이 있으니 이를 우선 반영하라"고 지시.
  5. 표준답변이 실제로 채택되면 POST /standard-answers/:id/useusage_count 증가(인기·신뢰 신호 축적).

8.7 레거시 데이터 적재(ingestion) 🔜 계획

기존 PMS에는 약 1,358건의 Q&A 후보가 있다(맑은소프트 사이트 1,146 + 내부 212, 200+ 프로젝트 분포). 이걸 검색 자산으로 끌어오는 게 초기 지식 확보의 핵심이다.

후보 추출 필터(예)

WHERE p.status = 1
  AND p.comm_cnt >= 1                       -- 답변(댓글)이 최소 1개
  AND p.is_task=0 AND p.is_schedule=0 AND p.is_poll=0 AND p.is_notice=0  -- 업무/일정/투표/공지 제외
  AND p.subject NOT LIKE '테스트%'
  AND CHAR_LENGTH(p.content) >= 20          -- 너무 짧은 글 제외

데이터 품질 이슈와 처리

이슈처리
본문이 HTML태그 제거·엔티티 디코드로 평문화
이미지만 있는 글Stage A(텍스트만) → B(OCR) → C(Vision LLM) 단계적
/data/... 내부 경로R2 서명 URL 또는 프록시로 치환
스레드형(댓글 여러 개)1개 문서로 압축(시간순 + 화자 라벨)
비공개 댓글(private_yn='Y', 약 9.3%)body_internal로만 활용, 챗봇(공개)엔 미노출
첨부파일(다수)종류별 단계 처리(아래)

첨부파일 처리 단계

종류Phase 1 MVPPhase 1 후반Phase 2
텍스트 문서(PDF/DOCX/HWP/XLSX)텍스트 추출→본문 부착청킹·구조 개선
이미지메타만(Stage A)OCR(Stage B)Vision LLM(Stage C)
슬라이드(PPTX)텍스트 추출슬라이드 단위 청킹
동영상메타만Queue + 트랜스크립트(Whisper)

한국어 함정: HWP(한컴) 파일이 약 5,094건. 표준 추출기로는 안 되고 hwp.js 같은 별도 추출기가 필요하다.

8.8 미정 결정 (TBD)

인덱싱 착수 직전에 정해야 할 것들:

  • 임베딩 모델: OpenAI vs 오픈모델 vs 한국어 특화 — 한국어 검색 품질·비용으로 결정(차원수가 인덱스 설계에 직결).
  • 인덱싱 동기/비동기 전환 시점: MVP는 동기, 동영상·대용량 도입 시 Cloudflare Queues + Indexer Worker로.
  • 하이브리드 가중치/결합식(8.5).

9. 인증·보안

9.1 백엔드 로그인 (JWT + 쿠키)

  • POST /auth/login : loginId+password 검증. 직원만 통과(이메일 @malgnsoft.com 또는 tb_user.company='맑은소프트').
  • 성공 시 httpOnly 쿠키 helper_session 발급. TTL 8시간. secure, sameSite=None(크로스 사이트 허용 — 프론트와 API 도메인이 다르므로).
  • GET /auth/me로 현재 세션 확인(만료 시 401 + 쿠키 삭제), POST /auth/logout으로 쿠키 제거.
  • 프론트엔드는 모든 요청에 credentials: "include"를 붙여 쿠키를 보낸다.

9.2 CORS

  • 허용 origin: *.pages.dev, *.malgnsoft.com, localhost/127.0.0.1.
  • credentials: true(쿠키 허용), 메서드 GET/PUT/POST/PATCH/DELETE/OPTIONS.

9.3 사용자 분류 (classify.ts)

  • 직원: @malgnsoft.com 또는 company='맑은소프트'
  • 협력사: 화이트리스트(플로즈/옐로우윈/온케어/송한나 등)
  • 고객: 그 외
  • ⚠️ 이름·작성 패턴 같은 걸로 추정하지 말 것 — 명시 규칙만 사용.

9.4 비공개 댓글

  • API 응답에서 private_yn='Y' 댓글의 본문은 마스킹(null) 처리.
  • LLM 입력에서도 정책에 따라 분리(Phase 1 상담사용 body_internal vs Phase 2 챗봇용 body_public).

9.5 🔜 계획 — Cloudflare Access

  • 관리자/탐색 라우트(/admin/*, /db/*)를 Cloudflare Access(Zero Trust)로 보호하고 @malgnsoft.com 화이트리스트 + 메일 OTP/SSO를 거는 설계가 있으나 아직 도입 전. 현재는 위 JWT 쿠키 방식이 실제 인증.

10. 로컬 개발 환경 셋업 (절차)

10.1 공통 준비물

  • Node.js(LTS) + pnpm + wrangler(Cloudflare CLI). npm i -g wrangler 또는 pnpm dlx wrangler.
  • 처음이면 wrangler login으로 Cloudflare 계정 인증.

10.2 백엔드 malgn-helper-api

cd ~/Projects/malgn-helper-api
pnpm install
# 시크릿은 .dev.vars 파일에 두거나(로컬), 배포본은 wrangler secret으로 관리
#   .dev.vars 예시:
#   OPENAI_API_KEY=sk-...
#   JWT_SECRET=...
pnpm dev            # wrangler dev → http://127.0.0.1:8787
# 문서 확인: http://127.0.0.1:8787/doc
  • 로컬에서 MySQL(Hyperdrive)·R2에 붙으려면 적절한 바인딩/접속정보가 필요하다. 빠른 확인은 /healthz, /db/ping부터.

10.3 프론트엔드 (pms / admin)

cd ~/Projects/malgn-helper-pms      # 또는 -admin
pnpm install
pnpm dev                            # http://localhost:3000
  • 이 둘은 API 기본 URL이 운영 Workers 주소로 하드코딩돼 있다. 로컬 API를 바라보게 하려면 코드의 API_BASE 상수를 임시로 바꾼다(커밋하지 말 것).

10.4 사용자 챗봇 malgn-helper

cd ~/Projects/malgn-helper
pnpm install && pnpm dev
  • 아직 스켈레톤이라 화면은 "준비 중" 텍스트만 나온다.

10.5 관리 허브 malgn-helper-mng

cd ~/Projects/malgn-helper-mng
pnpm install
pnpm dev                            # http://localhost:3000 (D1 없으면 시드 폴백)
# D1 영속 편집까지 보려면:
pnpm db:apply && pnpm db:seed       # 원격 D1에 스키마+시드 적용

11. 배포 절차

11.1 권장: 워크스페이스 일괄 스크립트

모든 레포는 malgn-helper(워크스페이스 루트)의 scripts/deploy.sh로 배포한다.

cd ~/Projects/malgn-helper
./scripts/deploy.sh <repo-name> "<commit message>"
  • <repo-name>: malgn-helper | malgn-helper-admin | malgn-helper-api | malgn-helper-pms | malgn-helper-mng
  • 스크립트가 순서대로 수행:
    1. 해당 레포에서 git add -A && git commit (변경 없으면 skip)
    2. git push
    3. pnpm run deploywrangler.toml 유무로 Pages/Workers 자동 분기
    4. docs/history/history.{yyyyMMdd}.md## 배포 섹션에 항목 append

예시

./scripts/deploy.sh malgn-helper-api "feat: 챗 엔드포인트 추가"
./scripts/deploy.sh malgn-helper-mng "chore: 진척 갱신"

11.2 배포 규칙 (중요)

  • 변경된 레포만 배포. 전체 일괄 배포 금지.
  • pnpm run deploy로 호출deploy는 pnpm 예약어라 pnpm deploy는 충돌. 항상 run 명시.
  • account_id 주입 방식 차이:
    • Workers(wrangler.jsonc)는 account_id 필드를 지원 → 파일에 명시(api).
    • Pages(wrangler.toml)는 account_id 필드 미지원deploy.shCLOUDFLARE_ACCOUNT_ID 환경변수로 주입(helper/admin/pms/mng). 수동 배포 시엔 CLOUDFLARE_ACCOUNT_ID=... pnpm run deploy.
  • Nuxt Pages 빌드 출력 디렉터리는 dist/ (pages_build_output_dir = "dist").
  • Secret 변경은 별도wrangler secret put <KEY> 후 배포.
  • 배포 실패 시 사유를 history에 함께 기록.
  • 같은 날 추가 배포는 같은 history 파일에 항목만 누적(덮어쓰기 금지).

11.3 mng 전용 주의 (D1)

mng는 D1 스키마/시드를 바꿨다면 배포 전에 따로 적용해야 한다(배포 스크립트엔 미포함).

cd ~/Projects/malgn-helper-mng
pnpm db:apply    # wrangler d1 migrations apply malgn-helper-project --remote
pnpm db:seed     # server/db/seed.sql 적용

11.4 수동 배포 (스크립트 미사용 시)

cd ~/Projects/<repo>
git add . && git commit -m "<message>"
git push
# Pages면:
CLOUDFLARE_ACCOUNT_ID=d2b8c5524b7259214fa302f1fecb4ad6 pnpm run deploy
# Workers(api)면:
pnpm run deploy
# 그 뒤 docs/history/history.{yyyyMMdd}.md에 배포 항목 직접 추가

12. 운영 컨벤션

  • 작업 이력: malgn-helper/docs/history/history.yyyyMMdd.md — 하루 한 파일에 누적. 배포·오류·결정사항 기록.
  • Git: 단일 main 브랜치. 커밋·푸시는 보통 요청 시. 무관한 파일을 끌어들이지 않기.
  • soft delete: 데이터는 물리 삭제하지 않고 status = -1로 표시.
  • LLM 비용 관측: 모든 호출이 hp_llm_log에 쌓이고, GET /admin/cost(admin cost.vue)에서 모델별/일별로 본다.
  • 문서 정본 동기화: 코드·구조가 바뀌면 관련 docs/*.md를 현행화. 특히 mngdocs/malgn-helper/docs/(워크스페이스 정본)을 반영.
  • mng 시드 3종 동기화: mng에서 진척 데이터를 바꿀 땐 server/db/seed.sql(D1 정본) + server/utils/boardSeed.ts(폴백) + app/utils/wbsData.ts(간트)를 함께 갱신.

13. 현재 구현 현황 vs 남은 작업

2026-06-09 기준 스냅샷. 가장 자주 묻는 "어디까지 됐나"를 정리.

✅ 동작 중 (운영/베타)

  • API: Hono Workers 운영. PMS 연동·브리핑·Q&A 평가·표준답변 CRUD·이미지 Vision 캡션·관리자 KPI/cost·인증(JWT 8h)·LLM 게이트웨이(malgn-helper2, gpt-4.1-mini) 안정화.
  • PMS 애드온: 브리핑/Q&A 평가 카드 + 임베드(?embed=1/?modal=open) + 실 API 연동 + 표준답변 저장 + inquiry-only 모드. PMS에서 일상적으로 사용.
  • 관리자단: 골격 + 5그룹×17메뉴 IA + 로그인 + 우선순위 4화면(홈 KPI/cost/qa-evals/images).
  • DB: Aurora MySQL에 hp_* 5테이블 적용 + 레거시 인덱스 보강.
  • 관리 허브(mng): 대시보드/현황판/WBS/문서/이력 운영, D1 + 시드 폴백.

🔜 남은 작업 (요약)

  • 검색/RAG 본체: OpenSearch 하이브리드 인덱싱·검색, 임베딩 모델 선정, 자료 업로드→인덱싱 파이프라인.
  • 관리자단: 자료 업로드(R2)·표준답변 관리·AI 시연 등 stub 12종 실데이터화.
  • 사용자 챗봇(malgn-helper): 채팅 UI 처음부터. (Phase 2)
  • 에스컬레이션 자동 분기·안전 가드·단위/통합/최종 테스트·개발자/상담사 교육·서비스 연동(SSO/CS).
  • Cloudflare Access 도입, 임시 /db/* 엔드포인트 제거.

단계별 목표(로드맵 요약)

  • Phase 1 완료 기준: 상담사가 관리자/PMS에서 문의를 받아 AI 추천을 채택/수정해 응답 가능 + 자료·표준답변·피드백 누적.
    • KPI: 추천 채택률 ≥60%(무수정 ≥30%), 인용 정확도 ≥95%, 잘못된 안내 0건, 응답 p95 ≤8초.
  • Phase 2 완료 기준: 고객이 챗봇과 직접 대화, 자동 해결 또는 안전 에스컬레이션.
    • KPI: 자동 해결률 ≥50%, 에스컬레이션 정확 ≥90%, 만족도 ≥80%, 환각 0건.

14. 자주 만나는 함정 (트러블슈팅)

겪었거나 겪기 쉬운 문제들:

  1. OpenAI 지역 차단/지연 — 한국 엣지에서 OpenAI 직접 호출이 막히거나 느릴 수 있다. → Workers placement.mode = "smart" + AI Gateway 경유로 우회.
  2. 이미지가 LLM/화면에서 안 보임 — 본문 이미지가 /data/... 상대경로라서. → fixPmsHtml()(pms) / 서버단 절대화로 https://ppm.malgn.co.kr/...로 변환.
  3. utf8mb4 UNIQUE 인덱스 길이 초과hp_image_asset.src_path 등에서 인덱스 바이트 한계. → UNIQUE prefix를 191자로.
  4. 평가 문구 잘림overall_verdict가 VARCHAR(20)이라 잘림. → VARCHAR(100) 으로 확장.
  5. Hyperdrive stale read — 캐시된 오래된 값이 읽힘. → 캐시 TTL 인지하고, 즉시성이 필요한 경로는 주의.
  6. CORS/쿠키 안 됨 — 프론트·API 도메인이 다르므로 credentials: include + 서버 sameSite=None; secure 둘 다 필요.
  7. pnpm deploy가 이상하게 동작 — pnpm 예약어 충돌. → 반드시 pnpm run deploy.
  8. Pages 배포에서 account_id 에러 — Pages wrangler.tomlaccount_id 미지원. → CLOUDFLARE_ACCOUNT_ID 환경변수로 주입.
  9. mng 데이터가 화면마다 다름 — 진척 시드 3파일(seed.sql/boardSeed.ts/wbsData.ts) 중 일부만 고쳐서 생기는 불일치. → 항상 셋을 함께 갱신.
  10. admin에서 API URL 바꿨는데 일부만 적용API_BASE가 여러 파일에 하드코딩. → 전체 검색해서 같이 수정(또는 추후 환경변수화).

이 문서를 고치는 사람에게: 코드가 바뀌면 여기 "설계 vs 구현" 표기(🔜 계획)와 13장 스냅샷을 같이 갱신해 주세요. 특히 검색(8장)·관리자단(5.3)·사용자 챗봇(5.4)은 진척이 빠른 영역입니다.

Malgn Helper(고객상담 AI 챗봇) 프로젝트 문서·작업 이력