ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 로컬 챗봇 시리즈 #8 — 봇은 도구 풀을 좁히는 장치다: '지식금고 검색가' 한 도구 봇이 가장 효과적인 이유
    IT 2026. 5. 9. 21:30
    로컬 챗봇 시리즈 #8 — 봇은 도구 풀을 좁히는 장치다: '지식금고 검색가' 한 도구 봇이 가장 효과적인 이유

    들어가며 — 봇은 GPTs의 라이트 버전이 아니라 도구 풀 좁히기 장치다

    "사용자 정의 봇"이라는 기능을 보면 ChatGPT의 GPTs를 흉내낸 라이트 버전 같다. 시스템 프롬프트 + 도구 묶음 + 선택적으로 외부 MCP 서버를 묶어둔 것이다. 사실은 맞다. 그런데 이걸 단순히 "GPTs 클론"으로 보면 가장 큰 가치를 놓친다.

    시리즈 #7에서 본 "도구 paradox" — 작은 모델은 도구 풀이 커지면 호출 정확도가 떨어진다. 봇은 그 paradox에 대한 답이다. "이 작업에는 이 3-5개 도구만 써라"라는 의도를 사용자가 작업 단위로 미리 정의해두는 메커니즘이다. 이번 글은 봇의 진짜 가치 — 도구 풀 좁히기의 효과를 한 사례("지식금고 검색가" 봇)로 풀어쓴다.


    1. "지식금고 검색가" 봇 — 도구 1개만 노출했더니 호출 빈도가 5배 됐다

    실제 사용 사례 하나를 보자. 지식금고에서 과거 노트를 자주 찾는다. 시리즈 #7의 도구 11개 풀에서는 모델이 망설이다 그냥 답해버리는 일이 많아 RAG 호출 빈도가 낮았다. 그래서 봇 하나를 만들었다.

    diagram

    이 그림은 한 봇의 정의를 통째로 보여준다 — 위에서부터 이름(노란색), 시스템 프롬프트(파란색), 허용 도구(녹색), MCP 서버(보라색)의 네 필드로 이뤄진다. 핵심은 녹색 박스의 도구 목록이 단 하나라는 점이다. 11개 도구 풀에서 단 1개만 노출하니, 모델이 도구 호출을 결정할 때 "search_knowledge_vault를 부를까, 안 부를까" 두 갈래뿐이다 — RAG 호출이냐 자연어 직답이냐. 시스템 프롬프트(파란색)가 그 두 갈래 중 무엇을 선호할지를 모델에게 강하게 가이드한다 — "무조건 RAG 먼저, 자체 지식으로 답하지 마라". 이 두 가지(좁힌 도구 풀 + 명확한 가이드)가 결합하니 모델이 거의 매번 올바른 선택을 한다.

    효과는 즉각이었다. 같은 사용자 질문에 대해 — 봇 비활성 시 RAG 호출률 ~30% (모델이 도구 11개 중 망설임), 봇 활성 시 ~95%로 올라갔다. 한 질문 한 호출이 거의 보장된다. 사용자 입장에서는 "내 지식금고 노트가 갑자기 실제로 챗봇에 작용하기 시작했다"는 인상을 받는다.


    2. 봇은 시스템 프롬프트 조립의 맨 끝에 합류한다

    시리즈 #2에서 시스템 프롬프트 조립 순서를 다뤘다(① BASE → ② 글로벌 CI → ③ 프로젝트 지침 → ④ 첨부 → ⑤ 메모리). 봇은 이 시퀀스에서 ③ 위에 추가로 끼어든다.

    diagram

    이 그림이 보여주는 봇 페르소나 블록(빨간색)의 위치가 중요하다. ③ 프로젝트 지침 바로 다음, ④ 첨부 직전이다. 즉 모든 일반적 지시(① BASE, ② 글로벌 CI, ③ 프로젝트) 뒤에 배치되어 모델이 가장 강하게 의식하는 위치를 차지한다. 시리즈 #2에서 다룬 "뒤에 오는 지시 우선" 원칙의 활용이다. 봇이 시스템 프롬프트의 마지막 페르소나 신호로 작동하니, 봇 활성 시 모델 행동이 봇 의도대로 강하게 정렬된다.

    구현은 헤더 한 줄과 분기 몇 줄이다.

    # proxy.handle_agent_chat
    bot_id = request.headers.get("X-Bot-ID")
    if bot_id:
        bot = _bot_mod.get_bot(bot_id)
        if bot:
            if bot.get("system_prompt"):
                system_prompt += f"\n\n[봇 페르소나: {bot['name']}]\n{bot['system_prompt']}"
            if bot.get("allowed_tools"):
                bot_allowed_tools = list(bot["allowed_tools"])  # → 도구 풀 좁힘
            if bot.get("mcp_servers"):
                bot_mcp_servers = list(bot["mcp_servers"])      # → MCP 동적 합류
    

    이 코드의 디자인 비결은 "두 가지 변경을 같은 헤더로 동시에 트리거"한다는 점이다. X-Bot-ID 한 헤더가 (a) 시스템 프롬프트에 페르소나 추가, (b) 도구 풀 화이트리스트 적용, (c) MCP 서버 활성화 — 세 변경을 한꺼번에 발동시킨다. 클라이언트 입장에서는 봇 셀렉터에서 한 번 클릭하면 헤더가 바뀌고, 다음 채팅부터는 다른 페르소나의 챗봇처럼 동작한다. 사용자 인지 비용이 낮다.

    봇 정의는 단순한 JSON 파일(data/bots/<id>.json)이다. 가져오기·내보내기·공유가 쉽고, 백엔드 데이터 모델 복잡도는 거의 0이다.


    3. "봇 풀"의 디자인 원칙 — 작업 단위로 좁게

    봇을 만들기 시작하면 처음에는 "범용 봇"을 만들고 싶어진다. 도구 9개를 묶고 시스템 프롬프트도 길게 쓰고 싶어진다. 그러면 효과가 없다 — 일반 챗봇과 같아진다. 봇의 가치는 "좁힘"에서 온다. 자주 쓰는 봇 3개 정도가 황금비다.

    diagram

    이 그림이 보여주는 세 봇의 공통점은 모두 도구 1-3개로 좁아져 있다는 점이다. 일반 챗봇 모드(도구 11개)는 "어떤 봇도 안 어울리는 자유 대화"용으로 남겨두고, 명확한 작업 단위가 있으면 즉시 봇을 활성화한다. 코딩 작업이면 Coder 봇, 가족 일정 관리면 가족일정 도우미 봇, 지식금고 노트 검색이면 지식금고 검색가 봇을 쓴다. 각 봇이 자기 영역에서는 도구 호출 정확도가 거의 100%에 가깝고, 자기 영역을 벗어나는 질문은 봇 시스템 프롬프트가 부드럽게 거절(또는 사용자가 다시 봇 비활성화)한다. 상단 셀렉터에서 한 번 클릭으로 페르소나·도구가 다 바뀐다.


    4. 트레이드오프 — 봇의 비용과 함정

    4-1. 화이트리스트가 작아질수록 회복 수단이 사라진다

    "지식금고 검색가" 봇처럼 도구 1개만 노출하면 그 도구가 실패할 때 회복이 어렵다. 구체적으로 — RAG 서버(vault-search)가 다운되어 있으면 모델이 search_knowledge_vault를 호출하고 "[도구 오류] search_kv: ConnectionError"만 받는다. 다음 시도할 도구가 없다. 모델은 자연어로 답하긴 하지만 사용자 의도(노트 검색)를 만족하지 못한다. 사용자는 "왜 봇이 멍청해졌지?"라고 느낀다.

    이 함정은 "좁힘"의 양면성을 보여준다. 도구를 좁히면 호출 정확도가 올라가지만 동시에 단일 장애점이 생긴다. 11개 도구 중 1개가 실패해도 나머지 10개로 우회할 수 있는 일반 챗봇과 달리, 1도구 봇은 그 1개의 가용성에 100% 의존한다. 운영 측면에서는 vault-search 같은 핵심 의존성의 모니터링이 더 중요해진다.

    완화책은 봇에 fallback 도구 1-2개를 더 두는 것이다(예: web_search). 그러면 RAG가 실패해도 web으로 우회한다. 하지만 그러면 "한 도구 봇"의 단순함과 정확도 이점이 줄어든다. 도구 1개로 정확도 95%였던 게 도구 2개로 늘리면 정확도 80%로 떨어질 수 있다. 가용성과 정확도 사이의 트레이드오프 — 사용자 패턴에 따라 결정한다. 핵심 도구의 가용성이 충분히 높다면 1도구 봇이 합리적이고, 자주 흔들린다면 fallback 추가가 합리적이다.

    4-2. 봇 시스템 프롬프트가 글로벌 CI 위에 누적된다

    봇 페르소나는 글로벌 Custom Instructions의 다음 자리에 추가되니, 두 개 합산이 토큰을 더 잡아먹는다. 봇 프롬프트 길이가 200자라면 글로벌 CI 4000자 + 봇 200자 = 4200자가 시스템 프롬프트에 들어간다. 매 요청 토큰 비용이 든다. 시리즈 #2에서 다룬 vLLM prefix caching이 이 비용을 거의 무료로 만들지만, 봇을 자주 전환하는 시나리오에서는 캐시 hit률이 떨어진다 — 매 봇 전환마다 시스템 프롬프트가 달라지니 새 prefix를 캐시에 빌드해야 한다.

    또 한 가지 함정 — 시리즈 #2의 "뒤에 오는 지시 우선" 원칙이 봇에도 적용된다. 봇 페르소나가 글로벌 CI보다 강하게 작동하니, 글로벌 CI에 모순되는 봇 페르소나를 만들면 봇이 이긴다. 의도된 동작이지만 사용자 인식이 필요하다. 예를 들어 글로벌 CI에 "코드 예시 짧게"라고 적어뒀는데 Coder 봇 시스템 프롬프트에 "긴 코드 예시 자세히"라고 쓰면, Coder 봇 활성 시 긴 코드가 나온다 — 의도일 수도 있고, 사용자 본인이 잊고 있던 모순일 수도 있다. 봇 시스템 프롬프트는 짧게(200자 이내), 글로벌 CI와 명백한 충돌을 피하는 형태로 작성하는 게 안전하다.

    4-3. MCP 도구 합류는 화이트리스트를 우회한다

    봇이 활성화한 MCP 서버의 도구는 allowed_tools 화이트리스트 적용 후에 추가된다. 즉 봇이 allowed_tools: ["search_knowledge_vault"]로 1개 도구만 허용한다고 적어도, 같은 봇이 mcp_servers: ["github"]로 GitHub MCP를 활성화하면 GitHub의 도구 5-10개가 추가로 합류한다. 결과적으로 봇 도구 풀이 1개 + 5-10개 = 6-11개로 늘어나 "도구 paradox" 영역으로 다시 진입할 수 있다.

    이 디자인 결정의 함의는 봇 디자이너가 "MCP 서버를 활성화한다는 것은 그 서버의 모든 도구를 신뢰한다"는 인식을 가져야 한다는 점이다. MCP 서버가 제공하는 모든 도구가 봇의 작업과 잘 맞는지 미리 확인해야 한다. 그렇지 않으면 봇 화이트리스트로 좁힌 효과가 MCP 합류로 깨진다. 더 정밀한 제어가 필요하면 — MCP 서버가 5개 도구를 노출하는데 그중 3개만 쓰고 싶다면 — 별도 화이트리스트 메커니즘이 필요한데 현재는 미구현이다. 단순함을 우선한 결정이지만 MCP 사용이 늘면 재검토해야 할 것이다.


    5. 마무리

    봇의 진짜 가치는 "GPTs의 라이트 버전"이 아니라 "도구 풀을 작업 단위로 미리 좁혀두는 사용자 도구"라는 점이다. 시리즈 #7에서 발견한 "도구 paradox"의 답이 봇 화이트리스트 한 줄 — 작은 모델일수록 그 효과가 크다.

    다른 부수 — 봇 정의는 data/bots/<id>.json, 활성화는 X-Bot-ID 헤더, UI는 상단 셀렉터 + 봇 관리/편집 모달, localStorage로 마지막 활성 봇 기억 — 는 표준 작업이다.

    다음 편은 MCP 통합이다. 봇의 mcp_servers 필드가 외부 표준 도구를 어떻게 동적으로 합류시키는지 다룬다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.