LLM 에이전트에게 돈을 쓰지 않고 웹 검색 기능을 제공하는 방법: 자체 호스팅 SearXNG, 캐싱 및 인용 확인
요약
LLM 에이전트가 웹 검색 기능을 사용할 때 발생하는 비용 및 속도 제한 문제를 해결하기 위한 아키텍처를 제시합니다. 자체 호스팅 SearXNG와 캐싱, 속도 제한 로직을 갖춘 'Search Gateway' 계층을 도입하여 효율성을 높이는 방법을 설명합니다.
핵심 포인트
- LLM 에이전트에게 직접 웹 검색 도구를 제공하는 것은 비용 폭증의 위험이 있습니다.
- 중간 Search Gateway 계층을 두어 캐싱, 속도 제한, 인용 확인 기능을 구현해야 합니다.
- SearXNG를 자체 호스팅하고 JSON 출력을 활성화하여 외부 API 호출 없이 검색 기능을 확보할 수 있습니다.
- 캐시 적중률 향상을 위해 쿼리 정규화(normalize) 및 세션별 예산 관리가 중요합니다.
이번 주 Hacker News에서 Web Search API에 관한 글이 거의 500점을 받았습니다. 댓글은 주로 세 가지 어려움에 집중되어 있었습니다: 쿼리당 비용, 속도 제한(rate limit), 그리고 에이전트가 출처를 '지어내는' 문제였습니다. 저는 팀을 위해 몇 가지 내부 에이전트를 만들었습니다: 라이브러리 changelog 검색 에이전트, CVE 요약 에이전트, 그리고 벤더 문서에 대한 질의응답 에이전트입니다. 이 세 개 모두 웹 검색이 필요했습니다. 에이전트가 반복문 안에서 스스로 검색을 호출하여 API 청구서가 급증하는 것을 몇 번 경험한 후, 저는 자체 호스팅 아키텍처로 전환하고 여러 보호 계층을 추가했습니다. 이 글에서는 그 설정을 공유합니다. 이는 2GB RAM의 VPS에서 개발자가 실행하기에 충분히 간단합니다.
개요: 에이전트에게 인터넷에 직접 호출하지 않도록 하라
가장 흔한 실수는 LLM에게 외부 API로 바로 호출하는 search(query) 도구를 제공하는 것입니다. 에이전트는 절약 개념이 없습니다. 세션 동안 같은 질문을 5번 검색할 준비가 되어 있거나, 어려운 질문에 직면했을 때 비슷한 쿼리 30개를 검색할 수 있습니다.
따라서 저는 중간에 search gateway 계층을 두어 네 가지 작업을 처리하게 했습니다: 캐싱(cache), 속도 제한(rate limit), 콘텐츠 가져오기(fetch content) 및 인용 확인(citation check).
flowchart LR
A[LLM Agent] -->|tool call| B[Search Gateway]
B --> C{Cache hit?}
...
SearXNG는 오픈 소스 메타 검색 엔진입니다. 여러 엔진의 결과를 모아서 JSON으로 반환합니다. 쿼리당 비용을 지불할 필요가 없습니다. 대신, 너무 자주 호출하면 상위(upstream) 엔진이 IP를 차단할 수 있다는 점을 감수해야 하며, 속도 제한기는 바로 이 문제를 처리하기 위해 존재합니다.
1단계: Docker로 SearXNG 구축 (5분 소요)
기본적으로 SearXNG는 HTML만 반환하므로 settings.yml에서 JSON 형식을 활성화해야 합니다. 이것이 많은 사람이 막히는 지점입니다: format=json을 호출하면 이유를 모른 채 바로 403 Forbidden 응답을 받습니다.
mkdir -p ~/searxng/config && cd ~/searxng
# 최소한의 설정 생성, JSON 출력 활성화
...
주의: 저는 이를 127.0.0.1에 바인딩하여 외부로 열지 않았습니다. 제한 기능이 없는 공개 SearXNG 인스턴스는 며칠 동안 봇에게 남용될 수 있으며, 귀하의 IP는 상위 엔진 목록에서 블랙리스트에 오를 것입니다.
2단계: 캐시와 속도 제한을 갖춘 Search Gateway
Gateway는 Python 3.12로 작성되었으며, httpx 0.27과 diskcache 5.6을 사용합니다. 저는 내부 에이전트 몇 개만으로도 디스크 캐시가 충분하기 때문에 Redis를 사용하지 않았고, 추가 서비스가 필요 없습니다.
import hashlib, time, threading
import httpx
from diskcache import Cache
...
주목할 만한 세부 사항이 몇 가지 있습니다. normalize() 함수는 "Python 3.13 Release"와 "python 3.13 release"가 동일한 캐시를 사용하도록 합니다. 제가 실제로 로그를 확인했을 때, 이 기능만으로도 캐시 적중률(cache hit rate)이 약 18%에서 약 35%로 증가했습니다. TTL은 기술적인 질문에 적합한 6시간으로 설정했습니다. 만약 에이전트가 뉴스를 검색한다면 15~30분으로 낮추세요.
또한 **세션별 예산(budget)**을 추가하는 것이 좋습니다. 예를 들어, 사용자 질문당 최대 8번의 검색을 제한하는 식입니다. 예산이 소진되면 도구는 "Search budget exhausted, answer with what you have"라는 문자열을 반환합니다. 최신 LLM은 이 메시지를 상당히 잘 이해하고 반복 루프를 중단할 것입니다.
콘텐츠 가져오기 및 허위 인용 차단 (Fetch nội dung và chặn trích dẫn bịa)
검색 엔진의 스니펫(Snippet)은 보통 1~2문장으로 짧아 답변하기에 충분하지 않습니다. 에이전트는 페이지 내용을 읽어야 하지만, 전체 HTML을 컨텍스트로 제공하면 토큰 소모가 크고 노이즈가 됩니다. 저는 주요 콘텐츠 부분을 추출하기 위해 trafilatura 1.12를 사용합니다.
더 중요한 부분은 **인용 확인(citation check)**입니다. LLM이 인용과 함께 답변을 작성한 후, 게이트웨이는 해당 인용된 부분이 실제 소스 페이지에 존재하는지 확인합니다.
sequenceDiagram
participant Agent
participant Gateway
...
import trafilatura
from rapidfuzz import fuzz
...
저는 정확한 일치(exact match) 대신 partial_ratio를 사용하는 rapidfuzz 3.x를 사용합니다. 이는 LLM이 구두점이나 공백을 약간 수정하는 경향이 있기 때문입니다. 임계값(threshold) 85는 약 200개의 답변에 대해 테스트한 후 도출한 수치입니다. 이보다 낮은 임계값은 허위 인용을 통과시키고, 더 높은 임계값은 유효한 인용을 놓칠 수 있습니다.
verify_citations가 비어있지 않은 목록을 반환하면, 저는 이를 LLM에게 다시 제공하고 다음과 같은 지침을 추가합니다: "다음 인용들은 소스에서 찾을 수 없으니, 해당 주장을 삭제하거나 수정하세요." 보통 한 번의 반복만으로 답변이 깨끗해집니다.
프로덕션 환경 운영 시 얻은 교훈들
프로덕션 환경 운영 시 얻은 교훈들
- 업스트림 엔진이 당신을 차단할 것입니다. Google이 가장 빠르게 차단하는 엔진입니다.
settings.yml에서 DuckDuckGo, Brave, Wikipedia, Stack Overflow와 같은 다른 엔진들을 추가하여 폴백(fallback) 기능을 활성화하세요.docker logs searxng로그를 모니터링하다가CAPTCHA나suspended메시지를 보면 속도를 줄여야 할 때입니다. - 웹 콘텐츠를 통한 프롬프트 주입(Prompt injection)은 실제로 발생합니다. 웹 페이지에
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기