Skip to main content

우선 가장 먼저: 원시 오류는 응답 본문에 정확히 한 번만 나타납니다

APIYI는 오류 세부 정보를 오직 API 응답 본문을 통해서만 반환합니다. 백엔드 로그는 과금 원장입니다. 과금이 발생한 호출만 기록합니다. 실패한 요청은 과금되지도 않고 거기에 나열되지도 않습니다.따라서 “백엔드 로그에서 찾을 수 없다”는 것은 “일어나지 않았다”는 뜻이 아닙니다. 그 오류의 유일한 기록은 클라이언트에만 남아 있다는 뜻입니다. 그것을 출력하지도, 영구 저장하지도 않았다면 완전히 사라진 것이며, 저희도 복구할 수 없습니다.
한 줄 요약: 반환된 그대로의 원시 응답 본문을 정확히 출력하십시오. 프로그램이 감싸서 만든 한 줄 문자열만 보관하지 마십시오. 400 Bad Request 같은 문자열은 진단에 거의 도움이 되지 않습니다. 실제 답은 버려진 JSON 안에 있습니다.

실제 사례: 400 Bad Request는 아무것도 알려주지 않습니다

한 고객이 딱 한 줄만 보고했습니다:
그 문자열은 클라이언트 프레임워크가 오류를 감싼 뒤 생성한 결과입니다. 모델 이름, HTTP 메서드, URL, 상태 코드는 보존했지만, 정말 중요한 한 가지인 응답 본문은 버려 버렸습니다. 지원팀이 제시할 수 있는 최선의 답변은 다음이었습니다:
400은 보통 콘텐츠 안전성 문제이거나 파라미터 문제입니다. 가장 가능성이 높은 것은 콘텐츠 안전성입니다.
그것은 추측이지 결론이 아닙니다. 왜냐하면 바로 그 동일한 호출에 대해 실제 응답 본문은 다음 셋 중 하나였을 수 있고, 각각은 완전히 다른 조치를 요구하기 때문입니다:
같은 400, 세 가지가 완전히 다른 조치입니다. 응답 본문을 버리면 세 갈래 결정을 추측으로 바꾸게 됩니다. 그리고 잘못된 추측은 고객 지원 메시지의 왕복과, 필요도 없던 재시도까지 비용으로 치르게 합니다.더 심각한 점은: 그 셋 중 두 경우는 아예 재시도하면 안 됩니다. 서로 구분할 수 없다면, 무작정 재시도하는 것밖에 선택지가 없고, 그만큼 시간과 쿼터를 모두 소모하게 됩니다.

백엔드 로그에 있는 것과 없는 것

먼저 바로잡아야 할 정신 모델은 다음과 같습니다: 백엔드 로그는 오류 로그가 아니라 과금 원장입니다.
거꾸로 읽으면 연결 문제를 진단하는 가장 강력한 단일 테스트가 됩니다: 로그에 과금 항목이 있다면 요청이 업스트림까지 도달했고 리소스를 사용한 것입니다. 없다면 문제는 거의 확실하게 업스트림에 도달하기 전에 발생한 것입니다(네트워크, 인증, 파라미터 검증). 자세한 내용은 로그에서 과금 금액 읽기를 보십시오.

반드시 유지해야 하는 7개 필드

이것은 실패한 호출 하나를 진단하는 데 필요한 모든 정보입니다. 이 중 하나라도 빠지면 진단은 다시 추측 수준으로 떨어집니다:
응답 본문을 잘라내지 마십시오. 200자로 자르는 것은 일반적인 업무 로그에서는 괜찮지만, 진단용으로는 유용한 세부 정보가 끝부분에 있는 경우가 많습니다. 적어도 앞의 2000자는 유지하십시오. 이미지 엔드포인트에서 base64가 로그를 가득 채울까 걱정된다면, status_code >= 400일 때만 전체 본문을 출력하십시오 — 오류 본문은 어차피 짧습니다.

오류 포착 패턴을 올바르게 처리하기

사실 원칙은 하나뿐입니다. 두 계층에서 모두 잡고, 어느 계층에서도 아무것도 버리지 않는 것입니다.
  • 전송 계층 실패: 연결 재설정, TLS 핸드셰이크 실패, 타임아웃, DNS 실패입니다. HTTP 응답은 전혀 없으며, 예외 텍스트만 받을 수 있습니다.
  • HTTP 계층 오류: 서버가 4xx / 5xx를 반환했습니다. 응답 본문이 있으므로, 반드시 읽어야 합니다.

