포스트

물류창고 재고 조사 RAG 챗봇 과제 실행 기록

주어진 과제 문서를 그대로 따라 실행하면서, 데이터 준비부터 검색·답변 생성·공개까지 실제로 한 작업과 결과를 기록한 학습일지입니다.

물류창고 재고 조사 RAG 챗봇 과제 실행 기록

아직 완전히 이해하지 못한 상태에서 실행한 기록입니다. 이 글은 “이렇게 설계했다”가 아니라 주어진 과제 문서를 그대로 따라 실행해 보며 무엇을 했고 무엇이 일어났는지를 적은 것입니다. 이해가 부족한 부분은 “지금 이해하지 못한 것”에 따로 모아 두었습니다. 나중에 다시 읽고 채워 넣기 위해 작성했습니다.

이 글의 위치

이 실습은 과제 문서를 기준으로 진행했습니다. 과제 문서는 “물류창고 재고 조사 안내 챗봇”을 만들기 위해 네 단계의 계약을 정해 두었습니다.

단계과제 문서가 정한 것
데이터청크는 { id, text, url, section } 배열, id는 WH-001부터 연속, text는 최소 120자, 원문에 없는 말을 보태지 않음
임베딩모델 onnx-community/embeddinggemma-300m-ONNX, dtype: "q8", 768차원, { pooling: "mean", normalize: true }, 문서·질문 접두어
검색정규화된 벡터의 내적(코사인 유사도), 상위 3개, 1위 점수가 0.55 미만이면 “관련 자료가 약함” 표시
생성Ollama qwen3.5:2b, 정해진 시스템 메시지, 프롬프트 ①~⑦ 순서, 근거 ID를 [WH-xxx]로 표시

작업 원칙은 과제 문서에 적힌 그대로 구현하고, 문제가 보이면 알리기만 하고 고치지 않는 것이었습니다. 고치는 일은 결과를 비교한 뒤에 따로 요청했습니다.

전체 흐름 한눈에 보기

순서한 일결과
1CLAUDE.md에 첫 버전 작성 (STEP 1)커밋
2CLAUDE.md에 데이터 계약 절 추가과제 문서 내용 반영
3위키백과 자료로 청크 초안 만들기10개 청크
4교육용 SOP(docs/sop.md) 작성 후 청크 3개 추가13개
5입고 검수 문서(docs/receiving.md) 청크 2개 추가15개
6임베딩 계약 절 추가, embedder.mjs와 build-index.mjs 작성벡터 15개, 768차원
7검색 계약 절 추가, search.mjs 작성질문별 상위 3개 출력
8--hybrid 옵션 추가 (BM25)옵션 동작, 효과는 확인 못 함
9생성 계약 절 추가, prompt.mjs와 ask.mjs(--show-prompt) 작성프롬프트 출력 확인
10ollama.mjs 작성, ask.mjs 완성 (스트리밍, ✓ 표시)실제 답변 확인
11대화형 모드 추가파이프 입력으로 확인
12시스템 메시지 문구 비교 실험최종 문구 선택
13README 작성 (8개 절)실험 결과 기록
14제출 전 점검데이터·명령·개인정보 확인
15커밋, 공개 저장소 분리, 기록 정리아래 “저장소와 공개” 참고

1. CLAUDE.md부터 만들기

