Vercel Sandbox, AI Gateway, Workflows를 활용해 코딩 에이전트를 추적하는 AI 엔진 최적화(AI Engine Optimization) 시스템을 어떻게 구축했는지 소개합니다.
AI는 사람들이 정보를 탐색하는 방식 자체를 바꿔 놓았습니다. 기업 입장에서는 LLM이 자사 웹 콘텐츠를 어떻게 검색하고 요약하는지 파악하는 것이 그 어느 때보다 중요해졌습니다.
저희는 각 모델이 Vercel과 관련 사이트를 어떻게 발견하고 해석하며 인용하는지 추적하기 위해 AI 엔진 최적화(AEO) 시스템을 구축하고 있습니다.
처음에는 일반 채팅 모델만 대상으로 한 프로토타입이었지만, 곧 그것만으로는 부족하다는 사실을 깨달았습니다. AI 가시성의 전체 그림을 얻으려면 코딩 에이전트까지 추적해야 했습니다.
일반 모델의 경우 추적이 비교적 간단합니다. AI Gateway를 통해 수십 개의 주요 모델(GPT, Gemini, Claude 등)에 프롬프트를 보내고, 응답 내용과 검색 행동, 인용 출처를 분석합니다.
그런데 코딩 에이전트는 작동 방식이 전혀 다릅니다. Vercel 사용자 상당수는 프로젝트 작업 중에 터미널이나 IDE에서 AI를 사용합니다. 초기 샘플링 결과, 코딩 에이전트는 프롬프트의 약 20%에서 웹 검색을 수행했습니다. 이러한 검색이 실제 개발 워크플로 중간에 일어나기 때문에, 응답 품질과 출처 정확도를 함께 평가하는 것이 특히 중요합니다.
코딩 에이전트의 AEO를 측정하려면 모델 단독 테스트와는 다른 접근 방식이 필요합니다. 코딩 에이전트는 단일 API 호출에 답하도록 설계된 것이 아닙니다. 프로젝트 내부에서 동작하며, 파일 시스템, 셸 접근, 패키지 매니저 등 완전한 개발 환경을 필요로 합니다.
이로 인해 새로운 과제가 생깁니다:
실행 격리: 임의의 코드를 실행할 수 있는 자율 에이전트를 어떻게 안전하게 구동할 것인가?
관측 가능성(Observability): 에이전트마다 트랜스크립트 형식, 도구 호출 규칙, 출력 구조가 전부 다른 상황에서 에이전트의 행동을 어떻게 포착할 것인가?
코딩 에이전트는 보통 API가 아닌 CLI를 통해 접근합니다. 프롬프트를 보내고 응답을 캡처하는 것뿐이라 해도, CLI를 설치하고 실행할 수 있는 완전한 런타임 환경이 필요합니다.
Vercel Sandbox는 수 초 만에 기동되는 일회용 Linux MicroVM을 제공해 이 문제를 해결합니다. 각 에이전트 실행은 자체 샌드박스를 할당받으며, 어떤 CLI를 사용하든 동일한 6단계 라이프사이클을 따릅니다.
샌드박스 생성. 적절한 런타임(Node 24, Python 3.13 등)과 타임아웃을 설정한 새 MicroVM을 기동합니다. 타임아웃은 절대 상한선으로, 에이전트가 멈추거나 무한 루프에 빠지면 샌드박스가 강제로 종료시킵니다.
에이전트 CLI 설치. 각 에이전트는 npm 패키지(@anthropic-ai/claude-code, @openai/codex 등)로 배포됩니다. 샌드박스에서 전역 설치하면 셸 명령으로 바로 사용할 수 있습니다.
인증 정보 주입. 각 에이전트에 프로바이더 API 키를 직접 전달하는 대신, 모든 LLM 호출을 Vercel AI Gateway로 라우팅하도록 환경 변수를 설정합니다. 이를 통해 에이전트마다 기반 프로바이더가 다르더라도 로깅, 속도 제한, 비용 추적을 통합 관리할 수 있습니다(물론 프로바이더 키를 직접 사용하는 것도 가능합니다).
프롬프트로 에이전트 실행. 에이전트별로 실행 패턴, 플래그, 설정 형식이 다르기 때문에 이 단계만 에이전트마다 달라집니다. 하지만 샌드박스 관점에서는 그저 하나의 셸 명령일 뿐입니다.
트랜스크립트 캡처. 에이전트 실행이 끝나면 수행 내역을 추출합니다. 어떤 도구를 호출했는지, 웹 검색을 수행했는지, 응답에서 무엇을 추천했는지 등의 기록입니다. 이 부분은 에이전트마다 다르며, 아래에서 자세히 다룹니다.
정리(Tear down). 샌드박스를 종료합니다. 도중에 문제가 발생하더라도 catch 블록이 샌드박스를 반드시 종료시키므로 리소스가 누수되지 않습니다.
코드로 보면 이 라이프사이클은 다음과 같습니다.
라이프사이클이 동일하기 때문에, 각 에이전트를 단순한 설정 객체로 정의할 수 있습니다. 새 에이전트를 추가하려면 항목 하나만 추가하면 되고, 샌드박스 오케스트레이션이 나머지를 처리합니다.
runtime는 MicroVM의 베이스 이미지를 결정합니다. 대부분의 에이전트는 Node 위에서 실행되지만, Python 런타임도 지원합니다.
setupCommands가 배열인 이유는 일부 에이전트에 전역 설치 외에 추가 설정이 필요하기 때문입니다. 예를 들어, Codex는 ~/.codex/config.toml에 TOML 설정 파일을 생성해야 합니다.
buildCommand는 프롬프트를 받아 실행할 셸 명령을 반환하는 함수입니다. 각 에이전트의 CLI마다 플래그와 실행 방식이 다릅니다.
비용과 로그를 AI Gateway에서 중앙 관리하고 싶었기 때문에, 샌드박스 내부에서 환경 변수로 프로바이더의 베이스 URL을 오버라이드했습니다. 에이전트 자체는 이 사실을 모른 채, 프로바이더와 직접 통신하는 것처럼 동작합니다.
Claude Code의 경우 다음과 같이 설정합니다:
ANTHROPIC_BASE_URL는 api.anthropic.com 대신 AI Gateway를 가리킵니다. 에이전트의 HTTP 요청이 Gateway로 전달되고, Gateway가 이를 Anthropic으로 프록시합니다.
ANTHROPIC_API_KEY은 의도적으로 빈 문자열로 설정합니다. Gateway가 자체 토큰으로 인증하므로, 에이전트에는 프로바이더 키가 필요하지도, 주어지지도 않습니다.
같은 패턴을 Codex(OPENAI_BASE_URL 오버라이드)나 베이스 URL 환경 변수를 지원하는 다른 에이전트에도 적용할 수 있습니다. 프로바이더 API 키를 직접 사용하는 것도 물론 가능합니다.
샌드박스에서 에이전트 실행이 끝나면, 에이전트가 수행한 모든 작업의 기록인 원본 트랜스크립트를 얻게 됩니다.
문제는 에이전트마다 형식이 전부 다르다는 점입니다. Claude Code는 JSONL 파일을 디스크에 기록하고, Codex는 JSON을 stdout으로 스트리밍합니다. OpenCode도 stdout을 사용하지만 스키마가 다릅니다. 같은 도구에 대해 서로 다른 이름을 쓰고, 메시지 중첩 구조도, 각종 규칙도 제각각입니다.
이 모든 데이터를 하나의 브랜드 파이프라인에 공급해야 했기에, 4단계 정규화 레이어를 구축했습니다:
트랜스크립트 캡처: 에이전트마다 트랜스크립트 저장 방식이 다르므로, 이 단계는 에이전트별로 구현됩니다.
파싱: 각 에이전트 전용 파서가 도구명을 정규화하고, 에이전트별 메시지 구조를 하나의 통합 이벤트 타입으로 평탄화합니다.
보강(Enrichment): 공통 후처리 단계로, 도구 인자에서 구조화된 메타데이터(URL, 명령어 등)를 추출합니다. 에이전트마다 인자 이름이 다른 부분도 여기서 통일됩니다.
요약 및 브랜드 추출: 통합된 이벤트를 집계 통계로 요약한 뒤, 일반 모델 응답에 사용하는 동일한 브랜드 추출 파이프라인에 투입합니다.
이 단계는 샌드박스가 아직 실행 중일 때(앞서 설명한 라이프사이클의 5단계) 수행됩니다.
Claude Code는 트랜스크립트를 샌드박스 파일 시스템에 JSONL 파일로 기록합니다. 에이전트 실행이 끝난 후 해당 파일을 찾아서 읽어와야 합니다:
Codex와 OpenCode는 둘 다 트랜스크립트를 stdout으로 출력하므로 캡처가 더 간단합니다. 출력에서 JSON 라인만 필터링하면 됩니다:
이 단계의 출력은 모든 에이전트에서 동일한 원본 JSONL 문자열입니다. 하지만 각 JSON 라인의 내부 구조는 에이전트마다 완전히 다르며, 이를 처리하는 것이 다음 단계의 역할입니다.
각 에이전트에 맞는 전용 파서를 만들어 두 가지 작업을 동시에 처리합니다: 도구명 정규화와 에이전트별 메시지 구조의 단일 이벤트 타입으로의 평탄화입니다.
도구명 정규화
같은 동작이 에이전트마다 다른 이름으로 불립니다:
동작 | Claude Code | Codex | OpenCode |
파일 읽기 |
|
|
|
파일 쓰기 |
|
|
|
파일 편집 |
|
|
|
명령어 실행 |
|
|
|
웹 검색 |
| (에이전트에 따라 상이) | (에이전트에 따라 상이) |
각 파서는 에이전트별 도구명을 약 10개의 표준 이름으로 매핑하는 룩업 테이블을 관리합니다:
메시지 구조 평탄화
이름뿐 아니라 이벤트 구조도 에이전트마다 다릅니다:
Claude Code는 메시지를 message 속성 안에 중첩시키고, content 배열에 tool_use 블록을 혼합합니다.
Codex는 도구 이벤트와 함께 Responses API 라이프사이클 이벤트(thread.started, turn.completed, output_text.delta)가 섞여 있습니다.
OpenCode는 도구 호출과 결과를 part.tool와 part.state를 통해 하나의 이벤트에 묶어서 전달합니다.
각 에이전트의 파서가 이러한 구조적 차이를 처리하고, 모든 것을 단일 TranscriptEvent 타입으로 통합합니다:
이 단계의 출력은 TranscriptEvent[]의 평탄한 배열이며, 어떤 에이전트에서 생성됐든 동일한 구조를 갖습니다.
파싱이 끝나면, 모든 이벤트에 대해 공통 후처리를 수행합니다. 이 단계에서는 도구 인자에서 구조화된 메타데이터를 추출하여, 다운스트림 코드가 Claude Code는 파일 경로를 args.path에, Codex는 args.file에 넣는다는 차이를 알 필요 없도록 합니다:
보강된 TranscriptEvent[] 배열을 집계 통계(도구 호출 유형별 총 횟수, 웹 요청 수, 오류 수 등)로 요약한 뒤, 일반 모델 응답에 사용하는 것과 동일한 브랜드 추출 파이프라인에 투입합니다. 이 지점부터는 데이터가 코딩 에이전트에서 왔는지, 모델 API 호출에서 왔는지 시스템이 구분하지 않으며, 구분할 필요도 없습니다.
이 전체 파이프라인은 Vercel Workflow로 실행됩니다. 프롬프트가 "agents" 타입으로 태그되면, 워크플로가 설정된 모든 에이전트에 병렬로 팬아웃하며 각각 별도의 샌드박스를 할당받습니다:
코딩 에이전트는 웹 검색 트래픽에서 무시할 수 없는 비중을 차지합니다. 무작위 프롬프트 샘플을 대상으로 한 초기 테스트에서 코딩 에이전트가 약 20%의 확률로 검색을 수행하는 것으로 나타났습니다. 더 많은 데이터가 쌓이면 에이전트 검색 행동에 대한 종합적인 분석이 가능해지겠지만, 이 결과만으로도 코딩 에이전트에 맞춘 콘텐츠 최적화가 중요하다는 점은 분명해졌습니다.
에이전트의 추천 방식은 모델 응답과 형태가 다릅니다. 코딩 에이전트가 특정 도구를 추천할 때는 import 구문이나 설정 파일, 배포 스크립트 등 해당 도구를 실제로 사용하는 동작 코드를 함께 생성하는 경향이 있습니다. 추천이 산문 속에 언급되는 것이 아니라 출력 결과물에 직접 포함되는 것입니다.
트랜스크립트 형식은 혼란 그 자체입니다. 에이전트 CLI 도구가 빠르게 업데이트되면서 상황은 더 복잡해지고 있습니다. 초기에 정규화 레이어를 구축해 둔 덕분에 끊임없는 파싱 오류를 피할 수 있었습니다.
동일한 브랜드 추출 파이프라인이 모델과 에이전트 모두에 적용됩니다. 어려운 부분은 그 앞 단계에 있습니다. 에이전트를 실행하고, 수행 내역을 포착하고, 평가 가능한 구조로 정규화하는 과정이 진짜 난관입니다.
도구 오픈소스 공개. 다른 팀도 일반 모델과 코딩 에이전트 모두를 대상으로 자체 AEO 평가를 추적할 수 있도록, 시스템의 OSS 버전을 공개할 계획입니다.
방법론 심층 분석. 전체 AEO 평가 방법론을 다루는 후속 포스트를 준비하고 있습니다. 프롬프트 설계, 이중 모드 테스트(웹 검색 vs. 학습 데이터), 쿼리를 일급 엔티티로 다루는 아키텍처, Share of Voice 지표 등을 포함할 예정입니다.
에이전트 커버리지 확대. 생태계가 성장함에 따라 더 많은 에이전트를 추가하고, 테스트하는 프롬프트 유형도 "도구 추천"뿐 아니라 프로젝트 스캐폴딩, 디버깅 등으로 확장할 예정입니다.