본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 05. 15. 21:46

Claude는 3회, Qwen은 6회: 모델별로 fix_verify의 retry cap을 변경하는 설계

요약

Codens Purple의 `fix_verify` 루프에서 에이전트의 재시도(retry) 횟수 제한(cap)을 모델별로 차등 적용하는 설계를 도입했습니다. 기존에는 모든 모델에 공통으로 cap=5를 사용했으나, Claude와 Qwen 등 주요 모델 간의 비용 구조 및 성능 특성 차이를 고려하여 Claude는 3회, Qwen은 6회 등으로 재시도 제한을 조정했습니다. 이 과정에서 Anthropic API와 자체 호스팅(self-hosted)한 Qwen을 병행 사용하는 다중 모델 프로덕션 환경 구축 경험과 그 운영상의 장점을 설명합니다.

핵심 포인트

  • 모델별 특성에 따라 에이전트의 retry cap을 차등 설정하여 비용 효율성을 극대화했습니다 (Claude: 3회, Qwen: 6회).
  • fix_verify 루프는 '코드 수정 → 검증(test/lint) → 실패 시 피드백 및 재시도' 과정을 반복하는 에이전트 기반 프로덕트입니다.
  • Anthropic API와 자체 호스팅한 open model (Qwen)을 병행 사용하며, 각 모델의 비용 구조(token cost vs. compute cost)를 분석하여 최적화했습니다.
  • API 방식은 multi-tenant 워크로드에 적합하며, Anthropic의 정책 변화는 초기 API 기반 설계가 올바른 선택이었음을 검증하는 계기가 되었습니다.

Codens Purple의 fix_verify

loop에서 agent의 retry cap을 모델별로 변경한 이야기를 쓰겠습니다. 구체적으로는 Claude 계열은 3회, Qwen 계열은 6회, 그 외의 모델은 5회를 기본값(default)으로 설정했습니다. 처음에는 모든 모델 공통으로 cap 5를 적용하여 운용했으나, 프로덕션(production)의 fix rate와 per-token cost를 살펴보던 중 "Claude와 Qwen을 같은 cap으로 설정하는 것이 오히려 이상하다"라고 판단하여, 모델 접두사(model prefix)에 따라 default를 전환하는 메커니즘을 도입한 것이 이번 CDTSK-1362입니다.

"동일한 task에 대해 왜 Claude는 3회에서 중단하면서 Qwen에는 6회까지 기회를 주는가"를 설명하는 것이 본론이며, 덧붙여 multi-model production을 Anthropic API + self-hosted Qwen으로 운영하고 있는 저희의 역사적인 선택에 대해서도 쓰겠습니다. 최근 Anthropic의 claude -p / Agent SDK / CI restricted from subscription 발표가 결과적으로 저희의 구성을 검증(validation)한 형태가 되었기에, 그 맥락에 대해서도 언급하겠습니다.

fix_verify loop란 무엇을 하는 loop인가

Codens Purple는 agent가 "코드를 수정한다 → verify (test / lint / type check)를 실행한다 → 실패하면 feedback을 넣고 재시도(retry)한다"라는 loop로 동작하는 프로덕트(product)입니다. fix_verify라는 이름 그대로, fix와 verify가 교대로 실행되는 단순한 구조입니다.

1회의 retry 비용은 대략적으로 말하면

  • agent에 대한 prompt cost (input token)
  • agent로부터의 응답 cost (output token)
  • verify 실행을 위한 CI / sandbox cost

의 합계입니다. Claude의 경우 이 중에서 input/output token cost가 지배적이며, Qwen을 self-hosted로 구동하는 케이스라면 서버 측의 compute가 지배적이 됩니다. 같은 "retry를 1회 늘리는 것"이라도, 모델 측의 단가 구조가 다르면 retry cap의 증감이 그대로 비례하여 작용하지는 않습니다.

retry cap을 늘리는 의미는 단순합니다. "한 번 더 시도하면 통과할지도 모른다"라는 기대입니다. 반대로 줄이는 의미는 "이 이상 시도해도 통과하지 못할 것이며, credit의 낭비다"라는 포기입니다. 문제는 이 곡선(curve)이 모델마다 전혀 다르다는 것을 시간이 흐른 뒤에야 깨달았다는 점에 있습니다.

