Field Log · Entry
Query Router와 Adaptive RAG: 질문마다 다른 검색 경로 (3/10)
오늘의 결론
- 모든 질문에 같은 retrieval pipeline을 적용하면 쉬운 질문은 비싸지고 어려운 질문은 부족하게 검색됩니다.
- Router는 주제만 분류하는 것이 아니라 지식 필요성·신선도·식별자·복잡도·모호성·위험도를 판단합니다.
- 정확한 ID 조회, 한 번의 검색, 여러 단계 검색, 사용자 확인은 서로 다른 route입니다.
- confidence가 낮으면 비싼 route로 무조건 보내지 말고 clarify·safe default·abstain 정책을 사용합니다.
- router 평가는 accuracy뿐 아니라 downstream quality, cost, latency를 합친 routing regret로 봅니다.
앞 글에서 안전하게 실행할 수 있는 tool contract를 만들었습니다. 이제 Agent가 어떤 질문에 어떤 tool 묶음을 쓸지 결정해야 합니다.
“고마워”
“DOC-4821의 상태는?”
“환불 기한은?”
“지난달과 지금 정책이 왜 달라졌는지 비교해 줘”
“어느 계정 말하는 거야?”
다섯 질문을 모두 hybrid top-50 → rerank → LLM으로 보내는 것은 단순하지만 좋은 설계는 아닙니다.
그림 1. Router는 답을 만들지 않는다. 질문과 runtime context를 route contract로 바꾸고, confidence·권한·예산 gate가 실제 실행 전략을 확정한다.
Routing은 Query rewriting과 다르다
두 작업을 먼저 구분합니다.
Query routing
어떤 전략, index, tool, model을 사용할지 고릅니다.
question → DIRECT_LOOKUP
question → ITERATIVE_SEARCH
question → CLARIFY
Query rewriting
선택된 검색 경로 안에서 실제 검색어를 바꿉니다.
“저번 규정이랑 뭐가 달라?”
→ “2026-06 refund policy revision differences”
Routing이 먼저이고 rewriting은 route의 내부 단계입니다. 둘을 한 prompt에 섞으면 실패 원인을 구분하기 어렵습니다.
첫 Router의 route 집합
Route를 너무 잘게 나누기 전에 사용자 관점의 행동 차이부터 잡습니다.
NO_RETRIEVAL
외부 지식이 필요하지 않습니다.
예:
- 인사와 감사
- 이미 제공된 텍스트의 형식 변환
- 현재 대화에 있는 문장의 번역
- 제품이 허용한 일반적인 글쓰기
주의: 모델이 안다고 느끼는 것과 freshness가 필요 없는 것은 다릅니다.
DIRECT_LOOKUP
정확한 key나 structured filter로 조회합니다.
예:
DOC-4821
INC-2026-0716-03
order_id=88421
policy/refund@2026-07-01
Vector search보다 key-value, SQL, metadata index가 정확하고 빠릅니다.
SINGLE_SEARCH
한 번의 retrieval과 context 구성으로 답할 가능성이 높습니다.
예:
- 현재 환불 기한은 며칠인가?
- VPN 연결 오류 코드 720의 해결 절차는?
- 최신 보안 정책의 MFA 예외 조건은?
ITERATIVE_SEARCH
질문을 분해하거나 중간 observation에 따라 추가 retrieval이 필요합니다.
예:
- 두 revision 비교
- 여러 문서의 근거를 연결하는 multi-hop 질문
- 첫 검색 결과가 다음 entity를 알려 주는 질문
- 긴 답변을 쓰며 섹션별 근거가 필요한 조사
CLARIFY
검색 전에 사용자 입력이 필요합니다.
예:
- “그 계정 상태 알려 줘” — 어떤 계정인가?
- “정책 비교해 줘” — 어느 정책과 어느 기간인가?
- 같은 이름의 project가 여러 tenant에 존재
모호한 질문을 비싼 multi-search로 보내면 검색량은 늘어도 의도는 확정되지 않습니다.
OUT_OF_SCOPE 또는 ESCALATE
제품 범위 밖이거나 human decision이 필요한 route입니다. 이는 오류가 아니라 정상적인 terminal decision입니다.
질문에서 무엇을 판별할까
Router가 보는 축을 명시하면 label guideline과 failure analysis가 쉬워집니다.
| 축 | 질문 | Route에 미치는 영향 |
|---|---|---|
| Knowledge need | 외부 source가 필요한가? | NO_RETRIEVAL 여부 |
| Freshness | 현재·최신·오늘 정보인가? | retrieval 강제 |
| Identifier | 안정적인 key가 있는가? | DIRECT_LOOKUP |
| Complexity | 독립적인 evidence가 몇 묶음 필요한가? | SINGLE vs ITERATIVE |
| Ambiguity | missing slot이 있는가? | CLARIFY |
| Domain | 어느 corpus/tool인가? | index와 capability 선택 |
| Risk | 의료·법률·금융·쓰기 action인가? | stricter route·human gate |
| Authorization | 사용할 수 있는 source인가? | 허용 route 제한 |
문장 길이만으로 complexity를 판단하지 않습니다.
긴 질문이지만 한 문서 한 span으로 답함 → SINGLE_SEARCH
짧은 질문 “둘 차이?” → 대화 context상 두 revision 비교 → ITERATIVE_SEARCH
Router output도 계약이다
{
"route": "ITERATIVE_SEARCH",
"confidence": 0.84,
"reason_codes": [
"COMPARISON_REQUEST",
"TWO_REVISIONS_REQUIRED",
"FRESHNESS_REQUIRED"
],
"domain": "policy",
"required_slots": {
"policy_name": "refund",
"from_revision": "2026-06",
"to_revision": "current"
},
"retrieval_budget": {
"max_searches": 3,
"max_reads": 6,
"max_candidates_per_search": 20
},
"router_version": "adaptive-router-1.3.0"
}
reason_codes는 장문의 model reasoning보다 집계하기 쉽습니다. retrieval_budget은 모델이 최종 결정하는 값이 아니라 route template에서 harness가 채웁니다.
규칙, 분류기, LLM 중 무엇을 쓸까
하나만 고를 필요가 없습니다. 비용이 싼 확실한 판단부터 순서대로 적용하는 cascade가 실용적입니다.
1단계: Deterministic rule
import re
DOC_ID = re.compile(r"\bDOC-[0-9]{4,10}\b", re.IGNORECASE)
def rule_route(query: str):
if DOC_ID.search(query):
return RouteDecision(
route="DIRECT_LOOKUP",
confidence=1.0,
reason_codes=["DOCUMENT_ID_PRESENT"],
)
return None
정확한 ID, slash command, product UI의 명시적 mode처럼 precision이 높은 signal에 적합합니다.
2단계: 작은 classifier
Embedding + linear model, BERT encoder, 작은 instruction model을 사용할 수 있습니다.
장점:
- 빠르고 저렴함
- 확률 calibration 가능
- label dataset으로 regression 관리 가능
단점:
- 새로운 domain과 복합 질문에 drift
- slot extraction을 별도로 해야 할 수 있음
3단계: LLM router
모호한 언어, 대화 context, 복합 constraint를 구조화하는 데 유리합니다.
단점:
- 지연과 비용
- 동일 질문에서 route가 달라질 수 있음
- confidence 숫자가 실제 calibration을 의미하지 않을 수 있음
4단계: Abstention policy
어느 판단도 충분히 확실하지 않으면 사용자에게 묻거나 safe default를 택합니다.
rule high precision? → accept
classifier p(route) ≥ 0.90? → accept
LLM agreement + slots full? → accept
missing critical slot? → CLARIFY
otherwise → safe default + low budget
Confidence는 숫자 하나보다 조건이다
Model이 confidence: 0.97을 출력했다고 97% 정확한 것은 아닙니다.
Router confidence를 구성하는 관찰 가능한 signal:
- top-1과 top-2 class probability margin
- 서로 다른 prompt/model의 route agreement
- required slot completeness
- deterministic identifier match
- out-of-distribution score
- 최근 calibration set에서의 empirical accuracy
- domain별 support 수
예:
def accept_route(prediction, calibration, slots):
if prediction.route == "CLARIFY":
return True
if not slots.critical_fields_complete:
return False
threshold = calibration.threshold_for(
domain=prediction.domain,
route=prediction.route,
)
return (
prediction.probability >= threshold
and prediction.margin >= 0.20
and not prediction.out_of_distribution
)
Threshold는 route별 비용과 위험에 따라 달라야 합니다. NO_RETRIEVAL을 잘못 선택해 최신 근거 없이 답하는 비용은 SINGLE_SEARCH를 과하게 선택하는 비용보다 클 수 있습니다.
Adaptive-RAG가 주는 핵심 아이디어
Adaptive-RAG는 질문 복잡도에 따라 no-retrieval, single-step, multi-step 전략을 동적으로 선택하는 framework를 제안합니다.
실무에 옮길 때 기억할 점:
- 복잡도는 route를 고르는 useful signal이지 유일한 signal이 아닙니다.
- 같은 질문도 corpus 품질, freshness, 사용자 권한에 따라 최적 route가 달라집니다.
- classifier label은 사람이 느낀 난이도보다 실제 strategy outcome으로 만드는 편이 낫습니다.
Label을 결과 기반으로 만들기
다음 질문에 사람이 바로 route label을 붙일 수 있습니다. 하지만 그 label이 실제 최적 전략이라는 보장은 없습니다.
더 강한 방법은 dev query에 가능한 strategy를 모두 실행해 utility를 계산하는 것입니다.
U(route, q)
= answer_quality
- λ_cost × cost
- λ_latency × latency
- λ_risk × policy_violation
예:
| Query | No retrieval | Single | Iterative | Oracle route |
|---|---|---|---|---|
| 인사 | 0.99 | 0.82 | 0.40 | NO_RETRIEVAL |
| 현재 환불 기한 | 0.31 | 0.94 | 0.88 | SINGLE_SEARCH |
| 두 revision 비교 | 0.18 | 0.60 | 0.91 | ITERATIVE_SEARCH |
Quality가 같다면 더 싸고 빠른 route를 label로 선택합니다.
def utility(run, weights):
return (
run.quality
- weights.cost * run.cost_usd
- weights.latency * run.latency_seconds
- weights.risk * run.policy_violations
)
oracle_route = max(candidate_runs, key=lambda r: utility(r, weights)).route
이렇게 만든 label은 현재 retrieval stack과 model에 종속됩니다. Stack이 바뀌면 oracle을 다시 계산해야 합니다.
Routing regret
Router accuracy만 보면 모든 오답을 같은 값으로 취급합니다.
gold SINGLE → predicted ITERATIVE
gold ITERATIVE → predicted NO_RETRIEVAL
첫 오류는 대체로 과한 비용이고, 둘째는 품질과 안전성 실패일 수 있습니다.
Routing regret를 다음처럼 정의할 수 있습니다.
regret(q)
= U(oracle_route, q) - U(chosen_route, q)
평균 regret와 critical slice regret를 함께 봅니다.
- freshness 질문
- exact identifier
- multi-hop comparison
- ambiguous slot
- high-risk domain
- unauthorized corpus
- Korean/English mixed query
Confusion matrix를 행동 비용으로 읽기
| Actual \ Predicted | NO | DIRECT | SINGLE | ITERATIVE | CLARIFY |
|---|---|---|---|---|---|
| NO | 92 | 0 | 8 | 0 | 0 |
| DIRECT | 0 | 88 | 10 | 2 | 0 |
| SINGLE | 3 | 4 | 82 | 9 | 2 |
| ITERATIVE | 2 | 0 | 18 | 76 | 4 |
| CLARIFY | 1 | 1 | 15 | 8 | 75 |
이 표에서 먼저 볼 위험:
ITERATIVE → NO: 근거 누락 가능CLARIFY → SINGLE/ITERATIVE: 잘못된 의도에 비용 소비DIRECT → SINGLE: 정확한 ID를 semantic search로 잃을 수 있음NO → ITERATIVE: 품질보다 latency·cost 낭비
단순 macro F1 외에 이 비용 비대칭을 반영합니다.
Router와 retrieval budget을 연결하기
Route는 이름이 아니라 실행 policy bundle이어야 합니다.
ROUTE_POLICIES = {
"NO_RETRIEVAL": RoutePolicy(
allowed_tools=frozenset(),
max_steps=2,
max_cost_usd=0.01,
),
"DIRECT_LOOKUP": RoutePolicy(
allowed_tools=frozenset({"get_document_by_id"}),
max_steps=3,
max_cost_usd=0.02,
),
"SINGLE_SEARCH": RoutePolicy(
allowed_tools=frozenset({"search_documents", "read_document_span"}),
max_searches=1,
max_reads=4,
max_steps=5,
max_cost_usd=0.04,
),
"ITERATIVE_SEARCH": RoutePolicy(
allowed_tools=frozenset({"search_documents", "read_document_span"}),
max_searches=3,
max_reads=8,
max_steps=10,
max_cost_usd=0.12,
),
}
LLM이 SINGLE_SEARCH를 선택하고 search를 다섯 번 호출하려 하면 harness가 차단합니다.
대화 Context를 어떻게 사용할까
사용자 질문만 보면 모호하지만 대화 state에는 slot이 있을 수 있습니다.
User: 환불 정책 7월 개정판을 열어 줘.
Agent: ...
User: 그 전 버전과 뭐가 달라?
Router input에는 전체 transcript 대신 정규화된 context를 줍니다.
{
"current_query": "그 전 버전과 뭐가 달라?",
"resolved_entities": [
{
"type": "policy",
"id": "refund",
"revision": "2026-07-01",
"source": "verified_tool_result"
}
],
"active_goal": "refund policy revision comparison",
"available_capabilities": ["documents:search", "documents:read"]
}
요약문이 entity를 지어내지 않도록 source와 verification state를 유지합니다.
Clarification 질문도 최적화 대상이다
나쁜 확인:
조금 더 자세히 말씀해 주세요.
좋은 확인:
비교할 환불 정책의 기준 시점을 알려 주세요.
2026-06 개정판과현재 개정판을 비교할까요?
좋은 clarification은 다음을 만족합니다.
- 하나의 결정적인 missing slot을 묻는다.
- 이미 아는 정보를 다시 묻지 않는다.
- 가능하면 선택지를 제공한다.
- 왜 필요한지 짧게 설명한다.
- 답변이 오면 route가 확정된다.
Clarification rate가 너무 높으면 router가 안전한 것이 아니라 사용자 경험을 회피하는 것일 수 있습니다.
Offline에서 Production으로 옮기는 순서
1. Log sampling
실제 query를 개인 정보 제거 후 route 후보별로 sampling합니다.
2. Outcome-based oracle
여러 strategy를 실행해 quality·cost·latency utility를 비교합니다.
3. Router train/dev/test
같은 template, user, conversation, source가 split을 넘지 않게 합니다.
4. Shadow mode
기존 pipeline은 그대로 실행하고 router decision만 기록합니다.
5. Low-risk route부터 적용
DIRECT_LOOKUP처럼 precision 높은 route를 먼저 켭니다.
6. Guarded rollout
domain·user cohort별로 단계적으로 확대합니다.
7. Drift monitoring
새 제품명, 새 ID 형식, 언어 분포, policy 변경을 감시합니다.
평가 지표
Router 자체
- per-class precision, recall, F1
- confusion matrix
- calibration error
- abstention coverage와 selective accuracy
- slot extraction exact match
- out-of-distribution detection
Downstream
- answer correctness와 faithfulness
- evidence Recall
- unsupported answer rate
- clarification success rate
- task completion
- mean/p95 latency
- tool calls와 cost
- routing regret
안정성
- 같은 query 반복 시 route consistency
- freshness·high-risk slice false negative
- unauthorized route attempt
- budget violation
흔한 실패 패턴
1. Topic classifier를 Router라고 부름
HR, IT, Legal만 분류하고 retrieval depth는 항상 같습니다.
처방: domain과 strategy를 별도 축으로 둡니다.
2. 질문 길이로 복잡도 판단
짧은 비교 질문과 긴 단일 lookup을 잘못 분류합니다.
처방: 필요한 evidence group과 dependency를 label guideline에 넣습니다.
3. LLM confidence를 calibration 없이 사용
0.95라는 숫자가 경험적 정확도와 연결되지 않습니다.
처방: held-out set에서 route·domain별 threshold를 정합니다.
4. 애매하면 무조건 가장 비싼 route
비용을 늘려도 missing user intent는 해결되지 않습니다.
처방: critical slot이 없으면 CLARIFY로 보냅니다.
5. Router accuracy만 최적화
비슷한 strategy 간 오답과 위험한 under-retrieval을 똑같이 셉니다.
처방: downstream utility와 routing regret를 사용합니다.
6. Route가 tool 권한을 확대
LLM이 Legal route를 골랐다는 이유로 legal corpus 권한을 얻습니다.
처방: route는 현재 capability의 부분집합만 선택할 수 있습니다.
Production 체크리스트
- routing과 query rewriting을 분리했다.
- NO, DIRECT, SINGLE, ITERATIVE, CLARIFY의 행동 차이가 명확하다.
- knowledge need, freshness, identifier, complexity, ambiguity, risk를 본다.
- 규칙→분류기→LLM→abstention cascade가 있다.
- route output은 schema와 stable reason code를 가진다.
- confidence threshold를 held-out data에서 calibration했다.
- route가 runtime capability를 확대하지 못한다.
- strategy outcome으로 oracle label을 만들었다.
- confusion matrix를 비용 비대칭 관점에서 본다.
- router metric과 downstream quality·cost·latency를 함께 본다.
- shadow mode와 guarded rollout이 있다.
- drift와 route consistency를 monitoring한다.
스스로 확인하기
DIRECT_LOOKUP과SINGLE_SEARCH를 나누면 어떤 오류와 비용을 줄일 수 있는가?- 질문 길이가 complexity의 좋은 proxy가 아닌 이유는 무엇인가?
ITERATIVE_SEARCH를NO_RETRIEVAL로 잘못 보낸 오류가 반대 오류보다 위험할 수 있는 이유는 무엇인가?- 사람이 붙인 난이도 label보다 outcome-based oracle이 강한 이유는 무엇인가?
- 현재 RAG query 중 먼저 deterministic route로 분리할 수 있는 패턴은 무엇인가?
다음 글에서는 route가 정해진 뒤 Agent가 action 순서를 고르는 ReAct, plan-and-execute, ReWOO, tree search를 비교하고 언제 계획하지 말아야 하는지 살펴봅니다.