DEV Community

Cover image for AI 에이전트용 API 오류 설계: 복구 가능한 오류
Rihpig
Rihpig

Posted on Originally published at apidog.com

AI 에이전트용 API 오류 설계: 복구 가능한 오류

AI 에이전트가 복구할 수 있는 API 오류 응답 설계

API가 400 Bad Request와 함께 {"error": "invalid input"}을 반환한다고 가정해 보겠습니다. 사람 개발자는 문서에서 페이로드를 확인하고 누락된 필드를 찾아 수정할 수 있습니다. 하지만 에이전트는 무엇을 고쳐야 하는지 알 수 없어 같은 요청을 반복하다가 결국 실패를 보고합니다.

오늘 Apidog를 사용해 보세요

오류 응답은 에이전트가 가장 많이 의존하지만, API 설계에서 자주 누락되는 부분입니다. 좋은 오류는 다음 세 가지를 알려줘야 합니다.

  • 무엇이 잘못되었는가?
  • 재시도할 수 있는가?
  • 무엇을 변경해야 하는가?

이 글은 API가 반환해야 할 오류 형식을 설명합니다. 클라이언트의 재시도, 백오프, 서킷 브레이커 구현은 에이전트 오류 복구에서 다룹니다.

Apidog는 성공 경로와 같은 위치에서 오류 응답을 정의하고, 목(mock)으로 만들고, 검증할 수 있어 유용합니다.

오류가 답해야 할 세 가지 질문

1. 요청 문제인가, 서버 문제인가?

  • 4xx: 요청을 변경하지 않으면 다시 실패합니다.
  • 5xx: 서버 문제이므로 나중에 같은 요청이 성공할 수 있습니다.

구분이 없으면 에이전트는 유효성 검사 오류를 무한히 재시도하거나, 일시적인 서버 오류를 너무 빨리 포기합니다.

2. 재시도해야 하는가?

상태 코드만으로 판단하게 하지 말고 명시적인 필드를 제공하세요.

  • 429: 대기 후 재시도
  • 409: 최신 상태를 다시 읽은 후 재시도
  • 422: 페이로드를 변경하지 않으면 재시도하지 않음

3. 무엇을 변경해야 하는가?

Validation failed는 충분하지 않습니다. 다음처럼 필드와 규칙을 구체적으로 설명해야 합니다.

countryUS일 때 customer.postal_code 필드가 필요합니다.

구체적인 오류 응답은 재시도 폭풍을 크게 줄입니다.

RFC 9457 기반의 구조화된 오류 형식

새로운 형식을 만들기보다 RFC 9457 HTTP API용 문제 상세 정보를 사용하세요.

{
  "type": "https://api.example.com/errors/validation-failed",
  "title": "Validation failed",
  "status": 422,
  "detail": "The field 'customer.postal_code' is required when 'country' is 'US'.",
  "instance": "/v1/orders",
  "errors": [
    {
      "field": "customer.postal_code",
      "code": "required_conditional",
      "message": "Required when country is US. Provide a 5-digit or 9-digit US postal code.",
      "example": "94107"
    }
  ],
  "retryable": false,
  "next_action": "Add customer.postal_code to the request body and send again."
}
Enter fullscreen mode Exit fullscreen mode

에이전트에게 특히 중요한 필드는 다음과 같습니다.

  • detail: 실제 필드와 실패한 규칙을 설명하는 문장
  • errors: 문제별 필드 경로, 안정적인 코드, 수정 방법
  • retryable: 재시도 가능 여부를 나타내는 불리언
  • next_action: 다음에 수행할 작업을 설명하는 명시적인 지시

유효성 검사 오류는 한 번에 모두 반환하세요. 문제를 하나씩 반환하면 한 번의 수정이 여러 번의 왕복으로 늘어납니다.

Google의 API 오류 설계 가이드도 오류 상세 정보를 산문보다 구조화된 목록으로 제공할 것을 권장합니다.

재시도 시점을 응답에 포함하기

일시적인 오류에는 정확한 대기 시간을 제공하세요. 30초를 기다려야 한다는 것을 알면 에이전트도 30초를 기다릴 수 있습니다.

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json

{
  "type": "https://api.example.com/errors/rate-limited",
  "title": "Rate limit exceeded",
  "status": 429,
  "detail": "You have used 1000 of 1000 requests in the current minute window.",
  "retryable": true,
  "retry_after_seconds": 30,
  "next_action": "Wait 30 seconds before sending this request again. Do not retry sooner."
}
Enter fullscreen mode Exit fullscreen mode

Retry-After 헤더는 초 단위 지연 시간 또는 HTTP 날짜를 지원합니다. 표준 클라이언트에는 헤더로 전달하고, 모델에는 본문에도 retry_after_seconds를 반복해 제공하세요.

속도 제한은 속도 제한 초과 가이드API 속도 제한 구현 방법에서 자세히 다룹니다.

동일한 패턴을 유지보수 중인 503과 잠긴 리소스에 대한 409에도 적용하세요. 대기가 필요한 오류라면 반드시 숫자로 대기 시간을 알려야 합니다.