초기 구현은 cap = 5 고정이었습니다. 깊게 생각하지 않고 "5회 정도 하면 통과하겠지"라는 생각으로 결정한 숫자였으며, 프로젝트별로 override할 수 있는 API는 있지만 default는 global 하나였습니다. 이 상태로 반년 정도 운용했습니다.

multi-model production이 되기까지의 경위

Codens는 처음에 Claude API만으로 동작했습니다. 왜 처음부터 Claude API (= raw Anthropic API, per-token billing)였느냐 하면, subscription을 agent에서 호출하는 구성일 경우 tenant가 늘어날 때 credit 배분, cost monitoring, ratelimit 분리가 어떻게든 파탄 나기 때문입니다. 저희처럼 여러 프로젝트와 여러 사용자의 task를 하나의 harness로 받아 처리하는 측은, 처음부터 API key로 호출하여 per-token으로 사용하는 것 외에 사실상 다른 선택지가 없었다는 느낌입니다.

subscription 계열은 기본적으로 「한 명의 human user가 IDE에서 대화하기 위한」 과금 모델이기 때문에, CI / background agent / multi-tenant 워크로드 (workload)에 적용하려고 하면 규약 측면에서도 구현 측면에서도 적절하지 않습니다. 반면 API는 per-token 방식이므로 「사용한 만큼 지불한다」, 「사용량을 tenant별로 분리할 수 있다」, 「rate limit 제어도 직접 가질 수 있다」라는 세 가지 요건을 갖추고 있어, 저희가 필요로 하는 것과 일치했습니다.

한동안 Claude API만으로 운용해 왔으나, 특정 task category (세밀한 syntax 수정이나 정형적인 refactor)에서는 Claude의 비용이 overkill이 되는 케이스가 보이기 시작했습니다. 1 token당 단가가 Claude보다 훨씬 저렴한 open model을 사용하면, accuracy (정확도)가 조금 떨어지더라도 retry (재시도)를 늘려 trade-off (절충)한다면 전체적으로는 cost (비용)가 낮아지지 않을까 하는 가설입니다.

그래서 Qwen을 AWS EC2 상의 호스트에 self-hosted로 구축하여, Claude와 병렬로 task에 배분할 수 있도록 했습니다. GPU를 보유해야 하므로 서버 측의 고정비는 높지만, 추론당 marginal cost (한계 비용)는 낮기 때문에 물량이 확보되는 task category에서는 충분히 수지타산이 맞습니다. 저희의 자체 인프라 측에서 동작하고 있으므로, Anthropic 측의 rate limit와 독립적으로 스케일 (scale)할 수 있다는 운영상의 이점도 있습니다.

그로부터 얼마 지나지 않아, Anthropic이 claude -p / Agent SDK / CI 용도는 API plan이 필요하다는 방침을 명문화했습니다. 이는 subscription을 통해 CI를 구축하고 있던 team들에게는 강제 이행 이벤트이지만, 저희처럼 처음부터 API로 호출하던 쪽에게는 완전히 non-event였습니다. 오히려 「API로 시작하길 잘했다」는 것을 사후에 Anthropic 스스로가 추인해 준 형태가 되었으며, multi-model + API 구성은 비용 측면뿐만 아니라 optionality (선택권) 측면에서도 정답이었다고 생각합니다.

같은 cap을 사용하던 시절의 문제

multi-model이 된 직후에는 fix_verify의 retry cap은 모든 model 공통인 5로 유지되었습니다. 이 상태로 한동안 돌려본 뒤, production의 fix rate (수정 성공률)와 cost를 tenant별로 집계했더니 보고 싶지 않은 숫자가 나왔습니다.

Claude 계열에서 fix_verify가 실행된 task를 살펴보면, cap = 5에 대해:

  • 1~3회차에서 fix에 성공하는 패턴이 대다수
  • 4회차 이후에 처음으로 통과하는 케이스는 거의 제로
  • 5회차까지 다 쓰고 결국 통과하지 못한 task는, 내용을 보면 「애초에 사양(specification) 이해가 어긋난」 계열이 대부분