과제 문서는 CLAUDE.md에 계약을 적어 두는 방식이었습니다. 한 번에 다 적지 않고, 절을 하나씩 추가했습니다.

  • 첫 버전 (STEP 1): 제품 설명, 비목표(웹 화면, 서버, 배포, 실시간 재고 조회, 로그인), 환경(내 PC, Node.js, Ollama)을 적었습니다. 이 커밋에서 Co-Authored-By 줄이 들어갔고, 사용자가 --amend로 지웠습니다.
  • 데이터 계약 절: 청크 원본 위치, 필드, id 규칙, text 규칙, url 형식을 적었습니다. 사용자가 준 내용을 거의 그대로 옮겼습니다.
  • 임베딩 계약 절: 모델, dtype, 옵션, 차원, 입력 접두어, embedder.mjs가 export할 이름, 벡터 저장 형식, 모델이 바뀌었을 때 검색을 거부하는 규칙을 적었습니다.
  • 검색 계약 절: 코사인 유사도 = 내적, top-k 기본 3, WEAK_THRESHOLD = 0.55, CLI는 직접 실행했을 때만 돌게 하는 규칙을 적었습니다.
  • 생성 계약 절: Ollama 호출 형식, NDJSON 처리, 시스템 메시지, 프롬프트 ①~⑦ 순서, ✓ 표시 방식을 적었습니다.
  • 스택과 작업 규칙 절 (나중에 추가): 의존성은 @huggingface/transformers 하나만 두기, 요청한 STEP의 파일만 고치기, 확인하지 못한 것은 [검증 필요]로 표시하기를 적었습니다.

계약을 바꿔야 할 때는 먼저 묻고 바꾸는 방식이었습니다. 예를 들어 실행 명령에 --k, --no-rag, 대화형 모드를 추가했을 때, 그리고 시스템 메시지를 바꿨을 때 CLAUDE.md도 함께 고쳤습니다.

2. 데이터: 청크 15개 만들기

청크 원본은 data/chunks.json입니다. 출처는 세 종류입니다.

(가) 위키백과 자료 10개 (WH-001 ~ WH-010)

과제 문서가 정한 위키백과 URL 7개를 바탕으로 청크 초안을 만들도록 요청했습니다. 과제 규칙에는 URL을 실제로 열어 보지 못한 항목은 [검증 필요]를 붙이라는 내용이 있었습니다. 이 글을 쓰는 시점에 [검증 필요] 표시는 남아 있지 않습니다. 다만 위키백과 원문을 어떤 방식으로 가져왔는지는 이 글에서 자세히 기록하지 않았습니다. 이 부분은 다시 확인이 필요합니다.

idsectionurl
WH-001재고의 정의위키백과 “재고”
WH-002재고자산의 정의와 종류위키백과 “재고자산”
WH-003재고의 범위위키백과 “재고관리”
WH-004재고관리의 목적위키백과 “재고관리”
WH-005상비품과 비상비품위키백과 “재고관리”
WH-006재고수량 계산식위키백과 “재고관리”
WH-007ABC 분석위키백과 “ABC 분석”
WH-008선입 선출법(FIFO)위키백과 “선입선출법과 후입선출법”
WH-009창고 관리 시스템(WMS)위키백과 “창고 관리 시스템”
WH-010RFID와 바코드의 차이위키백과 “RFID”

원문에 없는 말을 보태지 않는다는 규칙 때문에 두 군데를 손봤습니다.

  • WH-006 앞에 “재고수량을 정할 때”를 붙였습니다. 앞 문맥 없이 읽혀야 한다는 규칙 때문입니다.
  • WH-005에서 “앞에서 말한 바와 같이”를 뺐습니다.

WH-008(FIFO)은 회계에서 원가를 계산하는 방법이라서, 현장의 “먼저 들어온 것 먼저 내보내기” 작업 규칙과 뜻이 다릅니다. 이 점은 알려만 두고 고치지 않았습니다.

(나) 교육용 SOP 3개 (WH-011 ~ WH-013, docs/sop.md)

직접 쓴 자료라서 docs/sop.md에 두고 ## 절 하나를 청크 하나로 만들었습니다. 이 문서는 교육용 예시이고, 실제 회사의 절차가 아닙니다. 그래서 조사 주기 숫자(A등급 매월, B등급 분기, C등급 반기)는 제가 예시로 정한 값입니다. 처음에는 사용자가 “교육용 예시 SOP를 써 달라”고 요청했고, 문서 맨 위에 안내 문장을 넣었습니다.