내부 정보는 숨기되, 빈 오류는 피하기

스택 트레이스를 그대로 반환하면 프레임워크 버전, 파일 경로, SQL 일부가 노출될 수 있습니다. 이는 보안 문제이며, 신뢰할 수 없는 입력에 대한 API 테스트에서 다루는 위험과도 연결됩니다. 긴 내부 오류는 에이전트의 컨텍스트도 불필요하게 소모합니다.

반대로 본문 없는 500이나 {"error": true}도 유용하지 않습니다. 공개용 오류와 상관관계 ID를 함께 반환하세요.

{
  "type": "https://api.example.com/errors/internal",
  "title": "Internal error",
  "status": 500,
  "detail": "The order could not be created due to an internal error. No order was created.",
  "retryable": true,
  "retry_after_seconds": 5,
  "request_id": "req_01J8ZK3M2Q",
  "next_action": "Retry once after 5 seconds. If it fails again, stop and report request_id req_01J8ZK3M2Q."
}
Enter fullscreen mode Exit fullscreen mode

No order was created처럼 작업의 현재 상태를 명시하면 중복 생성 여부를 판단할 수 있습니다. 상태를 보장하기 어렵다면 작업을 멱등적으로 만들고, AI 에이전트를 위한 멱등 키 패턴을 적용하세요.

request_id는 로그에서 요청을 추적할 수 있게 합니다. API 가시성 관행과 함께 사용해 실제 로그 조회로 연결되도록 하세요.

오류를 OpenAPI 사양에 정의하기

OpenAPI 문서에 오류 형식이 없으면 생성된 클라이언트, 목, 에이전트 도구에는 해당 오류가 존재하지 않는 것과 같습니다.

responses:
  '201':
    description: Order created
    content:
      application/json:
        schema: { $ref: '#/components/schemas/Order' }
  '422':
    description: >
      Validation failed. Not retryable without changing the request body.
      The errors array names each invalid field.
    content:
      application/problem+json:
        schema: { $ref: '#/components/schemas/Problem' }
  '429':
    description: >
      Rate limited. Retryable. Wait for retry_after_seconds before sending again.
    content:
      application/problem+json:
        schema: { $ref: '#/components/schemas/Problem' }
Enter fullscreen mode Exit fullscreen mode

이 설명은 장식이 아닙니다. OpenAPI 사양을 에이전트 도구로 전환하는 방법처럼 사양에서 에이전트 도구를 생성할 때 모델이 실패 사례를 이해하는 데 사용됩니다.

성공뿐 아니라 오류도 테스트하기

오류 경로는 재현하기 번거롭다는 이유로 테스트에서 빠지기 쉽습니다. 목을 사용하면 안전하게 반복 테스트할 수 있습니다.

API 프로젝트에서 각 오류 응답을 정의하고, 에이전트가 다음 사례를 만날 수 있도록 목으로 구성하세요. Apidog에서는 엔드포인트에 실패 응답을 추가하고 목을 전환해 실제 시스템에 영향을 주지 않고 422, 429, 500을 반복 실행할 수 있습니다. 자세한 내용은 프로덕션 대신 목 API에 대해 에이전트 실행하기를 참고하세요.

최소한 다음 다섯 가지를 테스트에 포함하세요.

  1. 여러 필드의 유효성 검사 실패 모든 문제를 한 응답에 반환하고, 에이전트가 모두 수정하는지 확인합니다.
  2. 대기 시간이 있는 속도 제한 에이전트가 retry_after_seconds 이상 기다리는지 확인합니다.
  3. 쓰기 중 서버 오류 재시도해도 중복 리소스가 생성되지 않는지 확인합니다.
  4. 인증 실패 잘못된 토큰을 재시도하지 않고 중단하는지 확인합니다. 자격 증명 설계는 에이전트를 위한 최소 권한 API 키에서 다룹니다.
  5. 잘못된 오류 본문 유효한 JSON이 아닌 응답에서도 우아하게 실패하는지 확인합니다. 업스트림 프록시는 언제든 잘못된 응답을 반환할 수 있습니다.

이 시나리오를 저장하고 CI에서 실행하세요. 직렬 변환기 리팩토링처럼 성공 경로에는 영향을 주지 않는 변경이 오류 형식을 조용히 망가뜨릴 수 있습니다.

더 나은 오류의 효과

불필요한 재시도 감소

{"error": "invalid input"}을 받은 에이전트는 같은 페이로드를 두세 번 재시도할 수 있습니다. 누락된 필드와 수정 방법을 알려주면 한 번의 수정된 시도로 끝날 가능성이 높습니다.

에스컬레이션 감소

복구 가능한 오류를 명확하게 설명하면 작업이 사람에게 불필요하게 넘어가는 일을 줄일 수 있습니다.

디버깅 시간 단축

정확한 detailrequest_id가 있으면 사람이 로그를 추적하는 데 필요한 시간이 줄어듭니다. 이는 API 가시성의 상관관계 추적 원칙과 같습니다.

