본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 30. 00:17

AI 에이전트가 실제로 소비할 수 있는 API 문서 작성하기

요약

AI 에이전트가 API를 정확하게 호출할 수 있도록 하는 문서 작성법의 중요성을 다룹니다. 인간과 달리 추론 능력이 제한적인 에이전트를 위해 모호함을 제거하고 명확한 계약(contract) 중심의 문서를 설계해야 함을 강조합니다.

핵심 포인트

  • 에이전트는 인간처럼 문맥을 추론하거나 빈칸을 메울 수 없음
  • llms.txt는 발견(discovery)을 돕지만, 정확한 호출(contract)을 보장하지는 않음
  • 에이전트용 문서는 모호함 없는 명확한 파라미터와 필드 정의가 필수적임
  • Business-to-Agent 환경을 위한 새로운 문서 표준화가 필요함

당신의 문서는 추측할 수 있는 인간을 위해 작성되었습니다. 하지만 당신의 API를 호출하는 에이전트는 그럴 수 없습니다.

저는 아주 당혹스러운 방식으로 그 격차를 발견했습니다. 제가 만든 에이전트 중 하나가 제가 만든 API 중 하나를 호출하지 못했던 것입니다.

FamNest는 작은 에이전트 그래프 (agent graph)를 실행합니다. 라우터 (router)가 부모의 메시지를 리트리버 (retriever)에게 전달하면, 리트리버는 검증된 코퍼스 (corpus)를 바탕으로 답변의 근거를 마련하고, 코치 에이전트 (Groq, Llama 3.3 70B)가 답장 초안을 작성하며, 세이프티 리뷰어 (safety-reviewer) 에이전트가 승인한 후에야 인간에게 전달됩니다. 에이전트들은 제3자 통합업체가 하는 것과 동일한 방식으로 내부 엔드포인트 (endpoint)를 호출합니다. 그러던 어느 오후, 코치 에이전트가 리트리벌 (retrieval) 엔드포인트에 대해 계속해서 잘못된 호출을 생성하기 시작했습니다. 잘못된 필드 이름, 누락된 필수 필터, 때로는 존재하지도 않는 파라미터 (parameter)를 만들어내기도 했습니다.

엔드포인트가 고장 난 것이 아니었습니다. 문서가 문제였습니다. 문서는 빈칸을 채워 넣을 수 있는 인간을 위해 작성되었지만, 에이전트에게는 채울 수 있는 빈칸이 없었습니다. 오직 제가 제공한 토큰 (token)들뿐이었습니다.

이것이 핵심 교훈이며, 이는 단순한 트렌드보다 더 가치 있는 것입니다.

모두가 출시하고 있는 트렌드 — 그리고 그 한계

만약 당신이 2026년에 개발자 도구 (developer tooling)를 다뤄보았다면, llms.txt가 2024년 9월의 제안에서 일상적인 인프라의 일부로 변모하는 과정을 지켜보았을 것입니다. 이는 도메인 루트에 위치한 Markdown 파일로, 각 링크의 한 줄 요약과 함께 AI 시스템이 중요한 콘텐츠를 가리킬 수 있도록 안내합니다. Mintlify, Fern, GitBook은 이를 위한 원클릭 토글 기능을 제공합니다. Cursor, Windsurf, Claude Code, Copilot과 같은 IDE 에이전트들은 당신이 문서 사이트를 가리킬 때 이를 가져오며, 코드를 작성하기 전에 필요한 연결된 페이지들만 불러옵니다. LangChain은 심지어 이러한 파일들을 호스트 앱에 fetch_docs 도구로 전달하는 MCP 서버 (mcpdoc)를 출시하기도 했습니다.

사람들은 이를 Business-to-Agent 웹이라고 부르며, 그 프레임워크는 적절합니다. 과거에 인간이 탐색할 수 있는 사이트가 필요했듯이, 이제는 에이전트가 경로를 지정할 수 있는 접점 (surfaces)이 필요합니다. llms.txt를 배포하세요. 반나절 정도의 작업이면 충분합니다.

