LLM 에이전트: 이메일 도구에 대한 계약
요약
LLM 에이전트가 이메일과 같은 외부 도구를 사용할 때, 단순한 성공/실패 여부 이상의 상세하고 구조화된 결과가 필요합니다. 에이전트는 작업의 상태, 관찰 가능한 증거(예: 메시지 ID), 그리고 다음 안전한 단계에 대한 명확한 지침을 받아야 시스템 신뢰성을 높일 수 있습니다.
핵심 포인트
- 도구 호출은 단순히 '성공'만 반환해서는 안 되며, 상세한 결과와 증거가 필요합니다.
- 작업의 흐름(예: 사용자 생성 및 인증 링크 요청)은 여러 단계로 분리되어야 에이전트의 오판을 막습니다.
- 재시도 로직은 모호한 지침 대신 명확한 예산(maxAttempts, deadlineAt)과 간결한 영수증으로 정의해야 합니다.
- 작업 실행별 격리 및 멱등성 확보를 위해 `runId`와 같은 고유 식별자를 사용하는 것이 중요합니다.
LLM 에이전트는 몇 초 만에 이메일 API를 호출할 수 있습니다. 문제는 그 호출이 올바른지, 메시지가 이 작업을 위해 도착했는지, 그리고 다음 시도가 노이즈를 생성하지 않고 작업을 반복할 수 있는지 알아야 할 때 발생합니다.
자동화 흐름에서 일회용 메일 도구는 단순히 ok: true만 반환해서는 안 됩니다. 에이전트는 무엇이 일어났는지 설명하는 결과, 어떤 증거가 존재하는지, 그리고 다음 안전한 단계가 무엇인지 알아야 합니다. 인터페이스의 작은 차이지만 시스템의 신뢰성에 큰 변화를 가져옵니다.
문제는 이메일을 보내는 것이 아니다
온보딩을 테스트하는 에이전트를 상상해 봅시다. 사용자를 생성하고, 인증 링크를 요청한 다음, 이를 소비합니다. 여기에는 다양한 시간이 걸리는 여러 작업이 있습니다:
- API가 요청을 수락합니다.
- 워커가 메시지를 준비합니다.
- 공급자가 메시지를 전달합니다.
- 도구가 올바른 메시지를 찾습니다.
- 에이전트가 링크를 추출하고 사용합니다.
만약 이 모든 단계가 단일 작업으로 제시된다면, 에이전트는 너무 빨리 재시도하는 경향이 있습니다. 두 명의 사용자를 생성하거나, 오래된 메시지를 읽거나, 단순히 느렸을 때 제품에 문제가 있다고 결론 내릴 수 있습니다. 더 긴 프롬프트는 도구가 자신의 한계를 노출하지 못한다면 거의 도움이 되지 않습니다.
tepm mail com이나 tempail과 같은 오래된 메모에 나오는 용어들은 이 문제를 해결하지 못합니다. 중요한 결정은 다른 것입니다: 모든 호출은 실행 ID와 관찰 가능한 증거를 가져야 합니다.
이 경계를 정의하기 위해, 프롬프트 검토를 위한 최소 계약부터 시작하는 것이 유용합니다: 프롬프트는 결정할 수 있지만, 도구는 불변성을 보호해야 합니다.
도구를 위한 작은 계약
유용한 결과는 다음과 같은 형태를 가질 수 있습니다:
type MailToolResult =
|
{
status: "received";
...```
에이전트는 공급업체의 세부 정보를 알 필요가 없습니다. 최종 결과, 합리적인 대기 시간, 그리고 반복해서는 안 되는 오류를 구별할 수만 있으면 됩니다. `retryable`은 특히 중요합니다. 인증 거부를 요청 폭주로 변질시키는 것을 방지하기 때문입니다.
또한 각 작업에서 `runId`가 전달되는 것이 좋습니다. 이렇게 하면 전체 이메일 본문을 로그에 저장하지 않고도 사서함 생성, 양식 제출, 메시지 읽기를 상호 연관 지을 수 있습니다.
## 예산 및 증거를 갖춘 재시도
재시도는 “다시 시도해 봐”와 같은 모호한 지침이어서는 안 됩니다. 명시적인 예산을 정의해야 합니다:
type RetryBudget = {
maxAttempts: number;
deadlineAt: string;
...
각 시도 후, 도구는 간결한 영수증을 반환합니다: 상태, 지속 시간, 사용된 커서, 관찰된 메시지 수, 종료 이유. 에이전트는 원인을 지어내기보다 “메시지가 아직 나타나지 않았습니다”라고 전달할 수 있습니다.
자주 잊히는 비용이 있습니다. 더 오래 기다린다고 해서 테스트의 품질이 항상 높아지는 것은 아닙니다. 실행 창을 벗어난 메시지는 올바른 토큰을 포함하고 있더라도 지연된 것으로 표시되어야 합니다. 유효성은 내용뿐만 아니라 컨텍스트에 달려 있습니다.
러너는 `received` 결과가 나온 후 커서를 재사용해서는 안 됩니다. 플로우가 계속되면, `messageId`를 한 번 소비하거나 이미 사용되었음을 기록해야 합니다. 이렇게 하면 작업이 **멱등성(idempotent)**에 더 가까워지고 실수로 반복되는 것을 디버깅하기 쉬워집니다.
## 실행별 격리
설계는 다음과 같이 시각화할 수 있습니다:
agente LLM
│ runId와 함께 작업을 요청
▼
...
각 실행은 사서함 ID, 시작 시간, 만료 시간을 가져야 합니다. 공유 리소스를 사용하는 경우에도 최소한 `runId`로 분리하고 `receivedAt`으로 필터링해야 합니다. 공용 사서함이 처음에는 더 저렴해 보일 수 있지만, 조사 과정에서 발생하는 거짓 양성(false positive)은 비용이 많이 듭니다.
SaaS 플로우를 출시하기 전에 [출시 전 이메일 체크리스트](https://dev.to/hannahdev56/saas-checklist-de-emails-antes-de-lanzar-1o5l)도 검토할 가치가 있습니다. 에이전트가 제품 검증을 대체하는 것은 아니며, 반복 가능하게 만들고 더 나은 증거를 남길 수 있도록 도와줍니다.
## Q&A
### 에이전트는 전체 이메일을 읽어야 하나요?
일반적으로는 아닙니다. 도구는 `messageId`, 제목, 날짜, 검증 URL과 같이 필요한 필드만 추출해야 합니다. 전체 본문을 저장하는 것은 개인 정보 보호, 보존 및 디버깅을 복잡하게 만듭니다.
### 몇 번의 재시도가 합리적인가요?
제공업체의 SLA와 플로우 지속 시간에 따라 다르지만, 유용한 규칙은 시도 횟수 제한과 시간 제한을 모두 두는 것입니다. 하나만 존재할 경우, 느린 응답이 예측하기 어렵게 시스템을 고갈시킬 수 있습니다.
### 프롬프트는 어디에 들어가나요?
프롬프트는 `received`, `pending` 또는 `rejected`를 어떻게 해석할지 정의합니다. 만료된 URL이 유효한지 여부를 결정하거나 제한을 건너뛰어서는 안 됩니다. 그러한 규칙은 도구 근처의 코드에 존재해야 합니다.
## 구현 포인트
- 시작 시 `runId`를 생성하고 모든 호출에서 전파합니다.
- 모호한 메시지가 아닌, 타입이 지정된 상태를 반환합니다.
- 시도 예산과 최대 시간을 분리합니다.
- 커서와 시간 창을 기준으로 메시지를 필터링합니다.
- 토큰이나 전체 본문 없이 영수증을 기록합니다.
- 에이전트가 오류로 종료되더라도 테스트 식별자를 만료시킵니다.
핵심 아이디어는 간단합니다. 도구가 명확한 제한 사항을 노출할 때 모델들이 더 잘 결정할 수 있습니다. 잘 정의된 이메일 계약은 불확실한 대기 시간을 관찰 가능한 상태로 변환하며, 자동화를 더 안전하고, 조사하기 저렴하며, 덜 취약하게 만듭니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기