Skip to content

스트리밍 중단/0 Token/서명 무효: 자가 치유 메커니즘 및 문제 해결 경로

Antigravity Tools에서 /v1/messages(Anthropic 호환) 또는 Gemini 네이티브 스트리밍 인터페이스를 호출할 때 "스트리밍 출력 중단", "200 OK이지만 0 Token", "Invalid signature"와 같은 문제를 만나면, 이 과정은 UI에서 로그까지 문제 해결 경로를 제공합니다.

이 과정을 통해 할 수 있는 것

  • 0 Token/중단 문제가 프록시에서 보통 "peek 사전 읽기"에 의해 먼저 차단된다는 것을 알 수 있음
  • Proxy Monitor에서 이번 요청의 계정과 매핑 모델(X-Account-Email / X-Mapped-Model)을 확인할 수 있음
  • 로그를 통해 "상류 스트림 조기 종료", "백오프 재시도", "계정 로테이션" 또는 "서명 수정 재시도"인지 판단할 수 있음
  • 어떤 상황에서 프록시 자가 치유를 기다리고 어떤 상황에서 수동으로 개입해야 하는지 알 수 있음

현재 겪고 있는 문제

다음과 같은 "현상"을 볼 수 있지만 어디서부터 시작해야 할지 모를 수 있습니다:

  • 스트리밍 출력이 중간에 중단되고 클라이언트가 "멈춤"처럼 더 이상 진행하지 않음
  • 200 OK이지만 usage.output_tokens=0 또는 내용이 비어 있음
  • 400 오류에 Invalid \signature`, Corrupted thought signature, must be `thinking`` 등이 나타남

이러한 문제의 대부분은 "요청을 잘못 작성한 것"이 아니라 스트리밍 전송, 상류 속도 제한/변동, 또는 과거 메시지에 포함된 서명 블록이 상류 검증을 트리거한 것입니다. Antigravity Tools는 프록시 계층에서 여러 방어선을 만들었으며, 고정 경로에 따라 확인하기만 하면 어디서 막히는지 알 수 있습니다.

0 Token이란?

0 Token은 보통 한 번의 요청이 최종적으로 반환하는 output_tokens=0이며 "내용이 생성되지 않은 것"처럼 보이는 것을 말합니다. Antigravity Tools에서 더 일반적인 원인은 "스트리밍 응답이 실제 출력 전에 끝나거나 오류가 발생"하는 것이지, 모델이 정말로 0개 토큰을 생성한 것이 아닙니다. 프록시는 peek 사전 읽기를 사용하여 이러한 빈 응답을 차단하고 재시도를 트리거하려고 시도합니다.

프록시가 백그라운드에서 수행하는 세 가지 작업(먼저 멘탈 모델 구축)

1) 비스트리밍 요청이 자동으로 스트리밍으로 변환될 수 있음

/v1/messages 경로에서 프록시는 내부적으로 "클라이언트 비스트리밍 요청"을 스트리밍 요청으로 변환하여 상류에 요청하고, SSE를 받은 후 JSON으로 수집하여 반환합니다(이렇게 하는 이유는 로그에 "better quota"라고 명시되어 있음).

소스 코드 증거: src-tauri/src/proxy/handlers/claude.rs#L665-L913.

2) Peek 사전 읽기: "첫 번째 유효 데이터"를 기다렸다가 클라이언트에 스트림 전달

/v1/messages의 SSE 출력에 대해 프록시는 먼저 timeout + next()로 사전 읽기를 하여 하트비트/주석 행(:로 시작)을 건너뛰고, 첫 번째 "비어 있지 않고 하트비트가 아닌" 데이터를 얻을 때까지 기다렸다가 공식적으로 전달합니다. peek 단계에서 오류/시간 초과/스트림 종료가 발생하면 직접 다음 시도로 들어갑니다(다음 시도는 보통 계정 로테이션을 트리거함).

소스 코드 증거: src-tauri/src/proxy/handlers/claude.rs#L812-L926; Gemini 네이티브 스트리밍도 유사한 peek가 있음: src-tauri/src/proxy/handlers/gemini.rs#L117-L149.

3) 통합 백오프 재시도 + 상태 코드에 따라 "계정 로테이션 여부" 결정

프록시는 일반적인 상태 코드에 대해 명확한 백오프 전략을 만들었으며, 어떤 상태 코드가 계정 로테이션을 트리거하는지 정의했습니다.

소스 코드 증거: src-tauri/src/proxy/handlers/claude.rs#L117-L236.

🎒 시작 전 준비