idsectionurl
WH-011순환재고조사docs/sop.md#순환재고조사
WH-012실사 차이 처리docs/sop.md#실사-차이-처리
WH-013안전 수칙docs/sop.md#안전-수칙

(다) 입고 검수 문서 2개 (WH-014 ~ WH-015, docs/receiving.md)

사용자가 준 원문을 그대로 docs/receiving.md에 넣었습니다. 루트에 같은 이름의 파일이 하나 더 있었는데, 사용자가 지우라고 해서 지웠습니다.

idsectionurl
WH-014입고 검수 순서docs/receiving.md#입고-검수-순서
WH-015입고 수량 불일치docs/receiving.md#입고-수량-불일치

청크를 만든 뒤에는 모든 청크가 120자 이상인지, id가 연속인지, text가 원문과 같은지를 확인했습니다. 점검 결과 15개 모두 기준을 통과했고, [검증 필요] 표시는 남지 않았습니다.

3. 임베딩과 인덱스 만들기

src/embedder.mjs는 임베딩 계약이 정의된 유일한 파일입니다.

  • 파이프라인은 pipeline("feature-extraction", MODEL, { dtype: "q8" })로 한 번만 만들어 재사용합니다.
  • 처음 모델을 불러올 때 “임베딩 모델 불러오는 중…(첫 실행은 다운로드)”을 출력합니다.
  • 문서는 "title: " + section + " | text: " + text, 질문은 "task: search result | query: " + question으로 입력합니다.

node src/build-index.mjs를 실행하면 청크를 읽어 벡터를 만들고 data/vectors.json에 저장합니다. 실행 결과는 다음과 같습니다.

  • 저장: data/vectors.json — 15청크 × 768차원
  • 학습용으로 출력한 WH-001 벡터의 앞 5개 값: [0.061649, -0.004075, 0.017883, 0.007855, 0.008198]
  • 벡터 길이(노름): 1.000000

노름이 1.000000이라는 것은 정규화가 되었다는 뜻으로 과제 문서에 적혀 있었습니다. 다만 왜 그런 값이 나오는지는 아직 설명하지 못합니다.

build-index는 입력 검사도 합니다. 필드가 빠지거나 id가 중복되면 중단하고, 120자 미만이면 경고만 합니다. 다만 이상한 입력을 넣어 실제로 중단되는지는 권한 문제로 시험하지 못했습니다.

청크를 고친 뒤에는 build-index를 다시 실행해야 벡터가 바뀝니다. 이 점을 확인하지 않고 검색 결과를 보면 이전 결과가 그대로 나옵니다.

4. 검색 1단계

node src/search.mjs "질문"은 질문을 임베딩하고, 모든 청크와 내적을 구해 점수 순으로 정렬합니다. 출력은 순위, id, 점수, 막대(20칸), 섹션, 본문 앞 60자, 출처입니다.

질문 9개를 넣어 본 결과는 다음과 같습니다. 괄호 안은 2위와 3위입니다.

질문1위점수2위 · 3위
재고 실사 차이가 나면 어떻게 처리해?WH-0120.793WH-015 0.743 · WH-001 0.610
실사 수량이 장부랑 안 맞으면?WH-0120.707WH-015 0.671 · WH-006 0.624
ABC 분석이 뭐야?WH-0070.737WH-011 0.544 · WH-005 0.457
RFID랑 바코드 차이WH-0100.814WH-014 0.531 · WH-008 0.493
안전재고는 어떻게 계산해?WH-0060.706WH-001 0.630 · WH-002 0.594
A등급 품목은 얼마나 자주 세?WH-0110.638WH-007 0.633 · WH-005 0.578
우리 창고 지금 A-01 재고 몇 개야?WH-0150.554WH-001 0.541 · WH-011 0.536
내일 날씨 어때?WH-0060.454 ⚠WH-005 0.427 · WH-011 0.425
김치찌개 레시피 알려줘WH-0060.445 ⚠WH-005 0.370 · WH-011 0.369

