[LLM Wiki 18] QMD Structured Search: LLM Agent가 검색어를 직접 설계하는 방식
QMD structured search가 일반 query 검색과 어떻게 다르고, lex, vec, hyde typed query를 어떻게 실행해 RRF, chunk selection, rerank로 이어지는지 설명한다.
QMD의 structured search는 사용자가 자연어 질문 하나를 던지는 검색이 아니다. 검색을 호출하는 쪽, 특히 LLM agent나 MCP client가 검색 전략을 먼저 쪼개서 QMD에 넘기는 방식이다.
쉽게 말하면 일반 검색은 QMD가 질문을 보고 검색어 확장까지 맡는다. structured search는 호출자가 이미 lex, vec, hyde 같은 검색 목적별 query를 준비해서 넘긴다. QMD는 그 query들을 각각 실행하고, 결과를 합치고, chunk를 고르고, rerank해서 최종 결과를 만든다.

Structured Search가 필요한 이유
일반 query 검색은 편하다. 사용자는 질문 하나만 던지면 된다.
예를 들어 “인증 설정 어디서 해?”라고 물으면 QMD는 내부 query expansion 모델을 사용해 lexical 검색어, semantic 검색어, HyDE 문장을 만들어 검색할 수 있다.
하지만 agent가 이미 문맥을 알고 있다면 이야기가 달라진다. agent는 사용자의 질문뿐 아니라 대화 흐름, 현재 작업 파일, 이전 실패 원인, 찾으려는 코드의 의도까지 알고 있다. 이 경우 검색어를 QMD가 자동으로 추측하게 두는 것보다, agent가 직접 검색 계획을 세우는 편이 더 정확할 수 있다.
structured search는 이 지점을 위해 존재한다.
일반 검색:
- 입력: 자연어 질문 하나
- query expansion: QMD 내부 local LLM이 담당
- 장점: 간단함
- 단점: 호출자가 세밀하게 검색 의도를 통제하기 어려움
Structured search:
- 입력: typed search 배열
- query expansion: 호출자, 보통 LLM agent가 담당
- 장점: 검색 의도를 명시적으로 분리 가능
- 단점: 호출자가 검색 전략을 잘못 만들면 품질이 떨어짐
핵심 개념: 검색어가 하나가 아니라 역할별로 나뉜다
QMD structured search의 입력은 단순 문자열이 아니라 여러 개의 typed query다. 각 query는 type과 query를 가진다.
예시는 이런 형태다.
[
{
"type": "lex",
"query": "authentication config settings"
},
{
"type": "vec",
"query": "where the project explains how authentication settings are configured"
},
{
"type": "hyde",
"query": "Authentication settings are configured in the project configuration file and loaded during startup."
}
]
여기서 중요한 점은 세 query가 같은 일을 하지 않는다는 것이다.
lex는 정확한 단어를 찾는다. 파일명, 함수명, 설정 키, 에러 메시지처럼 문자 그대로 맞아야 하는 검색에 강하다.
vec는 의미가 비슷한 문서를 찾는다. 질문과 문서의 표현이 달라도 의미가 가까우면 잡을 수 있다.
hyde는 질문에 대한 가상의 답변 문서를 먼저 만든 뒤, 그 문장과 비슷한 실제 문서를 찾는 방식이다. 질문이 짧거나 모호할 때 검색 공간을 더 풍부하게 만들 수 있다.
QMD는 이 typed query들을 받아서 “검색어를 더 만들까?”라고 다시 묻지 않는다. 이미 확장된 검색 계획으로 보고 실행한다.
내부 동작 Flow
QMD 소스 기준으로 structuredSearch는 대략 아래 흐름으로 움직인다.