Python / requests

본문을 읽기 전에 raise_for_status()를 호출하지 마십시오. 그것이 발생시키는 HTTPError에는 400 Client Error: Bad Request for url: ...만 담기며, 실제 메시지는 아무도 읽지 않은 채 resp.text에 그대로 남습니다. 이것이 바로 이 페이지 상단의 사례가 발생하는 방식 중 하나입니다. 정말 사용해야 한다면, 먼저 resp.text를 꺼내십시오.

Python / OpenAI SDK

공식 SDK는 이미 예외 객체에 세 가지 정보를 모두 붙여 줍니다. 대부분의 사람들은 대신 자신의 한 줄짜리 메시지를 print합니다.
한 줄짜리라도 print(f"API error: {e}")이어야 하며 print("request failed")이어서는 안 됩니다. SDK 예외의 str(e)에는 이미 서버의 메시지가 포함되어 있습니다. 실제로 정보를 망가뜨리는 것은 예외 객체를 아예 버려 버리는 것입니다.

Node.js

SDK를 사용할 때는:
일반 fetch를 사용할 때는, 대부분 여기서 잘못됩니다:
이 페이지 상단의 400 Bad Request from POST https://api.apiyi.com/v1/images/edits는 말 그대로 ${resp.status} ${resp.statusText} from ${resp.method} ${resp.url}입니다. 본문이 전혀 읽히지 않았기 때문입니다.fetch는 HTTP 계층 오류에서 reject하지 않으며, 단지 resp.okfalse로 설정할 뿐입니다. 그 순간 resp.statusText를 던지면 응답 객체와 함께 본문도 사라집니다. throw하기 전에 항상 await resp.text() 하십시오. 이 한 줄이 진단 가능한 보고서와 답을 알 수 없는 보고서의 차이입니다.

cURL로 재현하기

누군가에게 문제 재현을 요청할 때 이 명령은 가장 적은 수고로 충분합니다. 상태 코드, 헤더, 본문, 시간 정보를 한 번에 보여 줍니다.
  • -i는 응답 헤더를 출력하며, x-request-id가 있는 곳이 바로 그곳입니다.
  • -sS는 진행 표시줄을 숨기지만 오류 출력은 유지합니다.
  • -w는 상태 코드와 전체 시간을 덧붙여 주며, 타임아웃 설정과 비교할 때 유용합니다.

래퍼와 사내 게이트웨이

좋은 예시

이 오류는 한 고객의 ComfyUI 노드에서 발생했습니다:
400 Bad Request보다 훨씬 더 지저분하지만 — 그럼에도 완전하므로, 방향은 몇 초 만에 결정됩니다: 결론은 바로 나옵니다. 이것은 콘텐츠 안전성이나 매개변수와 무관한 전송 계층 문제이며, 과금도 발생하지 않습니다(요청이 끝까지 완료되지 않았기 때문입니다). 문제 해결 경로: Image API 연결 끊김.
두 경우를 비교해 보십시오: 하나는 보기 좋게 포장되어 있지만 아무것도 설명하지 못하고(400 Bad Request), 다른 하나는 길고 지저분하지만 원인을 바로 가리킵니다(errno 10054). 진단에서는 친절하고 깔끔하게 다시 쓴 오류보다, 원시적이고 지저분하지만 완전한 오류가 언제나 더 낫습니다.

일반 도구에서 원시 출력을 찾는 위치

사내 게이트웨이를 위한 세 가지 규칙

1

그대로 통과시키고, 절대 다시 쓰지 마십시오

중간 계층은 문맥(어떤 서비스인지, 어떤 테넌트인지, 몇 번째 재시도인지)을 추가할 수는 있지만, 상위 error.message대체해서는 안 됩니다. 한 번 다시 쓰면 원문을 복구할 두 번째 장소가 없어집니다.
2

사용자에게 보이는 메시지를 원시 메시지와 별도로 저장하십시오

