반복과 자원 예산
70분 안팎
학습 목표
호출 수·누적 토큰·경과 시간의 상한과 같은 실패 3회 중단을 모의 이벤트로 판정합니다.
개념
실패를 수정할 능력과 멈출 능력을 함께 만듭니다
할 일 조회 순서 검사에서 같은 실패가 반복된다고 가정합니다. 다시 요청할 때마다 조금 다른 설명이 와도 실패 사례가 그대로라면 진전이 없을 수 있습니다. 반대로 실패 내용이 달라지더라도 호출이 끝없이 이어지면 시간과 자원을 잃습니다. 이번 과제는 호출 수, 누적 토큰, 모의 경과 시간, 연속 실패를 별도 기준으로 판정합니다. 한도 때문에 멈춘 실행을 완료로 보고하지 않고 어느 조건이 추가 실행을 막았는지 반환합니다.
단위와 측정 시점을 먼저 정합니다
calls는 다음 사건에서 소비할 모의 모델 호출 수, tokens는 그 사건의 입력과 출력 합계로 준비된 토큰 수, seconds는 모의 소요 초입니다. 실제 계정이나 제품 가격을 사용하지 않습니다. 이벤트 비용을 모두 알고 있는 고정 fixture라서 시작 전에 합계를 비교할 수 있습니다. Python 문자열의 len을 토큰 수로 계산하지 않습니다. 실제 연결에서는 사용량 보고와 시작 전 예약량을 구분하고 예상보다 큰 출력이 나온 경우를 별도 정책으로 처리해야 합니다. 이번 코드는 실제 과금 한도를 보장하는 도구가 아닙니다.
사용량을 더하기 전에 거절합니다
현재 used와 다음 cost를 더한 값이 limits보다 크면 그 사건을 받지 않습니다. 비교는 calls, tokens, seconds 순서이고 여러 조건이 동시에 넘으면 먼저 발견한 이름을 출력합니다. 같은 입력에서 같은 중단 사유가 나와야 테스트와 인계가 일치합니다. 거절된 사건의 사용량은 누적하지 않으며 이미 실행한 사건의 비용은 유지합니다. 실행 후에만 상한을 확인하면 비용 초과를 관찰할 수 있어도 다음 행동을 예방하지는 못합니다. 이번 과제는 시작 전 차단 위치를 익힙니다.
한도와 같은 값은 허용합니다
used가 2이고 다음 호출 비용이 1이며 calls 한도가 3이면 그 사건은 허용합니다. 합계가 4면 거절합니다. 비교 기호를 크게 또는 같게로 바꾸면 마지막 허용량을 사용할 수 없게 됩니다. 한도가 0이고 빈 사건 배열이면 사용량도 0으로 ready입니다. 한도가 0인데 양수 호출 사건이 있으면 첫 사건 전에 calls로 멈춥니다. 입력 계약은 모든 비용과 한도가 음이 아닌 정수라고 가정합니다. 이상 자료형과 음수를 처리하는 범위 검사는 이번 브라우저 문제에 포함하지 않습니다.
실패한 시도도 비용을 썼습니다
실행한 사건에 failure가 order라고 적혀 있으면 요구를 충족하지 못했지만 호출과 토큰과 시간이 이미 소비되었습니다. 실패하였으니 used를 빼는 구현은 같은 실패를 반복해도 한도가 줄지 않는 결과를 만듭니다. 실패 비용까지 포함해 누적한 뒤 다음 사건을 검사합니다. 거절된 사건과 실행 후 실패한 사건의 차이를 기록에서 구분합니다. 브라우저 출력의 accepted는 성공 건수가 아니라 실행을 허용한 사건 수입니다. 이름이 의미하는 범위를 설명할 수 있어야 수치를 잘못 해석하지 않습니다.
같은 실패는 안정된 ID로 셉니다
failure는 실패 사례를 나타내는 문자열이며 빈 문자열은 그 사건에서 실패가 없다는 뜻입니다. order, order, order가 연속이면 셋째 사건의 사용량까지 더한 뒤 repeat로 멈춥니다. 임시 경로나 시각이 붙은 오류 문장 전체를 ID로 쓰면 같은 원인도 다른 값이 됩니다. 미션은 unit:failed와 integration:failed처럼 단계와 결과를 묶습니다. 이런 식별자는 원인 분석을 대신하지 않으므로 중단 뒤에는 해당 사례의 재현 입력과 로그를 읽고 가설을 좁힙니다.
연속 횟수는 끊긴 지점을 반영합니다
order, order, title, order는 마지막 order의 연속 횟수가 1입니다. 다른 실패로 바뀌면 새 실패의 연속 횟수를 1로 시작합니다. order, order, 빈 문자열, order 역시 마지막 횟수가 1입니다. 성공 사건은 streak를 0으로 초기화합니다. 같은 실패가 전체 기록에 세 번 등장했다는 이유만으로 repeat를 반환하면 연속 조건을 잘못 구현한 것입니다. last와 streak를 함께 갱신하고 실패가 없는 분기도 직접 테스트합니다. 중단 이전 기록을 삭제하여 횟수를 초기화하지 않습니다.
총 시도 제한은 다른 목적입니다
모듈 미션은 전체 시도 세 번이라는 별도 상한도 적용합니다. 첫 세 시도에서 실패가 모두 다르면 연속 실패 규칙은 작동하지 않지만 넷째 시도를 시작하기 전에 iterations로 멈춥니다. 브라우저 과제는 입력 limits의 자원 상한과 연속 실패만 구현하므로 두 정책의 범위를 혼동하지 않습니다. 실패가 계속 바뀌는 경우에는 수정 범위가 너무 넓거나 검사 순서가 불안정한지 검토합니다. 다시 시작하려면 마지막 근거와 새 계획을 명시하고 이미 쓴 비용을 실제로 없던 일처럼 처리하지 않습니다.
시간 모형을 실제 deadline으로 설명하지 않습니다
이번 seconds는 이벤트가 제공한 정수의 합계입니다. 시계를 읽지 않고 잠들지도 않으므로 같은 입력은 바로 같은 출력을 만듭니다. 검사 프로그램이 실제로 오래 걸릴 때 강제 중단하는 timeout이나 프로세스 정리 기능은 없습니다. 미션도 이 모의 시간 예산을 사용합니다. 실제 실행 시간 한도를 연결할 때에는 경과 시간 관찰과 개별 호출 제한을 별도로 설계해야 합니다. 교육 실행에서 seconds 한도가 작동했다는 사실은 실제 장시간 명령을 중단한 증거가 아닙니다.
출력 네 칸으로 원인을 설명합니다
출력은 사유 accepted calls tokens seconds를 공백으로 구분한 한 줄입니다. ready는 제공된 사건을 모두 받아들였다는 뜻이며 앱 완료라는 뜻은 아닙니다. calls·tokens·seconds는 해당 다음 사건을 받기 전에 한도 초과를 발견한 사유입니다. repeat는 셋째 연속 실패를 실행한 뒤 멈춘 결과입니다. 예를 들어 tokens 1 1 4 2라면 한 사건만 허용했고 누적 호출 1, 토큰 4, 시간 2입니다. 거절된 둘째 사건 비용을 출력에 더하지 않았는지 확인합니다.
실패 메시지는 경계값을 먼저 보게 합니다
기대 accepted가 1인데 2면 상한 비교를 실행 뒤에 했는지 봅니다. 사용량이 0으로 돌아오면 실패 비용을 취소했는지 읽습니다. 반복 사유가 너무 일찍 나오면 streak가 0부터 시작해 사건 전에 증가했는지 확인합니다. KeyError는 입력 필드 이름이나 used 키가 다르다는 신호입니다. 합계 계산을 반올림하거나 가격으로 바꾸어 해결하지 않습니다. 이 과제는 정수 모의 단위를 정확히 누적하는 문제이며 실제 토큰 측정 정밀도와는 구분합니다.
중단 뒤 다음 조치를 남깁니다
repeat면 동일 실패의 입력·기대·실제를 묶어 사람에게 넘기고, tokens면 맥락 범위와 예약량을 검토합니다. seconds면 검사 범위를 줄일 수 있는지 또는 실제 시간 제한 연결이 필요한지 따로 판단합니다. 한도를 높이는 선택도 가능하지만 그 결정은 다음 계획에서 근거를 갖고 이루어져야 합니다. 실패를 통과시키려고 자동으로 상한을 늘리는 코드는 작성하지 않습니다. 더 읽기는 맥락을 줄이면서 중요한 제한을 보존하는 방법을 다룹니다. 이번 실습은 소비량 계산과 실행 전 중단을 담당합니다.
따라하기
한도에 정확히 도달
호출·토큰·시간 각각 같은 값까지 허용합니다.
def budget_reason(used, cost, limits):
for key in ('calls', 'tokens', 'seconds'):
if used[key] + cost[key] > limits[key]:
return key
return None
def consume(events, limits):
used = dict(calls=0, tokens=0, seconds=0)
last, streak, accepted = None, 0, 0
for e in events:
reason = budget_reason(used, e, limits)
if reason:
return reason, accepted, used
for key in used:
used[key] += e[key]
accepted += 1
failure = e['failure']
streak = (streak + 1 if failure == last else 1) if failure else 0
last = failure
if streak >= 3:
return 'repeat', accepted, used
return 'ready', accepted, used
e=[dict(calls=1,tokens=4,seconds=2,failure='')]
print(consume(e,dict(calls=1,tokens=4,seconds=2)))실행 결과
('ready', 1, {'calls': 1, 'tokens': 4, 'seconds': 2})
다음 사건 전에 차단
둘째 사건의 토큰이 들어가기 전에 멈춥니다.
def budget_reason(used, cost, limits):
for key in ('calls', 'tokens', 'seconds'):
if used[key] + cost[key] > limits[key]:
return key
return None
def consume(events, limits):
used = dict(calls=0, tokens=0, seconds=0)
last, streak, accepted = None, 0, 0
for e in events:
reason = budget_reason(used, e, limits)
if reason:
return reason, accepted, used
for key in used:
used[key] += e[key]
accepted += 1
failure = e['failure']
streak = (streak + 1 if failure == last else 1) if failure else 0
last = failure
if streak >= 3:
return 'repeat', accepted, used
return 'ready', accepted, used
e=[dict(calls=1,tokens=4,seconds=2,failure='order')]*2
print(consume(e,dict(calls=3,tokens=7,seconds=20)))실행 결과
('tokens', 1, {'calls': 1, 'tokens': 4, 'seconds': 2})
실패 연속성 비교
다른 실패가 사이에 끼면 반복 횟수를 다시 셉니다.
def budget_reason(used, cost, limits):
for key in ('calls', 'tokens', 'seconds'):
if used[key] + cost[key] > limits[key]:
return key
return None
def consume(events, limits):
used = dict(calls=0, tokens=0, seconds=0)
last, streak, accepted = None, 0, 0
for e in events:
reason = budget_reason(used, e, limits)
if reason:
return reason, accepted, used
for key in used:
used[key] += e[key]
accepted += 1
failure = e['failure']
streak = (streak + 1 if failure == last else 1) if failure else 0
last = failure
if streak >= 3:
return 'repeat', accepted, used
return 'ready', accepted, used
limits=dict(calls=10,tokens=100,seconds=100)
for failures in [['order']*3,['order','order','title','order']]:
e=[dict(calls=1,tokens=2,seconds=1,failure=f) for f in failures]
print(consume(e,limits))실행 결과
('repeat', 3, {'calls': 3, 'tokens': 6, 'seconds': 3})
('ready', 4, {'calls': 4, 'tokens': 8, 'seconds': 4})
확인 문제
실습
JSON 객체는 limits와 events입니다. limits는 calls·tokens·seconds 상한이며 events의 각 객체는 같은 세 비용과 failure 문자열을 가집니다. 숫자는 음이 아닌 정수이고 failure의 빈 문자열은 성공입니다. used는 모두 0에서 시작합니다. 다음 비용을 더하면 한도 초과인 사건은 받지 않으며 calls→tokens→seconds 순으로 사유를 고릅니다. 같은 비어 있지 않은 failure 3회 연속은 셋째 비용까지 소비한 뒤 repeat입니다. 다른 실패는 1부터, 성공은 0부터 다시 셉니다. 사건을 전부 받으면 ready입니다. 출력은 사유 accepted 누적calls 누적tokens 누적seconds 한 줄입니다. accepted는 성공 수가 아니라 허용한 사건 수입니다.
모범 답안
def budget_reason(used, cost, limits):
for key in ('calls', 'tokens', 'seconds'):
if used[key] + cost[key] > limits[key]:
return key
return None
def consume(events, limits):
used = dict(calls=0, tokens=0, seconds=0)
last, streak, accepted = None, 0, 0
for e in events:
reason = budget_reason(used, e, limits)
if reason:
return reason, accepted, used
for key in used:
used[key] += e[key]
accepted += 1
failure = e['failure']
streak = (streak + 1 if failure == last else 1) if failure else 0
last = failure
if streak >= 3:
return 'repeat', accepted, used
return 'ready', accepted, used
import json,sys
d=json.load(sys.stdin)
reason,n,used=consume(d['events'],d['limits'])
print(reason,n,used['calls'],used['tokens'],used['seconds'])
더 읽기
면접 질문
- AI가 만든 코드가 실행될 때 추가로 확인할 내용을 설명해 주시면 됩니다.