따라 해보세요

1단계: 호출 중인 인터페이스 경로 확인

이유/v1/messages(claude handler)와 Gemini 네이티브(gemini handler)의 자가 치유 세부 정보가 다르므로, 경로를 먼저 확인하여 잘못된 로그 키워드에서 시간을 낭비하는 것을 방지합니다.

Proxy Monitor를 열고 실패한 요청을 찾아 먼저 Path를 기록하세요:

  • /v1/messages: src-tauri/src/proxy/handlers/claude.rs의 로직 보기
  • /v1beta/models/...:streamGenerateContent: src-tauri/src/proxy/handlers/gemini.rs의 로직 보기

예상 결과: 요청 기록에서 URL/메서드/상태 코드(및 요청 소요 시간)를 볼 수 있습니다.

2단계: 응답 헤더에서 "계정 + 매핑 모델" 잡기

이유 동일한 요청이 실패/성공하는지는 종종 "이번에 어떤 계정을 선택했는지" "어떤 상류 모델로 라우팅되었는지"에 달려 있습니다. 프록시는 이 두 가지 정보를 응답 헤더에 쓰므로 먼저 기록하고 나중에 로그를 보고 대응할 수 있습니다.

실패한 요청에서 다음 응답 헤더를 찾으세요:

  • X-Account-Email
  • X-Mapped-Model