관찰한 것은 다음과 같습니다.

  • 단어가 겹치지 않아도 찾습니다. “장부랑 안 맞으면”이라는 질문도 “센 수량과 시스템 수량이 다르면”으로 시작하는 WH-012가 1위였습니다. 과제 문서는 이것을 “임베딩이 뜻을 비교하기 때문”이라고 설명합니다. 제가 그 설명을 직접 확인한 것은 아닙니다.
  • 1위는 항상 나옵니다. 날씨와 김치찌개 질문도 1위 청크가 나왔습니다. 그래서 점수 기준(0.55)이 따로 필요합니다.
  • 점수 기준 근처는 판단이 어렵습니다. A-01 질문은 0.554로 기준을 넘어서, 경고 없이 1위 청크가 나왔습니다. 자료에는 실시간 재고 수량이 없습니다.
  • 가까운 단어에 끌립니다. “안전재고” 질문의 2위는 “안전”이 아니라 “재고”라는 단어에 끌린 WH-001이었습니다.
  • 자료 추가 전후 비교: docs/receiving.md를 넣기 전에는 “트럭이 도착하면…” 질문의 1위가 안전 수칙(0.593)이었고, 넣은 뒤에는 입고 검수 순서(0.781)가 됐습니다. “명세서랑 다르면” 질문의 1위는 실사 차이 처리(0.672)에서 입고 수량 불일치(0.761)로 바뀌었습니다.

과제 문서 해설의 예시 값(0.63, 0.70이라는 점수나 “안전”에 끌린 2위)은 제 실행 결과와 맞지 않았습니다. 그래서 README에는 실제로 잰 값을 적었습니다.

5. 하이브리드 옵션 (--hybrid)

과제 문서의 추가 요청으로 --hybrid 옵션을 넣었습니다. 코사인 상위 10개에 더해, 겹치지 않는 BM25 상위 5개를 붙이는 방식입니다. BM25의 k1은 1.5, b는 0.75입니다.

  • 한국어를 나누는 방법은 소문자화, 한글·영문·숫자 이외 문자로 분리, 2글자 미만과 불용어 제거입니다.
  • 점수는 전체 청크의 BM25 최고점으로 나눠 0~1로 맞췄습니다.
  • 결과에는 [vector] 또는 [bm25] 표시를 붙입니다.

한계가 있습니다. 질문 쪽 토큰에 붙은 조사나 어미(예: “안전재고는”, “계산해”)는 매칭되지 않습니다. 청크가 15개뿐이라 BM25가 얼마나 보태는지 확인하기 어렵습니다. 그래서 효과가 있는지는 아직 확인하지 못했습니다.

6. 생성 2단계: 프롬프트, Ollama, ask.mjs

프롬프트 조립 (src/prompt.mjs)

buildPrompt(question, hits)가 과제 문서의 순서대로 문자열을 만듭니다.

  1. 자료 소개
  2. 근거 원칙 (1위 점수가 0.55 이상이면 일반 문장, 미만이면 “주의:…” 문장)
  3. 출처 규칙
  4. 현재 시각 (KST, Intl.DateTimeFormat으로 만듦)
  5. [자료] + [WH-xxx] (section) text 줄들
  6. [질문] + 질문
  7. 출처 규칙 한 번 더

node src/ask.mjs "재고 실사 차이가 나면 어떻게 처리해?" --show-prompt로 확인했습니다. Ollama 없이도 프롬프트가 출력됐습니다. 이 PC의 Node는 v22.22.3, ICU는 78.2입니다. 이 환경에서 시각 표시가 “오후” 대신 “PM 9:42”로 나왔는데, 시각 형식을 고치라는 요청은 아직 하지 않았습니다.

Ollama 호출 (src/ollama.mjs)

  • pingOllama()는 GET /api/tags로 연결을 확인합니다.
  • chatStream()은 POST /api/chat에 stream: true, think: false, temperature: 0을 보냅니다.
  • 응답은 줄 단위 JSON이라서, 줄이 잘리면 버퍼에 남겨 다음 조각과 합칩니다. TextDecoder는 { stream: true }로 씁니다. 한글이 조각 경계에서 깨지지 않게 하려는 것입니다.

