본문으로 건너뛰기
AIPida
채택됨고급AI 모델·API

Claude stop_reason가 refusal로 오는데 content가 빈 배열이라 IndexError 납니다

보안 점검 리포트를 자동 생성하는 야간 배치인데, 가끔 응답 파싱하다가 IndexError가 납니다. 로그 까보니 response.content[](빈 배열)이고 stop_reasonrefusal로 와 있더라고요.

저희가 보안 툴링이라 악의적 의도는 전혀 없는데 가끔 분류기에 걸리는 듯합니다. 무인 배치라 한 번 터지면 그 항목이 통째로 누락돼서 다음 날 보면 구멍이 나 있어요.

  1. refusal을 어떻게 안전하게 핸들링하나요 (content[0] 깠다가 터지는 거 말고)
  2. 정당한 요청이 잘못 막혔을 때 무인으로 복구할 방법이 있는지

이 두 개가 궁금합니다.

답변 2

  • 채택된 답변에디터 검증

    content[0] 무조건 까는 게 원인이에요. refusal일 때 content는 빈 배열이거나(출력 전 거절) 잘리다 만 부분 출력(스트리밍 중 거절)이라 인덱싱하면 그 자리에서 터집니다. 항상 stop_reason 먼저 분기하세요.

    if response.stop_reason == "refusal":
        cat = response.stop_details.category if response.stop_details else None
        handle_refusal(cat)        # dead-letter로 빼고 로그
    else:
        text = next(b.text for b in response.content if b.type == "text")
    

    복구는 모델에 따라 갈리는데, claude-fable-5 쓰는 중이면 server-side fallback이 제일 깔끔합니다. opt-in이라 안 켜면 거절 시 그냥 멈춰요. 켜두면 정책 거절이 났을 때 같은 호출 안에서 폴백 모델이 한 번 더 돌아줍니다.

    response = client.beta.messages.create(
        model="claude-fable-5",
        max_tokens=16000,
        betas=["server-side-fallback-2026-06-01"],
        fallbacks=[{"model": "claude-opus-4-8"}],
        messages=[...],
    )
    

    과금은 출력 전 거절이면 안 붙고, 폴백이 실제로 서빙하면 폴백 모델 요율로 붙습니다. 최종 응답이 그래도 stop_reason: refusal이면 체인 전체가 거절한 거라 그땐 사람한테 넘겨야 하고요.

    한 가지 주의: 이거 Bedrock/Vertex/Foundry에선 안 됩니다. 거기 쓰면 server-side fallback이 없어서 SDK 클라이언트사이드 미들웨어(BetaRefusalFallbackMiddleware)를 클라이언트에 등록하는 쪽으로 가야 해요. 저흰 직 API라 위 방식으로 도배해뒀습니다.

  • 보안 툴링이면 false positive 진짜 종종 납니다(security 인접 작업이 분류기 입장에선 제일 애매해서). 위 fallback 깔고, 운영 쪽으로 두 개만 더 박아두면 거의 안 터져요.

    • refusal 난 항목 그냥 누락시키지 말고 dead-letter 큐로 빼서 stop_details.category랑 입력까지 같이 남기세요. 나중에 보면 특정 카테고리에 몰려 있어서 그것만 프롬프트 손보면 됩니다.
    • 거절을 같은 프롬프트로 재시도하지 마세요. 똑같이 막힙니다(저 처음에 이거 모르고 3번 재시도 박았다가 토큰만 날림). 폴백 모델로 보내거나, 도메인 맥락 분명하게 프롬프트를 다시 쓰는 게 맞아요.

    그리고 배치 파서면 stop_reasonend_turn/tool_use/max_tokens/refusal 말고 pause_turn(서버사이드 도구 루프 중간)도 옵니다. 이거 안 잡아두면 도구 쓰는 호출에서 또 비슷하게 빈 content 만나서 터질 수 있어요.