내 브라우저 에이전트가 느렸던 이유는 GPT-5 때문이 아니라 프롬프트에 쓰레기를 계속 집어넣었기 때문이었다
요약
브라우저 에이전트의 성능 저하 원인이 모델 성능이 아닌 프롬프트 비대화(prompt bloat)에 있음을 지적합니다. 불필요한 컨텍스트를 제거하고 프롬프트 캐싱을 활용하는 효율적인 컨텍스트 관리 전략을 제안합니다.
핵심 포인트
- 에이전트 지연의 주범은 과도한 스크린샷과 전체 이력 등 프롬프트 비대화임
- 불필요한 컨텍스트는 지연 시간, 신뢰성, 비용 문제를 동시에 유발함
- 프롬프트 캐싱 효율을 높이기 위해 프롬프트 접두사를 안정적으로 유지해야 함
- 페이지 전체 덤프 대신 필요한 파편(fragments)만 전달하는 최적화가 필요함
나는 계속해서 잘못된 것을 탓하고 있었다.
브라우저 에이전트(browser agent)가 느리게 느껴질 때, 나는 GPT-5를 탓했다. 그다음에는 Claude Opus 4.6을, 그다음에는 Playwright를, 그다음에는 API 속도 제한(rate limits)을, 그다음에는 브라우저 자체를 탓했다.
실제 트레이스(traces)를 살펴본 후, 답은 훨씬 덜 영광스러웠다:
나는 매 단계마다 모델에 너무 많은 쓰레기를 보내고 있었다.
전체 페이지 덤프(Full page dumps). 스크린샷(Screenshots). 반복되는 지침(Repeated instructions). 전체 작업 이력(Full action history). 오래된 추론(Old reasoning). 더 많은 스크린샷. 동일한 작업 텍스트의 재전송.
이 내용이 익숙하게 들린다면, 당신의 병목 현상(bottleneck)은 모델의 품질이 아닐 수도 있다. 그것은 프롬프트 비대화(prompt bloat)일 수 있다.
나는 r/openclaw에서 이에 대한 좋은 토론을 접했다. 더 토큰 효율적(token-efficient)이고 빠른 브라우저 사용에 관한 스레드였다. 이는 내가 실제로 보고 있었던 것과 일치했다. 많은 "브라우저 에이전트 성능 문제"는 사실 컨텍스트 관리(context management) 문제라는 것이다.
우리 대부분이 처음에 만드는 잘못된 루프
순진한 브라우저 에이전트 루프는 보통 다음과 같다:
- 페이지 열기
- 스크린샷 캡처
- 페이지 텍스트 추출
- 전체 작업 지침 포함
- 전체 이전 이력 포함
- 모델에게 다음에 무엇을 할지 질문
- 20번 반복
이 방식은 데모용으로는 작동한다.
하지만 프로덕션(production) 환경에서는 엉망이 된다.
10단계나 20단계에 이르면, 당신의 모델은 더 이상 깨끗한 상태(clean state)에서 결정하는 것이 아니다. 그것은 쓰레기 매립지를 뒤지고 있는 것이다.
이는 세 가지를 동시에 해친다:
- 지연 시간 (latency)
- 신뢰성 (reliability)
- 비용 (cost)
그러고 나서 사람들은 실제 문제가 모든 프롬프트가 이전보다 커지고 있다는 것임에도 불구하고, 속도 제한 디버깅(rate limit debugging)이나 LLM 타임아웃 문제 해결(llm timeout troubleshooting)을 시작한다.
트레이스가 보통 보여주는 것
느린 에이전트 실행을 조사할 때, 나는 보통 다음과 같은 것들의 조합을 발견한다:
- 매 턴마다 반복되는 동일한 시스템 지침 (system instructions)
- 매 턴마다 반복되는 동일한 워크플로우 지침 (workflow instructions)
- 페이지가 대부분 텍스트임에도 불구하고 첨부된 전체 스크린샷
- 관련 있는 파편(fragments)만 대신 페이지 전체 텍스트를 덤프함
- 짧은 요약 대신 보존되는 전체 작업 이력 (full action history)
- 영원히 끌고 오는 오래된 추론 흔적 (old reasoning traces)
이것은 "더 많은 컨텍스트"가 아니다.
그것은 스스로 초래한 지연 시간(self-inflicted latency)이다.
프롬프트 캐싱 (Prompt caching)이 설계 규칙을 바꾼다
이 부분이 너무 자주 무시됩니다.
만약 OpenAI 호환 API를 사용하고 있다면, 프롬프트 캐싱 (Prompt caching)은 루프를 구성하는 방식에 영향을 미쳐야 합니다.
두 가지 세부 사항이 매우 중요합니다:
- 캐싱은 1024개 이상의 토큰(tokens)에서 자동으로 활성화됩니다.
- 캐시 라우팅 (cache routing)은 일반적으로 안정적인 프롬프트 접두사 (prompt prefix)에 의존합니다.
따라서 프롬프트의 상단이 계속해서 바뀐다면, 캐시 히트 (cache hit) 확률을 낮추게 됩니다.
프롬프트의 첫 부분이 안정적이라면, 저렴하고 빠른 경로를 확보할 가능성이 훨씬 높아집니다.
이는 캐싱된 입력 (cached input)이 캐싱되지 않은 입력 (uncached input)보다 훨씬 저렴할 수 있기 때문에 중요합니다.
그러니 GPT-5에서 Claude Opus 4.6으로, 또는 Claude에서 어떤 오픈 모델 (open model)로 전환하기 전에, 더 부끄러운 질문을 스스로에게 던져보세요:
나는 캐싱이 작동할 수 있도록 프롬프트를 설계했는가?
실수: 상단에 동적인 쓰레기를 배치하는 것
많은 에이전트 루프 (agent loops)가 다음과 같이 동작합니다:
[최신 스크린샷 블롭 (latest screenshot blob)]
[현재 페이지 덤프 (current page dump)]
[최신 DOM 직렬화 (latest DOM serialization)]
...
이것은 거꾸로 된 방식입니다.
캐시 친화적인 (cache-friendly) 프롬프트를 원한다면, 상단은 지루하고 안정적이어야 합니다.
다음과 같은 방식이 더 적절합니다:
[안정적인 시스템 프롬프트 (stable system prompt)]
[안정적인 작업 프레임워크 (stable task framing)]
[안정적인 도구 지침 (stable tool instructions)]
...
같은 모델, 같은 브라우저임에도 성능은 매우 다릅니다.
Browser Use는 이미 이 내용을 말해주고 있습니다
회의적인 시각으로 Browser Use 설정을 읽어본다면, 설정 옵션(knobs)들이 꽤 많은 것을 드러내고 있음을 알 수 있습니다.
| 옵션 | 중요한 이유 |
|---|---|
use_vision=False | 시각적 컨텍스트 (visual context)가 실제로 필요한 경우가 아니라면 스크린샷을 보내지 마세요 |
| ... |
이것은 모델 업그레이드 목록이 아닙니다.
컨텍스트 (context) 낭비를 멈추는 방법들의 목록입니다.
실용적인 Browser Use 설정
제가 시작할 때 사용할 형태는 다음과 같습니다:
from browser_use import Agent, ChatBrowserUse
agent = Agent(
...
핵심 아이디어는 간단합니다:
- 메인 모델이 결정을 내리게 합니다.
- 더 저렴하거나 작은 모델이 추출 (extraction) 작업을 수행하게 합니다.
- 히스토리 (history)를 짧게 유지합니다.
- 스크린샷은 필요할 때만 사용합니다.
만약 GPT-5.5가 시간의 절반을 스크린샷과 거대한 페이지 덤프를 다시 읽는 데 쓰고 있다면, 그것은 모델의 문제가 아닙니다.
루프에 실제로 들어가야 할 것
Playwright나 Selenium을 사용하여 직접 하네스 (harness)를 구축하고 있다면, 가차 없이 행동하십시오.
매 단계마다 보내야 할 좋은 것들
- 안정적인 시스템 프롬프트 (system prompt)
- 안정적인 작업 정의 (task definition)
- 현재 URL
- 압축된 구조화된 상태 (compact structured state)
- 현재 목표와 관련된 가시적 요소들
- 마지막 1~3개의 액션 (actions)
- 변경된 사항에 대한 짧은 요약
매 단계마다 보내면 안 되는 나쁜 것들
- 기본값으로 전체 스크린샷을 보내는 것
- 전체 DOM 덤프 (DOM dumps)
- 단 하나의 섹션만 중요할 때 전체 페이지 텍스트를 보내는 것
- 절대 변하지 않는 반복적인 지시 사항
- 이전 턴의 사고 사슬 (chain-of-thought) 또는 내부 추론 (internal reasoning)
- "혹시 몰라서" 보내는 거대한 히스토리 객체들
마지막 항목이 치명적입니다.
대부분의 에이전트는 길고 가공되지 않은 히스토리를 필요로 하지 않습니다. 그들에게 필요한 것은 압축된 메모리 (compressed memory)입니다.
압축이 연산 능력보다 중요하다
이 부분은 더 많은 에이전트 빌더들이 내재화해야 할 지점이라고 생각합니다.
최고의 브라우저 에이전트는 단순히 브라우징을 더 잘하는 것이 아닙니다.
그들은 압축을 더 잘합니다.
그들은 다음과 같은 것을:
Stripe로 이동하세요.
payouts를 클릭하세요.
날짜 필터를 지난달로 설정하세요.
...
다음과 같이 바꿉니다:
기술 실행: monthly_finance_export
이것은 엄청난 차이입니다.
OpenClaw와 관련된 Reddit 스레드들이 이 점을 매우 잘 설명했습니다. 몇몇 사람들은 워크플로우 지속성 (workflow persistence)과 재사용 가능한 기술 (reusable skills)이 에이전트를 취약한 존재가 아닌, 실제로 사용할 수 있는 존재로 만드는 핵심 요소라고 설명했습니다.
이는 제 경험과도 일치합니다.
에이전트가 워크플로우를 지속하거나 이름이 지정된 기술을 호출할 수 있다면, 매 실행마다 동일한 루틴을 다시 설명하기 위해 지불해야 하는 토큰 세금 (token tax)을 더 이상 내지 않아도 됩니다.
추출과 추론의 분리
이것은 가장 쉽게 얻을 수 있는 성과 중 하나입니다.
더 작은 모델이 할 수 있는 저렴한 추출 (extraction) 작업을 프런티어 모델 (frontier model)이 수행해서는 안 됩니다.
예를 들어:
- 가시적인 텍스트, 레이블, 폼 상태 (form state)를 추출하는 데는 더 작은 모델을 사용합니다.
- 고차원적인 의사결정에는 GPT-5 또는 Claude Opus 4.6을 사용합니다.
이렇게 하면 에이전트를 멍청하게 만들지 않으면서도 낮은 지연 시간 (latency)과 낮은 비용을 얻을 수 있습니다.
의사 코드 (pseudocode)로 표현하면 다음과 같습니다:
state = small_extraction_model.extract(page)
decision = frontier_model.decide(task, summarized_history, state)
execute(decision)
그러한 패턴은 하나의 비싼 모델에게 모든 것을 강요하는 것보다 훨씬 더 건강한 방식입니다.
Playwright 예시: 소설이 아닌 델타(deltas)를 보내세요
만약 직접 루프(loop)를 구축하고 있다면, 제가 권장하는 방향은 다음과 같습니다.
from playwright.async_api import async_playwright
async def get_compact_state(page):
...
그런 다음 모델에 다음과 같은 내용을 보냅니다:
{
"task": "지난달 지급 내역 CSV 내보내기",
"url": "https://dashboard.stripe.com/payouts",
...
보통 이 정도면 충분합니다.
프롬프트(prompt)에 세상의 모든 것을 쏟아부을 필요는 없습니다.
빠르게 테스트해보고 싶다면 설치 단계
Playwright
pip install playwright
npx playwright install
Browser Use
pip install browser-use
또는 uv를 사용하여:
uv add browser-use
스크린샷이 실제로 중요한 경우
제가 비전(vision)을 절대 사용하지 말아야 한다고 주장하는 것은 아닙니다.
스크린샷이 큰 도움이 되는 실제 사례들이 있습니다:
- 레이아웃에 민감한 UI (layout-sensitive UIs)
- 캔버스(canvas) 중심의 앱
- 시각적 QA (visual QA)
- 텍스트 추출이 핵심을 놓치는 모호한 상태
- 안티 봇(anti-bot) 또는 이상한 모달(modal) 동작
하지만 이는 항상 켜두는 스크린샷이 아니라, 조건부로 풍부하게 제공해야 한다는 주장입니다.
use_vision='auto'가 종종 더 성숙한 기본값(default)이 됩니다.
모델을 교체하기 전에 시도해 볼 4가지 해결책
브라우저 에이전트(browser agent)가 느리게 느껴진다면, 저는 다음 사항들을 먼저 시도해 볼 것입니다.
1. 프롬프트 접두사(prefix) 고정하기
프롬프트의 상단을 안정적으로 유지하세요.
지침(instructions)을 계속 다시 쓰거나 직렬화(serialization) 순서를 변경하지 마세요.
2. 기본적으로 스크린샷 끄기
스크린샷 없이 시작한 다음, 성공률을 높이는 경우에만 다시 추가하세요.
3. 추출과 추론 분리하기
페이지 추출에는 더 저렴한 모델을 사용하고, 의사 결정에는 더 강력한 모델을 사용하세요.
4. 메모리를 공격적으로 다듬기 (Trim memory)
최근 작업과 압축된 요약(compact summary)만 유지하세요.
전체 기록(transcript)을 계속 다시 재생하지 마세요.
표준 컴퓨팅(Standard Compute)의 역할
이것이 바로 제가 에이전트에게 가격 모델이 중요하다고 생각하는 이유이기도 합니다.
브라우저 루프를 반복하며 개선(iteration)할 때, 토큰당 과금(per-token billing) 방식은 실험을 저해합니다.
여러분은 다음을 테스트하고 싶을 것입니다:
- 서로 다른 컨텍스트 윈도우 (context windows)
- 서로 다른 추출 모델 (extraction models)
- 더 많은 단계 (more steps)
- 재시도 (retries)
- 대안적인 라우팅 전략 (alternate routing strategies)
잘못된 프롬프트 설계 결정 하나하나가 토큰 단위로 과금된다면, 비용은 순식간에 불어납니다.
이 지점에서 Standard Compute가 흥미로운 이유는 토큰당 과금 (per-token billing) 방식 대신 월정액 요금제를 제공하는 OpenAI 호환 API를 제공하기 때문입니다. 따라서 n8n, Make, Zapier, OpenClaw 또는 커스텀 Playwright 워크플로우에서 에이전트를 실행하고 있다면, 매 실행마다 발생하는 토큰 소모 (token burn)에 집착하지 않고도 루프를 실제로 미세 조정할 수 있습니다.
그렇다고 해서 좋은 프롬프트 설계의 필요성이 사라지는 것은 아닙니다.
그저 이를 수정하는 과정에서 발생하는 기묘한 세금(비용 부담)을 제거해 줄 뿐입니다.
불편한 진실
많은 브라우저 에이전트들이 데모에서는 마법처럼 느껴지지만 실제 운영 환경에서는 엉망인 이유는 동일합니다:
단계가 거듭될수록 프롬프트가 점점 더 비대해지기 때문입니다.
그러면 누군가는 모델이 느리다고 말합니다.
때로는 모델이 실제로 느릴 때도 있습니다.
하지만 대부분의 경우, 모델은 단지 당신이 요청한 것을 정확히 수행하고 있을 뿐입니다. 즉, 중복된 지침을 다시 읽고, 필요하지 않은 스크린샷을 파싱하며, 5단계 전에 압축되었어야 할 히스토리를 계속 들고 다니는 것입니다.
이것은 사실 지능의 문제가 아닙니다.
패킹 (packing)의 문제입니다.
이번 주에 느릿느릿한 에이전트를 디버깅하고 있다면, 1단계, 5단계, 그리고 20단계에서의 페이로드 (payload)를 점검해 보세요.
만약 프롬프트가 계속 커지고 있다면, 거기서부터 시작하십시오.
GPT-5를 탓하기 전에.
Claude를 탓하기 전에.
속도 제한 (rate limits)을 탓하기 전에.
그리고 무엇보다 프롬프트에 쓰레기를 더 추가하기 전에 말입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기