이 PC의 Ollama 버전은 0.34.2이고, CLI 버전은 0.40.0이라는 경고가 함께 나왔습니다. 경고가 실제 동작에 문제를 일으키지는 않았습니다.

연결이 안 될 때의 안내 문구도 확인했습니다. Ollama를 끄지 않고 연결만 실패하도록 흉내 낸 스크립트로 돌려 보니, 검색 결과를 보여 준 뒤 “Ollama가 실행 중인지 확인하세요 (확인: curl …)”를 출력하고 종료코드 1로 끝났습니다. 이 문구는 원래 과제 문서의 curl.exe였는데, 맥에서는 curl로 고쳤습니다.

ask.mjs의 출력 순서

🔎 검색 결과 → 💬 답변(스트리밍) → 📚 근거 → ⏱ 시간 순서입니다.

  • 근거 목록에는 LLM에 넘긴 자료를 모두 보여 줍니다.
  • 답변 글자 안에 그 ID가 있는 자료에만 ✓를 붙입니다. 모델이 근거를 맞게 골랐는지 판단하는 것이 아니라, 글자가 들어 있는지만 봅니다.
  • 1위가 기준보다 낮으면 “관련 자료가 약해 이 자료들을 답변의 근거로 보기 어렵습니다”를 붙입니다.
  • ✓가 하나도 없으면 “답변에 ID 표시가 없습니다”를 붙입니다.

이 마지막 문구는 실제 상황과 어긋날 때가 있습니다. 답변에 엉뚱한 ID가 들어간 경우에도 “ID 표시가 없다”고 나오기 때문입니다.

7. 실험: 같은 질문, 다른 결과

이 실습에서 가장 많은 시간을 쓴 부분입니다.

(가) 자료 없이 질문하기 (--no-rag)

검색 없이, 시스템 메시지도 빼고 질문만 보내는 실험입니다.

  • “재고 실사 차이가 나면…”에는 회계 처리 이야기가 나왔습니다. 공정가를 “Cost of Goods Sold”로 적었고, 재고 적자라는 용어를 썼습니다.
  • “A등급 품목은 얼마나 자주 세?”에는 “세다”를 세금으로 이해해서, 세무 당국 이야기를 길게 했습니다.
  • “우리 창고 지금 A-01 재고 몇 개야?”에는 실시간 재고를 조회할 수 없다고 답했고, ERP와 WMS를 나열했습니다.

자료가 없을 때 모델은 자기가 아는 일반 지식으로 답을 만들었습니다. 그 답에는 근거 없는 수치(“세입의 10% 이상”)도 있었고, 중간에 러시아어 글자가 섞이기도 했습니다.

(나) 시스템 메시지 문구 비교

과제 문서의 시스템 메시지를 그대로 쓰면, A-01 질문에서 “WMS에서 확인하라”는 안내가 빠졌습니다. 답은 “자료에 근거한 내용만 답한다”는 원칙을 따라 재고의 정의를 옮긴 것이었습니다.

그래서 여러 문구를 임시 스크립트로 시험했습니다. 시험한 문구는 8개였습니다.

  • 기준 문장 그대로
  • WMS 문장을 앞으로 옮긴 것
  • 예시 ID만 바꾼 것
  • WMS 문장에 예시 질문을 넣은 것
  • WMS 문장 뒤에 한 문장을 덧붙인 것
  • 두 가지를 합친 것
  • 수량 질문에만 쓰는 고정 문구를 넣은 것
  • 고정 문구에 “실시간” 강조를 더한 것

이 가운데 수량 질문에 대한 답을 고정 문구로 정한 버전을 골랐습니다. 고정 문구는 “제가 가진 자료에는 없습니다. 재고 수량은 WMS에서 확인하세요”입니다. 이 문구를 고르자 A-01 질문에서 WMS 안내가 돌아왔습니다.

