페이지 목차
이 페이지에서 검색
멀티 에이전트
이 페이지에 묻기
멀티 에이전트 · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.
생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.
MULTI · AGENT
멀티 에이전트
생성 수정
도구 실행·검색·기억·그래프 흐름·전문 에이전트의 협업을 구성하고 권한·평가·사람의 확인으로 통제합니다.
26개 문서 · 2일차 · 16 / 26
15차시 · 검색 품질 개선
지도 위치 : 2일차 · README 4장의 ③ 지식 층 — 지식 층의 마지막 칸, 실제 직원의 말투에도 찾아지게 만드는 자리 | 이전: 14차시 근거 기반 답변 | 다음: 16차시 멀티턴 대화와 세션 관리 키워드 : 하이브리드 검색, 리랭킹, 쿼리 재작성
이 차시를 다 읽으면
- 실사용 질문이 검색을 실패하게 만드는 유형(구어체 · 줄임말 · 사내 은어 · 번호)을 말할 수 있다.
- 질문-정답 청크 평가셋으로 Recall@k 를 측정하고, 개선 전에 측정부터 하는 이유를 설명할 수 있다.
- BM25와 의미 검색을 하이브리드 로 합치는 이유와 RRF의 직관을 설명할 수 있다.
- 리랭킹(교차 인코더)과 쿼리 재작성이 각각 무엇을 고치는지 구별할 수 있다.
- 개선을 한 번에 하나씩 적용하고, 그때마다 늘어나는 비용 · 지연을 함께 기록할 수 있다.
0. 이 차시의 판별 축
| # | 축 | 왼쪽 (0) | 오른쪽 (1) | 이 축으로 갈리는 것 | 큰 축 (README 5장) |
|---|---|---|---|---|---|
| 1 | ⭐ 구문론 ↔ 의미론 | 키워드 검색 (BM25) — 같은 글자 | 의미 검색 (임베딩) — 가까운 뜻 | 번호 · 은어 ↔ 바꿔 말한 표현 | ⑧ 구문론 ↔ 의미론 |
| 2 | ⭐ 하나 ↔ 여럿 | 검색기 하나, 한 단계 | 검색기 여럿을 합침, 두 단계(후보 → 재정렬) | 하이브리드 · 리랭킹 | ⑤ 하나 ↔ 여럿 |
| 3 | 증거 ↔ 주장 | Recall@k 표로 "나아졌다" | "체감상 좋아졌다" | 개선을 믿어도 되는가 | 파생 축 (부모 ⑧) |
| 4 | 한 건의 비용 ↔ 전체 비용 | 검색 한 번의 비용 · 지연 | 단계를 더할 때마다 쌓이는 비용 · 지연 | 개선의 대가 | 파생 축 (부모 ⑤) |
이 차시의 질문 — 13 · 14차시의 오피스봇은 "결혼하면 며칠 쉬어?"는 잘 찾았습니다. 그런데 실제 직원은 "육휴 쓰면 성과급 깎여?", "23조 뭐였지", "G포털 비번 초기화" 같은 말로 묻습니다. 문서는 공식어로, 질문은 입말로 쓰여 있습니다. 이 차시는 그 간격을 메우는 기법 셋을 다루되, 먼저 재고, 하나씩 바꾸고, 다시 재는 순서를 지킵니다.
1. 실사용 표현이 검색을 흔드는 방식
| 유형 | 직원의 질문 | 문서의 표현 | 의미 검색 | 키워드 검색 |
|---|---|---|---|---|
| 구어체 | "결혼하면 며칠 쉬어?" | 경조휴가 · 본인 결혼 | ⭕ | ❌ |
| 줄임말 | "육휴 쓰면 성과급 깎여?" | 육아휴직 · 성과급 지급 기준 | ⚠️ 줄임말을 모를 수 있음 | ❌ |
| 사내 은어 | "G포털 비번 초기화" | 그룹웨어 비밀번호 재설정 | ❌ 모델이 모르는 말 | ⚠️ 문서에 은어가 있으면 ⭕ |
| 번호 | "23조 뭐였지" | 제23조(연차휴가 신청) | ⚠️ 번호는 뜻이 약함 | ⭕ |
| 대명사 | (앞 대화 뒤) "그거 며칠이야?" | — | ❌ "그거"가 무엇인지 모름 | ❌ |
⭐ 표를 세로로 읽으면, 한 가지 검색 방식이 모든 줄을 이기지 못합니다. 그래서 이 차시의 기법들은 서로를 대체하지 않고 약한 줄을 메웁니다.
2. ⭐ 측정 먼저 — Recall@k
2.1 평가셋과 지표
13 · 14차시까지는 질문 몇 개를 눈으로 확인했습니다. 개선을 시작하기 전에 고정된 평가셋 을 만듭니다(11차시의 회귀 테스트와 같은 원리).
| # | 질문 (실제 직원 말투) | 유형 | 정답 청크 id |
|---|---|---|---|
| 1 | 결혼하면 며칠 쉬어? | 구어체 | 취업규칙#제24조#1 |
| 2 | 육휴 쓰면 성과급 깎여? | 줄임말 | 복리후생#성과급#2 |
| 3 | G포털 비번 초기화 어떻게 해? | 은어 | IT_FAQ#Q17 |
| 4 | 23조 뭐였지 | 번호 | 취업규칙#제23조#1 |
| … | (유형별로 고르게, 총 50~100개) |
Recall@k — [검색 평가 지표]인데, [ 정답 청크가 검색 결과 상위 k개 안에 들어 있는 질문의 비율 ]이라는 점에서 답의 문장이 맞는지를 보는 답변 평가 지표와 구별된다. Recall@5 = 0.8이면 "질문 열 개 중 여덟 개는 정답 청크가 상위 다섯 안에 있다"는 뜻입니다.
def recall_at_k(evalset, search_fn, k=5):
hit = sum(1 for q in evalset if q["answer_id"] in [c["id"] for c in search_fn(q["question"], k)])
return hit / len(evalset)
🔍 읽는 포인트 : 정답 id가 상위 k개 안에 있으면 1, 없으면 0 — 그 평균입니다. 모델 호출 없이 계산되는 결정론적 숫자 입니다.
2.2 왜 검색만 따로 재나
14차시의 답이 틀렸을 때 원인은 둘 중 하나입니다 — 검색이 정답 청크를 못 가져왔거나 , 가져왔는데 생성이 잘못 썼거나 . Recall@k는 앞의 것만 따로 잽니다. 검색이 못 가져온 청크는 어떤 프롬프트로도 답에 들어갈 수 없으므로, 검색을 먼저 확인합니다.
2.3 결과표 양식
| 방법 | Recall@1 | Recall@5 | 구어체 | 줄임말 | 은어 | 번호 | 평균 지연 | 질문당 추가 비용 |
|---|---|---|---|---|---|---|---|---|
| 의미 검색만 (13차시) | 0 | |||||||
| + 하이브리드 (3절) | ||||||||
| + 리랭킹 (4절) | ||||||||
| + 쿼리 재작성 (5절) |
(빈칸 양식 — 실습에서 채웁니다. 유형별 칸은 해당 유형 질문만의 Recall@5)
3. 하이브리드 검색 — BM25와 의미 검색 합치기
3.1 BM25
BM25 — [키워드 검색의 점수 방식]인데, [ 질문의 단어가 문서에 얼마나 자주 나오는지와, 그 단어가 전체 문서에서 얼마나 드문지(드물수록 가중)로 점수를 매긴다 ]는 점에서 뜻의 거리를 재는 임베딩 검색과 구별된다.
from rank_bm25 import BM25Okapi
bm25 = BM25Okapi([tokenize(c["text"]) for c in chunks])
scores = bm25.get_scores(tokenize(question))
🔍 읽는 포인트 : tokenize가 핵심입니다. ⚠️ 한국어는 "연차는 · 연차를 · 연차가"처럼 조사가 붙어 띄어쓰기로 자르면 같은 단어로 인식되지 않습니다. 형태소 분석기나 글자 조각(n-gram)으로 자르는 방법이 있고, 어느 쪽이 나은지는 평가셋으로 정합니다.
3.2 하이브리드와 RRF
하이브리드 검색 (Hybrid Search) — [검색 방식]인데, [ 키워드 검색과 의미 검색의 결과를 합쳐 하나의 순위로 만든다 ]는 점에서 한 가지 방식만 쓰는 검색과 구별된다.
문제는 두 검색의 점수 단위가 다르다 는 것입니다(BM25 점수 12.3과 코사인 유사도 0.78은 비교할 수 없음). 그래서 점수 대신 순위만 씁니다. 대표적인 방법이 RRF(Reciprocal Rank Fusion, 순위 역수 합) 입니다 — 각 목록에서의 순위 r에 대해 1 / (60 + r)을 더합니다.
| 청크 | 의미 검색 순위 | BM25 순위 | RRF 점수 (k=60) | 최종 |
|---|---|---|---|---|
| 제23조 | 1 | 3 | 1/61 + 1/63 ≈ 0.0323 | 1위 |
| IT_FAQ#Q17 | 5 | 1 | 1/65 + 1/61 ≈ 0.0318 | 2위 |
| 제24조 | 2 | (없음) | 1/62 ≈ 0.0161 | 3위 |
⭐ 직관: 양쪽 목록에 다 올라온 청크가 위로 갑니다. 한쪽에서만 높은 청크도 사라지지 않고 남습니다. 60은 관례적으로 쓰는 값으로, 1위와 2위의 차이를 너무 크게 만들지 않는 역할을 합니다.
def rrf(rank_lists, k=60):
scores = {}
for ranks in rank_lists: # 예: [의미 검색 id 순위, BM25 id 순위]
for r, cid in enumerate(ranks, start=1):
scores[cid] = scores.get(cid, 0) + 1 / (k + r)
return sorted(scores, key=scores.get, reverse=True)
🔍 읽는 포인트 : 순위 목록 여러 개를 받아 1/(k+순위)를 더하고 정렬합니다. 점수 단위를 맞출 필요가 없습니다.
⚠️ BM25 쪽에도 13차시의 권한 필터 (access_dept)를 똑같이 걸어야 합니다. 하이브리드를 붙이면서 한쪽 경로로 권한이 새는 일이 흔합니다.
4. 리랭킹 — 두 단계 검색
4.1 교차 인코더
교차 인코더 (Cross-encoder) — [검색 순위 모델]인데, [ 질문과 청크를 한 쌍으로 함께 읽고 관련도 점수를 낸다 ]는 점에서 질문과 청크를 따로 벡터로 바꿔 거리만 재는 임베딩 검색(bi-encoder)과 구별된다.
| 임베딩 검색 (따로 읽음) | 교차 인코더 (함께 읽음) | |
|---|---|---|
| 청크 벡터를 미리 만들어 둘 수 있나 | ⭕ 적재 때 한 번 (13차시) | ❌ 질문이 올 때마다 쌍으로 계산 |
| 속도 | 수천 개도 빠름 | 느림 — 후보 수십 개가 한계 |
| 정확도 | 대략적 | 질문과 청크의 세부까지 비교해 더 정밀 |
4.2 두 단계로 나누기
청크 수천 개 ─[1단계: 하이브리드, 빠르고 넓게]─▶ 후보 30개 ─[2단계: 리랭커, 느리지만 정밀]─▶ 상위 5개 ─▶ 14차시 주입
리랭킹 (Reranking) — [검색 후처리]인데, [ 1단계 검색이 넓게 가져온 후보만 더 정밀한 모델로 다시 정렬한다 ]는 점에서 처음부터 전체를 한 번에 정렬하는 단일 단계 검색과 구별된다.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3") # 예시 모델 — 한국어 지원 여부 확인
scores = reranker.predict([(question, c["text"]) for c in candidates])
top5 = [c for _, c in sorted(zip(scores, candidates), key=lambda x: x[0], reverse=True)][:5]
🔍 읽는 포인트 : (질문, 청크) 쌍 을 넣습니다. 이것이 "함께 읽는다"의 모양입니다. 후보 30개면 쌍도 30개 — 그래서 1단계가 후보를 줄여 줘야 합니다.
💡 리랭킹은 "정답이 30위 안에는 있는데 5위 밖에 있는" 경우를 고칩니다. 정답이 30위 안에도 없다면 리랭킹으로는 못 고칩니다 — 1단계(하이브리드 · 쿼리 재작성 · 청킹)를 봐야 합니다. Recall@30과 Recall@5를 함께 보면 어느 쪽 문제인지 갈립니다.
5. 쿼리 재작성 — 질문을 검색용 문장으로
쿼리 재작성 (Query Rewriting) — [검색 전처리]인데, [ LLM이 사용자의 질문을 검색에 맞는 문장(들)로 바꾼 뒤 검색한다 ]는 점에서 질문을 그대로 검색하는 방식과 구별된다.
| 종류 | 직원의 질문 | 재작성 결과 |
|---|---|---|
| 검색용 문장으로 | "G포털 비번 초기화 어떻게 해?" | "그룹웨어 비밀번호 재설정 절차" |
| 여러 질문으로 | "육휴 쓰면 성과급 깎여?" | "육아휴직 기간 성과급 지급 기준" / "휴직자 평가 제외 규정" |
| 대화 맥락의 대명사 풀기 | (경조휴가 이야기 뒤) "그거 며칠이야?" | "본인 결혼 경조휴가 일수" |
class Rewrite(BaseModel):
queries: list[str]
rw = client.messages.parse(
model="claude-haiku-5-5", max_tokens=1024,
system=REWRITE_SYSTEM, # 사내 용어집(G포털=그룹웨어, 육휴=육아휴직 ...)을 포함
messages=[{"role": "user", "content": question}],
output_format=Rewrite,
).parsed_output
🔍 읽는 포인트
claude-haiku-5-5— 재작성은 짧고 자주 하는 일이라 가볍고 싼 모델 을 씁니다(2차시의 모델 선택). 가격은 공식 페이지 확인.REWRITE_SYSTEM의 사내 용어집 — 모델이 모르는 은어는 재작성으로도 못 풉니다. 용어집을 넣어 주는 것이 핵심입니다.- 여러 질문이 나오면 각각 검색해 RRF로 합칩니다 (3.2).
⚠️ 재작성이 뜻을 바꿀 수 있습니다("성과급 깎여?" → "성과급 인상 기준"). 그래서 ① 원래 질문도 함께 검색하고 ② 재작성 결과를 로그에 남겨 평가 때 확인합니다.
앞으로 다시 만납니다 — 세 번째 줄(대명사 풀기)은 이전 대화 가 있어야 가능합니다. 16차시에서 대화 이력을 세션으로 관리하면서 회수합니다.
6. 개선 순서 — 한 번에 하나씩
6.1 순서
| 순서 | 할 일 | 고치는 것 |
|---|---|---|
| 0 | ⭐ 평가셋 · Recall@k 기준선 측정 | — (출발점) |
| 1 | 놓친 질문의 정답 청크를 열어 봄 — 청킹 · 로딩 문제인가 (12차시) | 깨진 표, 반쪽 조항 |
| 2 | 하이브리드 | 번호 · 은어 · 고유명사 |
| 3 | 리랭킹 | 정답이 후보에는 있는데 순위가 낮음 |
| 4 | 쿼리 재작성 | 구어체 · 줄임말 · 대명사 |
⭐ 한 번에 하나만 바꾸고 다시 잽니다. 셋을 한꺼번에 넣고 점수가 올랐다면, 무엇이 효과였는지, 무엇은 오히려 깎아 먹었는지 알 수 없습니다. 그리고 1번을 건너뛰지 마세요 — 적재가 깨져 있으면 위의 기법은 깨진 청크를 더 정교하게 찾을 뿐입니다(12차시 2절).
6.2 개선의 대가
| 추가하는 것 | 추가 비용 | 추가 지연 | 운영 부담 |
|---|---|---|---|
| 하이브리드 (BM25) | 거의 없음 | 작음 | 키워드 인덱스를 문서 개정 때 함께 갱신 |
| 리랭킹 | 서버 자원 (또는 API 비용) | 후보 수에 비례 | 모델 하나 더 운영 |
| 쿼리 재작성 | ⚠️ 질문마다 LLM 호출 1회 | LLM 왕복 1회 | 용어집 관리, 재작성 로그 점검 |
⚠️ 쿼리 재작성은 모든 질문에 LLM 호출을 하나 더합니다. 10차시 3.3의 곱셈(요청 수 × 날짜)을 다시 하세요. 단계가 늘면 응답 시간도 늘어 사용자가 기다립니다. 2.3의 결과표에 지연 · 비용 칸이 있는 이유 입니다 — Recall이 조금 오른 대가가 너무 크면 멈추는 것도 결정입니다(README 문장 1).
7. 오피스봇 적용 — 유형별 처방
| 약한 유형 (2.3 표에서) | 먼저 볼 것 | 처방 |
|---|---|---|
| 번호 ("23조") | BM25 결과 | 하이브리드 |
| 은어 ("G포털") | 용어집 | 쿼리 재작성 + 용어집, 또는 적재 시 청크에 동의어 추가 |
| 줄임말 ("육휴") | 의미 검색 결과 | 쿼리 재작성 |
| 정답이 6~30위 | Recall@30 vs Recall@5 | 리랭킹 |
| 정답이 30위 밖 | 정답 청크 원문 | 청킹 · 로딩 재점검 (12차시) |
8. 실습 — 맡기고 검수하기
지시
오피스봇 규정 검색의 품질을 측정하고 개선해줘.
- 평가셋: 내가 준 evalset.jsonl (질문·유형·정답 청크 id). 수정 금지
- 참고: rank_bm25, sentence-transformers(CrossEncoder), Claude API structured outputs 공식 문서. 버전·주소를 보고에 적을 것
- 단계별로 하나씩 추가하고 매 단계 측정: ① 의미 검색만 ② +BM25 하이브리드(RRF, k=60) ③ +리랭킹(후보 30→5) ④ +쿼리 재작성(claude-haiku-5-5, 용어집 포함, 원 질문도 함께 검색)
- 모든 단계에서 access_dept 권한 필터 유지 (BM25 경로 포함)
- 측정: Recall@1, @5, @30, 유형별 Recall@5, 질문당 평균 지연, 질문당 추가 LLM 토큰
- 재작성 결과는 질문별로 로그에 남길 것
추측하지 말고 실행 출력 원문을 붙여.
보고 형식: 2.3과 같은 [방법 | Recall@1 | Recall@5 | 유형별 | 지연 | 추가 비용] 표
+ 단계마다 새로 맞힌 질문 / 새로 틀린 질문 목록
받을 증거
- 단계별 결과표와 측정 스크립트 실행 출력 원문
- 새로 틀린 질문 목록 (개선이 다른 질문을 망쳤는가)
- 쿼리 재작성 로그 10건 샘플
내가 직접 확인할 것
| 볼 것 | 이걸로 아는 것 |
|---|---|
| 각 단계의 숫자가 앞 단계와 하나만 다른 조건에서 나왔는가 | 한 번에 하나만 바꿨는가 (6.1) |
| 번호 유형의 Recall이 하이브리드에서 오르는가 | BM25가 제 몫을 하는가 (3절) |
| 재작성 로그에서 뜻이 바뀐 사례가 있는가 | 재작성의 부작용 (5절) |
| 개발팀 사용자로 하이브리드 검색 시 인사팀 전용 청크가 0건인가 | 권한 필터가 모든 경로에 있는가 (3.2) |
| 마지막 단계의 지연 · 비용을 10차시 방식으로 하루치로 곱해 봄 | 개선의 대가가 감당할 만한가 (6.2) |
부록 A. 스스로 확인하기
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | 실사용 질문이 검색을 흔드는 다섯 유형은? | 1절 |
| 2 | Recall@k의 정의와, 검색만 따로 재는 이유는? | 2.1, 2.2 |
| 3 | BM25에서 한국어가 까다로운 이유는? | 3.1 |
| 4 | RRF가 점수 대신 순위를 쓰는 이유는? | 3.2 |
| 5 | 교차 인코더와 임베딩 검색은 무엇으로 갈리나? | 4.1 |
| 6 | 리랭킹으로 고칠 수 없는 경우는? 어떻게 알아보나? | 4.2 |
| 7 | 쿼리 재작성의 세 종류와 부작용은? | 5절 |
| 8 | 개선을 한 번에 하나씩 해야 하는 이유는? | 6.1 |
| 9 | 각 개선이 늘리는 비용 · 지연은? | 6.2 |
부록 B. 자주 하는 실수
| 실수 | 바로잡기 |
|---|---|
| 측정 없이 "리랭커 넣으면 좋아진다" | ❌ 기준선부터. 숫자로 비교 |
| 평가셋을 개선하면서 함께 고침 | ❌ 고정해야 전후 비교가 됨 |
| 세 기법을 한꺼번에 적용 | ⚠️ 무엇이 효과였는지 모름 |
| BM25를 띄어쓰기로만 토큰화 | ⚠️ 한국어 조사 문제. 토큰화 방법도 평가로 |
| BM25 경로에 권한 필터를 빠뜨림 | ❌ 하이브리드로 권한이 샘 |
| 전체 청크에 교차 인코더 | ❌ 너무 느림. 후보만 |
| 재작성 질문만 검색 | ⚠️ 뜻이 바뀔 수 있음. 원 질문도 함께 |
| 비용 · 지연은 안 적고 Recall만 보고 | ⚠️ 개선의 대가도 같은 표에 |
부록 C. NotebookLM 복습 프롬프트
이 15차시 노트만 근거로 6문제를 내줘. 비전공자 기준으로.
"측정 먼저와 Recall@k", "하이브리드와 RRF의 직관", "리랭킹(교차 인코더)과 두 단계 검색", "쿼리 재작성과 한 번에 하나씩 바꾸기"는 반드시 포함해줘.
답은 내가 답한 다음에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.
LangTailor