즉, Claude에 대해서는 4회차 이후의 retry가 거의 순수하게 credit의 낭비가 되고 있으며, retry를 늘려도 이 「사양 이해가 어긋나 있다」는 근본적인 문제는 해결되지 않는다는 것입니다. Claude는 1 attempt (시도)당 정밀도가 높기 때문에, 2~3회 내에 통과하지 못하는 task는 5회든 10회든 통과하지 못한다는 사실을 집계 결과로부터 읽어낼 수 있었습니다.

반면 Qwen 계열을 동일한 cap = 5로 보면:

  • 1~2회차의 fix rate는 Claude보다 명확히 낮음
  • 3~5회차에서 처음으로 통과하는 케이스가 Claude보다 확연히 많음
  • cap이 5에서 중단된 task 중에 「한 번 더 시도할 수 있었다면 통과했을 것」이라는 흔적이 포함되어 있는 형태

이는 model의 특성 혹은 동작의 차이인데, Qwen은 「첫 수정에서 모든 테스트를 통과하려다 부분적으로 빗나감 → feedback을 받아 타겟을 좁혀나감」이라는 path (경로)를 밟는 경우가 많아, retry를 거듭할수록 fix rate curve (수정 성공률 곡선)가 개선되는 타입이었습니다. Claude의 「한 번에 거의 정답이거나, 근본적으로 안 되거나」 하는 curve와는 별개의 것이었습니다.

처음에는 단일 캡 (single cap)으로 해결하려 하여, "일단 캡을 7로 올리면 Qwen 쪽은 커버할 수 있지 않을까"라고도 생각했지만, 이렇게 하면 Claude 측의 크레딧 (credit) 낭비가 심해집니다. 반대로 캡을 3으로 낮추면 Qwen의 합격률이 너무 낮아져서, 결국 재시도 (retry) 과정에서 다른 모델로 재실행하는 핸드오프 (hand-off)가 늘어나게 됩니다. 동일한 캡으로 두 모델을 모두 최적화하는 것은 원리적으로 불가능하다고 판단하여, 모델별로 기본값 (default)을 나누기로 했습니다.

모델별 기본값 (per-model default)으로 나눈 구현

구현은 CDTSK-1362를 통해 반영했습니다. 수행한 작업은 4가지입니다.

  • purple_projects 테이블에 nullable한 fix_verify_retry_cap 컬럼을 추가 (NULL이면 모델 기반 기본값 사용)
  • _default_fix_verify_cap(model)이라는 헬퍼 (helper)를 신설하여, 모델 이름의 접두사 (prefix)에 따라 3 / 6 / 5를 반환
  • API 스키마 (schema) 측에서 프로젝트 단위의 오버라이드 (override)를 Optional[int] + Field(ge=1, le=20)로 받을 수 있도록 설정
  • TaskUseCase 내에서 "프로젝트의 오버라이드 값 또는 모델 기본값"을 결정하여 유효 캡 (effective_cap)으로 사용

헬퍼는 다음과 같습니다.

def _default_fix_verify_cap(model: str) -> int:
    """Return per-model default retry cap for fix_verify loop."""
    if model.startswith("claude"):
        ...

접두사 (prefix)로 대략적으로 구분한 이유는, claude-sonnet-4-5이든 claude-opus-4-7이든 기본 곡선 (curve)은 동일하고, qwen2.5-coder이든 qwen3-coder이든 기본 곡선은 동일하다는 프로덕션 데이터 (production data)로부터의 판단입니다. 완전한 모델 단위의 테이블로 만들 수도 있었지만, 새로운 모델을 추가할 때마다 테이블을 업데이트하지 않으면 기본값이 적용되지 않는 운영 부하를 피하기 위해 접두사 기반 (prefix base)으로 결정했습니다.

API 스키마 측은 다음과 같은 형태입니다.