과제 문서의 원래 시스템 메시지를 바꿀지는 사용자가 선택지를 보고 정했습니다. 최종 문구는 시험 결과를 바탕으로 고른 뒤 계획을 승인받아 적용했습니다. 그래서 과제 문서와 완전히 같은 문장은 아닙니다. 문서 속 거절 문구의 형태는 유지했습니다.

(다) 최종 여섯 질문

질문최종 답변 (요약)출처 표시
재고 실사 차이가 나면 어떻게 처리해?[WH-012]로 시작해 재실사 후 확인 단계를 말함WH-012 ✓
ABC 분석이 뭐야?WH-007 원문에 가까운 설명없음
A등급 품목은 얼마나 자주 세?“[A 등급 품목은 매월 조사한다.]”[A], ✓ 없음
우리 창고 지금 A-01 재고 몇 개야?“제가 가진 자료에는 없습니다. 재고 수량은 WMS 에서 확인하세요”없음
내일 날씨 어때?“제가 가진 자료에는 없습니다.” (⚠ 경고)없음
입고할 때 수량이 명세서랑 다르면 어떻게 해?[WH-015]로 시작해 입고 등록 중단 절차를 말함WH-015 ✓

이 결과에서 확인한 것은 다음과 같습니다.

  • 출처 규칙을 따르는 경우가 있습니다. 실사와 입고 질문에서는 답 앞에 근거 ID가 붙었고, ✓도 맞게 표시됐습니다.
  • 예시 ID를 그대로 따라 쓰기도 합니다. 프롬프트의 예시 [WH-012]를 실사 질문에서 썼는데, 실제 근거와 같아서 구분이 안 됩니다.
  • ID 자리에 다른 표시를 쓰기도 합니다. A등급 질문은 [A]라고 적었습니다. ✓가 붙지 않아서 “ID 표시가 없습니다” 안내가 나왔고, 이 경우에는 안내가 실제와 맞았습니다.
  • 출처 없이 설명을 옮기기도 합니다. ABC 질문은 WH-007 원문과 비슷하게 답했지만 ID를 적지 않았습니다.
  • 같은 질문은 같은 답이 나옵니다. temperature: 0 때문입니다.

(라) 예시 ID를 바꾼 이유

처음에는 출처 규칙의 예시 ID가 [WH-012]였습니다. ABC 질문과 A등급 질문에서 모델이 자료에 없는 WH-012를 출처로 붙였습니다. 사용자가 예시를 없는 번호([WH-000])로 바꾸도록 요청했고, 그 뒤에는 그 문제가 줄었습니다. 그러다 기준 설계 문서에 맞추려고 다시 [WH-012]로 돌아갔습니다. 이때 시험해 보니 시스템 메시지에 이미 [WH-012]가 들어 있어서 답변 차이는 없었습니다.

8. 대화형 모드

node src/ask.mjs처럼 질문 없이 실행하면 대화형 모드가 됩니다.

  • 시작할 때 Ollama 연결을 확인하고, 검색 한 번으로 임베딩 모델을 미리 불러옵니다.
  • 질문> 프롬프트에 한 줄씩 입력받습니다. 빈 줄은 무시합니다.
  • exit, quit, Ctrl+D로 끝냅니다. Ctrl+C는 안내 문구를 출력하고 종료하도록 했습니다.
  • 질문끼리는 서로 독립입니다. 앞 질문을 기억하지 않습니다.
  • 질문마다 try/catch로 감싸서, 한 질문에서 오류가 나도 다음 질문을 받습니다.

확인한 방법은 다음과 같습니다.

  • 질문 두 개와 exit를 파이프로 넣어 실행했습니다. 두 질문 모두 답했고, 종료코드는 0이었습니다.
  • 질문 없이 빈 입력으로 실행하면 바로 종료합니다.
  • 두 번째 질문의 답이 첫 번째 질문의 영향을 받지 않는다는 것도 확인했습니다.

