Field Log · Entry

Query Router와 Adaptive RAG: 질문마다 다른 검색 경로 (3/10)

질문 분석 결과에 따라 검색 없음, 정확 조회, 단일 검색, 반복 검색, 사용자 확인 경로로 분기하고 confidence와 예산 gate가 경로를 통제하는 Adaptive RAG router

오늘의 결론

  • 모든 질문에 같은 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으로 보내는 것은 단순하지만 좋은 설계는 아닙니다.

질문 특성 분석에서 검색 없음, 정확 조회, 단일 검색, 반복 검색, 확인 질문으로 분기하는 Adaptive RAG router

그림 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가 정확하고 빠릅니다.

한 번의 retrieval과 context 구성으로 답할 가능성이 높습니다.

예:

  • 현재 환불 기한은 며칠인가?
  • VPN 연결 오류 코드 720의 해결 절차는?
  • 최신 보안 정책의 MFA 예외 조건은?

질문을 분해하거나 중간 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
Ambiguitymissing 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

예:

QueryNo retrievalSingleIterativeOracle route
인사0.990.820.40NO_RETRIEVAL
현재 환불 기한0.310.940.88SINGLE_SEARCH
두 revision 비교0.180.600.91ITERATIVE_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 \ PredictedNODIRECTSINGLEITERATIVECLARIFY
NO920800
DIRECT0881020
SINGLE348292
ITERATIVE2018764
CLARIFY1115875

이 표에서 먼저 볼 위험:

  • 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한다.

스스로 확인하기

  1. DIRECT_LOOKUPSINGLE_SEARCH를 나누면 어떤 오류와 비용을 줄일 수 있는가?
  2. 질문 길이가 complexity의 좋은 proxy가 아닌 이유는 무엇인가?
  3. ITERATIVE_SEARCHNO_RETRIEVAL로 잘못 보낸 오류가 반대 오류보다 위험할 수 있는 이유는 무엇인가?
  4. 사람이 붙인 난이도 label보다 outcome-based oracle이 강한 이유는 무엇인가?
  5. 현재 RAG query 중 먼저 deterministic route로 분리할 수 있는 패턴은 무엇인가?

다음 글에서는 route가 정해진 뒤 Agent가 action 순서를 고르는 ReAct, plan-and-execute, ReWOO, tree search를 비교하고 언제 계획하지 말아야 하는지 살펴봅니다.

참고자료