- 입력 검증
searches 배열이 비어 있으면 실패한다. 각 query는 한 줄이어야 하고, type에 맞게 검증된다. vec와 hyde에서는 부정 검색, 예를 들어 -draft 같은 표현을 지원하지 않는다. 제외 조건은 lex 쪽에서 처리하는 것이 맞다.
- 검색 타입별 라우팅
lex query는 FTS 검색으로 간다. QMD는 SQLite FTS5 기반의 full-text search를 사용한다.
vec와 hyde query는 embedding으로 변환한 뒤 vector search로 간다. 여러 semantic query는 batch embedding으로 처리된다.
- 후보 결과 생성
각 query는 별도 검색 결과 목록을 만든다. lex 결과, vec 결과, hyde 결과는 서로 다른 ranking을 가진다.
- RRF로 결과 융합
QMD는 Reciprocal Rank Fusion, 즉 RRF로 여러 ranking 목록을 합친다. 점수 자체를 억지로 같은 스케일로 맞추는 대신 “각 검색에서 몇 등으로 나왔는가”를 기준으로 합친다.
소스상 첫 번째 ranked list에는 더 높은 가중치가 붙는다. 즉 structured search를 호출하는 쪽은 가장 중요한 query를 앞에 배치해야 한다.
- 문서 단위에서 chunk 단위로 내려가기
검색 결과는 문서 단위로 나올 수 있지만, 최종 답변에 필요한 것은 문서 전체가 아니라 관련 chunk다. QMD는 후보 문서에서 chunk를 고르고, primary query와 intent term을 이용해 더 관련 있는 chunk를 찾는다.
- Rerank
후보 chunk들을 reranker가 다시 평가한다. 이 단계는 “검색으로 찾은 후보 중 실제 질문에 더 맞는 조각”을 앞으로 올리는 역할이다.
- 최종 점수 blending
QMD는 RRF rank와 reranker score를 섞는다. 상위 RRF 후보는 RRF 비중을 더 크게 유지하고, 뒤쪽 후보는 reranker 점수의 영향을 더 받는다. 이 구조는 초반 검색 신호를 완전히 버리지 않으면서, chunk 수준의 정밀도를 높이려는 설계다.
MCP에서의 의미
QMD의 MCP query tool은 두 가지 방식으로 호출될 수 있다.
첫 번째는 query 하나를 넘기는 방식이다.
{
"query": "Where is authentication configured?"
}
이 방식에서는 QMD가 query expansion을 수행할 수 있다.
두 번째는 searches 배열을 넘기는 방식이다.
{
"searches": [
{
"type": "lex",
"query": "authentication config"
},
{
"type": "vec",
"query": "how authentication configuration is loaded by the system"
},
{
"type": "hyde",
"query": "Authentication configuration is loaded from a config file during application startup."
}
]
}
이때 query와 searches는 같이 쓰지 않는다. searches를 쓰면 호출자가 검색 전략을 직접 설계한 것으로 본다.
이 구조는 agentic search에 잘 맞는다. LLM agent가 먼저 사용자의 의도를 해석하고, 검색 목적을 여러 갈래로 나눈 다음, QMD는 로컬 인덱스에서 빠르게 실행하는 식이다.
예제로 보는 Structured Search
질문이 “QMD에서 structured search는 어디서 처리돼?”라고 해보자.
좋지 않은 검색은 이렇게 하나만 던지는 것이다.
{
"query": "structured search"
}
이 방식도 작동은 하지만, 검색 의도가 좁다. 소스 코드에서는 structuredSearch라는 함수명으로 나올 수도 있고, MCP에서는 searches라는 입력 필드로 노출될 수도 있다. 설명 문서에서는 typed query, query expansion bypass, pre-expanded queries 같은 표현으로 설명될 수도 있다.
structured search라면 이렇게 나눌 수 있다.
[
{
"type": "lex",
"query": "structuredSearch searches"
},
{
"type": "lex",
"query": "pre-expanded queries"
},
{
"type": "vec",
"query": "code path that handles multiple typed search queries from an MCP caller"
},
{
"type": "hyde",
"query": "The MCP query tool accepts a searches array and executes lexical and semantic searches without query expansion."
}
]
여기서 lex는 함수명과 필드명을 잡고, vec는 의미적으로 관련된 구현 설명을 잡고, hyde는 “정답 문서라면 이런 문장일 것”이라는 가상의 문서 형태로 검색 공간을 넓힌다.
언제 쓰면 좋은가