Gemini 이미지 오류 처리에서 사용하는 세 부분 구조를 따르십시오: userMessage(최종 사용자용 친절한 문구), devMessage(개발자용 분류), rawResponse(수정하지 않은 응답 본문)입니다. 앞의 두 개는 마음껏 다듬고, 세 번째는 그대로 저장하십시오.
3

알 수 없는 오류를 절대 내보내지 마십시오

폴백 분기에서는 status, x-request-id 및 본문의 처음 2000자를 기록하십시오. 원문 텍스트를 담고 있는 “분류되지 않은 오류”는 진단할 수 있지만, 깔끔한 “알 수 없는 오류”는 그렇지 않습니다.

진단을 불가능하게 만드는 안티패턴

  • except Exception as e: print("request failed") — 예외 객체가 사라져서 어떤 계층이 실패했는지도 알 수 없습니다;
  • 상태 코드는 기록하지만 본문은 기록하지 않기 — 이 페이지 맨 위에 있는 바로 그 경우입니다;
  • raise_for_status()resp.text를 먼저 읽지 않고 호출하기 — 메시지는 여전히 메모리에 있지만, 아직 조회되지 않았을 뿐입니다;
  • fetch에서 if (!resp.ok) throw new Error(resp.statusText)하기 — 본문이 응답 객체와 함께 버려집니다;
  • 성공한 재시도 후 깨끗한 200만 남겨두기 — 각 시도를 별도로 기록하십시오, 그렇지 않으면 전송이 몇 번 실패했는지 보이지 않고, 자신의 재시도를 게이트웨이 동작으로 착각할 수 있습니다;
  • stdout에만 기록하거나, 덮어쓰기로 매일 순환하기 — 고객이 문제를 보고할 때쯤이면 원본 기록은 대개 이미 지나가 버립니다;
  • 화면의 휴대전화 사진으로 문제를 보고하기 — 대신 텍스트를 붙여넣으십시오; 스크린샷은 오류의 한 줄을 절반쯤 잘라내는 경우가 자주 있습니다.

지원에 문의해야 하는 경우

먼저 위의 캡처 및 해석 단계를 진행하십시오. 다음 중 어느 하나라도 해당하면 자료를 지원팀에 전달하십시오.
  • 전체 응답 본문이 있고 error.message가 업스트림을 가리키는 경우(upstream_error, 원시 업스트림 5xx, 또는 명시적인 채널 오류)
  • 동일한 요청 매개변수가 다른 모델에서는 동작하거나 다른 시점에는 동작하지만, 특정한 한 모델에서만 일관되게 실패하는 경우
  • 오류가 500 + write_response_body_failed 또는 이와 유사한 다운스트림 전달 실패이며, 일관되게 재현되는 경우입니다(이 경우 과금되지 않습니다. 연결 끊김을 참조하십시오)
  • 과금이 실제 호출과 일치하지 않는다고 의심되는 경우 — 정확히 맞아떨어지는 유일한 기준은 request_id입니다

지원 티켓 템플릿(복사하여 붙여넣기)

WeCom 지원

WeCom 지원 QR 코드코드를 스캔하거나 이 카드를 클릭하여 WeCom 지원에 연결하십시오.또한 Telegram의 @apiyi001로 문의하시거나 [email protected]로 이메일을 보내실 수 있습니다.
위 템플릿을 텍스트로 보내십시오. 어떤 설명보다 훨씬 효율적입니다. request_id가 있으면 “대략 몇 시였고, 어떤 모델이었습니까?”를 묻지 않고도 해당 호출 하나의 전체 트레이스로 바로 들어갈 수 있습니다. request_id를 조회하는 방법은 로그 쿼리 API를 참조하십시오.

관련 문서

API 매뉴얼

공통 오류 코드, 인증 및 요청 제한

연결 끊김

ECONNRESET, errno 10054 및 SSL EOF에 대한 전체 문제 해결 절차

로그 조회 API

API를 통해 호출 로그를 가져오는 방법 — request_id를 조회하고 과금을 대조하는 방법

과금 금액 확인

실패한 호출이 왜 로그에 기록되지 않는지, 그리고 이를 진단 테스트로 사용하는 방법

타임아웃 설정

모델 유형별 타임아웃 단계와, 늘려도 도움이 되지 않을 때 확인할 사항

이미지 API 핵심 사항

동기 호출, base64 접두사 차이, 400 invalid_image_file 전처리