View

728x90
반응형

Claude Skills로 API를 래핑하는 개발자들이 자주 채택하는 디렉토리 구조가 있다. references/, scripts/, envs/다. 처음에는 각자의 스타일대로 작성하다가 두세 번 갈아엎고 나면 비슷한 모양으로 수렴된다. 나도 키움증권 API 스킬을 만들며 SKILL.md 한 파일에 모든 것을 담았다가, 두 차례의 리팩토링 끝에 결국 이 구조로 안착했다.

이 구조가 자리잡은 배경

Claude Skills는 2025년 후반에 정식 공개됐고, 그 직후부터 anthropics/skills 저장소(github.com/anthropics/skills)의 예제 대부분이 이 구조를 채택했다. 처음에는 Anthropic이 가이드를 강하게 박아 넣은 줄 알았으나, 이후 한국 개발자들이 자발적으로 올린 스킬들도 결국 유사한 형태로 굳어지는 것을 확인했다.

Anthropic 공식 예제부터 커뮤니티까지 이어지는 공통 패턴

공식 저장소를 열어보면 바로 확인된다.

my-api-skill/
├── SKILL.md
├── references/
│   ├── endpoints.md
│   ├── schemas.md
│   └── examples.md
├── scripts/
│   ├── auth.py
│   ├── client.py
│   └── helpers.py
└── .env.example

SKILL.md가 엔트리포인트이고, 세부 정보는 references/에, 실제 호출 로직은 scripts/에, 비밀 정보는 envs에 둔다. 이 분리가 왜 유효한지는 뒤에서 상세히 풀어본다.

초창기에는 SKILL.md 하나에 모든 것을 담았다

초기 스킬들을 살펴보면 다소 흥미롭다. 단일 SKILL.md 파일 안에 API 문서, 파이썬 코드, 환경변수 설명까지 모두 포함돼 있다. 그러다 컨텍스트 윈도우가 넘치거나, LLM이 앞부분만 읽고 뒤를 무시하거나, 코드 한 줄을 수정하려고 마크다운 안에서 코드블록을 찾아 헤매는 일이 반복됐다.

결국 "이것을 분리해야겠다" 는 결론에 다수가 도달했다. 현재의 구조는 그렇게 나온 결과다.

references 디렉토리 - LLM에 전달하는 도큐먼트 창고

출처: skywork.ai

references/는 LLM이 API를 정확하게 호출할 수 있도록 돕는 문서 모음이다. 이 부분이 부실하면 LLM이 헛소리를 하거나 존재하지 않는 엔드포인트를 호출하려 든다. 키움 API 스킬을 처음 만들 때 references 없이 구동했다가 LLM이 OpenAPI 스펙에 없는 가짜 엔드포인트(/v1/order/place 같은 것)를 만들어내는 장면을 두 번이나 목격했다.

무엇을 담아야 하는가

엔드포인트 URL과 메서드, 요청·응답 스키마, 인증 방식, 실제 호출 페이로드 예시, 에러 코드 테이블 정도가 포함된다. 다만 이를 한 파일에 몰아넣으면 안 되고, 토픽별로 쪼개야 LLM이 필요한 부분만 선별해 읽는다.

마크다운 vs JSON vs YAML

references/ 내부를 어떤 포맷으로 작성할지에 대한 질문이 자주 제기되는데, 나는 마크다운으로 통일한다. JSON·YAML은 기계가 읽기 좋은 포맷이지 LLM이 읽기 좋은 포맷은 아니다.

포맷 토큰 효율 가독성 LLM 이해도
마크다운 높음 매우 좋음 매우 좋음
JSON 낮음 (중괄호/따옴표 과다) 나쁨 좋음
YAML 중간 좋음 중간

동일한 엔드포인트 스펙을 마크다운에서 JSON으로 옮기자 토큰이 약 35% 증가했다(키움 tr_id 일람표 기준, tiktoken cl100k_base로 계산). 마크다운은 표와 자연어 설명을 섞을 수 있어 맥락까지 함께 전달된다.

파일을 쪼개야 하는 이유

한 파일에 모두 담으면 호출할 때마다 전체 파일이 컨텍스트에 로드돼 토큰이 낭비되고, 길이가 길어지면 LLM이 뒤쪽을 무시한다. 일부 엔드포인트만 수정해도 diff가 지저분해지는 것은 덤이다.

쪼갤 때는 도메인별로 묶는 방식이 무난하다. 통상 다음과 같은 형태로 귀결된다.

references/
├── overview.md          # API 전체 개요 + 인증
├── endpoints-auth.md    # 인증 관련 엔드포인트
├── endpoints-trading.md # 매매 관련 엔드포인트
├── endpoints-account.md # 계좌 조회 엔드포인트
├── schemas.md           # 공통 스키마
└── error-codes.md       # 에러 코드 테이블

