문서 중심 그래프를 만들고 나서 검색 결과를 봤더니 기존 벡터 검색과 크게 다르지 않았습니다. 관계를 여러 단계 따라갈 수 있게 멀티턴 탐색을 넣었고, 컨텍스트가 커지자 도구 결과를 500자로 잘랐습니다. 그 절단이 표와 긴 원문의 뒷부분을 합성 단계에 도달하지 못하게 만들고 있었습니다. 탐색 범위를 넓히면서 무엇을 잃었는지 정리합니다.
그래프를 만들었는데 검색은 벡터 검색과 비슷했습니다
1편에서 문서가 발견 범위를 정하도록 파이프라인을 바꿨습니다. 그래프에는 이전보다 많은 개념과 관계가 들어왔습니다.
검색 결과를 열어보니 기존 벡터 검색과 크게 다르지 않았습니다.
이유는 검색 쪽에 있었습니다. 질문과 가까운 원문을 찾고 주변 트리플을 한 번 조회하는 방식이었습니다. 그러면 문서 여러 곳에 흩어진 관계나 질문에 직접 쓰이지 않은 연결은 쓰이지 않습니다.
그래프를 만든 이유는 비슷한 문장을 하나 더 찾기 위해서가 아니었습니다. 벡터 검색이 놓치는 관계와 경로를 따라가기 위해서였는데, 검색 구조가 그 경로를 한 걸음밖에 못 가고 있었습니다.
질문에 따라 필요한 걸음 수가 달랐습니다.
"배송비 기준은 무엇인가"
→ 한 문장에서 답이 나온다. 한 번의 조회로 충분하다
"이 정책의 예외 승인 주체와 그 근거 문서를 알려 달라"
→ 정책을 찾고 → 예외 관계를 따라가고 → 승인 주체를 확인하고 → 원문을 읽는다
질문 유형마다 전용 쿼리를 만드는 방법도 있습니다. 조합이 늘어날수록 분기와 프롬프트 규칙이 함께 커집니다.
그래서 4월에 모델이 생각과 행동과 관측을 반복하는 구조를 넣고, 그래프와 원문과 정형 질의 도구 가운데 다음 행동을 고르게 했습니다. 멀티턴 자체가 목적은 아니었습니다. 답을 만들기 전에 필요한 관계를 스스로 찾아갈 수 있는지 확인하려는 것이었습니다.
검색을 답변 생성이 아니라 도구 선택 문제로 봤습니다
한 턴은 세 부분으로 구성됩니다.
Thought 지금 부족한 정보가 무엇인가
Action 허용된 도구 가운데 무엇을 호출할 것인가
Result 도구가 반환한 구조화된 근거
도구는 역할이 겹치지 않게 나눴습니다. 클래스와 인스턴스의 트리플을 찾는 그래프 검색, 정방향과 역방향 이웃을 따라가는 관계 탐색, 출처 청크로 원문을 가져오는 조회, 원문 안에서 키워드를 찾는 보조 검색, 표 데이터의 집계와 조인을 처리하는 읽기 전용 SQL입니다.
모델은 저장소에 직접 접근하지 않습니다. 허용된 도구와 인자 스키마 안에서만 움직입니다. SQL도 조회 구문만 허용하고 실행 전에 스키마를 제공합니다.
최대 턴 수에 닿으면 지금까지 모은 근거로 답을 합성합니다. 답을 못 찾았다는 이유로 루프가 끝없이 이어지지 않도록 종료 조건을 실행 계약에 넣었습니다.
모델에게 탐색 권한을 주는 설계에서 실제로 정해야 하는 것은 얼마나 자유롭게 두느냐가 아니라 어떤 단위로 행동하게 할 것인가입니다. 행동 단위를 도구로 고정하면 유연성을 얻으면서 실행 범위는 통제할 수 있습니다.
컨텍스트를 줄이려고 자른 것이 하필 답의 근거였습니다
멀티턴은 검색 범위를 넓히는 대신 컨텍스트를 빠르게 키웁니다. 한 턴에서 가져온 청크와 관계를 다음 턴에 전부 다시 넣으면, 모델은 새 근거보다 이전 출력의 반복을 더 많이 읽습니다.
그래서 최근 세 턴만 남기고 이전 턴은 호출한 도구 중심으로 요약했습니다. 도구 결과도 500자로 잘랐습니다.
컨텍스트는 줄었습니다. 그리고 표와 긴 원문의 뒷부분이 사라졌습니다.
최대 턴에 도달해 강제로 합성할 때는 최근 메시지만 사용했습니다. 응답 데이터의 출처 목록에는 이름이 남아 있는데, 그 근거의 내용은 합성 컨텍스트에 없는 상태가 됐습니다.
탐색 단계 도구를 여러 번 부르며 근거를 모은다
↓ 500자 절단 + 최근 3턴 유지
합성 단계 모아둔 것 중 일부만 보고 답을 만든다
출처 목록에는 전부 남아 있다
여기서 저희가 두 가지를 한 가지로 다루고 있었다는 것이 드러났습니다.
모델이 이미 시도한 도구를 기억하는 일과, 최종 합성에 필요한 근거를 온전히 보존하는 일은 다른 문제였습니다. 앞의 것은 요약해도 됩니다. 뒤의 것은 요약하면 답이 바뀝니다. 같은 절단 규칙을 둘에 함께 적용한 것이 잘못이었습니다.
이 한계는 나중에 검색 구조 전체를 다시 보게 된 핵심 이유가 됐습니다.
컨텍스트를 줄이는 작업에서 무엇을 자를지 정할 때, 그 내용이 다음 판단에 쓰이는지와 최종 산출물에 쓰이는지를 따로 봐야 합니다. 둘의 수명이 다르면 보관 방식도 달라야 합니다.
탐색 경로를 로그가 아니라 응답 데이터로 만들었습니다
멀티턴의 내부 로그만으로는 사용자가 답이 어디서 왔는지 알기 어렵습니다.
각 도구 호출에서 방문한 노드와 엣지를 따로 모아 응답에 포함했습니다. 이 데이터는 디버깅 정보가 아니라 검색 결과의 한 부분입니다.
프론트엔드는 이 경로를 3D 그래프에서 강조했습니다. 대규모 그래프의 물리 계산은 별도 워커로 분리하고, 노드 수에 따라 갱신 주기와 링크 거리를 조절했습니다.
여기에도 경계가 있었습니다. 당시 응답의 방문 노드는 안정적인 URI가 아니라 라벨이나 지역 이름이었고, 화면도 라벨로 강조했습니다.
경로를 관찰할 수는 있었지만 같은 라벨을 가진 노드를 구분하는 식별자 계약은 아니었습니다. 1편에서 출처가 관계가 아닌 주어 노드에 붙었던 것과 같은 종류의 간격이고, 7편의 근거 강조에서 그대로 다시 나타납니다.
멀티턴은 넓힌 만큼 흔들림도 키웠습니다
관계를 여러 단계 따라가야 하는 질문에서는 멀티턴이 유용했습니다. 첫 조회에 답이 없어도 이웃 탐색과 원문 조회를 조합해 근거를 보강할 수 있었고, 탐색 경로도 화면에서 확인할 수 있었습니다.
대가가 세 갈래로 나왔습니다.
질문마다 호출 횟수가 달라졌습니다. 같은 근거를 여러 턴에 걸쳐 쌓으면서 원문 수가 과도하게 늘어나는 경우가 생겼습니다. 그리고 한 문장에서 답이 나오는 단일 사실 질문까지 여러 턴을 쓰면서, 답과 무관한 숫자와 문장이 합성 컨텍스트에 섞였습니다.
운영에서 더 크게 문제가 된 것은 평균 응답 시간이 아니라 긴 꼬리 지연이었습니다. 대부분은 빠른데 일부가 아주 느리면, 사용자는 그 일부를 기억합니다.
당시에는 탐색 범위를 넓히는 것이 우선이어서 이 구조를 골랐습니다. 나중에 질문별 실행 경로를 실제로 측정해보고 나서야 모든 질문에 루프가 필요한 것은 아니었다는 것을 확인했습니다. 그 측정과 결과는 6편에서 이어집니다.
넓히는 설계에는 좁히는 조건이 함께 필요했습니다
이 편에서 저희가 한 선택은 검색을 한 번의 조회에서 도구 선택 문제로 바꾼 것입니다. 그 선택 자체는 지금도 유효합니다.
빠뜨린 것은 넓힌 다음에 좁히는 조건이었습니다. 언제 루프가 필요한지, 무엇을 요약해도 되고 무엇은 원본으로 남겨야 하는지, 노드를 무엇으로 식별할지를 같이 정하지 않았습니다.
탐색 능력을 더하는 일은 대체로 눈에 보이고, 그 능력이 만드는 흔들림은 운영에 들어가서야 보입니다.
그리고 검색보다 앞에 놓인 문제가 먼저 커졌습니다. 그래프를 만드는 빌드 자체가 몇 시간짜리 작업이 되면서, 지금 어디까지 처리됐는지 묻는 요청에 답할 수 없게 됐습니다. 다음 편에서는 그 긴 빌드를 제어 가능한 작업으로 만든 과정을 다룹니다.
이전 편 → 질문이 답의 범위까지 정하고 있었습니다 (1편)

