도구 호출 실패, 재시도인가 보상인가
「도구 호출 10개 중 9개가 성공하고 마지막 하나가 실패하면 어떻게 할 것인가」가 묻는 것은 예외 처리가 아니라 **부작용 모델링(side-effect modeling)**이다.
워크플로를 상태 머신으로 쪼개고, 각 도구 동작에 읽기 전용 / 멱등 쓰기 / 롤백 가능 쓰기 / 불가역 쓰기라는 리스크 등급을 매긴다. 실행 시 상태와 멱등 키를 기록하고, 실패 후에는 먼저 실제 업무 상태를 조회한 다음 재시도, 스킵, 롤백, 보상, 사람 전환 중 무엇을 할지 결정한다.
핵심 결론: 실패 복구의 관건은 Agent를 더 용감하게 재시도시키는 것이 아니라, 어떤 동작이 이미 발생했는지 알게 하는 것이다.
왜 「재시도」가 틀린 답인가
실제 워크플로에서 많은 동작은 한 번 실행되면 간단히 취소할 수 없다.
동작의 롤백 가능성 분류
| 어떤 것은 취소 가능 | 롤백 |
|---|
| 어떤 것은 보상만 가능 | 새로운 업무 동작으로 상쇄 |
| 어떤 것은 사람이 처리해야 함 | 사람 전환 |
3단 판단 + 4종 동작의 복구 전략
- 먼저 실패가 어느 단계에서 발생했는지 판단
- 그다음 앞서 성공한 동작에 부작용이 있는지 판단
- 마지막으로 롤백 가능한지 / 보상이 필요한지 판단
| 읽기 전용 조작 | 바로 재시도 |
|---|
| 멱등 쓰기 조작 | 상태 조회 후 재시도 |
| 불가역 쓰기 조작 | 맹목적 재시도 금지. 보상 또는 사람 확인으로 |
1단계: 워크플로를 상태 머신으로 쪼개기
사용자 검증(S1) → 자격 잠금(S2) → 쿠폰 생성(S3) → 알림 발송(S4) → 감사 기록(S5)
인터페이스의 성공/실패만 봐서는 안 된다. 겉보기에는 둘 다 "부분 실패"지만 실제로는 완전히 다른 두 상황을 예로 들면:
| 상황 | 의미 | 복구 전략 |
|---|
| 쿠폰 생성 성공 + 알림 실패 | 쿠폰은 나갔는데 사용자가 모름 | 알림만 재발송하면 됨 |
| 쿠폰 생성 실패 + 알림 미발송 | 아무 일도 일어나지 않음 | 안전하게 재실행 가능 |
| 결제 타임아웃 | 즉시 한 번 더 차감하면 안 됨 | 먼저 결제 상태 조회 |
| SMS 발송 실패 | 네트워크나 게이트웨이 이상 | 안전한 재시도 허용 |
| 쿠폰 발급 성공 | 레코드를 간단히 삭제하면 안 됨 | 알림 재발송 / 고객센터 후속 기록 |
결론: Agent는 작업이 어느 상태에 멈춰 있는지 반드시 알아야 한다.
2단계: 도구에 복구 의미론 정의하기 (tool schema 확장)
도구 schema에는 파라미터만이 아니라 네 가지 메타 정보를 더 써야 한다:
- 멱등한가
- 롤백 가능한가
- 현재 상태를 어떻게 조회하는가
- 실패 코드의 의미는 무엇인가
3단계: 보상 동작과 사람의 경계 설정
보상 ≠ 롤백. 보상은 새로운 업무 동작으로 영향을 상쇄하는 것이지 원래 동작을 취소하는 것이 아니다. 예:
- 쿠폰을 잘못 발급함 → 올바른 쿠폰 발급 + 잘못된 쿠폰 동결
- 메일을 잘못 보냄 → 회수 불가, 정정 안내를 보낼 수밖에 없음
「고위험 동작 통제」: Agent가 「보상 전략 제안」 → 확인 대기 → 「사람/사용자가 확인 후 실행」. 하단 경고문: 「Agent가 모든 부작용을 독단적으로 처리하는 것 금지」
어떻게 테스트하나? — 장애 주입 4장면
| # | 주입할 장애 |
|---|
| 1 | 어떤 단계가 타임아웃했지만 실제로는 성공(가장 위험한 유형) |
| 2 | 어떤 단계가 부분 성공 |
| 3 | 보상 도구 자체도 실패 |
| 4 | 상태 조회 응답이 지연 |
진단 요점: 많은 시스템이 평소에 문제없어 보이는 것은 성공 경로만 테스트했기 때문이다. 진짜 상용 사고는 대개 반쯤 성공, 반쯤 실패, 중복 제출 사이에서 발생한다.
핵심 통찰
- 실패 복구의 제1원리적 질문은 "세상이 바뀌었는가"이지 "호출이 무엇을 반환했는가"가 아니다 — 인터페이스가 실패를 반환했다고 동작이 발생하지 않은 것은 아니다(타임아웃했지만 실제로는 성공한 경우가 가장 전형적인 반례다). 따라서 상태 조회는 어떤 복구 결정보다도 우선해야 한다.
- 부작용은 타입 시스템으로 모델링할 수 있다 — 읽기 전용 / 멱등 쓰기 / 롤백 가능 쓰기 / 불가역 쓰기 이 네 등급은 본질적으로 「리스크」를 도구 정의에 인코딩해, 복구 전략을 사람이 현장에서 판단하는 대신 자동으로 추론할 수 있게 한다.
- 보상과 롤백은 서로 다른 두 패러다임이다 — 롤백은 「없던 일로 만드는 것」이고 보상은 「한 가지 일을 더 해서 영향을 상쇄하는 것」이다. 현실 세계의 불가역 동작(이미 발송된 메일, 이미 통지한 사용자)에는 보상이라는 길밖에 없으며, 이것이 분산 트랜잭션에서 Saga 패턴의 핵심 사상이다.
- 멱등 키는 재시도 능력의 전제 조건이다 — 멱등 키가 없으면 어떤 재시도도 도박이다. 멱등 키가 있어야 재시도가 「위험한 조작」에서 「안전한 조작」으로 바뀐다.
- Agent의 자율권은 리스크에 따라 수렴시켜야 한다 — 저위험 동작은 완전 자동, 고위험 보상 동작은 사람 확인을 강제. 「Agent가 제안하고 사람이 결정한다」는 것은 고위험 장면의 기본 협업 형태이지 능력 부족에 따른 타협이 아니다.
- 테스트의 가치는 이상 경로를 덮는 데 있고, 성공 경로 테스트는 거의 무효다 — 장애 주입으로 「반쯤 성공」 상태를 일부러 만들어야 한다. 상용 사고는 거의 전부 이 구간에 집중되기 때문이다.
- 평가는 동종 작업의 전체적 안정성을 봐야지 단일 답변의 인상을 보면 안 된다 — 「답이 듣기 좋다」는 사람을 속이기 가장 쉬운 지표다. 고정된 회귀 샘플 + 다차원 지표(정확률/경로 안전/인용 품질/비용·지연)만이 진짜 변화를 반영한다.
엔지니어링 실전 Tips
- 타임아웃 ≠ 실패: 결제 타임아웃 시 첫 동작은 언제나 「결제 상태 조회」이지 「한 번 더 차감」이 아니다. 이것이 가장 전형적인 금전 손실 사고의 원인이다.
- 멱등 키는 호출을 발기하기 전에 생성해 영속화해야 한다. 그렇지 않으면 재시도할 때 쓸 키가 없다.
- 상태 머신의 입도는 「부작용이 있는 모든 동작」까지 세밀해야 한다. 굵은 입도의 상태로는 「쿠폰 생성 성공, 알림 실패」 같은 중간 상태를 구분할 수 없다.
- 실패 코드는 error 하나만 두지 말고 분류할 것: 최소한 「확실히 미실행」, 「확실히 실행됨」, 「상태 불명」 세 종류로 구분하고, 세 번째만 상태 조회로 보낼 것.
- 보상 동작 자체도 실패할 수 있으므로 일급 객체로 테스트에 포함할 것(장애 주입 3항).
- 고위험 보상은 반드시 사람 확인 채널을 거쳐야 하며 Agent에게 직접 실행 권한을 주지 말 것.
- 회귀 샘플셋은 고정해야 한다. 그렇지 않으면 Prompt 변경 후의 지표 변화를 귀인할 수 없다.
- 상용 trace 샘플링 → 실패 샘플을 평가셋에 환류시키는 것이, 평가셋을 실제 분포에 계속 근접시키는 데이터 플라이휠이다.
용어
멱등(Idempotent) — MDN 문서
같은 요청을 한 번 실행하든 연속해서 여러 번 실행하든 서버에 대한 기대 효과가 동일하다면, 그 HTTP 메서드는 멱등하다고 한다.