실전 예시 - 증권 API 스킬의 references 구성

키움증권(kiwoom-api)이나 한국투자증권(korea-investment-api)처럼 엔드포인트가 200개를 넘는 스킬은 references/ 내부를 국내주식·해외주식·선물옵션·계좌조회·체결조회로 카테고리를 분리해둔다. LLM이 "삼성전자 매수해줘"를 받으면 endpoints-domestic-trading.md만 읽고 작업하며, 해외주식 문서는 건드리지도 않는다.

내 키움 스킬 기준으로 references를 분리하기 전후의 컨텍스트 토큰이 호출 한 번당 약 18k에서 4k 수준으로 감소했다. 프로덕션에서 반복 호출하는 환경이라면 비용 차이가 곧바로 청구서에 반영된다.

scripts 디렉토리 - 실제 호출 로직을 담는 위치

출처: blog.miguelgrinberg.com

scripts/는 실제 API 호출 파이썬 코드가 배치되는 곳이다. LLM에 매번 즉석 코딩을 시키지 말고, 미리 작성해둔 함수를 호출하게 해야 안정적이다.

파이썬이 사실상 표준이다

bash나 node.js로 스킬을 작성하는 경우도 간혹 있으나, 공식 저장소 기준으로는 파이썬이 거의 전부를 차지한다(2026-04 시점 anthropics/skills의 스크립트 파일 비율로는 파이썬이 90%에 근접). 이유는 단순하다. requests/httpx 같은 HTTP 라이브러리 생태계가 두텁고, 타입 힌트를 붙이면 LLM이 함수 시그니처를 정확히 읽는다. 대부분의 API 제공자가 파이썬 SDK를 가장 먼저 내놓는 점도 한몫한다.

bash는 OAuth2 같은 다단계 인증이나 중첩 JSON 파싱에서 고통스럽다. 특별한 이유가 없다면 파이썬을 택하는 것이 맞다.

함수 단위 vs 파일 단위 분리 기준

scripts 내부에서 파일을 어떻게 나눌지 고민될 때는 대체로 다음과 같이 잡는다. 인증 로직은 거의 모든 API에서 별도 파일로 분리하는 편이 정신 건강에 이롭다(auth.py). 클라이언트 초기화, 공통 헤더, 타임아웃 등은 client.py로 묶고, 도메인별 엔드포인트 래퍼는 trading.py/account.py 식으로 갈라둔다. 유틸은 helpers.py에 모은다.

반대로 서로 강하게 의존하거나, 같은 도메인 안에서 다섯 개 이내인 함수들은 한 파일에 두어도 무방하다. 파일 하나가 200~400줄 정도면 적당하고, 1000줄을 넘어가면 분리 신호다.

LLM이 잘 호출하도록 만드는 함수 시그니처 패턴

LLM이 함수를 호출할 때 혼동을 일으키는 패턴이 있다. 다음과 같은 경우는 피해야 한다.

# 나쁜 예시 - LLM이 무엇을 넣어야 할지 모름
def trade(sym, qty, t, p=None):
    pass

# 좋은 예시 - 타입 힌트 + docstring + 키워드 인자 강제
def place_order(
    symbol: str,
    quantity: int,
    order_type: Literal["buy", "sell"],
    price: float | None = None,
) -> OrderResult:
    """
    주식 주문 실행.

    Args:
        symbol: 종목코드 (예: "005930")
        quantity: 주문 수량
        order_type: "buy" 또는 "sell"
        price: 지정가 (None이면 시장가)

    Returns:
        OrderResult: 주문 번호, 체결 상태 포함
    """
    pass

타입 힌트, docstring 안에 포함되는 실제 예시 값, 인자 이름의 명확함. 이 세 가지만 갖춰도 LLM의 호출 정확도가 눈에 띄게 향상된다. 내 경우 t처럼 한 글자짜리 인자명을 order_type으로 변경하자 잘못된 인자 위치에 값을 넣는 실수가 거의 사라졌다.

에러 핸들링은 의미 있게

단순히 raise만 던지면 LLM이 복구하지 못한다. 어떤 에러인지, 무엇을 고쳐야 하는지 메시지에 담아야 한다.

# 별로다
raise Exception("Error")

# 더 낫다
raise APIError(f"Invalid symbol: {symbol}. Must be 6-digit stock code.")

# 최선이다
raise APIError(
    message=f"Invalid symbol: {symbol}",
    suggestion="Stock code must be 6 digits (e.g., '005930')",
    retry_possible=True,
)