이 두 항목은 /v1/messages와 Gemini handler 모두에서 설정됩니다(예: /v1/messages의 SSE 응답: src-tauri/src/proxy/handlers/claude.rs#L887-L896; Gemini SSE: src-tauri/src/proxy/handlers/gemini.rs#L235-L245).

예상 결과: X-Account-Email은 이메일이고 X-Mapped-Model은 실제 요청된 모델명입니다.

3단계: app.log에서 "peek 단계에서 실패"인지 판단

이유 peek 실패는 보통 "상류가 전혀 유효 데이터를 출력하지 않음"을 의미합니다. 이러한 문제의 가장 일반적인 처리 방법은 재시도/계정 로테이션입니다. 프록시가 트리거했는지 확인해야 합니다.

먼저 로그 파일을 찾으세요(로그 디렉토리는 데이터 디렉토리의 logs/이며, 하루별로 app.log*에 롤링 작성됨).

bash
ls -lt "$HOME/.antigravity_tools/logs" | head
powershell
Get-ChildItem -Force (Join-Path $HOME ".antigravity_tools\logs") | Sort-Object LastWriteTime -Descending | Select-Object -First 5

그 다음 최신 app.log*에서 다음 키워드를 검색하세요:

  • /v1/messages(claude handler): Stream error during peek / Stream ended during peek / Timeout waiting for first data(src-tauri/src/proxy/handlers/claude.rs#L828-L864)
  • Gemini 네이티브 스트리밍: [Gemini] Empty first chunk received, retrying... / Stream error during peek / Stream ended immediately(src-tauri/src/proxy/handlers/gemini.rs#L117-L144)

예상 결과: peek 재시도가 트리거되면 로그에 "retrying..."와 유사한 경고가 나타나며 그 후 다음 시도 attempt로 들어갑니다(보통 계정 로테이션을 가져옴).

4단계: 400/Invalid signature인 경우 프록시가 "서명 수정 재시도"를 했는지 확인

이유 서명 유형 오류는 종종 과거 메시지의 Thinking 블록/서명 블록이 상류 요구 사항에 맞지 않아서 발생합니다. Antigravity Tools는 "과거 thinking 블록 다운그레이드 + 수정 프롬프트 주입"으로 재시도를 시도합니다. 먼저 자가 치유가 완료되도록 해야 합니다.

다음 두 가지 신호로 수정 로직에 들어갔는지 판단할 수 있습니다:

  1. 로그에 Unexpected thinking signature error ... Retrying with all thinking blocks removed.가 나타남(src-tauri/src/proxy/handlers/claude.rs#L999-L1025)
  2. 이후 과거 Thinking 블록이 Text로 변환되고 마지막 user message에 수정 프롬프트가 추가됨(src-tauri/src/proxy/handlers/claude.rs#L1027-L1102; Gemini handler도 contents[].parts에 동일한 프롬프트를 추가함: src-tauri/src/proxy/handlers/gemini.rs#L300-L325)

예상 결과: 프록시는 짧은 지연 후 자동으로 재시도(FixedDelay)하며 다음 시도 attempt로 들어갈 수 있습니다.

확인 체크리스트 ✅

  • [ ] Proxy Monitor에서 요청 경로(/v1/messages 또는 Gemini 네이티브)를 확인할 수 있음
  • [ ] 이번 요청의 X-Account-EmailX-Mapped-Model을 얻을 수 있음
  • [ ] logs/app.log*에서 peek/재시도 관련 키워드를 검색할 수 있음
  • [ ] 400 서명 오류를 만났을 때 프록시가 "수정 프롬프트 + thinking 블록 정리" 재시도 로직에 들어갔는지 확인할 수 있음

일반적인 문제

시나리오할 수 있는 방법(❌)권장 방법(✓)
0 Token을 보면 즉시 수동으로 여러 번 재시도클라이언트 재시도 버튼을 계속 누르고 로그를 전혀 보지 않음먼저 Proxy Monitor + app.log를 한 번 보고 peek 단계 조기 종료인지 확인(자동 재시도/로테이션됨)
Invalid \signature``를 만나면 즉시 데이터 디렉토리를 비움.antigravity_tools 전체를 삭제하여 계정/통계 모두 사라짐먼저 프록시가 "서명 수정 재시도"를 수행하게 하세요. 로그가 명확하게 복구 불가능하다고 표시할 때만 수동 개입 고려
"서버 측 변동"을 "계정 고장"으로 취급400/503/529는 무조건 계정 로테이션로테이션 효과 여부는 상태 코드에 따라 다름. 프록시 자체에 should_rotate_account(...) 규칙이 있음(src-tauri/src/proxy/handlers/claude.rs#L226-L236)

이 과정 요약

  • 0 Token/스트리밍 중단은 프록시에서 보통 먼저 peek 사전 읽기를 거침. peek 단계 실패는 재시도를 트리거하고 다음 attempt attempt로 들어감
  • /v1/messages는 비스트리밍 요청을 내부적으로 스트리밍으로 변환한 후 JSON으로 다시 수집할 수 있으므로 "왜 스트리밍 문제처럼 보이는지" 이해에 영향을 줌
  • 서명 무효 유형 400 오류는 프록시가 "수정 프롬프트 + thinking 블록 정리"로 재시도를 시도하므로 이 자가 치유 경로가 통과하는지 우선 확인

다음 과정 예고

다음 과정에서는 **엔드포인트 빠른 참조표**를 학습합니다.


부록: 소스 코드 참조

클릭하여 소스 코드 위치 펼치기

업데이트 시간: 2026-01-23

기능파일 경로행 번호
Claude handler: 백오프 재시도 전략 + 로테이션 규칙src-tauri/src/proxy/handlers/claude.rs117-236
Claude handler: 내부에서 비스트리밍을 스트리밍으로 변환(better quota)src-tauri/src/proxy/handlers/claude.rs665-776
Claude handler: peek 사전 읽기(하트비트/주석 건너뜀, 빈 스트림 방지)src-tauri/src/proxy/handlers/claude.rs812-926
Claude handler: 400 서명/블록 순서 오류 수정 재시도src-tauri/src/proxy/handlers/claude.rs999-1102
Gemini handler: peek 사전 읽기(빈 스트림 200 OK 방지)src-tauri/src/proxy/handlers/gemini.rs117-149
Gemini handler: 400 서명 오류 수정 프롬프트 주입src-tauri/src/proxy/handlers/gemini.rs300-325
서명 캐시(세 계층: tool/family/session, TTL/최소 길이 포함)src-tauri/src/proxy/signature_cache.rs5-207
Claude SSE 변환: 서명 캡처 및 서명 캐시 작성src-tauri/src/proxy/mappers/claude/streaming.rs639-787

핵심 상수:

  • MAX_RETRY_ATTEMPTS = 3: 최대 재시시 횟수(src-tauri/src/proxy/handlers/claude.rs#L27)
  • SIGNATURE_TTL = 2 * 60 * 60 초: 서명 캐시 TTL(src-tauri/src/proxy/signature_cache.rs#L6)
  • MIN_SIGNATURE_LENGTH = 50: 서명 최소 길이(src-tauri/src/proxy/signature_cache.rs#L7)

핵심 함수:

  • determine_retry_strategy(...): 상태 코드에 따라 백오프 전략 선택(src-tauri/src/proxy/handlers/claude.rs#L117-L167)
  • should_rotate_account(...): 상태 코드에 따라 계정 로테이션 여부 결정(src-tauri/src/proxy/handlers/claude.rs#L226-L236)
  • SignatureCache::cache_session_signature(...): 세션 서명 캐시(src-tauri/src/proxy/signature_cache.rs#L149-L188)