하지만 이것이 실제로 무엇을 해결하는지 주목하십시오. 바로 발견 (discovery)입니다. 이것은 "어떤 페이지가 중요한가"에 답합니다. 하지만 제 코치 에이전트를 고장 냈던 더 어려운 질문에 대해서는 아무것도 말해주지 않습니다:

에이전트가 일단 엔드포인트(endpoint)를 찾아냈다면, 당신의 설명이 모호할 때 이를 복구해 줄 인간(human in the loop) 없이도 단 한 번에 정확하게 호출할 수 있을까요?

그것은 발견(discovery)의 문제가 아닙니다. 그것은 계약(contract)의 문제입니다. 그리고 바로 이 지점에서 대부분의 문서가 조용히 실패합니다.

에이전트는 다른 종류의 독자입니다

당신의 문서를 읽는 인간은 평생 쌓아온 사전 지식(priors)을 가지고 있습니다. 그들은 userId가 아마도 UUID일 것이라고 추론합니다. 예시가 snake_case를 사용하는 것을 보고 그에 맞춰 조정합니다. 400 에러가 발생하면 어깨를 으쓱하고, 에러 메시지를 읽고, 다시 시도합니다. 정말 막히면 동료에게 물어봅니다. 인간을 위한 문서는 인간이 그 간극을 메울 수 있기 때문에 충분히 괜찮을 수 있습니다.

에이전트는 아무것도 메우지 못합니다. 에이전트에게 있는 것은 당신의 토큰(tokens)과 확률 분포(probability distribution)뿐입니다. 에이전트는 구조를 패턴 매칭(pattern-matches)합니다. 만약 당신의 예시가 하나의 필드만 보여준다면, 에이전트도 하나의 필드만 생성합니다. 만약 당신이 문장으로 에러를 설명한다면, 에이전트는 그 문장을 처리해야 할 분기(branch)가 아니라 단순한 부연 설명(flavor)으로 취급합니다. 모호함은 에이전트를 신중하게 만드는 것이 아니라, 확신에 찬 채 틀리게 만듭니다.

따라서 문서는 더 이상 단순한 설명서가 아니라 인터페이스(interface) 그 자체가 됩니다. 에이전트가 당신의 엔드포인트에 대해 알 수 있는 모든 것은 텍스트 안에 있습니다. 만약 어떤 사실이 페이지에 없다면, 그것은 존재하지 않는 것입니다.

이는 좋은 엔드포인트 문서가 무엇을 포함해야 하는지에 대한 프레임을 재구성합니다. 다음은 제 문서에서 빠져 있었던 다섯 가지 요소입니다.

  1. 산문 형태의 설명이 아닌, 타입이 지정된 스키마 (A typed schema, not a prose description)

산문(Prose)은 다음과 같이 말합니다: "사용자의 질문과 선택 사항인 주제 태그 목록을 보내세요."

스키마는 무엇이 허용되는지를 정확하게 말해주며, 에이전트는 추측 없이 이를 패턴 매칭할 수 있습니다:

ts// retrieve — request const RetrieveRequest = z.object({ query: z.string().min(1).max(2000), topics: z.array(z.enum(["sleep", "feeding", "behavior", "self_care"])) .max(4) .default([]), topK: z.number().int().min(1).max(10).default(5), });

차이점은 열거형(enum), 범위(bounds), 그리고 기본값(default)에 있습니다. "선택 사항인 주제 태그 목록"이라는 표현은 제 코치 에이전트가 "toddler_tantrums" 같은 것을 지어내게 만들었습니다. z.enum([...])은 유효한 집합을 추측 불가능하게 만들어 틀릴 여지를 없앱니다. 스키마에 대한 문단을 작성하지 말고, 스키마 자체를 공개하세요.

  1. 포괄적인 예시 — 예외적인 경로 (unhappy paths) 포함

에이전트(Agents)는 예시를 복사합니다. 당신이 보여주는 것이 곧 당신이 받게 될 결과입니다. 만약 당신이 제공하는 유일한 예시가 정상적인 경로 (happy path)뿐이라면, 모델은 정상적인 경로를 생성하는 방법만을 알게 됩니다.

따라서 저는 빈 결과(empty result)와 거부된 요청(rejected request)을 각주가 아닌 일급 예시 (first-class examples)로 문서화합니다:

// 200 — 결과 발견
{ "matches": [{ "id": "c_18", "score": 0.82, "text": "..." }], "truncated": false }

// 200 — 유효한 쿼리이나 관련 내용 없음 (에러 아님)
{ "matches": [], "truncated": false }

// 422 — 쿼리 검증 실패
{ "error": "validation_error", "field": "topics", "detail": "unknown topic 'toddler_tantrums'" }

중간 사례는 인간은 생략하지만 에이전트에게는 절실히 필요한 부분입니다. "일치하는 항목 없음"은 정상적인 결과이지 실패가 아닙니다. 만약 이를 명시하지 않는다면, 에이전트는 빈 배열을 버그로 취급하고 영원히 재시도할 것입니다.

  1. 복구 의미론 (recovery semantics)을 포함한 에러 분류 체계 (error taxonomy)

대부분의 문서는 에러를 설명합니다. 하지만 에이전트에게는 에러에 대해 무엇을 해야 하는지 알려주어야 합니다. "속도 제한(rate-limited) 시 429를 반환함"은 단순한 설명입니다. 에이전트에게는 결정(decision)이 필요합니다.

그래서 저는 모든 행이 특정 동작(action)으로 끝나는 표를 제공합니다:

코드error원인호출자가 해야 할 일
422validation_error잘못된 입력detail에 명시된 필드를 수정하십시오; 변경 없이 재시도하지 마십시오
...

인간은 참조를 위해 이 표를 읽습니다. 에이전트는 이를 제어 흐름 그래프 (control-flow graph)로 읽습니다. "재시도하지 마십시오"라고 적힌 셀들은 혼란에 빠진 에이전트가 새벽 3시에 당신의 엔드포인트(endpoint)를 계속해서 두드리는 것을 막아주는 역할을 합니다.

  1. 명시적인 결정론 (determinism) / 멱등성 (idempotency) 계약

제가 모든 엔드포인트 문서에 추가한 가장 유용한 단 한 문장은 이것이었습니다: "이 작업을 재시도해도 안전한가?"

에이전트는 재시도를 합니다. 네트워크는 불안정하며, 멱등성 (idempotency)이 보장되지 않는 재시도 호출은 카드 결제를 중복으로 처리하거나 불안해하는 학부모에게 두 번의 답장을 보내는 결과를 초래합니다. 부수 효과 (side effect)가 있는 모든 작업에 대해, 저는 이제 문서 자체에 계약 내용을 명시합니다:

멱등성 (Idempotency): POST /coach/reply 및 모든 결제 경로에 필수 사항입니다. `Idempotency-Key` 헤더 (UUID)를 전송하세요. 동일한 키 + 동일한 본문으로 재전송 시 원래의 결과를 반환합니다. 동일한 키 + 다른 본문 → 409. 조회 (GET /retrieve)는 부수 효과 (side-effect)가 없으며 자유롭게 재시도해도 안전합니다.

그 단락은 스스로 치유되는 재시도 루프와 피해를 입히는 루프 사이의 차이를 만듭니다. 또한 이는 인간은 추론할 수 있지만 에이전트는 단순히 하지 못하는 종류의 일입니다. 모델에게 당신의 결제 웹훅 (webhook)이 재전송에 안전하다는 것을 알려주는 사전 지식 (prior)은 없습니다. 당신이 직접 말해줘야 합니다.

  1. 데이터로서의 인증 및 제한 사항, 구전 지식이 아닌 방식으로

"인증된 요청만 허용합니다. 스팸을 보내지 마세요"는 계약이 아닙니다. 권한 범위 (Scopes), 정확한 헤더, 속도 제한 (rate limit), 그리고 윈도우 (window)는 에이전트가 읽고 스스로 조절할 수 있도록 구조화된 필드로 문서에 포함되어야 합니다:

plaintext Auth: Authorization 헤더의 Bearer 토큰. /retrieve에 대한 Scope는 coach:read. Limits: 토큰당 분당 60회 요청. 초과 시 → 429 + Retry-After (초).

이제 에이전트는 제한에 걸려 넘어짐으로써 한계를 발견하는 대신, 스스로 속도를 조절할 수 있습니다.

