지난 글에서
dialecticReasoningLevel을 low에서 max로 바꿨다. 무거운 모델을 쓰면 정확하고,
가벼운 모델을 쓰는 low는 가끔 헷갈린다는 결론이었다. 근데 다시 생각해보니 이상한
지점이 있었다 — low와 max는 모델만 다른 게 아니라, 검색을 몇 번 반복할 수 있는지
(MAX_TOOL_ITERATIONS)도 같이 달랐다. 그러니까 정확도 차이가 진짜 모델 때문인지,
반복 횟수 때문인지 아직 분리해서 확인한 적이 없었다.
두 변수를 갈라놓고 테스트하기
Honcho의 설정 파일을 보면 각 reasoning level마다 모델과 반복 횟수가 항상 세트로 묶여 있다.
minimal: 가벼운 모델, 반복 1회
low: 가벼운 모델, 반복 5회
medium: 무거운 모델, 반복 2회
high: 무거운 모델, 반복 4회
max: 무거운 모델, 반복 10회
그래서 설정을 일시적으로 바꿔서 두 가지 조합을 따로 만들어봤다.
- Variant A: 가벼운 모델 +
max의 반복 횟수(10회) - Variant B: 무거운 모델 +
minimal의 반복 횟수(1회)
Variant A부터 테스트했다. 지난 글에서 헷갈렸던 그 질문(“전국물류센터협회” 관련)을 다시 던져봤는데, 이번엔 정확했다.
Variant B는 결과가 아예 다른 방식으로 이상했다.
"최근에 업무 문서 목록을 정리하며 다음과 같은 내용을 변경했습니다.
* **삭제:** 기존의 '물류센터협회'라는 명칭을 제외했습니다."
여러 번 다시 물어봐도 매번 “삭제” 부분까지만 답하고 끊겼다. “추가” 부분은 언급도
못 하고 잘린 거다. 이건 이름을 헷갈린 게 아니라, minimal 단계의 짧은 출력
토큰 제한(250자)과 반복 1회짜리 예산 안에서 답을 다 못 쓴 거였다.
즉 가벼운 모델도 반복 횟수를 충분히 주면 정확했고, 무거운 모델도 예산이 너무 빡빡하면 (틀리는 게 아니라) 답을 다 못 마치는 채로 잘렸다. 정확도를 가른 건 모델 크기가 아니라 반복 횟수/토큰 예산 쪽이었다.
더 넓게 검증해보기
한 가지 케이스로 결론 내리기엔 부족해서, 성격이 다른 질문 다섯 개를 더 만들어서
Variant A(가벼운 모델 + 반복 10회), 운영 중이던 설정(무거운 모델 + 반복 10회),
low(가벼운 모델 + 반복 5회, 기본값) 세 가지로 각각 물어봤다.
| 질문 유형 | Variant A | 무거운 모델(운영 중) | low(기본값) |
|---|---|---|---|
| 이름 헷갈림 (1차) | ✅ | ✅ | ❌ 틀림 |
| 이름 헷갈림 (2차, 3차) | ✅ | ✅ | ✅ |
| 숫자 (커버리지 %) | ✅ | ✅ | ✅ |
| 인과관계 (빌드 시간) | ✅ | ✅ (관련 없는 정보 살짝 추가) | ✅ |
| 조건부 규칙 (알림) | ✅ | ✅ | ✅ |
| 예외 상황 (리뷰 지연) | ✅ | ✅ | ✅ |
7개 질문 중 low가 틀린 건 처음 발견했던 딱 1건뿐이었고, Variant A와 무거운 모델
쪽은 7개 다 맞았다. 그리고 속도 차이가 확실했다 — 무거운 모델은 질문당 평균
6.15초, Variant A(가벼운 모델 + 반복 10회)는 평균 0.86초. 정확도는 비슷한데
7배 가까이 빨랐다.
그래서 전부 가벼운 모델로 통일했다
가격도 확인했다. gemini-3-flash-preview(무거운 쪽)는 입력 100만 토큰당 $0.50,
출력 $3.00. gemini-3.1-flash-lite는 각각 $0.25, $1.50 — 정확히 절반이다.
정확도는 비슷하고, 속도는 7배 빠르고, 가격은 절반이라는 결과를 보고
medium/high/max/dream에 쓰던 무거운 모델을 전부 가벼운 모델로 바꿨다.
반복 횟수 설정(MAX_TOOL_ITERATIONS)은 그대로 유지했다 — 이번 실험에서 확인한
핵심이 바로 그 부분이었으니까.
근데 이게 실제로 언제 호출되는 거지?
여기까지 하고 나니 다른 질문이 생겼다. 이 dialectic 호출을 Hermes가 언제 쓰는 건지 궁금해졌다. 명령어로 직접 부를 때만 쓰이는 건지, 아니면 대화할 때마다 자동으로 도는 건지 코드를 열어봤다.
답은 거의 매 턴마다 자동으로였다. Hermes의 Honcho 플러그인은 두 겹으로 동작한다.
- 기본 컨텍스트(
peer.context()) — 사용자 프로필/요약을 매 턴 자동으로 끌어와서 대화에 끼워 넣는다. - dialectic 보충 질의 — 지금까지 테스트했던 그
.chat()호출과 같은 거다. 이것도 자동으로 백그라운드에서 돈다.
이 둘을 합친 게 설정 파일의 recallMode: "hybrid"다. 자동 주입 + 필요하면
에이전트가 직접 검색 도구를 추가로 호출하는 것까지 둘 다 가능한 모드다.
다만 “매 턴마다”라고 해도 단순하게 매번 새로 부르는 게 아니라, 캐시와 백그라운드 스레드로 짜여 있다. 호출이 타임아웃(기본 8초) 안에 안 끝나면 지금 응답은 그냥 나가고, 결과는 다음 턴에 반영된다. “ok”, “응” 같은 의미 없는 짧은 응답에는 아예 호출하지 않는다.
그래서 아까 모델을 가볍게 바꾼 게 성능 최적화 이상의 의미가 있었다. 느리면
그 턴엔 결과가 못 붙고 한 턴 밀리는 구조라서, 빠를수록 “지금 이 턴에” 최신
컨텍스트가 반영될 확률이 높아진다. 무거운 모델(610초)일 때는 종종 한 턴
늦게 반영됐을 텐데, 지금(0.81초대)은 거의 항상 그 턴 안에 끝날 것이다.
저장량이 늘어나면 느려질까
마지막으로 궁금했던 건 확장성이었다 — 대화가 쌓일수록 검색이 느려지지 않을까.
부분적으로는 맞다. 벡터 검색(pgvector의 HNSW 인덱스)은 데이터가 늘어도 완만하게 (로그 스케일로) 느려지는 방식이라 급격하게 나빠지진 않지만, 관련성 높은 결과가 많아질수록 LLM한테 넘기는 컨텍스트도 길어질 수 있어서 그만큼 답변 시간도 조금씩 늘어날 여지는 있다.
근데 그걸 막는 장치들도 이미 설정에 들어 있었다.
WORKING_REPRESENTATION_MAX_OBSERVATIONS = 100— 활성 관찰 목록 자체에 상한선.dream(메모리 정리 작업) — 새 문서가 50개 쌓이거나 일정 시간(8시간 이상)이 지나면 자동으로 돌면서 중복된 관찰을 합치고 오래된 걸 정리한다.- 컨텍스트 주입량 자체도 상한이 있다 — Hermes 쪽 설정(
dialecticMaxChars: 600)과 Honcho 쪽 설정(GET_CONTEXT_MAX_TOKENS = 100000) 둘 다.
그러니까 “저장량에 비례해서 계속 느려지는” 단순한 구조는 아니고, 검색 방식 자체가 완만하게 늘고 별도로 정리·상한선까지 걸어놔서 어느 정도 안정적으로 유지되게 설계돼 있다.
남는 생각
이번엔 처음에 냈던 결론(“무거운 모델이 정확하다”)이 절반만 맞는 얘기였다. 진짜 원인은 모델이 아니라 반복 횟수였고, 그걸 알고 나니 오히려 더 싸고 빠른 선택지가 나왔다. 처음 낸 결론에서 멈췄다면 계속 비싸고 느린 설정을 그냥 쓰고 있었을 거다.