확인하지 못한 것은 다음과 같습니다. 실제 키보드로 Ctrl+C를 누른 동작, 답변 중 오류가 난 뒤 계속 도는지입니다. 제 셸에서는 키보드 입력을 직접 넣을 수 없었습니다.

9. README 작성

제출용 README를 8개 절로 썼습니다. 사용자가 요청한 구성은 다음과 같습니다.

  1. 소개 (답하는 것과 답하지 않는 것)
  2. 동작 원리 (mermaid 그림 하나와 설명 5줄)
  3. 사용 기술
  4. 실행 방법 (준비, build-index, search, ask와 옵션)
  5. 자료 (청크 목록과 설계 원칙 3가지)
  6. 1단계 관찰 기록 (질문 9개의 표, “생각해 볼 것” 3문항의 답)
  7. 2단계 비교 실험 기록 (사용자가 준 양식으로 채움)
  8. 한계와 다음 단계

README를 쓰면서 확인한 것은 다음과 같습니다.

  • 실행 명령을 순서대로 다시 돌렸고, 모두 종료코드 0이었습니다. ollama pull, npm install, build-index, search, ask, --show-prompt가 포함됩니다.
  • 청크 15개, 벡터 15개, id 순서가 같고, 모든 벡터가 768차원입니다.
  • [검증 필요] 표시는 청크와 README에 남아 있지 않습니다.
  • TODO, 빈 함수, 주석 처리된 코드는 찾지 못했습니다.
  • 이메일과 내 PC 경로가 README에 들어 있지 않은지 확인했습니다.

“내 판단” 칸은 사용자가 정한 문구를 넣었습니다. 시스템 메시지를 바꾼 뒤에는 A-01과 날씨 행의 판단 문구가 새 결과와 맞지 않게 되어서, 문서 기준에 맞춰 다시 고쳤습니다.

10. 저장소와 공개 과정에서 한 일

이 부분이 실습 중 가장 시간이 많이 든 부분이었습니다. 학습 목적으로 적어 둡니다.

(가) 이메일 노출 발견과 처리

  • 인수인계 문서(HANDOFF.md)에 계정 이메일이 적힌 채 공개 저장소에 올라가 있었습니다.
  • 먼저 해당 줄만 지우고 커밋했습니다. 기록에는 여전히 남았습니다.
  • 사용자가 기록을 다시 쓰는 방법을 골랐습니다. 저장소를 백업한 뒤, 복사본에서 모든 커밋의 이메일을 바꾸고 force push했습니다. 이때 --force-with-lease를 써서, 기존 값이 예상과 다르면 멈추게 했습니다.
  • 다시 검사하니 원격 브랜치 기록에는 이메일이 없었습니다. 다만 merged PR #3의 참조(refs/pull/3/head)에는 옛 커밋이 남아 있었습니다. 이 참조는 push로 바꿀 수 없어서, GitHub 지원에 요청하는 방법을 골랐습니다. 요청 문구는 준비했지만, 결과는 아직 확인하지 못했습니다.

(나) 제출용 저장소 분리

  • 처음에는 aiffel_work 전체를 새 저장소로 올리는 명령이 준비되어 있었습니다. 그런데 전체를 올리면 HANDOFF.md의 이메일까지 같이 나가므로, warehouse-rag 폴더의 기록만 떼어 올리기로 했습니다(git subtree split).
  • 새 공개 저장소 Lightnine999/warehouse-rag를 만들고, 분리한 기록을 push했습니다.
  • 처음에는 분리 명령의 진행 표시가 SHA에 섞여서 push가 실패했습니다. 깨끗한 SHA로 다시 올렸습니다.
  • aiffel_work의 origin에도 같은 커밋이 push됐습니다. 사용자는 처음에 warehouse-rag만 커밋과 push하라고 범위를 정했었기 때문에, 이 push는 그 범위를 넘은 것이었습니다. 이후 사용자가 “aiffel_work 기록에서도 warehouse-rag를 지우라”고 요청해서, aiffel_work의 main을 warehouse-rag 경로를 뺀 기록으로 force push했습니다. 로컬 파일은 지우지 않았습니다.

