본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 17. 02:22

답장 루프 구축하기: 수신, 사고, 응답 (Receive, Think, Respond)

요약

이메일 에이전트 구축 시 발생할 수 있는 무한 루프와 데이터 누락 문제를 방지하기 위한 '수신-사고-응답(Receive-Think-Respond)' 루프 설계 가이드를 제공합니다. 웹훅 처리, 컨텍스트 관리, 상태 머신 활용을 통한 안정적인 에이전트 구현 방법을 다룹니다.

핵심 포인트

  • 웹훅은 비동기로 빠르게 승인하고 별도 프로세스로 처리해야 함
  • 발신자 확인을 통해 에이전트 자신의 답장에 재응답하는 무한 루프 방지
  • 전체 스레드 컨텍스트를 활용하되 토큰 절약을 위해 요약 기법 사용
  • 상태 머신(State Machine)을 도입하여 대화 단계별 라우팅 구현

약 1 MB. 이것은 message.created 웹훅 (webhook)의 형태가 조용히 변하는 본문 크기 임계값입니다. 트리거가 message.created.truncated로 바뀌고 본문이 완전히 생략됩니다. 만약 당신의 이메일 에이전트 (agent)가 웹훅 페이로드 (payload)에서 직접 본문을 읽는다면, 몇 달 동안은 잘 작동하다가 전달된 계약서가 포함된 단 하나의 답장을 소리 없이 놓치게 될 것입니다. 이 세부 사항은 이 주제 전체를 잘 보여주는 예시입니다. 수신-사고-응답 (receive-think-respond) 루프는 개념적으로 단순하며, 모든 흥미로운 버그는 그 경계면(edges)에 존재합니다.

Nylas Agent Account (베타 버전)를 에이전트의 메일함으로 사용하여 루프를 제대로 연결해 보겠습니다.

1단계 — 수신 (Receive): 웹훅은 택배가 아니라 초인종입니다

메일이 도착하면 message.created 웹훅이 발생합니다. 이를 알림으로만 취급하세요:

app.post("/webhooks/nylas", async (req, res) => {
  res.status(200).end(); // 빠르게 승인(ack)하고, 비동기(async)로 작업하세요

...

저 코드에는 세 개의 핵심적인 라인이 있습니다. 권한(grant) 확인은 다른 계정의 트래픽을 차단합니다. from 확인은 매우 중요한데, 웹훅은 발신 메일에 대해서도 발생하기 때문입니다. 이를 건너뛰면 당신의 에이전트는 자신의 답장에 다시 답장을 하는 무한 루프에 빠지게 됩니다. 그리고 thread_id 조회는 답장이 답장으로 인식되는 방식입니다. 메시지는 In-Reply-ToReferences 헤더 (header)를 사용하여 스레드 (thread)로 그룹화되므로, 에이전트가 원본 메시지를 보냈다면 들어오는 답장은 이미 상태(state)를 가지고 있는 스레드에 도착하게 됩니다. 당신 쪽에서 헤더를 파싱 (parsing)할 필요가 없습니다.

2단계 — 사고 (Think): 생성 전의 컨텍스트 (context)

페이로드는 subject, from, snippet과 같은 요약 필드를 포함합니다. 모델이 결정을 내리기 전에 실제 데이터를 가져오세요:

const fullMessage = await nylas.messages.find({
  identifier: AGENT_GRANT_ID,
  messageId: msg.id,
...

"좋아요, 목요일에 합시다"라고 답하는 LLM (Large Language Model)은 무엇이 제안되었는지 알아야 합니다. 전체 스레드가 대화의 메모리 (memory)가 됩니다. 긴 스레드의 경우, 모든 메시지를 그대로 가져올 필요는 없습니다. 초기 대화 내용은 요약하고 마지막 3~4개만 전체 내용을 전달하세요. 동일한 컨텍스트를 유지하면서 토큰 (token)은 훨씬 적게 사용합니다.

자체적인 상태 머신 (state machine)이 컨텍스트의 나머지 절반을 제공합니다. thread_id를 키로 하는 대화 기록은 step 필드를 추적하며, 핸들러 (handler)는 모델 호출이 일어나기 전에 이를 기반으로 라우팅 (routing)을 수행합니다:

async function routeReply(message, history, context) {
  switch (context.step) {
    case "awaiting_confirmation":
...

"예"라는 대답은 에이전트 (agent)가 무엇을 물었느냐에 따라 의미가 달라지며, default 분기 (branch)가 중요합니다. 알 수 없는 상태는 즉흥적으로 대응하는 대신 에스컬레이션 (escalate)되어야 합니다. 멀티턴 레시피 (multi-turn recipe)에서 얻은 또 다른 유용한 팁은 다음과 같습니다. LLM이 답변 텍스트와 함께 nextStep 값을 반환하도록 하여, 코드가 대화의 흐름을 추측하는 대신 모델 자체가 상태 머신 (state machine)을 진행시키도록 하는 것입니다.

3단계 — 응답 (Respond): 하나의 파라미터가 스레딩을 처리합니다

const sent = await nylas.messages.send({
  identifier: AGENT_GRANT_ID,
  requestBody: {
...

reply_to_message_id를 전달하면 플랫폼이 발신 메시지에 In-Reply-ToReferences를 설정하므로, 수신자의 메일 클라이언트가 끊어진 새 이메일 대신 스레드 (threaded)로 연결된 답장을 렌더링합니다. 이를 생략하면 모든 답장이 새로운 스레드로 시작되며, 이는 상대방 인간에게 에이전트가 고장 난 것처럼 느끼게 만드는 가장 빠른 방법입니다. 상세한 메커니즘은 handle-replies 레시피 (handle-replies recipe)에서 자세히 다룹니다.

전송 후에는 대화 기록을 업데이트하세요: 턴 (turn) 횟수를 올리고, 다음 step을 설정하며, lastActivityAt을 기록합니다.

실패 모드 (failure modes), 피해 정도에 따른 순위

자기 응답 루프 (Self-reply loops). 위에서 다루었지만, 가장 흔히 저지르는 실수 (footgun) 1위입니다. from 체크 하나만 누락되어도 자신과 무한 대화를 나누게 됩니다.

중복된 답장 (Duplicate replies). Webhook 재전송 및 동시 작업자(concurrent workers) 모두 핸들러를 다시 트리거합니다. 이는 규모에 관계없이 어떤 볼륨에서도 발생할 수 있습니다. 중복 제거(dedup)와 잠금(locking) 없이는 동일한 인바운드 메시지가 두 번의 LLM 호출과 두 개의 답장을 생성하게 됩니다. 일관성 유지(idempotency)를 사후 보강 작업이 아닌, 출시 필수 요구 사항으로 간주해야 합니다.

빠른 수정 (Rapid-fire corrections). 인간은

Completion은 step: "completed"와 동일한 동작이며, 이는 단순히 장부 기록을 위한 것이 아닙니다. 잠재 고객이 미팅을 예약하거나 지원 질문에 대한 답변이 완료되었을 때, 해당 레코드를 완료(done)로 표시하면 해당 스레드(thread)로 들어오는 다음 인바운드(inbound)의 라우팅 방식이 변경됩니다. 즉, 문맥에 맞지 않는 연속적인 응답을 생성하는 대신 라우터의 closed 분기로 연결됩니다. 상태 머신(state machine)의 종료(exit) 조건이 바로 중간 상태들을 신뢰할 수 있게 만드는 핵심입니다.

입구(front door)에 관한 마지막 주의 사항: 핸들러(handler)가 어떤 작업을 수행하기 전에 반드시 X-Nylas-Signature 헤더를 검증하십시오. 검증되지 않은 웹훅(webhook) 엔드포인트는 인터넷상의 누구나 당신의 에이전트(agent)가 이메일을 보내도록 허용하는 API가 됩니다.

시작하는 방법

다음 순서대로 루프(loop)를 구축하십시오: 세 가지 가드 체크(guard checks)가 포함된 웹훅 핸들러(webhook handler) → 스레드 가져오기(thread fetching) → 하드코딩된 답장 (아직 LLM 사용 금지) → 실제 메일 클라이언트에서 스레딩(threading)이 작동하는지 확인 → 그 다음 모델(model)로 교체. LLM을 먼저 연결하는 것은 전형적인 실수입니다. 그렇게 하면 프롬프트(prompt) 품질과 웹훅(webhook) 전달 문제를 동시에 디버깅하게 됩니다.

어떤 실패 모드(failure mode)가 당신을 가장 먼저 괴롭혔나요? 제 경험은 매우 보편적이어서 아마 이렇게 추측하겠습니다: 에이전트가 자기 자신에게 답장을 보냈을 것입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0