LLM은 에러 메시지를 읽고 다음 행동을 결정한다. 메시지가 부실하면 그대로 포기하거나 엉뚱한 방향으로 진행한다.

requirements.txt는 두어야 하는가?

두어야 한다. 의존성을 명시하지 않으면 다른 환경에서 구동할 때 즉시 터진다. requirements.txt 또는 pyproject.toml 둘 중 하나는 반드시 포함하는 것이 맞다. 그리고 의존성은 최소화한다. requests 하나로 끝낼 작업에 httpx, aiohttp까지 끌어오지 말 것.

envs 관리 - 진짜 골치 아픈 영역

출처: blog.yarsalabs.com

환경변수 관리는 스킬 제작 시 실수가 가장 많이 발생하는 영역이다. 한 번의 실수로 API 키가 GitHub에 올라가면 대형 사고로 번진다. 진지하게 경고한다.

.env vs env.example vs envs 디렉토리

가장 흔한 구성은 루트에 .env와 .env.example을 두는 방식이다. .env에는 실제 값이 들어가며 .gitignore로 차단하고, .env.example에는 키 이름과 더미 값만 넣어 git에 올린다.

my-skill/
├── .env            # 실제 API 키 (gitignore)
├── .env.example    # 템플릿 (git에 올림)
└── ...

환경을 dev/staging/prod로 나눠야 할 때는 envs/ 디렉토리를 추가로 만들어 환경별 파일을 따로 둔다. 보안 민감도가 높은 프로덕션에서는 파일을 만들지 않고 OS 환경변수에서 직접 읽는 방식을 쓰기도 하지만, 로컬 개발이 불편해 일반 스킬에서는 잘 쓰이지 않는다. 대부분의 스킬은 .env와 .env.example 두 개로 충분하다.

git에 올리면 안 되는 것을 명확히 분리

.gitignore 설정이 핵심이다. 최소한 다음 수준은 갖춰야 한다.

.env
.env.local
.env.*.local
envs/.env.dev
envs/.env.staging
envs/.env.prod

그리고 .env.example은 반드시 커밋해야 한다. 다른 사람이 클론했을 때 무엇을 채워야 하는지 알 수 있어야 하기 때문이다.

# .env.example
API_KEY=your_api_key_here
API_SECRET=your_api_secret_here
BASE_URL=https://api.example.com
TIMEOUT_SECONDS=30

실제 값은 비워두고 키 이름과 예시 값만 보여주는 방식이다.

API 키를 LLM에 직접 노출해서는 안 된다

이것이 보안에서 가장 자주 무너지는 지점이다. API 키는 파이썬 코드에서 os.environ으로 읽어야 하며, LLM이 열람할 수 있는 references/에 평문으로 적으면 안 된다.

# scripts/client.py
import os

def get_api_key() -> str:
    key = os.environ.get("API_KEY")
    if not key:
        raise ConfigError("API_KEY not set. See .env.example for required vars.")
    return key

이처럼 스크립트 내부에서만 키를 다루면, LLM은 함수를 호출할 뿐 키 값을 직접 열람하지 않는다. 분리의 핵심이 여기에 있다.

dotenv 라이브러리 vs os.environ 직접 사용

로컬 개발 편의성을 원한다면 python-dotenv를 쓴다.

from dotenv import load_dotenv
load_dotenv()  # .env 파일 자동 로드

import os
api_key = os.environ["API_KEY"]

프로덕션이나 CI/CD에서는 시스템 환경변수에 직접 주입하므로 dotenv 없이 os.environ만 사용한다. 스킬 개발 중에는 dotenv가 편리하다.

한국 개발자들이 만든 스킬 분석

한국 개발자들이 올린 스킬을 보면 이 구조가 얼마나 확고히 자리잡았는지 체감된다. 내가 자주 들여다본 것은 kiwoom-api, korea-investment-api 두 개인데, 두 스킬은 도메인이 같고 작성자도 비슷한 시기에 작업한 것이라 비교에 용이하다.

증권 API 스킬들의 공통점

두 스킬 모두 references/ 안에 도메인별 엔드포인트 문서를 분리해뒀고(국내주식/해외주식/선물옵션/계좌/체결), scripts/에서는 OAuth 토큰을 관리하는 인증 모듈을 별도 파일로 빼뒀다. 토큰 만료·재발급 로직이 본 코드에 섞이면 디버깅이 지옥이 되므로 거의 강제에 가깝다. 환경변수에서는 API 키, 시크릿, 계좌번호를 분리해 관리하고, 주문 실행 함수는 dry-run 옵션을 기본 True로 두는 안전장치까지 박아뒀다. 실수로 매수가 한 번 체결되면 끝장이라는 사실을 모두 한 번씩 겪은 흔적이다.

