페이지 목차
이 페이지에서 검색
멀티 에이전트
이 페이지에 묻기
멀티 에이전트 · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.
생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.
MULTI · AGENT
멀티 에이전트
생성 수정
도구 실행·검색·기억·그래프 흐름·전문 에이전트의 협업을 구성하고 권한·평가·사람의 확인으로 통제합니다.
26개 문서 · 2일차 · 14 / 26
13차시 · 임베딩과 벡터 DB
지도 위치 : 2일차 · README 4장의 ③ 지식 층 — 잘라 둔 청크를 뜻으로 찾을 수 있게 저장하는 자리 | 이전: 12차시 문서 로딩과 청킹 | 다음: 14차시 근거 기반 답변 키워드 : 임베딩, 유사도 검색, 벡터 DB
이 차시를 다 읽으면
- 임베딩을 유와 종차로 정의하고, 인코딩 · 토큰화와 구별할 수 있다.
- 코사인 유사도를 "방향이 얼마나 같은가"로 설명하고, 축 ⑧로 키워드 검색과 의미 검색을 가를 수 있다.
- 임베딩 모델을 고를 때의 기준(한국어 · 실행 위치 · 비용)을 말하고, ⚠️ 적재와 질의에 같은 모델 을 써야 하는 이유를 설명할 수 있다.
- 벡터 DB를 정의하고, Chroma 코드에서 저장 · 검색 · 메타데이터 필터 줄을 짚을 수 있다.
- 인덱스가 디스크에 영구 저장된다는 것(축 ①)과, 문서가 개정되면 적재를 어떻게 다시 하는지 말할 수 있다.
0. 이 차시의 판별 축
| # | 축 | 왼쪽 (0) | 오른쪽 (1) | 이 축으로 갈리는 것 | 큰 축 (README 5장) |
|---|---|---|---|---|---|
| 1 | ⭐ 구문론 ↔ 의미론 | 키워드 검색 — 같은 글자가 있는가 | 의미 검색 — 뜻이 가까운가 | "결혼하면 며칠 쉬어?"로 "경조휴가"를 찾는가 | ⑧ 구문론 ↔ 의미론 |
| 2 | 디스크 ↔ 메모리 | 벡터 인덱스 — 디스크에 영구 | 이번 요청의 messages — 끝나면 사라짐 | 무엇을 미리 만들어 두는가 | ① 디스크 ↔ 메모리 |
| 3 | 로컬 ↔ 원격 | 내 서버의 오픈소스 임베딩 모델 | 임베딩 API 서비스 | 문서 원문이 밖으로 나가는가 | ④ 로컬 ↔ 원격 |
| 4 | 정확 ↔ 근사 | 모든 벡터와 비교 | 길잡이 구조로 후보만 비교 (ANN) | 검색 속도 ↔ 놓칠 가능성 | 이 차시의 축 |
이 차시의 질문 — 12차시에서 규정 문서를 조항 단위 청크로 잘랐습니다. 직원이 "결혼하면 며칠 쉬어?"라고 물으면, 수백 개 청크 중 "제24조(경조휴가)"를 어떻게 찾을까요? 질문에는 "경조"라는 글자가 없습니다. 이 차시는 글의 뜻 을 숫자로 바꿔 가까운 뜻 을 찾는 방법과, 그 숫자를 저장해 두는 창고를 다룹니다.
1. 임베딩 — 뜻을 좌표로
1.1 정의와 경계
임베딩 (Embedding) — [텍스트 표현 방식]인데, [ 글의 뜻을 숫자 목록(벡터)으로 바꿔, 뜻이 비슷한 글끼리 벡터가 가깝게 놓이게 한다 ]는 점에서 글자를 정해진 규칙으로 바이트로 바꾸는 인코딩(UTF-8 등)이나, 글을 조각으로 나눠 번호를 붙이는 토큰화와 구별된다.
| 인코딩 (UTF-8) | 토큰화 (2차시) | 임베딩 | |
|---|---|---|---|
| 결과 | 바이트 열 | 토큰 번호 열 | 실수 수백~수천 개의 벡터 |
| 거리에 뜻이 있는가 | ❌ | ❌ 번호가 가깝다고 뜻이 가깝지 않음 | ⭕ 가까우면 뜻이 비슷 |
| 누가 정하나 | 규칙 | 토크나이저 규칙 | 학습된 모델 |
⭐ 핵심은 마지막 줄의 둘째 칸입니다. 임베딩에서만 "거리"가 "뜻의 차이" 를 나타냅니다.
"결혼하면 며칠 쉬어?" ●
● "제24조(경조휴가) 본인 결혼 5일 ..."
"가족 사망 시 휴가" ●
● "제7조(비밀번호) 90일마다 변경"
● "VPN 접속 오류 해결"
(뜻의 지도를 2차원으로 그린 그림 — 실제는 수백 차원)
1.2 반례 — 이건 임베딩이 아닙니다
| 대상 | 왜 아닌가 |
|---|---|
토큰 번호 목록 [1523, 88, 4012] | 숫자이지만 거리에 뜻이 없음 |
| 단어 등장 횟수 표 | 글자가 같아야만 가까워짐. 키워드 쪽 (2절) |
| Claude의 답 | 임베딩 모델은 글을 만들지 않고 벡터만 냄 |
1.3 코사인 유사도 — 방향이 얼마나 같은가
코사인 유사도 (Cosine Similarity) — [벡터 간 유사도 척도]인데, [ 두 벡터의 길이는 무시하고 가리키는 방향이 얼마나 같은지를 -1~1로 잰다 ]는 점에서 두 점 사이의 직선 거리를 재는 유클리드 거리와 구별된다. 1에 가까울수록 뜻이 비슷합니다.
| 질문 | 청크 | 코사인 유사도 (예시 — 실측 아님) |
|---|---|---|
| 결혼하면 며칠 쉬어? | 제24조(경조휴가) 본인 결혼 5일 … | 0.78 |
| 결혼하면 며칠 쉬어? | 제23조(연차휴가 신청) … | 0.52 |
| 결혼하면 며칠 쉬어? | 제7조(비밀번호) … | 0.11 |
💡 비유(기억 보조용): 두 사람이 같은 쪽을 가리키고 있는가 를 보는 것입니다. 팔 길이(글 길이)는 상관없습니다.
2. 축 ⑧ — 키워드 검색 ↔ 의미 검색
| 질문 | 찾아야 할 청크 | 키워드 검색 (구문) | 의미 검색 (의미) |
|---|---|---|---|
| "결혼하면 며칠 쉬어?" | 제24조(경조휴가) | ❌ 겹치는 글자 없음 | ⭕ 뜻이 가까움 |
| "재택근무 가능해?" | 제31조(원격근무) | ❌ | ⭕ |
| "제23조 알려줘" | 제23조 | ⭕ 정확히 일치 | ⚠️ 번호는 뜻이 약해 다른 조항이 섞일 수 있음 |
| "VPN-302 오류" | IT FAQ의 VPN-302 항목 | ⭕ | ⚠️ 비슷한 오류 코드들이 다 가까움 |
⭐ 의미 검색은 표현이 달라도 뜻이 같으면 찾고, 키워드 검색은 번호 · 코드 · 고유명사 에 강합니다. 어느 한쪽이 이기는 것이 아니라 잘하는 일이 다릅니다. 앞에서 배운 구조화 출력(4차시)에서 스키마 검증(형식)과 분류 정확도(뜻)가 다른 것처럼, 검색에서도 같은 축이 갈립니다.
앞으로 다시 만납니다 — 15차시에서 둘을 합치는 하이브리드 검색 을 다룹니다.
3. 임베딩 모델 고르기
3.1 Claude API는 임베딩을 주지 않는다
Claude API는 글을 생성 하는 API이고, 임베딩 기능은 제공하지 않습니다. 임베딩은 별도 모델 을 씁니다.
| 선택지 | 예 | 실행 위치 (축 ④) | 장점 | 주의 |
|---|---|---|---|---|
| 오픈소스 모델 | sentence-transformers로 다국어 모델 (예: BAAI/bge-m3) | 내 서버 | 문서가 밖으로 안 나감, 호출 비용 없음 | 서버 자원 · 설치 · 운영 |
| 임베딩 API 서비스 | 예: Voyage AI 등 | 외부 | 설치 없음, 관리 쉬움 | ⚠️ 규정 원문 · 질문이 외부로 전송, 호출당 비용 |
고르는 기준: ① 한국어를 잘 다루는가 (모델 설명서에서 다국어 · 한국어 지원 확인) ② 문서가 밖으로 나가도 되는가 ③ 벡터 크기와 속도 ④ 비용. 모델 이름과 성능 순위는 자주 바뀌므로 공식 문서 · 모델 설명서로 확인 합니다.
from sentence_transformers import SentenceTransformer
embedder = SentenceTransformer("BAAI/bge-m3") # 예시 모델 — 실제 선택은 평가로
vectors = embedder.encode([c["text"] for c in chunks], normalize_embeddings=True)
🔍 읽는 포인트 : 모델 이름 한 줄과 encode 한 줄입니다. normalize_embeddings=True는 벡터 길이를 1로 맞춰 코사인 유사도 계산을 단순하게 합니다.
3.2 ⚠️ 적재와 질의에 같은 모델
임베딩 모델마다 좌표계가 다릅니다. A 모델로 만든 청크 벡터와 B 모델로 만든 질문 벡터는 같은 지도 위의 점이 아닙니다. 에러도 나지 않고(벡터 크기가 우연히 같다면) 엉뚱한 결과만 나옵니다.
| 상황 | 결과 |
|---|---|
| 적재 A 모델, 질의 A 모델 | ⭕ 정상 |
| 적재 A 모델, 질의 B 모델 | ❌ 결과가 무작위에 가까움 (크기가 다르면 에러) |
| 모델을 바꾸고 싶다 | ⭕ 모든 청크를 새 모델로 다시 적재 (7절) |
💡 사용한 임베딩 모델 이름을 컬렉션 정보나 설정 파일에 기록해 두세요. 몇 달 뒤 누군가 질의 쪽 모델만 바꾸는 사고를 막습니다.
4. 벡터 DB — 뜻으로 찾는 창고
4.1 정의와 경계
벡터 DB (Vector Database) — [데이터베이스]인데, [ 벡터를 저장하고, 주어진 벡터와 가까운 것을 빠르게 찾는 데 특화되어 있다 ]는 점에서 값이 정확히 같은 행을 찾는 일반 관계형 DB와 구별된다.
| 이름 | 형태 | 맞는 곳 |
|---|---|---|
| Chroma | 파이썬에서 바로 쓰는 가벼운 DB, 파일로 저장 | ⭐ 실습 · 작은 서비스 (이 노트의 예시) |
| FAISS | 벡터 검색 라이브러리 (DB 기능은 직접) | 대량 벡터 · 빠른 검색 |
| pgvector | PostgreSQL 확장 | 이미 Postgres를 쓰는 회사 |
| Qdrant | 독립 서버 | 운영 규모 서비스 |
4.2 Chroma로 저장하고 찾기
import chromadb
db = chromadb.PersistentClient(path="./office_index") # 디스크에 저장
col = db.get_or_create_collection(name="policies") # 거리 방식 설정은 버전마다 다름 — 문서 확인
col.add(ids=[c["id"] for c in chunks],
documents=[c["text"] for c in chunks],
embeddings=vectors.tolist(),
metadatas=[c["metadata"] for c in chunks])
q_vec = embedder.encode(["결혼하면 며칠 쉬어?"], normalize_embeddings=True)
res = col.query(query_embeddings=q_vec.tolist(), n_results=5,
where={"access_dept": {"$in": ["ALL", session.dept]}})
🔍 읽는 포인트
PersistentClient(path=...)— 인덱스를 디스크의 폴더 에 저장합니다(5절).col.add(...)— 네 가지를 같은 순서로 넣습니다: 번호(ids) · 원문 · 벡터 · 메타데이터(12차시).embedder.encode([...질문...])— 질문도 적재 때와 같은embedder로 바꿉니다(3.2).n_results=5— 가까운 청크 5개. 이 숫자가 뒤에서 말하는 k 입니다.where=— ⭐ 메타데이터 필터. 다음 절의 권한 장치입니다.
5. 축 ① — 인덱스는 디스크에 영구
이번 요청의 messages (9차시) | 벡터 인덱스 | |
|---|---|---|
| 어디에 | 앱 메모리 | 디스크 (./office_index) |
| 언제 만드나 | 요청마다 | 적재할 때 한 번 |
| 서버를 재시작하면 | 사라짐 | 남아 있음 |
| 만드는 비용 | 작음 | 큼 — 청크 전부를 임베딩 |
임베딩은 청크 수만큼 모델을 돌려야 해서 비쌉니다. 그래서 적재는 미리 한 번 하고 디스크에 두며, 질의할 때는 질문 하나만 임베딩합니다(12차시 2절의 적재 ↔ 질의). Chroma에도 메모리 전용 클라이언트가 있지만, 그 경우 프로그램이 꺼지면 인덱스가 사라져 매번 다시 적재해야 합니다.
6. 메타데이터 필터 — 권한은 검색에서 거른다
12차시에서 청크마다 access_dept를 붙였습니다. 이제 그것을 씁니다.
| ❌ 부탁 | ⭕ 강제 |
|---|---|
| 모든 청크를 검색한 뒤, 프롬프트에 "인사팀 전용 문서는 답에 쓰지 마" | 검색 요청의 where에 사용자 부서를 넣어 애초에 꺼내지 않음 |
| 인젝션 한 줄이면 뚫림 (10차시 5절) | 모델은 그 청크를 본 적이 없음 |
⭐ 필터 값 session.dept는 앞에서 배운 권한 경계(8차시)처럼 로그인 세션 에서 옵니다. 직원이 "나 인사팀이야"라고 써도 바뀌지 않습니다.
7. 근사 탐색과 적재 재실행
7.1 ANN — 빠르게, 대신 가끔 놓친다
근사 최근접 탐색 (ANN, Approximate Nearest Neighbor) — [벡터 검색 방식]인데, [ 모든 벡터와 비교하지 않고 미리 만들어 둔 길잡이 구조(예: HNSW)를 따라가 가까운 후보를 빠르게 찾는다 ]는 점에서 전부 비교하는 정확 탐색과 구별된다.
| 정확 탐색 | 근사 탐색 (ANN) | |
|---|---|---|
| 속도 | 벡터 수에 비례해 느려짐 | 대량에서도 빠름 |
| 정확도 | 진짜 가장 가까운 k개 | 가끔 하나쯤 놓칠 수 있음 |
| 맞는 규모 | 수천 개 | 수십만 개 이상 |
오피스봇의 규정 청크는 수천 개 수준이라 어느 쪽이든 충분히 빠릅니다. 대부분의 벡터 DB는 ANN 인덱스를 기본으로 쓰며, 정확도와 속도를 맞바꾸는 설정값을 둡니다(세부는 각 DB 문서 확인).
7.2 문서가 개정되면 — 적재를 다시
| 일어난 일 | 할 일 |
|---|---|
| 취업규칙 개정 | 그 문서의 청크를 source 기준으로 지우고 새로 로딩 → 청킹 → 임베딩 → 저장 |
| 문서 폐지 | 그 문서의 청크 삭제 |
| 임베딩 모델 교체 | ⚠️ 모든 문서 전체 재적재 (3.2) |
| 청킹 방식 변경 | 전체 재적재 |
col.delete(where={"source": "취업규칙_2025.pdf"}) # 옛 판 청크 삭제 후 새 판을 add
🔍 읽는 포인트 : 옛 청크를 지우지 않고 새 청크만 넣으면, 검색 결과에 옛 규정과 새 규정이 함께 나와 모델이 옛 일수로 답할 수 있습니다. 청크 id를 "파일명#조항#순번"처럼 정해 두면 무엇이 어느 판인지 추적하기 쉽습니다.
앞으로 다시 만납니다 — 14차시에서 이 검색 결과를 프롬프트에 넣어 근거로 답하게 하고, 15차시에서 검색이 놓치는 질문을 측정하고 고칩니다.
8. 실습 — 맡기고 검수하기
지시
12차시의 chunks.jsonl을 임베딩해서 Chroma에 저장하고 검색 함수를 만들어줘.
- 참고: chromadb, sentence-transformers 공식 문서. 설치 버전과 확인한 주소를 보고에 적을 것
- 임베딩: 한국어를 지원하는 오픈소스 다국어 모델을 내 서버에서 실행 (외부 임베딩 API 쓰지 말 것)
모델 이름을 설정 파일 한곳에 두고, 적재와 질의가 같은 설정을 읽게 할 것
- PersistentClient로 ./office_index에 저장. 청크 id는 "파일명#조항#순번"
- search(question, dept, k=5): where 필터로 access_dept가 "ALL" 또는 dept인 청크만
- 재적재 함수 reload(source): 해당 source 청크 삭제 후 다시 적재
테스트 질문 10개(내가 준 목록)를 dept="개발팀"과 dept="인사팀"으로 각각 검색
추측하지 말고 실행 출력 원문을 붙여.
보고 형식: [질문 | dept | 1위 청크 id | 1위 유사도 | 5개 안에 정답 조항이 있나] 표
+ 서버(파이썬) 재시작 후 같은 검색을 다시 돌린 출력
받을 증거
- 위 형식의 표와 검색 출력 원문
- 재시작 후에도 같은 결과가 나온 출력
reload("취업규칙_2026.pdf")전후의 청크 수
내가 직접 확인할 것
| 볼 것 | 이걸로 아는 것 |
|---|---|
| "결혼하면 며칠 쉬어?"의 상위 결과에 경조휴가 조항이 있는가 | 의미 검색이 동작하는가 (1.3, 2절) |
| "제23조 알려줘"의 1위가 제23조인가 | 번호 질문에서 의미 검색의 약점 (2절 — 15차시로) |
| 개발팀으로 검색한 결과에 인사 평가 지침 청크가 하나도 없는가 | 권한 필터가 검색 단계에서 강제되는가 (6절) |
| 재시작 후 결과가 같은가 | 인덱스가 디스크에 영구 저장되는가 (5절) |
| 설정 파일의 모델 이름이 한 곳에만 있는가 | 적재 · 질의 같은 모델 (3.2) |
reload 후 청크 수가 늘지 않고 같은가 | 옛 청크가 남지 않는가 (7.2) |
부록 A. 스스로 확인하기
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | 임베딩은 인코딩 · 토큰화와 무엇이 다른가? | 1.1 |
| 2 | 코사인 유사도는 무엇을 재나? | 1.3 |
| 3 | 의미 검색이 약하고 키워드 검색이 강한 질문의 예는? | 2절 |
| 4 | Claude API로 임베딩을 만들 수 없을 때의 선택지와 각각의 축 ④ 위치는? | 3.1 |
| 5 | 적재와 질의에 다른 임베딩 모델을 쓰면? | 3.2 |
| 6 | Chroma 코드에서 권한을 거르는 줄은? | 4.2, 6절 |
| 7 | 벡터 인덱스를 디스크에 두는 이유는? | 5절 |
| 8 | ANN은 무엇을 얻고 무엇을 내주나? | 7.1 |
| 9 | 규정이 개정되면 어떤 순서로 다시 적재하나? | 7.2 |
부록 B. 자주 하는 실수
| 실수 | 바로잡기 |
|---|---|
| "Claude API로 임베딩하면 되겠지" | ❌ 임베딩은 별도 모델 |
| 질의 쪽 임베딩 모델만 새 것으로 교체 | ❌ 좌표계가 달라짐. 전체 재적재 |
| 영어 전용 임베딩 모델로 한국어 규정 적재 | ⚠️ 한국어 · 다국어 지원 확인 |
| 권한을 프롬프트로 "그 문서는 쓰지 마" | ❌ 검색의 where 필터로 강제 |
| 메모리 전용 클라이언트로 운영 | ⚠️ 재시작하면 인덱스가 사라짐 |
| 개정판을 add만 하고 옛 판을 안 지움 | ❌ 옛 규정이 검색됨. source 기준 삭제 후 적재 |
| 외부 임베딩 API에 사내 문서를 확인 없이 전송 | ⚠️ 무엇이 밖으로 나가는지 먼저 결정 (축 ④) |
부록 C. NotebookLM 복습 프롬프트
이 13차시 노트만 근거로 6문제를 내줘. 비전공자 기준으로.
"임베딩 ↔ 인코딩", "키워드 검색 ↔ 의미 검색(구문론 ↔ 의미론)", "적재와 질의에 같은 모델", "메타데이터 필터로 권한 강제"는 반드시 포함해줘.
답은 내가 답한 다음에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.
LangTailor