이런 오류는 사람 개발자에게도 유용합니다. 어떤 필드가 잘못되었는지 구체적으로 알려주는 오류가 지나치게 상세하다고 불평할 사람은 거의 없습니다.

에스컬레이션을 고려한 설계

에이전트가 복구할 수 없는 오류도 있습니다.

  • 필요한 권한이 없음
  • 계정이 폐쇄됨
  • 사람의 판단이 필요한 정책 위반
  • 허용되지 않는 범위의 요청

이 경우 오류는 깨끗한 인계를 지원해야 합니다. 무엇이 발생했는지, 사람이 해야 할 일이 무엇인지, 추적할 상관관계 ID를 제공하세요.

코딩 런타임에서 에이전트를 운영한다면 주변 플랫폼이 인계 지점이 될 수 있습니다. Sharkly는 에이전트 결과와 실행 추적을 Task에 보관하고, 응답 또는 검토가 필요한 항목을 받은 편지함으로 라우팅합니다. 오류 메시지가 단순히 “잘못된 입력”에 그치지 않고, 검토자가 다음 조치를 이해할 수 있도록 해야 합니다.

에이전트가 산문을 추측하게 하지 않기

다음과 같은 응답은 에이전트가 추측해야 하므로 피하세요.

{ "message": "Sorry, that didn't work. Please check your details and try again." }
Enter fullscreen mode Exit fullscreen mode

더 나쁜 경우는 실패를 200 상태 코드와 함께 반환하는 것입니다. 그러면 클라이언트 라이브러리, 재시도 정책, 대시보드, 모니터링이 실패를 감지하지 못합니다.

해결 방법은 두 가지입니다.

  1. 모든 실패에 안정적인 기계 판독용 코드를 부여합니다. 예를 들어 not enough라는 문구 대신 insufficient_funds를 사용합니다.
  2. 실패를 성공 상태 코드로 반환하지 않습니다.

에이전트가 읽을 수 있는 오류 체크리스트

  • 모든 API 오류가 하나의 일관된 구조화 형식을 사용합니다.
  • detail이 특정 필드 또는 조건을 설명합니다.
  • 유효성 검사 오류가 모든 문제를 필드 경로와 함께 반환합니다.
  • 모든 오류에 retryable 불리언이 있습니다.
  • 재시도 가능한 오류가 헤더와 본문에 초 단위 대기 시간을 포함합니다.
  • 쓰기 실패 후 생성 또는 변경된 리소스가 있는지 알려줍니다.
  • 모든 오류에 로그로 추적 가능한 상관관계 ID가 있습니다.
  • 스택 트레이스, 프레임워크 문자열, SQL을 노출하지 않습니다.
  • 오류 응답이 에이전트가 읽을 수 있는 설명과 함께 사양에 문서화되어 있습니다.
  • 모든 오류에 목이 있고, 저장된 테스트가 CI에서 실행됩니다.

오류는 인터페이스입니다. 실제 호출자가 다음 행동을 결정할 수 있도록 설계하세요. 에이전트가 오류를 만나기 전에 오류 형식을 정의하고 목으로 검증하려면 Apidog를 다운로드하세요.

자주 묻는 질문

RFC 9457을 사용해야 하나요?

일관된 기존 형식이 없다면 RFC 9457을 사용하세요. 표준화보다 중요한 것은 모든 엔드포인트가 같은 형식을 사용하는 것입니다. 기존 형식을 유지하더라도 retryablenext_action을 추가하세요.

next_action을 응답에 포함해도 안전한가요?

고정된 템플릿에서 생성한다면 안전합니다. 사용자 입력을 그대로 넣으면 에이전트가 지시로 해석할 수 있으므로 피해야 합니다. 자세한 내용은 신뢰할 수 없는 입력에 대한 API 테스트를 참고하세요.

유효성 검사 오류에는 400422 중 무엇을 사용해야 하나요?

  • 깨진 JSON처럼 요청 형식이 잘못된 경우: 400
  • 요청은 파싱되었지만 비즈니스 규칙을 위반한 경우: 422

이미 하나의 상태 코드로 통일했다면 변경보다 문서화가 우선입니다.

상세 정보는 어느 정도가 적절한가요?

호출자가 행동할 수 있을 정도면 충분합니다. 보통 필드 이름, 규칙, 예시 값이면 됩니다. 내부 식별자, 쿼리, 스택 프레임은 제외하세요.

오류 메시지도 컨텍스트 창을 차지하나요?

그렇습니다. 재시도마다 장황한 오류가 반복되면 빠르게 누적됩니다. 오류 응답을 수백 토큰 이내로 유지하세요. 에이전트용 API 응답 다듬기는 성공 응답과 실패 응답 모두에 적용할 수 있는 방법을 설명합니다.

재시도할 수 없는 오류를 어떻게 막나요?

retryable: false를 설정하고 next_action에 중단 또는 수정 방법을 명시하세요. 또한 도구 래퍼에서도 이를 강제해 모델의 판단에만 의존하지 않도록 하세요.

Top comments (0)