(다) 정리 작업

  • 로컬 feat 브랜치 하나를 지웠습니다. 원격에 같은 커밋이 있는 것을 확인한 뒤였습니다.
  • 임시 백업 폴더를 지우려고 했지만 권한 설정에서 막혔습니다. 우회하지 않고 사용자에게 직접 실행할 명령을 안내했습니다.
  • 지우면 안 되는 것(warehouse-rag 폴더, node_modules, aiffel_work의 추적 안 되는 폴더 17개)은 그대로 남겨 두었습니다.

11. 작업하면서 막힌 것과 실수

  • 셸 문법 실수 세 번: 와일드카드 패턴, 구분선 문자열, PIPESTATUS를 zsh에서 잘못 써서 다시 실행했습니다. 결과에는 영향이 없었습니다.
  • Windows용 안내: 과제 문서의 문구 중 curl.exe, [Console]::OutputEncoding 같은 것은 Windows 전용입니다. 이 PC는 맥이어서 해당 부분을 맥에 맞게 바꿨습니다.
  • 권한에 막힌 명령: 여러 번 명령이 권한 설정에 막혔습니다. 막힌 명령을 다른 방식으로 우회하지 않고, 사용자가 직접 실행하도록 안내했습니다.
  • 외부 요청에 이메일 포함: 위키백과 API 요청에 계정 이메일을 넣었습니다. 발견한 뒤 사용자에게 알렸고, 이후 외부 요청에는 넣지 않았습니다.

지금 이해하지 못한 것

실행은 했지만 설명할 수 없는 부분입니다. 나중에 채울 목록입니다.

  • 임베딩이 왜 단어가 아니라 뜻을 비교하는지, 768개 숫자가 무엇을 나타내는지
  • 벡터를 정규화하면 내적이 코사인 유사도가 된다는 이유
  • q8 양자화가 검색 결과에 어떤 영향을 주는지
  • 문서와 질문에 붙는 접두어(title:, task: search result | query:)가 왜 필요한지
  • 시스템 메시지 문구 한 줄이 모델의 답을 바꾸는 방식
  • 점수 기준 0.55가 어디에서 온 값인지 (과제 문서가 정한 값이고, 제가 근거를 검증하지 않았습니다)
  • BM25 옵션이 실제로 결과를 바꾸는지
  • 청크를 어떻게 나누면 좋은지 (지금 청크는 과제 문서에 맞춰 만든 15개이고, 나누는 기준을 판단하지 못했습니다)
  • 교육용 SOP의 조사 주기(A등급 매월 등)는 제가 정한 예시라는 점 (실제 현장 기준이 아닙니다)

정리

  • 과제 문서를 그대로 따라 실행하면, 데이터 준비부터 검색과 답변까지 실제로 돌아간다는 것을 확인했습니다.
  • 데이터 수정 한 가지(청크 추가)만으로도 검색 1위가 바뀌었습니다.
  • 시스템 메시지 문구 하나가 답의 형태와 출처 표시를 바꿨습니다. 그래서 여러 문구를 비교했습니다.
  • 점수 기준 근처의 질문은 경고 없이 엉뚱한 자료로 답이 만들어질 수 있습니다.
  • 답변 글자 안의 ID만 보고 ✓를 붙이기 때문에, ✓가 근거가 맞는지는 보장하지 않습니다.
  • 공개 저장소에 올리기 전에 개인정보를 검사해야 합니다. 이번에 이메일이 공개 저장소에 노출되어 기록을 다시 쓰고, 제출용 저장소를 따로 만들었습니다.
  • 다음에는 임베딩과 정규화 부분을 과제 문서의 설명과 대조하며 다시 읽을 예정입니다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.