정직함을 유지하세요: 단일 진실 공급원 (one source of truth)

문서와 코드가 일치하지 않는 순간 이 모든 것은 부패합니다. 그리고 에이전트는 인간처럼 오래된 문서를 냄새로 맡아낼 수 없습니다. 따라서 계약은 수동으로 관리되는 것이 아니라 생성되어야 합니다.

저의 체인은 의도적으로 지루하게 설계되었습니다: 타입이 지정된 Next.js 핸들러가 Zod 스키마로 검증하고, 스키마가 OpenAPI 명세 (spec)를 생성하며, 저의 llms.txt가 생성된 참조로 링크를 겁니다. 제가 수정하는 것은 오직 스키마뿐입니다. 문서는 실제 사실의 하류 (downstream)에 있기 때문에 내용이 어긋날 수 없습니다.

  • Zod 스키마 ──► 요청 검증 (런타임)

    └────────► OpenAPI 명세 ──► /llms.txt 항목 ──► 에이전트가 이를 읽음

핸들러가 변경되면 하류의 모든 산출물도 함께 변경됩니다. 코드가 거짓말을 하지 않는 한, 문서는 거짓말을 하지 않습니다.

이를 실제로 증명하는 테스트

제가 엔드포인트(endpoint) 문서를 신뢰하기 전에 실행하는 확인 방법은 다음과 같습니다. 코드베이스나 컨텍스트 없이 오직 문서만 제공된 새로운 모델에게 (a) 유효한 호출(call)을 구성하고, (b) 시드된 에러(seeded error)를 처리하도록 요청합니다. 만약 모델이 이를 수행하지 못한다면, 그 격차는 모델이 아니라 문서에 있는 것입니다. 저는 이를 엔드포인트 옆에 아주 작은 스냅샷 테스트(snapshot tests)로 유지하며, 이를 통해 문서 회귀(doc regression)가 발생하면 다른 버그와 마찬가지로 CI(지속적 통합)에서 실패하도록 만듭니다.

제 코칭 에이전트(coach agent)가 고장 났을 때, 이 테스트가 있었다면 몇 초 만에 잡아냈을 것입니다. 콜드 모델(cold model)에 입력된 검색 문서(retrieval doc)는 제가 운영 환경(production)에서 목격했던 것과 정확히 일치하는 잘못된 형식의 호출을 생성했습니다. 왜냐하면 모호함이 바로 그 페이지에 있었기 때문입니다.

이것은 새로운 것이 아닙니다. 단지 독자가 바뀌었을 뿐입니다.

이 모든 것이 새로운 규율은 아닙니다. "모호하지 않고(Unambiguous), 완전하며(complete), 검증 가능한(verifiable)" 것은 제가 모든 것을 작성할 때 기준으로 삼는 요구사항 표준인 IEEE 29148의 중추입니다. 변한 점은 당신의 인터페이스를 사용하는 소비자가 더 이상 모호한 명세(spec)를 대충 넘겨짚어 이해할 수 있는 사람이 아니라는 점입니다. 2026년에는 통합자(integrator)의 절반이 에이전트이며, 그들은 당신의 문서를 문자 그대로, 철저하게, 그리고 어떠한 자비(charity)도 없이 읽습니다.

에이전트들이 당신을 찾을 수 있도록 llms.txt를 배포하세요. 하지만 그들이 도착했을 때 성공하게 만드는 것은 더 오래되었고 덜 화려한 것입니다. 바로 오독할 수 없을 정도로 정밀한 계약(contract)입니다. 에이전트 중심의 웹(agentic web)은 더 예쁜 문서를 필요로 하지 않습니다. 잘못 추측할 수 없는 문서를 필요로 합니다.

저는 바쁜 부모들을 위한 AI 웰니스 코치인 FamNest를 구축하고 있으며, 멀티 에이전트 시스템(multi-agent systems)의 운영 신뢰성과 안전성에 대해 글을 씁니다. 만약 당신이 에이전트 호출이 가능한 API를 문서화하고 있으며, 그 계약(contract)에 대해 제2의 시각을 원한다면, 제 노트는 공개되어 있습니다.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0