* 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
(폐쇄망 사내 에이전트 ⑧) 소형모델
4B 모델로 에이전트를 굴리면 두 종류의 벽을 만난다. 하나는 컨텍스트가 좁다는 것(10,000토큰), 다른 하나는 모델이 자주 헤맨다는 것. 둘 다 “큰 모델을 쓰면 해결”이지만 폐쇄망 사무 PC에 그런 선택지는 없다. 이번 편은 런타임이 이 둘을 대신 메운 커밋 세 개다.
① 도구 정의 지연 로딩 — 이름은 싸고 스키마는 비싸다
충격적인 실측부터. Tool 35종의 정의(JSON 스키마)가 7,081토큰 — 컨텍스트 10,000의 71%를 매 요청 고정으로 먹고 있었다. 엑셀을 안 쓰는 대화에도 excel.* 5종 1,350토큰이 매번 실렸다. 대화에 남는 자리는 2,882토큰. 클로드 코드가 MCP 도구를 다루는 방식을 참고해 지연 로딩을 넣었다.
commit add558c (2026-08-15)
feat: Tool 정의 지연 로딩 — 컨텍스트 여유 2,756 → 7,330토큰
코어 그룹(filesystem·search·todo·skill·tool)만 상시 탑재.
tool.load 신설: 닫힌 그룹을 연다. READ_ONLY다 —
여는 것은 정의를 프롬프트에 싣는 일이고,
그 안의 도구 실행에는 각자의 위험 등급과 승인이 그대로 걸린다.
실측: 프롬프트에 실리는 tool 36종 7,244토큰 → 15종 2,670토큰
대화 여유 2,756토큰(28%) → 7,330토큰(73%)
판단 하나가 인상적이다. “이름+한줄 카탈로그”를 실을까 고민했는데 카탈로그만 973토큰, 그룹 이름 12개는 30토큰이었다. 4B에게는 “엑셀 도구가 있다” 정도면 판단에 충분하고 세부는 연 뒤에 보면 된다 — 그래서 30토큰 쪽을 택했다. 토큰 예산 편의 “적게, 그러나 정확하게”가 그대로 실전이 된 사례다. 그리고 감춘 것이지 없앤 게 아니라서, 정책·승인·감사 경로는 한 줄도 안 바뀌었다.
② 그런데 4B가 그룹을 못 찾는다 — 발견성
지연 로딩을 넣자마자 우려가 실측으로 나왔다. 질문에 영단어 그룹명이 그대로 나온 git·shell은 잘 여는데, “이 xlsx 좀 봐줘”에서 excel 그룹을 열 생각을 못 한다. filesystem.read로 바이너리를 열려다 실패하고 “전용 도구가 필요하다”며 포기했다. 큰 모델이면 알아서 이었을 연결을, 4B는 못 잇는다.
commit 7a25049 (2026-08-15)
fix: 지연 로딩 발견성 — 4B가 "xlsx→excel 그룹"을 잇지 못하던 것
- tool.load 설명에 그룹별 "언제 여는가" 힌트 8그룹 ~80토큰
excel(xlsx 엑셀 표 읽기·필터·강조·생성), pdf(PDF 문서 읽기)…
- 시스템 프롬프트에 한 줄: "파일 형식이나 작업에 맞는 tool이
안 보이면 tool.load로 그 그룹을 먼저 열어라."
재시험: excel(시트·합계 정확)·word(결론 정확)·pdf 모두 해소
해법이 ‘더 많은 정보’가 아니라 정확히 조준된 80토큰이라는 게 핵심이다. 973토큰짜리 전체 카탈로그 대신, 파일 형식→그룹을 잇는 힌트만. 소형 모델 대응은 결국 “모델이 못 하는 그 한 걸음”을 찾아 그 자리에만 다리를 놓는 일이다.
③ 성공만 반복하는 무한 루프 — 진전의 정의
4편에서 단계 상한을 없앤 대가가 여기서 나왔다. 실사용 로그(08:08)에 이런 게 찍혔다.
filesystem.list maxResults 100 → 50 → (없음) → 50 → 100 → 5 → 1
전부 SUCCESS — 38초 동안 같은 폴더 목록만 일곱 번
코드로 — 감춘 것이지 없앤 것이 아니다
지연 로딩의 실제 구현은 레지스트리 데코레이터 하나다. 클래스 주석이 설계 문서 그 자체라 그대로 옮긴다.
// packages/tool-runtime/src/DeferredToolRegistry.ts
/**
* Tool 정의를 필요할 때만 모델에 노출하는 레지스트리 데코레이터.
*
* 대가: 그룹을 열면 프롬프트 앞머리가 달라져 KV 접두사가 깨진다.
* 대화당 그룹을 여는 횟수만큼 콜드 평가가 다시 든다(실측 20.8초).
* 매 턴이 아니라 여는 순간 한 번뿐이고,
* 그 대신 이후 모든 턴이 4,000토큰 가까이 가벼워진다.
*
* 실행은 막지 않는다. find·all은 그대로 위임한다 —
* 정의를 감추는 것이지 도구를 없애는 것이 아니다.
* 정책·승인·감사는 종전과 완전히 같은 경로를 지난다.
*/
export class DeferredToolRegistry implements ToolRegistry { … }
주석의 ‘대가’ 문단이 핵심이다. 지연 로딩은 공짜가 아니라 KV 캐시(4편)와의 트레이드오프다 — 그룹을 여는 순간 콜드 20.8초를 한 번 다시 낸다. 그 비용까지 실측해 적어놨기에 “자주 쓰는 그룹은 코어로 승격” 같은 다음 조정의 근거가 남는다. 최적화 하나를 넣을 때 그것이 깨는 다른 최적화를 명시하는 습관.
재미있는 버그 흔적도 하나 있다 — 열 수 있는 그룹 목록을 도구 설명에 싣는데, 그 목록을 만들려고 전체 도구 정의를 훑으면 자기 자신을 다시 불러 무한 재귀가 난다. 그래서 그룹 이름은 등록 시점에 한 번만 모아 둔다. 메타 도구(도구를 다루는 도구)를 만들 때 밟기 쉬운 지뢰다.
소형 모델 대응 3종의 공통 패턴
| 문제 | 런타임의 해법 | 비용 |
|---|---|---|
| 컨텍스트 부족 | 정의 지연 로딩 | 그룹 오픈 시 콜드 1회 |
| 연결 못 함(xlsx→excel) | 조준된 힌트 80토큰 | 80토큰 |
| 성공 무한 반복 | 결과 지문(FNV-1a) 판정 | 해시 계산뿐 |
셋 다 모델을 바꾸지 않고, 비용을 계측하고, 문제 지점에만 개입한다. “프롬프트에 지시를 더 쓰자”는 유혹이 들 때마다 이 표를 떠올리면 좋다 — 지시는 매 턴 토큰을 내지만, 구조는 한 번 내면 끝이다.
✅ 확인
- 내 에이전트의 도구 정의가 몇 토큰인지 실측했다 (전체 컨텍스트 대비 %).
- 지연 로딩을 쓴다면 발견성 실패 케이스(모델이 그룹을 못 찾는)를 실모델로 테스트했다.
- 루프 종료 조건에 ‘새 정보 없음’ 판정이 있다 — 성공/실패만으로는 못 끊는 패턴이 있다.
인자가 조금씩 다르니 중복 차단을 피하고, 매번 ‘성공’이니 연속 실패 카운터는 계속 리셋된다. 종료 조건이 실패에만 걸려 있으면 이 패턴을 못 끊는다. 커밋의 진단이 정확하다 — 진전의 기준은 “성공했는가”가 아니라 “새 정보를 얻었는가”여야 한다. 그래서 결과의 지문(FNV-1a 해시)을 남겨, 인자가 달라도 같은 내용을 다시 받으면 성공을 실패로 바꿔 모델에게 방향을 알리고, 3회면 그간의 작업을 담아 종료하게 했다. 결과가 매번 다르면 같은 도구를 몇 번을 불러도 안 끊는다 — 정상 진행이니까.
셋의 공통점 — 전부 프롬프트 튜닝이 아니라 런타임 구조로 풀었다. 소형 모델에게 ‘더 잘하라’고 말하는 대신, 못 하는 지점을 계측으로 찾아 코드가 받친다. “큰 모델이면 됐을 것”이 이 프로젝트의 기본 반례 문구다.
자주 묻는 질문 (FAQ)
도구 정의 지연 로딩은 어떤 프로젝트에나 유효한가요?
도구 수가 많고 컨텍스트가 작을수록 효과가 큽니다. 이 프로젝트 실측으론 35종 중 코어 15종만 상시 탑재해 대화 여유가 28%에서 73%로 늘었습니다. 단 소형 모델은 ‘어느 그룹을 열지’를 못 찾을 수 있어 발견성 힌트를 함께 설계해야 합니다.
에이전트 무한 루프는 보통 어떻게 막나요?
단계 상한은 정상 작업까지 자르는 둔한 도구입니다. 이 프로젝트는 동일 호출 차단, 연속 실패 상한에 더해 ‘결과 지문이 같으면 진전 없음’이라는 판정을 씁니다. 성공/실패가 아니라 새 정보 획득 여부로 진전을 재는 게 핵심입니다.

답글 남기기