LLM 엔지니어링10장 · MCP 와 Prompt·Context Engineering12 / 22← 이전목차다음 →MetaContents

10장. MCP 와 Prompt·Context Engineering

실패 복구 설계

전화가 안 되면 잠시 뒤 다시 걸고, 그래도 안 되면 문자를 보내고, 급하면 직접 찾아갑니다. 실패 복구는 "다시 해 보기 → 다른 길 → 사람에게"의 차례를 미리 정해 두는 것입니다.

다시 시도(백오프), 고쳐서 다시 묻기, 다른 길로, 사람에게 넘기기의 네 걸음 흐름도

1번에서 일시적 오류는 잠깐 기다렸다 다시 하는데, 기다리는 시간을 0.5초, 1초, 2초처럼 늘립니다(백오프). 2번에서 출력이 틀리면 까닭을 붙여 다시 묻습니다. 3번에서 같은 길이 계속 막히면 다른 도구나 다른 모델로 갑니다. 4번에서 그래도 안 되면 사람에게 넘깁니다. 여기서 다시 걸기가 재시도, 문자가 다른 길입니다.

백오프 재시도

Python
import random, time

def flaky(rng):
    """설명용: 열 번에 네 번꼴로 일시적 오류를 내는 가짜 도구"""
    if rng.random() < 0.4:
        raise ConnectionError("일시적인 연결 오류")
    return "성공"

def with_retry(fn, tries=3, base=0.01):
    for k in range(tries):
        try:
            return fn(), k + 1
        except ConnectionError:
            if k == tries - 1:
                return "실패", tries
            time.sleep(base * 2 ** k)          # 0.01초, 0.02초, 0.04초로 늘린다

rng = random.Random(7)
no_retry = sum(with_retry(lambda: flaky(rng), tries=1)[0] == "성공" for _ in range(100))
rng = random.Random(7)
retry3 = sum(with_retry(lambda: flaky(rng), tries=3)[0] == "성공" for _ in range(100))
print("재시도 없음:", no_retry, "/ 100 · 세 번까지:", retry3, "/ 100")

재시도 없이 100번 중 57번, 세 번까지 하면 88번 성공합니다. 실패율이 40% 인 도구도 세 번 해 보면 셋 다 실패할 확률이 0.4 × 0.4 × 0.4 = 6.4% 로 줄어듭니다. 숫자는 설명용 가짜 도구의 것이고, 기다리는 시간도 줄여 두었습니다(실제로는 0.5초, 1초, 2초 정도).

코드에 나오는 함수

코드쉬운 뜻
random.Random(7)씨앗 7로 시작하는 난수 생성기다. 씨앗이 같으면 매번 같은 난수가 같은 차례로 나온다
rng.random()0 이상 1 미만의 난수 하나를 뽑는다. 0.4 보다 작을 확률이 40% 다
raise ConnectionError(...)연결 오류를 일부러 일으킨다. 가짜 도구가 실패하는 흉내다
try: … except ConnectionError:연결 오류가 나면 멈추지 말고 아래 줄(다시 하기)로 간다
time.sleep(base * 2 ** k)그만큼 초를 쉰다. 2 ** k 는 2 의 k 제곱이라 1, 2, 4 배로 늘어난다
lambda: flaky(rng)이름 없는 작은 함수다. "부르면 flaky(rng) 를 실행하라"를 통째로 넘긴다
with_retry(...)[0] == "성공"돌려받은 (결과, 시도 횟수) 가운데 결과가 "성공"이면 True 다
sum(... for _ in range(100))100번 해서 True 인 횟수를 센다. True 는 1, False 는 0 으로 더해진다

이 코드가 뜻하는 것

통화가 잘 안 되는 친구에게 전화하는 일로 읽어 봅니다. 안 받으면 잠깐 기다렸다 다시 걸고, 또 안 받으면 조금 더 기다렸다 겁니다. 세 번까지 해도 안 되면 그만둡니다. 여기서 전화 걸기가 fn(), 안 받음이 ConnectionError, 점점 길게 기다리기가 base * 2 ** k, 세 번까지가 tries=3 입니다.

줄하는 일쉽게 말하면
def flaky(rng)열 번에 네 번꼴로 실패하는 가짜 도구잘 안 받는 친구
for k in range(tries):정한 횟수만큼 시도한다최대 세 번 걸기
return fn(), k + 1성공하면 결과와 몇 번째에 됐는지 돌려준다"두 번째에 받았다"
if k == tries - 1: return "실패", tries마지막 시도도 실패하면 그만둔다세 번째도 안 받으면 포기
time.sleep(base * 2 ** k)실패할 때마다 기다리는 시간을 두 배로 늘린다점점 길게 기다렸다 다시 걸기
rng = random.Random(7) (두 번)두 실험을 같은 씨앗에서 시작한다같은 조건에서 견주기

결과 57번과 88번은 각각 이론값 60번(성공률 60%)과 93.6번(1 - 0.064)에 가깝습니다. 100번만 해 보았으므로 이론값과 조금 다릅니다. 재시도는 일시적 오류를 크게 줄이지만, 횟수를 늘릴수록 기다리는 시간도 함께 늘어납니다.

작게 줄여서 눈으로 보기

실제 서비스에 가까운 0.5초에서 시작하면 기다리는 시간이 어떻게 늘고, 모두 실패할 확률이 어떻게 줄어드는지 봅니다.

Python
base = 0.5
for k in range(3):
    print(f"{k + 1}번째 실패 뒤 기다리기: {base * 2 ** k}초")

p = 0.4                                # 한 번 실패할 확률
for tries in [1, 2, 3, 4]:
    print(f"{tries}번 해서 모두 실패할 확률: {p ** tries:.3f}")
출력
1번째 실패 뒤 기다리기: 0.5초
2번째 실패 뒤 기다리기: 1.0초
3번째 실패 뒤 기다리기: 2.0초
1번 해서 모두 실패할 확률: 0.400
2번 해서 모두 실패할 확률: 0.160
3번 해서 모두 실패할 확률: 0.064
4번 해서 모두 실패할 확률: 0.026
결과읽는 법
0.5초, 1.0초, 2.0초실패할 때마다 기다리는 시간이 두 배가 된다. 이것이 백오프다
0.400 → 0.064세 번 하면 모두 실패할 확률이 40% 에서 6.4% 로 준다
0.064 → 0.026네 번째 시도는 3.8%p 만 더 줄인다. 시도마다 얻는 몫이 작아진다

with_retry 는 마지막 시도 뒤에는 기다리지 않으므로, 세 번까지 하면 0.5초와 1초를 합쳐 최대 1.5초를 기다립니다. 시도를 늘릴수록 얻는 것은 줄고 기다림은 길어지므로, 횟수 한도는 보통 서너 번에서 멈춥니다. 서버가 한꺼번에 몰린 요청 때문에 실패했다면, 모두 같은 간격으로 다시 보내지 않게 기다리는 시간에 작은 난수를 섞기도 합니다.

주의할 점

  • 거짓 보고는 재시도로 막을 수 없습니다. 에이전트가 "예약했습니다"라고 하면, 답을 보여 주기 전에 예약 목록을 읽어 정말 있는지 확인합니다(프로젝트).
  • 사람에게 넘길 때는 무엇을 시도했고 무엇이 실패했는지를 함께 넘깁니다. 받은 사람이 처음부터 다시 묻지 않아도 되게 합니다.
← 이전 절10장 · 실패의 종류다음 절 →10장 · 실습 지도