
AI 에이전트를 배치 처리할 때 발생하는 데이터 유출 문제
요약
n8n의 AI 에이전트 노드에서 배치 처리 시 대화 기록이 다른 아이템으로 유출되는 데이터 오염 버그를 분석합니다. 배치 요청 병합 과정에서 메타데이터가 공유되어 발생하는 문제와 그 근본 원인을 다룹니다.
핵심 포인트
- 배치 처리 시 동일 라운드 내 아이템 간 대화 컨텍스트 유출 발생
- 에러 없이 잘못된 답변을 생성하는 조용한 데이터 오염 문제
- 공유 객체 병합 과정에서 첫 번째 항목의 메타데이터만 유지되는 것이 원인
- 도구 호출 이력(previousRequests)의 필터링 없는 무조건적 삽입 문제
이 글은 Sentry가 지원하는 DEV's Summer Bug Smash: Clear the Lineup에 제출된 게시물입니다.
프로젝트 개요
n8n은 오픈 소스 워크플로 자동화 (workflow automation) 플랫폼입니다. Zapier와 비슷하지만, 셀프 호스팅이 가능하고 노드 기반이며 개발자를 위해 구축되었습니다. 가장 많이 사용되는 기능 중 하나는 **AI 에이전트 노드 (AI Agent node)**로, 이는 LLM을 도구 호출 루프 (tool-calling loop)로 감싸는 역할을 합니다. 즉, 모델이 워크플로 내의 다른 노드들을 "도구 (tools)"로서 호출하고, 그 결과를 확인하며, 여러 아이템과 여러 라운드에 걸쳐 다음에 무엇을 할지 결정할 수 있습니다.
비용을 절감하고 속도 제한 (rate limits)을 준수하기 위해, 에이전트 노드는 **배치 처리 (Batch Processing)**를 지원합니다. 이는 여러 입력 아이템을 한 번에 하나씩 처리하는 대신, 도구 호출 루프를 통해 동시에 실행하는 방식입니다. 바로 이 기능에서 버그가 발생했습니다.
버그 수정 또는 성능 개선
버그: 배치 크기 (batch size)가 2 이상인 상태에서 배치 처리가 활성화되어 있고, 동일한 배치 내의 두 아이템이 동일한 라운드에서 모두 도구를 호출해야 할 때, 2라운드부터 한 아이템의 대화 기록 (conversation history)이 다른 아이템으로 조용히 유출되거나 아예 누락되는 현상이 발생합니다. 구체적으로 설명하면: 아이템 0은 베를린의 get_weather를 호출하고, 아이템 1은 ACME의 get_stock_price를 호출합니다. 3라운드에 이르면, 아이템 1의 재구성된 대화 컨텍스트 (conversation context)에는 자신의 주식 가격 호출 대신 아이템 0의 베를린 날씨 호출이 포함됩니다. 아이템 1을 처리하는 LLM은 이제 완전히 다른 아이템의 기록을 바탕으로 추론하게 됩니다.
이것은 단순한 외관상의 버그가 아닙니다. AI 에이전트가 정답을 내놓을지를 결정하는 바로 그 기능(도구 호출 메모리)에서 발생하는 조용한 데이터/컨텍스트 오염 (data/context corruption)입니다. 또한 이는 조용히 실패합니다. 예외(exception)도 발생하지 않고, UI에 에러도 표시되지 않으며, 그저 타인의 데이터로 만들어진 잘못된 답변이 나올 뿐입니다.
**근본 원인 (Root cause)**은 실제 엔진의 라운드 트립 (round-trip)을 통해 추적되었습니다:
executeBatch.ts는 한 라운드에 대한 모든 항목의 나가는 요청(outgoing requests)을 하나의 공유 객체로 병합합니다. 이 과정에서 첫 번째 항목의metadata(previousRequests— 해당 항목 고유의 도구 호출(tool-call) 이력 포함)만 유지하고, 나머지 모든 항목의metadata는 조용히 폐기합니다.buildSteps.ts는 다음 라운드를 위해 항목의 대화 내용을 재구성할 때, 해당 병합 과정에서 살아남은previousRequests를 어떤 항목의 이력인지에 대한 필터링 없이 모든 항목의 단계(steps)에 무조건적으로 삽입(splice)합니다.
종합하자면: 배치 내에서 우연히 첫 번째로 처리되는 항목이 공유 이력 슬롯을
의도적인 범위 제한(scoping call) 한 가지: 이 동일한 근본 원인은 각 항목의 iterationCount(
그다음 저는 "이전" 이슈에 대해 Seer (Sentry의 AI 근본 원인 분석 도구)를 실행했습니다. 결과는 놀라웠습니다. Seer의 근본 원인 분석(root cause analysis)은 buildSteps에서의 무조건적인 splice(항목별 파티셔닝 없음)라는 정확한 메커니즘을 독립적으로 식별해냈으며, 제 리포지토리의 실제 파일들(executeBatch.ts, buildSteps.ts 316–395행, prepareItemContext.ts, runAgent.ts)을 증거로 인용했습니다. 이는 Seer가 단순히 이벤트 텍스트만 읽은 것이 아니라, 연결된 리포지토리를 실제로 읽었다는 것을 의미합니다.
저는 한 걸음 더 나아가 보았습니다. "그래, 계획을 세워줘"라고 요청하자, 제가 실제로 적용한 수정 사항과 단계별로(step for step) 일치하는 4단계 계획이 생성되었습니다. 즉, ToolCallData에 itemIndex 태그를 달고, buildSteps에서 필터링하며, executeBatch에서 병합하는 과정이 동일한 라인 번호까지 일치했습니다. 그 후 "그래, 코드 수정안을 작성해줘"라고 요청하자, 제가 이미 배포했던 것과 거의 동일한 diff(차이점)가 생성되었습니다.
그것은 네 가지의 독립적인 분석 — 저의 분석, Seer의 근본 원인(root cause) 분석, Seer의 계획, 그리고 (아래를 참조하세요) Gemini의 분석 — 이 모두 동일한 진단과 동일한 해결책으로 수렴하고 있음을 보여줍니다. 유일하게 진실하게 짚고 넘어갈 만한 차이점은 다음과 같습니다: Seer가 생성한 필터는 하위 호환성(backward-compatibility)을 위한 안전망으로서 태그가 지정되지 않은(itemIndex === undefined) 항목들을 모든 항목으로 통과시키도록 설계되었지만, 저의 방식은 엄격합니다. 이 코드베이스에서는 그 차이가 나타나지 않지만(앞으로는 모든 단계에서 무조건적으로 태그가 지정됨), 이는 단순한 승인이 아닌 명확히 식별 가능한 설계상의 선택입니다.
그리고 Seer의 자동 수정(autofix)이 드러낸 가장 유용한 격차는 다음과 같습니다: Seer가 생성한 디프(diff)에는 테스트가 전혀 포함되어 있지 않았습니다. n8n의 자체 기여 가이드라인은 모든 PR(Pull Request)에 테스트를 요구하며(테스트가 없는 라벨 없는 PR은 14일 후 자동으로 종료됨), 따라서 이처럼 정답에 가까운 디프라 할지라도 그 자체만으로는 병합 가능한 제출물이 될 수 없습니다. 이것이 AI가 생성한 패치와 사람이 검증한 수정 사항 사이의 구체적인 차이점입니다. 저의 패치는 수정 전에는 실패하고 수정 후에는 통과함이 확인된 회귀 테스트(regression tests)를 포함하여 배포됩니다.
또한 저는 Seer가 계획으로부터 직접 PR을 초안하도록 했습니다. Seer는 제 포크(fork)에 PR #2를 생성했는데, 이는 Sentry가 포착된 오류로부터 코드 인지형 자율 풀 리퀘스트(autonomous, code-aware pull request)에 이르기까지 모든 과정을 수행할 수 있다는 훌륭한 증거입니다. 제가 실제로 제출한 것(PR #34360)은 테스트를 거치고 사람이 검증한 버전입니다.
Google AI의 최적 활용법
저는 Gemini와 함께 세 번의 메시지로 구성된 디버깅 세션을 진행했습니다. Gemini가 독립적으로 동일한 근본 원인(root cause)에 도달할 수 있는지 확인하기 위해, 저의 결론을 먼저 말하지 않은 채 실제(수정되지 않은) 코드와 증상을 입력값으로 제공했습니다.
Prompt 1에서는 증상(3라운드째에 item 1이 item 0의 히스토리를 상속받는 문제)을 바탕으로, executeBatch.ts의 병합(merge) 로직과 buildSteps.ts의 splice를 통해 버그를 진단하도록 요청했습니다. Gemini는 참조 하이재킹(reference-hijacking) 병합과 무조건적인 splice를 그 메커니즘으로 정확히 식별했습니다. 또한 Gemini는 request.actions.push.apply(...)가 참조(reference)를 통해 item 0의 원래 actions 배열을 변형(mutate)시킨다는
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기