structured search는 agent가 검색 의도를 잘 알고 있을 때 좋다.
예를 들어 다음 상황에서 유리하다.
- 사용자의 질문이 짧지만 대화 문맥이 긴 경우
- 정확한 키워드와 의미 검색을 같이 써야 하는 경우
- 함수명, 설정 키, 에러 메시지와 자연어 설명을 동시에 찾아야 하는 경우
- LLM agent가 이미 여러 검색 가설을 만들 수 있는 경우
- MCP client가 검색 전략을 통제해야 하는 경우
반대로 사용자가 그냥 문서 검색을 간단히 하고 싶다면 일반 query 검색이 낫다. structured search는 더 강력하지만, 검색 전략을 작성하는 비용이 있다.
Structured Search 작성 팁
첫 번째 query가 중요하다. QMD는 RRF fusion에서 첫 번째 ranked list에 더 큰 가중치를 준다. 가장 확실한 검색 의도를 첫 번째에 둬야 한다.
lex에는 고유명사를 넣는다. 함수명, class명, 설정 키, CLI 옵션, 에러 메시지, 파일명 같은 것은 lex가 잘 잡는다.
vec에는 의미를 풀어 쓴다. 사용자가 말한 표현 그대로보다 “어떤 설명을 찾고 싶은지”를 문장으로 쓰는 편이 좋다.
hyde에는 답변 문서처럼 쓴다. 질문을 다시 쓰는 것이 아니라, 실제 문서에 있을 법한 설명문을 만든다.
intent를 활용하면 chunk 선택이 좋아진다. QMD는 primary query뿐 아니라 intent term도 chunk 선택에 활용할 수 있다.
vec와 hyde에 부정 검색을 넣지 않는다. 제외 조건은 lex나 상위 호출 로직에서 처리하는 것이 안전하다.
한계
structured search는 마법이 아니다. 검색 계획이 틀리면 결과도 틀어진다.
가장 큰 한계는 책임이 호출자에게 이동한다는 점이다. 일반 query 검색에서는 QMD가 query expansion을 맡지만, structured search에서는 agent나 client가 그 역할을 한다. LLM agent가 잘못된 가설을 만들면 엉뚱한 문서를 강하게 끌어올릴 수 있다.
또 하나의 한계는 query 수가 많아질수록 노이즈도 늘어난다는 점이다. 다양한 검색어를 넣는 것은 좋지만, 관련 없는 query를 많이 넣으면 RRF 단계에서 후보 풀이 흐려진다.
그리고 structured search는 “답변 생성”이 아니다. QMD는 검색 후보를 찾고 정렬한다. 검색 결과를 읽고 최종 답변을 구성하는 일은 여전히 agent나 상위 애플리케이션의 역할이다.
정리
QMD structured search의 핵심은 검색 제어권의 이동이다.
일반 query 검색에서는 사용자가 질문하고 QMD가 검색어 확장을 한다. structured search에서는 LLM agent가 검색어를 설계하고 QMD가 실행 엔진처럼 움직인다.
그래서 structured search는 단순 검색 기능이라기보다 agentic retrieval 인터페이스에 가깝다. LLM이 생각하고, QMD가 로컬 인덱스에서 찾고, RRF와 rerank가 후보를 정리한다.
이 구조를 잘 쓰면 local-first 검색 시스템에서도 꽤 정교한 검색 전략을 만들 수 있다. 특히 QMD처럼 Markdown 문서, FTS5, vector search, local reranker를 함께 쓰는 시스템에서는 structured search가 “LLM이 검색을 어떻게 지시할 것인가”를 명시적으로 표현하는 중요한 접점이 된다.