도메인은 거의 같지만 작성자도 다르고 코드 스타일도 다른데, 폴더 구조와 분리 기준만 놓고 보면 거의 쌍둥이다. 구조가 자연스럽게 같은 방향으로 수렴했다는 증거다.

공통으로 발견되는 구조적 결정들

두 스킬에서 반복적으로 관찰되는 결정이 몇 가지 있다. 인증 로직은 무조건 별도 파일로 분리하고, 에러 응답은 references/error-codes.md에 표 형태로 정리한다. 읽기 연산과 쓰기 연산을 별도 파일로 분리하는 것도 공통이며(주문/취소는 손이 많이 가기 때문이다), 계좌번호·사용자 ID 같은 민감 정보는 환경변수로만 접근하도록 구성해뒀다. 새 스킬을 만들 때 이 패턴을 그대로 따라가면 초기 단계에서 헤맬 일이 줄어든다.

이 구조를 따르면 얻는 것들

협업 시 PR 리뷰가 수월해진다. 엔드포인트 추가는 references/, 로직 수정은 scripts/, 설정 변경은 envs로 가야 한다는 원칙이 명확하므로 리뷰어가 어디를 봐야 하는지 즉시 파악한다. LLM 관점에서도 references만 읽고 정확하게 함수 호출이 가능해 토큰 소비가 감소한다. 새 엔드포인트 추가는 references에 문서 한 줄을 늘리고 scripts에 함수 하나를 추가하면 끝나는 단순 작업이 된다. 민감 정보가 코드에 섞이지 않아 실수로 커밋해도 .gitignore가 막아주는 안전망까지 확보된다.

이렇게 작성하면 망한다 (안티패턴)

반대로 다음 패턴은 반드시 피해야 한다.

모든 로직을 SKILL.md 안에 담기

초보자들이 가장 흔히 저지르는 실수다. 마크다운 안에 파이썬 코드블록을 넣고, 환경변수 설명도 넣고, API 문서까지 모두 담는다. 토큰이 폭발하고 유지보수가 지옥이 된다. 파일을 쪼개는 것이 맞다.

scripts 없이 LLM에 즉석 코딩을 맡기기

"LLM이 알아서 호출하겠지"라며 스크립트를 짜두지 않으면, 호출할 때마다 LLM이 매번 requests 코드를 새로 작성한다. 에러 처리가 들쭉날쭉하고, 동일한 작업을 매번 재발명하는 비효율이 누적된다. 미리 작성해둔 함수를 호출하는 편이 코드를 한 줄씩 새로 쓰는 것보다 빠르고 안정적이다.

.env를 git에 커밋하기

.gitignore를 빠뜨리고 .env를 커밋해 API 키가 노출되면 끝장이다. GitHub을 돌아다니는 수많은 자동화 봇이 공개 리포의 키를 몇 분 안에 긁어 사용한다. 결국 키 로테이션, 비용 정산, 사과 포스트 작성까지 패키지로 따라온다. 진지하게 경계해야 한다.

references에 API 키 직접 기재하기

간혹 "예시니까 괜찮겠지"라며 references/examples.md에 실제 API 키를 박아두는 사람이 있으나 절대 안 된다. LLM은 이를 그대로 읽어 에이전트 로그에 노출한다. 예시에도 반드시 YOUR_API_KEY 같은 플레이스홀더를 사용해야 한다.

정석을 따르되 프로젝트에 맞게 변형하기

references/, scripts/, envs/ 구조가 자주 채택되는 이유는 단순하다. 토큰을 적게 소비하고, 협업이 편리하며, 보안 분리가 자연스럽게 이뤄지고, 새 기능 추가가 단순한 패턴 작업이 된다. Anthropic 공식 예제와 한국 개발자 스킬들이 유사한 형태로 수렴한 것은 이 네 가지 효용이 같은 방향을 가리키기 때문이다.

다만 이 구조가 모든 프로젝트에 그대로 들어맞지는 않는다. 작은 스킬은 scripts/ 없이 단일 파이썬 파일로도 충분할 수 있고, 환경을 여러 개 운영하는 큰 스킬은 envs/를 더 세분화해야 할 수 있다. 일단 이 구조로 뼈대를 잡은 뒤 프로젝트 특성에 맞게 다듬는 편이, 맨땅에서 직접 설계하는 것보다 시간이 덜 든다. 이미 만든 스킬이 이 구조를 따르지 않고 있다면 한 번 정리해볼 만하다. 두 번째 리팩토링쯤 되면 모두가 비슷한 모양으로 회귀하는 데에는 분명한 이유가 있다.


참고 자료

728x90
반응형
Share Link
reply
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31