class ProjectUpdate(BaseModel):
    fix_verify_retry_cap: Optional[int] = Field(
        default=None, ge=1, le=20,
        ...

상한선을 20으로 설정한 이유는, 그 이상으로 높여도 Qwen에서도 수정률 곡선 (fix rate curve)이 평탄해지는 (flat) 것을 확인했기 때문입니다. 하한선인 1은 디버그 (debug) 용 (1회 시도 후 종료하여 검증 결과를 보고 싶은 경우)을 상정하고 있습니다.

DB 마이그레이션 (migration)은 alembic으로 1행을 추가했습니다.

op.add_column(
    "purple_projects",
    sa.Column("fix_verify_retry_cap", sa.Integer(), nullable=True),
    ...

그리고 TaskUseCase 내에서 실제로 사용하는 부분입니다.

effective_cap = (
    pp.fix_verify_retry_cap
    or _default_fix_verify_cap(execute_model)
    ...

pp.fix_verify_retry_cap이 NULL (= 미설정)이면 모델 기본값을 가져오고, 어떤 값이 들어있으면 프로젝트 오버라이드 (project override)를 존중한다는 단순한 3줄의 코드입니다. or를 사용하여 작성한 이유는, 스키마 측에서 fix_verify_retry_cap이 0이 되는 것을 금지하고 있으므로 falsy가 None과 동일하기 때문입니다.

이 3줄을 넣은 것만으로 Claude 계열 task는 cap 3에서 짧게 끊기게 되었고, Qwen 계열 task는 cap 6까지 retry (재시도)를 허용하게 되어, 프로덕션 (production)의 cost (비용) / fix rate (수정률) 균형이 눈에 띄게 변했습니다. 구체적으로는 Claude 측에서 "4~5회차를 소비하고 결국 실패하는" task의 credit (크레딧) 소비가 단번에 사라졌고, Qwen 측에서 "한 번만 더 있었으면 통과했을" 경우가 줄어드는 이중 효과가 나타나고 있습니다.

tradeoff: 새로운 model을 추가할 때마다 발생하는 default 결정 비용

이 설계에는 명확한 tradeoff (트레이드오프)가 있는데, 그것은 "새로운 model 계열을 도입할 때마다 해당 model의 최적인 retry cap default (재시도 캡 기본값)를 결정해야 한다"는 운영 부하입니다.

prefix (접두사)가 claude도 아니고, qwen도 아닌 model을 넣을 경우, _default_fix_verify_cap은 5를 반환합니다. 이는 "우선 안전한 default"라는 의미로 5를 설정해 둔 것일 뿐, 해당 model에 있어 optimal (최적)이라는 뜻은 아닙니다. 정말로 optimal을 결정하려면 Claude / Qwen에서 했던 것과 마찬가지로, 어느 정도의 production volume (프로덕션 볼륨)을 실행하여 fix rate curve (수정률 곡선)를 관측해야 합니다.

실무적으로는 새로운 model을 넣은 직후에는 cap 5 (= 안전한 default)로 2주 정도 흘려보낸 뒤, fix rate curve를 집계한 후 default를 덮어쓰는 방식으로 운영하고 있습니다. 완전한 "전자동으로 최적 cap을 결정하는" 단계까지는 도달하지 못했으며, 이 부분은 판단이 포함된 operations (운영) 영역으로 남겨두고 있습니다.

또 하나, model prefix match (모델 접두사 매칭)로 대략적으로 구분하고 있는 것의 부작용으로서, claude-3-haiku와 같이 향후 "Claude 계열이지만 1 attempt (시도)의 cost가 낮은 model"이 등장했을 때, cap 3를 그대로 유지해도 되는가에 대한 논의가 나올 것 같습니다. 이는 문제가 발생했을 때 생각하기로 했습니다.

"모든 model에 동일한 retry cap을 적용하는 것"에서, 프로덕션 데이터를 보고 model별로 분리하는 단계까지 가져온 작은 이야기였습니다. Claude가 3이고 Qwen이 6라는 숫자 자체는 저희의 workload (워크로드) 고유의 것이며, 타사가 같은 숫자를 사용하게 된다는 보장은 없지만, "단일 cap을 model별로 분할해야 할 타이밍이 반드시 온다"는 구조적인 이야기는 multi-model production (멀티 모델 프로덕션)을 운영하는 사람들에게는 공통된 이야기라고 생각합니다.

Codens는 API 기반의 multi-model 구성으로, 이러한 세밀한 cap 설계를 하나씩 채워가며 운영하고 있습니다. 관심이 있다면 https://www.codens.ai/ 를 살펴보시기 바랍니다.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0